mirror of
https://github.com/containers/kubernetes-mcp-server.git
synced 2025-10-23 01:22:57 +03:00
refactor(kubernetes): move provider initialization into factory (#365)
Signed-off-by: Calum Murray <cmurray@redhat.com>
This commit is contained in:
@@ -2,7 +2,6 @@ package kubernetes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
||||||
"k8s.io/client-go/discovery/cached/memory"
|
"k8s.io/client-go/discovery/cached/memory"
|
||||||
@@ -12,10 +11,6 @@ import (
|
|||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
KubeConfigTargetParameterName = "context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ManagerProvider interface {
|
type ManagerProvider interface {
|
||||||
GetTargets(ctx context.Context) ([]string, error)
|
GetTargets(ctx context.Context) ([]string, error)
|
||||||
GetManagerFor(ctx context.Context, target string) (*Manager, error)
|
GetManagerFor(ctx context.Context, target string) (*Manager, error)
|
||||||
@@ -25,20 +20,6 @@ type ManagerProvider interface {
|
|||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type kubeConfigClusterProvider struct {
|
|
||||||
defaultContext string
|
|
||||||
managers map[string]*Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ManagerProvider = &kubeConfigClusterProvider{}
|
|
||||||
|
|
||||||
type singleClusterProvider struct {
|
|
||||||
strategy string
|
|
||||||
manager *Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ManagerProvider = &singleClusterProvider{}
|
|
||||||
|
|
||||||
func NewManagerProvider(cfg *config.StaticConfig) (ManagerProvider, error) {
|
func NewManagerProvider(cfg *config.StaticConfig) (ManagerProvider, error) {
|
||||||
m, err := NewManager(cfg)
|
m, err := NewManager(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,137 +27,13 @@ func NewManagerProvider(cfg *config.StaticConfig) (ManagerProvider, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
strategy := resolveStrategy(cfg, m)
|
strategy := resolveStrategy(cfg, m)
|
||||||
switch strategy {
|
|
||||||
case config.ClusterProviderKubeConfig:
|
|
||||||
return newKubeConfigClusterProvider(m)
|
|
||||||
case config.ClusterProviderInCluster, config.ClusterProviderDisabled:
|
|
||||||
return newSingleClusterProvider(m, strategy)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"invalid ClusterProviderStrategy '%s', must be 'kubeconfig', 'in-cluster', or 'disabled'",
|
|
||||||
strategy,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newKubeConfigClusterProvider(m *Manager) (*kubeConfigClusterProvider, error) {
|
factory, err := getProviderFactory(strategy)
|
||||||
// Handle in-cluster mode
|
|
||||||
if m.IsInCluster() {
|
|
||||||
return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments")
|
|
||||||
}
|
|
||||||
|
|
||||||
rawConfig, err := m.clientCmdConfig.RawConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
allClusterManagers := map[string]*Manager{
|
return factory(m, cfg)
|
||||||
rawConfig.CurrentContext: m, // we already initialized a manager for the default context, let's use it
|
|
||||||
}
|
|
||||||
|
|
||||||
for name := range rawConfig.Contexts {
|
|
||||||
if name == rawConfig.CurrentContext {
|
|
||||||
continue // already initialized this, don't want to set it to nil
|
|
||||||
}
|
|
||||||
|
|
||||||
allClusterManagers[name] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &kubeConfigClusterProvider{
|
|
||||||
defaultContext: rawConfig.CurrentContext,
|
|
||||||
managers: allClusterManagers,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSingleClusterProvider(m *Manager, strategy string) (*singleClusterProvider, error) {
|
|
||||||
if strategy == config.ClusterProviderInCluster && !m.IsInCluster() {
|
|
||||||
return nil, fmt.Errorf("server must be deployed in cluster for the in-cluster ClusterProviderStrategy")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &singleClusterProvider{
|
|
||||||
manager: m,
|
|
||||||
strategy: strategy,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
|
|
||||||
contextNames := make([]string, 0, len(k.managers))
|
|
||||||
for cluster := range k.managers {
|
|
||||||
contextNames = append(contextNames, cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextNames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kubeConfigClusterProvider) GetTargetParameterName() string {
|
|
||||||
return KubeConfigTargetParameterName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kubeConfigClusterProvider) GetManagerFor(ctx context.Context, context string) (*Manager, error) {
|
|
||||||
m, ok := k.managers[context]
|
|
||||||
if ok && m != nil {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
baseManager := k.managers[k.defaultContext]
|
|
||||||
|
|
||||||
if baseManager.IsInCluster() {
|
|
||||||
// In cluster mode, so context switching is not applicable
|
|
||||||
return baseManager, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := baseManager.newForContext(context)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k.managers[context] = m
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kubeConfigClusterProvider) GetDefaultTarget() string {
|
|
||||||
return k.defaultContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) {
|
|
||||||
m := k.managers[k.defaultContext]
|
|
||||||
|
|
||||||
m.WatchKubeConfig(onKubeConfigChanged)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kubeConfigClusterProvider) Close() {
|
|
||||||
m := k.managers[k.defaultContext]
|
|
||||||
|
|
||||||
m.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
|
|
||||||
return []string{""}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleClusterProvider) GetManagerFor(ctx context.Context, target string) (*Manager, error) {
|
|
||||||
if target != "" {
|
|
||||||
return nil, fmt.Errorf("unable to get manager for other context/cluster with %s strategy", s.strategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.manager, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleClusterProvider) GetDefaultTarget() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleClusterProvider) GetTargetParameterName() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleClusterProvider) WatchTargets(watch func() error) {
|
|
||||||
s.manager.WatchKubeConfig(watch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleClusterProvider) Close() {
|
|
||||||
s.manager.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) newForContext(context string) (*Manager, error) {
|
func (m *Manager) newForContext(context string) (*Manager, error) {
|
||||||
|
|||||||
109
pkg/kubernetes/provider_kubeconfig.go
Normal file
109
pkg/kubernetes/provider_kubeconfig.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KubeConfigTargetParameterName is the parameter name used to specify
|
||||||
|
// the kubeconfig context when using the kubeconfig cluster provider strategy.
|
||||||
|
const KubeConfigTargetParameterName = "context"
|
||||||
|
|
||||||
|
// kubeConfigClusterProvider implements ManagerProvider for managing multiple
|
||||||
|
// Kubernetes clusters using different contexts from a kubeconfig file.
|
||||||
|
// It lazily initializes managers for each context as they are requested.
|
||||||
|
type kubeConfigClusterProvider struct {
|
||||||
|
defaultContext string
|
||||||
|
managers map[string]*Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ManagerProvider = &kubeConfigClusterProvider{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProvider(config.ClusterProviderKubeConfig, newKubeConfigClusterProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKubeConfigClusterProvider creates a provider that manages multiple clusters
|
||||||
|
// via kubeconfig contexts. Returns an error if the manager is in-cluster mode.
|
||||||
|
func newKubeConfigClusterProvider(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) {
|
||||||
|
// Handle in-cluster mode
|
||||||
|
if m.IsInCluster() {
|
||||||
|
return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawConfig, err := m.clientCmdConfig.RawConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allClusterManagers := map[string]*Manager{
|
||||||
|
rawConfig.CurrentContext: m, // we already initialized a manager for the default context, let's use it
|
||||||
|
}
|
||||||
|
|
||||||
|
for name := range rawConfig.Contexts {
|
||||||
|
if name == rawConfig.CurrentContext {
|
||||||
|
continue // already initialized this, don't want to set it to nil
|
||||||
|
}
|
||||||
|
|
||||||
|
allClusterManagers[name] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &kubeConfigClusterProvider{
|
||||||
|
defaultContext: rawConfig.CurrentContext,
|
||||||
|
managers: allClusterManagers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
|
||||||
|
contextNames := make([]string, 0, len(k.managers))
|
||||||
|
for cluster := range k.managers {
|
||||||
|
contextNames = append(contextNames, cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextNames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubeConfigClusterProvider) GetTargetParameterName() string {
|
||||||
|
return KubeConfigTargetParameterName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubeConfigClusterProvider) GetManagerFor(ctx context.Context, context string) (*Manager, error) {
|
||||||
|
m, ok := k.managers[context]
|
||||||
|
if ok && m != nil {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
baseManager := k.managers[k.defaultContext]
|
||||||
|
|
||||||
|
if baseManager.IsInCluster() {
|
||||||
|
// In cluster mode, so context switching is not applicable
|
||||||
|
return baseManager, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := baseManager.newForContext(context)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k.managers[context] = m
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubeConfigClusterProvider) GetDefaultTarget() string {
|
||||||
|
return k.defaultContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) {
|
||||||
|
m := k.managers[k.defaultContext]
|
||||||
|
|
||||||
|
m.WatchKubeConfig(onKubeConfigChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *kubeConfigClusterProvider) Close() {
|
||||||
|
m := k.managers[k.defaultContext]
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
47
pkg/kubernetes/provider_registry.go
Normal file
47
pkg/kubernetes/provider_registry.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderFactory creates a new ManagerProvider instance for a given strategy.
|
||||||
|
// Implementations should validate that the Manager is compatible with their strategy
|
||||||
|
// (e.g., kubeconfig provider should reject in-cluster managers).
|
||||||
|
type ProviderFactory func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error)
|
||||||
|
|
||||||
|
var providerFactories = make(map[string]ProviderFactory)
|
||||||
|
|
||||||
|
// RegisterProvider registers a provider factory for a given strategy name.
|
||||||
|
// This should be called from init() functions in provider implementation files.
|
||||||
|
// Panics if a provider is already registered for the given strategy.
|
||||||
|
func RegisterProvider(strategy string, factory ProviderFactory) {
|
||||||
|
if _, exists := providerFactories[strategy]; exists {
|
||||||
|
panic(fmt.Sprintf("provider already registered for strategy '%s'", strategy))
|
||||||
|
}
|
||||||
|
providerFactories[strategy] = factory
|
||||||
|
}
|
||||||
|
|
||||||
|
// getProviderFactory retrieves a registered provider factory by strategy name.
|
||||||
|
// Returns an error if no provider is registered for the given strategy.
|
||||||
|
func getProviderFactory(strategy string) (ProviderFactory, error) {
|
||||||
|
factory, ok := providerFactories[strategy]
|
||||||
|
if !ok {
|
||||||
|
available := GetRegisteredStrategies()
|
||||||
|
return nil, fmt.Errorf("no provider registered for strategy '%s', available strategies: %v", strategy, available)
|
||||||
|
}
|
||||||
|
return factory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegisteredStrategies returns a sorted list of all registered strategy names.
|
||||||
|
// This is useful for error messages and debugging.
|
||||||
|
func GetRegisteredStrategies() []string {
|
||||||
|
strategies := make([]string, 0, len(providerFactories))
|
||||||
|
for strategy := range providerFactories {
|
||||||
|
strategies = append(strategies, strategy)
|
||||||
|
}
|
||||||
|
sort.Strings(strategies)
|
||||||
|
return strategies
|
||||||
|
}
|
||||||
66
pkg/kubernetes/provider_single.go
Normal file
66
pkg/kubernetes/provider_single.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// singleClusterProvider implements ManagerProvider for managing a single
|
||||||
|
// Kubernetes cluster. Used for in-cluster deployments or when multi-cluster
|
||||||
|
// support is disabled.
|
||||||
|
type singleClusterProvider struct {
|
||||||
|
strategy string
|
||||||
|
manager *Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ManagerProvider = &singleClusterProvider{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProvider(config.ClusterProviderInCluster, newSingleClusterProvider(config.ClusterProviderInCluster))
|
||||||
|
RegisterProvider(config.ClusterProviderDisabled, newSingleClusterProvider(config.ClusterProviderDisabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSingleClusterProvider creates a provider that manages a single cluster.
|
||||||
|
// Validates that the manager is in-cluster when the in-cluster strategy is used.
|
||||||
|
func newSingleClusterProvider(strategy string) ProviderFactory {
|
||||||
|
return func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) {
|
||||||
|
if strategy == config.ClusterProviderInCluster && !m.IsInCluster() {
|
||||||
|
return nil, fmt.Errorf("server must be deployed in cluster for the in-cluster ClusterProviderStrategy")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &singleClusterProvider{
|
||||||
|
manager: m,
|
||||||
|
strategy: strategy,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
|
||||||
|
return []string{""}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleClusterProvider) GetManagerFor(ctx context.Context, target string) (*Manager, error) {
|
||||||
|
if target != "" {
|
||||||
|
return nil, fmt.Errorf("unable to get manager for other context/cluster with %s strategy", s.strategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.manager, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleClusterProvider) GetDefaultTarget() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleClusterProvider) GetTargetParameterName() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleClusterProvider) WatchTargets(watch func() error) {
|
||||||
|
s.manager.WatchKubeConfig(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleClusterProvider) Close() {
|
||||||
|
s.manager.Close()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user