Skip to content

Commit d67ac47

Browse files
committed
review(kubernetes): force usage of Derived kubernetes
Addresses comment by ardaguclu
1 parent 54d564a commit d67ac47

File tree

9 files changed

+129
-152
lines changed

9 files changed

+129
-152
lines changed

pkg/kubernetes/configuration.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var InClusterConfig = func() (*rest.Config, error) {
2121
}
2222

2323
// resolveKubernetesConfigurations resolves the required kubernetes configurations and sets them in the Kubernetes struct
24-
func resolveKubernetesConfigurations(kubernetes *kubernetes) error {
24+
func resolveKubernetesConfigurations(kubernetes *Manager) error {
2525
// Always set clientCmdConfig
2626
pathOptions := clientcmd.NewDefaultPathOptions()
2727
if kubernetes.Kubeconfig != "" {
@@ -45,56 +45,60 @@ func resolveKubernetesConfigurations(kubernetes *kubernetes) error {
4545
return err
4646
}
4747

48-
func (k *kubernetes) IsInCluster() bool {
49-
if k.Kubeconfig != "" {
48+
func (m *Manager) IsInCluster() bool {
49+
if m.Kubeconfig != "" {
5050
return false
5151
}
5252
cfg, err := InClusterConfig()
5353
return err == nil && cfg != nil
5454
}
5555

56-
func (k *kubernetes) configuredNamespace() string {
57-
if ns, _, nsErr := k.clientCmdConfig.Namespace(); nsErr == nil {
56+
func (m *Manager) configuredNamespace() string {
57+
if ns, _, nsErr := m.clientCmdConfig.Namespace(); nsErr == nil {
5858
return ns
5959
}
6060
return ""
6161
}
6262

63-
func (k *kubernetes) NamespaceOrDefault(namespace string) string {
63+
func (m *Manager) NamespaceOrDefault(namespace string) string {
6464
if namespace == "" {
65-
return k.configuredNamespace()
65+
return m.configuredNamespace()
6666
}
6767
return namespace
6868
}
6969

70+
func (k *Kubernetes) NamespaceOrDefault(namespace string) string {
71+
return k.manager.NamespaceOrDefault(namespace)
72+
}
73+
7074
// ToRESTConfig returns the rest.Config object (genericclioptions.RESTClientGetter)
71-
func (k *kubernetes) ToRESTConfig() (*rest.Config, error) {
72-
return k.cfg, nil
75+
func (m *Manager) ToRESTConfig() (*rest.Config, error) {
76+
return m.cfg, nil
7377
}
7478

7579
// ToRawKubeConfigLoader returns the clientcmd.ClientConfig object (genericclioptions.RESTClientGetter)
76-
func (k *kubernetes) ToRawKubeConfigLoader() clientcmd.ClientConfig {
77-
return k.clientCmdConfig
80+
func (m *Manager) ToRawKubeConfigLoader() clientcmd.ClientConfig {
81+
return m.clientCmdConfig
7882
}
7983

80-
func (k *kubernetes) ConfigurationView(minify bool) (runtime.Object, error) {
84+
func (m *Manager) ConfigurationView(minify bool) (runtime.Object, error) {
8185
var cfg clientcmdapi.Config
8286
var err error
83-
if k.IsInCluster() {
87+
if m.IsInCluster() {
8488
cfg = *clientcmdapi.NewConfig()
8589
cfg.Clusters["cluster"] = &clientcmdapi.Cluster{
86-
Server: k.cfg.Host,
87-
InsecureSkipTLSVerify: k.cfg.Insecure,
90+
Server: m.cfg.Host,
91+
InsecureSkipTLSVerify: m.cfg.Insecure,
8892
}
8993
cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{
90-
Token: k.cfg.BearerToken,
94+
Token: m.cfg.BearerToken,
9195
}
9296
cfg.Contexts["context"] = &clientcmdapi.Context{
9397
Cluster: "cluster",
9498
AuthInfo: "user",
9599
}
96100
cfg.CurrentContext = "context"
97-
} else if cfg, err = k.clientCmdConfig.RawConfig(); err != nil {
101+
} else if cfg, err = m.clientCmdConfig.RawConfig(); err != nil {
98102
return nil, err
99103
}
100104
if minify {

pkg/kubernetes/configuration_test.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import (
1212

1313
func TestKubernetes_IsInCluster(t *testing.T) {
1414
t.Run("with explicit kubeconfig", func(t *testing.T) {
15-
k := kubernetes{
15+
m := Manager{
1616
Kubeconfig: "kubeconfig",
1717
}
18-
if k.IsInCluster() {
18+
if m.IsInCluster() {
1919
t.Errorf("expected not in cluster, got in cluster")
2020
}
2121
})
@@ -27,10 +27,10 @@ func TestKubernetes_IsInCluster(t *testing.T) {
2727
defer func() {
2828
InClusterConfig = originalFunction
2929
}()
30-
k := kubernetes{
30+
m := Manager{
3131
Kubeconfig: "",
3232
}
33-
if !k.IsInCluster() {
33+
if !m.IsInCluster() {
3434
t.Errorf("expected in cluster, got not in cluster")
3535
}
3636
})
@@ -42,10 +42,10 @@ func TestKubernetes_IsInCluster(t *testing.T) {
4242
defer func() {
4343
InClusterConfig = originalFunction
4444
}()
45-
k := kubernetes{
45+
m := Manager{
4646
Kubeconfig: "",
4747
}
48-
if k.IsInCluster() {
48+
if m.IsInCluster() {
4949
t.Errorf("expected not in cluster, got in cluster")
5050
}
5151
})
@@ -57,10 +57,10 @@ func TestKubernetes_IsInCluster(t *testing.T) {
5757
defer func() {
5858
InClusterConfig = originalFunction
5959
}()
60-
k := kubernetes{
60+
m := Manager{
6161
Kubeconfig: "",
6262
}
63-
if k.IsInCluster() {
63+
if m.IsInCluster() {
6464
t.Errorf("expected not in cluster, got in cluster")
6565
}
6666
})
@@ -72,8 +72,8 @@ func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) {
7272
t.Skip("Skipping test on non-linux platforms")
7373
}
7474
tempDir := t.TempDir()
75-
k := kubernetes{Kubeconfig: path.Join(tempDir, "config")}
76-
err := resolveKubernetesConfigurations(&k)
75+
m := Manager{Kubeconfig: path.Join(tempDir, "config")}
76+
err := resolveKubernetesConfigurations(&m)
7777
if err == nil {
7878
t.Errorf("expected error, got nil")
7979
}
@@ -90,8 +90,8 @@ func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) {
9090
if err := os.WriteFile(kubeconfigPath, []byte(""), 0644); err != nil {
9191
t.Fatalf("failed to create kubeconfig file: %v", err)
9292
}
93-
k := kubernetes{Kubeconfig: kubeconfigPath}
94-
err := resolveKubernetesConfigurations(&k)
93+
m := Manager{Kubeconfig: kubeconfigPath}
94+
err := resolveKubernetesConfigurations(&m)
9595
if err == nil {
9696
t.Errorf("expected error, got nil")
9797
}
@@ -123,16 +123,16 @@ users:
123123
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil {
124124
t.Fatalf("failed to create kubeconfig file: %v", err)
125125
}
126-
k := kubernetes{Kubeconfig: kubeconfigPath}
127-
err := resolveKubernetesConfigurations(&k)
126+
m := Manager{Kubeconfig: kubeconfigPath}
127+
err := resolveKubernetesConfigurations(&m)
128128
if err != nil {
129129
t.Fatalf("expected no error, got %v", err)
130130
}
131-
if k.cfg == nil {
131+
if m.cfg == nil {
132132
t.Errorf("expected non-nil config, got nil")
133133
}
134-
if k.cfg.Host != "https://example.com" {
135-
t.Errorf("expected host https://example.com, got %s", k.cfg.Host)
134+
if m.cfg.Host != "https://example.com" {
135+
t.Errorf("expected host https://example.com, got %s", m.cfg.Host)
136136
}
137137
})
138138
}

pkg/kubernetes/events.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"strings"
1010
)
1111

12-
func (k *kubernetes) EventsList(ctx context.Context, namespace string) ([]map[string]any, error) {
12+
func (k *Kubernetes) EventsList(ctx context.Context, namespace string) ([]map[string]any, error) {
1313
var eventMap []map[string]any
1414
raw, err := k.ResourcesList(ctx, &schema.GroupVersionKind{
1515
Group: "", Version: "v1", Kind: "Event",

pkg/kubernetes/kubernetes.go

Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import (
66
"github.com/manusa/kubernetes-mcp-server/pkg/helm"
77
v1 "k8s.io/api/core/v1"
88
"k8s.io/apimachinery/pkg/api/meta"
9-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
109
"k8s.io/apimachinery/pkg/runtime"
11-
"k8s.io/apimachinery/pkg/runtime/schema"
1210
"k8s.io/client-go/discovery"
1311
"k8s.io/client-go/discovery/cached/memory"
1412
"k8s.io/client-go/dynamic"
@@ -19,7 +17,6 @@ import (
1917
"k8s.io/client-go/tools/clientcmd"
2018
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2119
"k8s.io/klog/v2"
22-
"k8s.io/metrics/pkg/apis/metrics"
2320
"strings"
2421
)
2522

@@ -29,36 +26,11 @@ const (
2926

3027
type CloseWatchKubeConfig func() error
3128

32-
type Kubernetes interface {
33-
WatchKubeConfig(onKubeConfigChange func() error)
34-
Close()
35-
Derived(ctx context.Context) DerivedKubernetes
36-
ConfigurationView(minify bool) (runtime.Object, error)
37-
IsOpenShift(ctx context.Context) bool
29+
type Kubernetes struct {
30+
manager *Manager
3831
}
3932

40-
type DerivedKubernetes interface {
41-
IsOpenShift(ctx context.Context) bool
42-
CacheInvalidate()
43-
NewHelm() *helm.Helm
44-
EventsList(ctx context.Context, namespace string) ([]map[string]any, error)
45-
NamespacesList(ctx context.Context, options ResourceListOptions) (runtime.Unstructured, error)
46-
PodsListInAllNamespaces(ctx context.Context, options ResourceListOptions) (runtime.Unstructured, error)
47-
PodsListInNamespace(ctx context.Context, namespace string, options ResourceListOptions) (runtime.Unstructured, error)
48-
PodsGet(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error)
49-
PodsDelete(ctx context.Context, namespace, name string) (string, error)
50-
PodsLog(ctx context.Context, namespace, name, container string) (string, error)
51-
PodsRun(ctx context.Context, namespace, name, image string, port int32) ([]*unstructured.Unstructured, error)
52-
PodsTop(ctx context.Context, options PodsTopOptions) (*metrics.PodMetricsList, error)
53-
PodsExec(ctx context.Context, namespace, name, container string, command []string) (string, error)
54-
ProjectsList(ctx context.Context, options ResourceListOptions) (runtime.Unstructured, error)
55-
ResourcesList(ctx context.Context, gvk *schema.GroupVersionKind, namespace string, options ResourceListOptions) (runtime.Unstructured, error)
56-
ResourcesGet(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error)
57-
ResourcesCreateOrUpdate(ctx context.Context, resource string) ([]*unstructured.Unstructured, error)
58-
ResourcesDelete(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) error
59-
}
60-
61-
type kubernetes struct {
33+
type Manager struct {
6234
// Kubeconfig path override
6335
Kubeconfig string
6436
cfg *rest.Config
@@ -72,8 +44,8 @@ type kubernetes struct {
7244
dynamicClient *dynamic.DynamicClient
7345
}
7446

75-
func NewKubernetes(kubeconfig string) (Kubernetes, error) {
76-
k8s := &kubernetes{
47+
func NewKubernetes(kubeconfig string) (*Manager, error) {
48+
k8s := &Manager{
7749
Kubeconfig: kubeconfig,
7850
}
7951
if err := resolveKubernetesConfigurations(k8s); err != nil {
@@ -102,11 +74,11 @@ func NewKubernetes(kubeconfig string) (Kubernetes, error) {
10274
return k8s, nil
10375
}
10476

105-
func (k *kubernetes) WatchKubeConfig(onKubeConfigChange func() error) {
106-
if k.clientCmdConfig == nil {
77+
func (m *Manager) WatchKubeConfig(onKubeConfigChange func() error) {
78+
if m.clientCmdConfig == nil {
10779
return
10880
}
109-
kubeConfigFiles := k.clientCmdConfig.ConfigAccess().GetLoadingPrecedence()
81+
kubeConfigFiles := m.clientCmdConfig.ConfigAccess().GetLoadingPrecedence()
11082
if len(kubeConfigFiles) == 0 {
11183
return
11284
}
@@ -132,33 +104,33 @@ func (k *kubernetes) WatchKubeConfig(onKubeConfigChange func() error) {
132104
}
133105
}
134106
}()
135-
if k.CloseWatchKubeConfig != nil {
136-
_ = k.CloseWatchKubeConfig()
107+
if m.CloseWatchKubeConfig != nil {
108+
_ = m.CloseWatchKubeConfig()
137109
}
138-
k.CloseWatchKubeConfig = watcher.Close
110+
m.CloseWatchKubeConfig = watcher.Close
139111
}
140112

141-
func (k *kubernetes) Close() {
142-
if k.CloseWatchKubeConfig != nil {
143-
_ = k.CloseWatchKubeConfig()
113+
func (m *Manager) Close() {
114+
if m.CloseWatchKubeConfig != nil {
115+
_ = m.CloseWatchKubeConfig()
144116
}
145117
}
146118

147-
func (k *kubernetes) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
148-
return k.discoveryClient, nil
119+
func (m *Manager) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
120+
return m.discoveryClient, nil
149121
}
150122

151-
func (k *kubernetes) ToRESTMapper() (meta.RESTMapper, error) {
152-
return k.deferredDiscoveryRESTMapper, nil
123+
func (m *Manager) ToRESTMapper() (meta.RESTMapper, error) {
124+
return m.deferredDiscoveryRESTMapper, nil
153125
}
154126

155-
func (k *kubernetes) Derived(ctx context.Context) DerivedKubernetes {
127+
func (m *Manager) Derived(ctx context.Context) *Kubernetes {
156128
authorization, ok := ctx.Value(AuthorizationHeader).(string)
157129
if !ok || !strings.HasPrefix(authorization, "Bearer ") {
158-
return k
130+
return &Kubernetes{manager: m}
159131
}
160132
klog.V(5).Infof("%s header found (Bearer), using provided bearer token", AuthorizationHeader)
161-
derivedCfg := rest.CopyConfig(k.cfg)
133+
derivedCfg := rest.CopyConfig(m.cfg)
162134
derivedCfg.BearerToken = strings.TrimPrefix(authorization, "Bearer ")
163135
derivedCfg.BearerTokenFile = ""
164136
derivedCfg.Username = ""
@@ -167,41 +139,42 @@ func (k *kubernetes) Derived(ctx context.Context) DerivedKubernetes {
167139
derivedCfg.AuthConfigPersister = nil
168140
derivedCfg.ExecProvider = nil
169141
derivedCfg.Impersonate = rest.ImpersonationConfig{}
170-
clientCmdApiConfig, err := k.clientCmdConfig.RawConfig()
142+
clientCmdApiConfig, err := m.clientCmdConfig.RawConfig()
171143
if err != nil {
172-
return k
144+
return &Kubernetes{manager: m}
173145
}
174146
clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
175-
derived := &kubernetes{
176-
Kubeconfig: k.Kubeconfig,
147+
derived := &Kubernetes{manager: &Manager{
148+
Kubeconfig: m.Kubeconfig,
177149
clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil),
178150
cfg: derivedCfg,
179-
scheme: k.scheme,
180-
parameterCodec: k.parameterCodec,
181-
}
182-
derived.clientSet, err = clientgokubernetes.NewForConfig(derived.cfg)
151+
scheme: m.scheme,
152+
parameterCodec: m.parameterCodec,
153+
}}
154+
derived.manager.clientSet, err = clientgokubernetes.NewForConfig(derived.manager.cfg)
183155
if err != nil {
184-
return k
156+
return &Kubernetes{manager: m}
185157
}
186-
derived.discoveryClient = memory.NewMemCacheClient(discovery.NewDiscoveryClient(derived.clientSet.CoreV1().RESTClient()))
187-
derived.deferredDiscoveryRESTMapper = restmapper.NewDeferredDiscoveryRESTMapper(derived.discoveryClient)
188-
derived.dynamicClient, err = dynamic.NewForConfig(derived.cfg)
158+
derived.manager.discoveryClient = memory.NewMemCacheClient(discovery.NewDiscoveryClient(derived.manager.clientSet.CoreV1().RESTClient()))
159+
derived.manager.deferredDiscoveryRESTMapper = restmapper.NewDeferredDiscoveryRESTMapper(derived.manager.discoveryClient)
160+
derived.manager.dynamicClient, err = dynamic.NewForConfig(derived.manager.cfg)
189161
if err != nil {
190-
return k
162+
return &Kubernetes{manager: m}
191163
}
192164
return derived
193165
}
194166

195-
func (k *kubernetes) CacheInvalidate() {
196-
if k.discoveryClient != nil {
197-
k.discoveryClient.Invalidate()
167+
// TODO: check test to see why cache isn't getting invalidated automatically https://github.com/manusa/kubernetes-mcp-server/pull/125#discussion_r2152194784
168+
func (k *Kubernetes) CacheInvalidate() {
169+
if k.manager.discoveryClient != nil {
170+
k.manager.discoveryClient.Invalidate()
198171
}
199-
if k.deferredDiscoveryRESTMapper != nil {
200-
k.deferredDiscoveryRESTMapper.Reset()
172+
if k.manager.deferredDiscoveryRESTMapper != nil {
173+
k.manager.deferredDiscoveryRESTMapper.Reset()
201174
}
202175
}
203176

204-
func (k *kubernetes) NewHelm() *helm.Helm {
177+
func (k *Kubernetes) NewHelm() *helm.Helm {
205178
// This is a derived Kubernetes, so it already has the Helm initialized
206-
return helm.NewHelm(k)
179+
return helm.NewHelm(k.manager)
207180
}

0 commit comments

Comments
 (0)