Skip to content

Commit 31e90fb

Browse files
authored
refactor(kubernetes): move provider initialization into factory (#365)
Signed-off-by: Calum Murray <[email protected]>
1 parent 99e9543 commit 31e90fb

File tree

4 files changed

+224
-145
lines changed

4 files changed

+224
-145
lines changed

pkg/kubernetes/provider.go

Lines changed: 2 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package kubernetes
22

33
import (
44
"context"
5-
"fmt"
65

76
"github.com/containers/kubernetes-mcp-server/pkg/config"
87
"k8s.io/client-go/discovery/cached/memory"
@@ -12,10 +11,6 @@ import (
1211
"k8s.io/client-go/tools/clientcmd"
1312
)
1413

15-
const (
16-
KubeConfigTargetParameterName = "context"
17-
)
18-
1914
type ManagerProvider interface {
2015
GetTargets(ctx context.Context) ([]string, error)
2116
GetManagerFor(ctx context.Context, target string) (*Manager, error)
@@ -25,158 +20,20 @@ type ManagerProvider interface {
2520
Close()
2621
}
2722

28-
type kubeConfigClusterProvider struct {
29-
defaultContext string
30-
managers map[string]*Manager
31-
}
32-
33-
var _ ManagerProvider = &kubeConfigClusterProvider{}
34-
35-
type singleClusterProvider struct {
36-
strategy string
37-
manager *Manager
38-
}
39-
40-
var _ ManagerProvider = &singleClusterProvider{}
41-
4223
func NewManagerProvider(cfg *config.StaticConfig) (ManagerProvider, error) {
4324
m, err := NewManager(cfg)
4425
if err != nil {
4526
return nil, err
4627
}
4728

4829
strategy := resolveStrategy(cfg, m)
49-
switch strategy {
50-
case config.ClusterProviderKubeConfig:
51-
return newKubeConfigClusterProvider(m)
52-
case config.ClusterProviderInCluster, config.ClusterProviderDisabled:
53-
return newSingleClusterProvider(m, strategy)
54-
default:
55-
return nil, fmt.Errorf(
56-
"invalid ClusterProviderStrategy '%s', must be 'kubeconfig', 'in-cluster', or 'disabled'",
57-
strategy,
58-
)
59-
}
60-
}
61-
62-
func newKubeConfigClusterProvider(m *Manager) (*kubeConfigClusterProvider, error) {
63-
// Handle in-cluster mode
64-
if m.IsInCluster() {
65-
return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments")
66-
}
67-
68-
rawConfig, err := m.clientCmdConfig.RawConfig()
69-
if err != nil {
70-
return nil, err
71-
}
72-
73-
allClusterManagers := map[string]*Manager{
74-
rawConfig.CurrentContext: m, // we already initialized a manager for the default context, let's use it
75-
}
76-
77-
for name := range rawConfig.Contexts {
78-
if name == rawConfig.CurrentContext {
79-
continue // already initialized this, don't want to set it to nil
80-
}
81-
82-
allClusterManagers[name] = nil
83-
}
84-
85-
return &kubeConfigClusterProvider{
86-
defaultContext: rawConfig.CurrentContext,
87-
managers: allClusterManagers,
88-
}, nil
89-
}
90-
91-
func newSingleClusterProvider(m *Manager, strategy string) (*singleClusterProvider, error) {
92-
if strategy == config.ClusterProviderInCluster && !m.IsInCluster() {
93-
return nil, fmt.Errorf("server must be deployed in cluster for the in-cluster ClusterProviderStrategy")
94-
}
95-
96-
return &singleClusterProvider{
97-
manager: m,
98-
strategy: strategy,
99-
}, nil
100-
}
101-
102-
func (k *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
103-
contextNames := make([]string, 0, len(k.managers))
104-
for cluster := range k.managers {
105-
contextNames = append(contextNames, cluster)
106-
}
107-
108-
return contextNames, nil
109-
}
110-
111-
func (k *kubeConfigClusterProvider) GetTargetParameterName() string {
112-
return KubeConfigTargetParameterName
113-
}
114-
115-
func (k *kubeConfigClusterProvider) GetManagerFor(ctx context.Context, context string) (*Manager, error) {
116-
m, ok := k.managers[context]
117-
if ok && m != nil {
118-
return m, nil
119-
}
120-
121-
baseManager := k.managers[k.defaultContext]
122-
123-
if baseManager.IsInCluster() {
124-
// In cluster mode, so context switching is not applicable
125-
return baseManager, nil
126-
}
12730

128-
m, err := baseManager.newForContext(context)
31+
factory, err := getProviderFactory(strategy)
12932
if err != nil {
13033
return nil, err
13134
}
13235

133-
k.managers[context] = m
134-
135-
return m, nil
136-
}
137-
138-
func (k *kubeConfigClusterProvider) GetDefaultTarget() string {
139-
return k.defaultContext
140-
}
141-
142-
func (k *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) {
143-
m := k.managers[k.defaultContext]
144-
145-
m.WatchKubeConfig(onKubeConfigChanged)
146-
}
147-
148-
func (k *kubeConfigClusterProvider) Close() {
149-
m := k.managers[k.defaultContext]
150-
151-
m.Close()
152-
}
153-
154-
func (s *singleClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
155-
return []string{""}, nil
156-
}
157-
158-
func (s *singleClusterProvider) GetManagerFor(ctx context.Context, target string) (*Manager, error) {
159-
if target != "" {
160-
return nil, fmt.Errorf("unable to get manager for other context/cluster with %s strategy", s.strategy)
161-
}
162-
163-
return s.manager, nil
164-
}
165-
166-
func (s *singleClusterProvider) GetDefaultTarget() string {
167-
return ""
168-
}
169-
170-
func (s *singleClusterProvider) GetTargetParameterName() string {
171-
return ""
172-
}
173-
174-
func (s *singleClusterProvider) WatchTargets(watch func() error) {
175-
s.manager.WatchKubeConfig(watch)
176-
}
177-
178-
func (s *singleClusterProvider) Close() {
179-
s.manager.Close()
36+
return factory(m, cfg)
18037
}
18138

18239
func (m *Manager) newForContext(context string) (*Manager, error) {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package kubernetes
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/containers/kubernetes-mcp-server/pkg/config"
8+
)
9+
10+
// KubeConfigTargetParameterName is the parameter name used to specify
11+
// the kubeconfig context when using the kubeconfig cluster provider strategy.
12+
const KubeConfigTargetParameterName = "context"
13+
14+
// kubeConfigClusterProvider implements ManagerProvider for managing multiple
15+
// Kubernetes clusters using different contexts from a kubeconfig file.
16+
// It lazily initializes managers for each context as they are requested.
17+
type kubeConfigClusterProvider struct {
18+
defaultContext string
19+
managers map[string]*Manager
20+
}
21+
22+
var _ ManagerProvider = &kubeConfigClusterProvider{}
23+
24+
func init() {
25+
RegisterProvider(config.ClusterProviderKubeConfig, newKubeConfigClusterProvider)
26+
}
27+
28+
// newKubeConfigClusterProvider creates a provider that manages multiple clusters
29+
// via kubeconfig contexts. Returns an error if the manager is in-cluster mode.
30+
func newKubeConfigClusterProvider(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) {
31+
// Handle in-cluster mode
32+
if m.IsInCluster() {
33+
return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments")
34+
}
35+
36+
rawConfig, err := m.clientCmdConfig.RawConfig()
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
allClusterManagers := map[string]*Manager{
42+
rawConfig.CurrentContext: m, // we already initialized a manager for the default context, let's use it
43+
}
44+
45+
for name := range rawConfig.Contexts {
46+
if name == rawConfig.CurrentContext {
47+
continue // already initialized this, don't want to set it to nil
48+
}
49+
50+
allClusterManagers[name] = nil
51+
}
52+
53+
return &kubeConfigClusterProvider{
54+
defaultContext: rawConfig.CurrentContext,
55+
managers: allClusterManagers,
56+
}, nil
57+
}
58+
59+
func (k *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
60+
contextNames := make([]string, 0, len(k.managers))
61+
for cluster := range k.managers {
62+
contextNames = append(contextNames, cluster)
63+
}
64+
65+
return contextNames, nil
66+
}
67+
68+
func (k *kubeConfigClusterProvider) GetTargetParameterName() string {
69+
return KubeConfigTargetParameterName
70+
}
71+
72+
func (k *kubeConfigClusterProvider) GetManagerFor(ctx context.Context, context string) (*Manager, error) {
73+
m, ok := k.managers[context]
74+
if ok && m != nil {
75+
return m, nil
76+
}
77+
78+
baseManager := k.managers[k.defaultContext]
79+
80+
if baseManager.IsInCluster() {
81+
// In cluster mode, so context switching is not applicable
82+
return baseManager, nil
83+
}
84+
85+
m, err := baseManager.newForContext(context)
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
k.managers[context] = m
91+
92+
return m, nil
93+
}
94+
95+
func (k *kubeConfigClusterProvider) GetDefaultTarget() string {
96+
return k.defaultContext
97+
}
98+
99+
func (k *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) {
100+
m := k.managers[k.defaultContext]
101+
102+
m.WatchKubeConfig(onKubeConfigChanged)
103+
}
104+
105+
func (k *kubeConfigClusterProvider) Close() {
106+
m := k.managers[k.defaultContext]
107+
108+
m.Close()
109+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package kubernetes
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
7+
"github.com/containers/kubernetes-mcp-server/pkg/config"
8+
)
9+
10+
// ProviderFactory creates a new ManagerProvider instance for a given strategy.
11+
// Implementations should validate that the Manager is compatible with their strategy
12+
// (e.g., kubeconfig provider should reject in-cluster managers).
13+
type ProviderFactory func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error)
14+
15+
var providerFactories = make(map[string]ProviderFactory)
16+
17+
// RegisterProvider registers a provider factory for a given strategy name.
18+
// This should be called from init() functions in provider implementation files.
19+
// Panics if a provider is already registered for the given strategy.
20+
func RegisterProvider(strategy string, factory ProviderFactory) {
21+
if _, exists := providerFactories[strategy]; exists {
22+
panic(fmt.Sprintf("provider already registered for strategy '%s'", strategy))
23+
}
24+
providerFactories[strategy] = factory
25+
}
26+
27+
// getProviderFactory retrieves a registered provider factory by strategy name.
28+
// Returns an error if no provider is registered for the given strategy.
29+
func getProviderFactory(strategy string) (ProviderFactory, error) {
30+
factory, ok := providerFactories[strategy]
31+
if !ok {
32+
available := GetRegisteredStrategies()
33+
return nil, fmt.Errorf("no provider registered for strategy '%s', available strategies: %v", strategy, available)
34+
}
35+
return factory, nil
36+
}
37+
38+
// GetRegisteredStrategies returns a sorted list of all registered strategy names.
39+
// This is useful for error messages and debugging.
40+
func GetRegisteredStrategies() []string {
41+
strategies := make([]string, 0, len(providerFactories))
42+
for strategy := range providerFactories {
43+
strategies = append(strategies, strategy)
44+
}
45+
sort.Strings(strategies)
46+
return strategies
47+
}

0 commit comments

Comments
 (0)