Skip to content

Commit 32b388a

Browse files
committed
feat: configuration view works in cluster
1 parent 094da78 commit 32b388a

File tree

4 files changed

+91
-28
lines changed

4 files changed

+91
-28
lines changed

pkg/kubernetes/configuration.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,24 @@ import (
66
)
77

88
func ConfigurationView() (string, error) {
9-
// TODO: consider in cluster run mode (current approach only shows kubeconfig)
10-
cfg, err := resolveConfig().RawConfig()
11-
if err != nil {
9+
var cfg clientcmdapi.Config
10+
var err error
11+
inClusterConfig, err := InClusterConfig()
12+
if err == nil && inClusterConfig != nil {
13+
cfg = *clientcmdapi.NewConfig()
14+
cfg.Clusters["cluster"] = &clientcmdapi.Cluster{
15+
Server: inClusterConfig.Host,
16+
InsecureSkipTLSVerify: inClusterConfig.Insecure,
17+
}
18+
cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{
19+
Token: inClusterConfig.BearerToken,
20+
}
21+
cfg.Contexts["context"] = &clientcmdapi.Context{
22+
Cluster: "cluster",
23+
AuthInfo: "user",
24+
}
25+
cfg.CurrentContext = "context"
26+
} else if cfg, err = resolveConfig().RawConfig(); err != nil {
1227
return "", err
1328
}
1429
if err = clientcmdapi.MinifyConfig(&cfg); err != nil {

pkg/kubernetes/kubernetes.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
"sigs.k8s.io/yaml"
1414
)
1515

16+
// InClusterConfig is a variable that holds the function to get the in-cluster config
17+
// Exposed for testing
18+
var InClusterConfig = rest.InClusterConfig
19+
1620
type Kubernetes struct {
1721
cfg *rest.Config
1822
clientSet *kubernetes.Clientset
@@ -71,24 +75,13 @@ func marshal(v any) (string, error) {
7175

7276
func resolveConfig() clientcmd.ClientConfig {
7377
pathOptions := clientcmd.NewDefaultPathOptions()
74-
//cfg, err := pathOptions.GetStartingConfig()
75-
//if err != nil {
76-
// return nil, err
77-
//}
78-
//if err = clientcmdapi.MinifyConfig(cfg); err != nil {
79-
// return nil, err
80-
//}
81-
//if err = clientcmdapi.FlattenConfig(cfg); err != nil {
82-
// return nil, err
83-
//}
84-
//return cfg, nil
8578
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
8679
&clientcmd.ClientConfigLoadingRules{ExplicitPath: pathOptions.GetDefaultFilename()},
8780
&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}})
8881
}
8982

9083
func resolveClientConfig() (*rest.Config, error) {
91-
inClusterConfig, err := rest.InClusterConfig()
84+
inClusterConfig, err := InClusterConfig()
9285
if err == nil && inClusterConfig != nil {
9386
return inClusterConfig, nil
9487
}

pkg/mcp/configuration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func (s *Server) initConfiguration() []server.ServerTool {
1919
func configurationView(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2020
ret, err := kubernetes.ConfigurationView()
2121
if err != nil {
22-
err = fmt.Errorf("failed to get configuration view: %v", err)
22+
err = fmt.Errorf("failed to get configuration: %v", err)
2323
}
2424
return NewTextResult(ret, err), nil
2525
}

pkg/mcp/configuration_test.go

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package mcp
22

33
import (
4+
"github.com/manusa/kubernetes-mcp-server/pkg/kubernetes"
45
"github.com/mark3labs/mcp-go/mcp"
6+
"k8s.io/client-go/rest"
57
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
68
"sigs.k8s.io/yaml"
79
"testing"
@@ -13,63 +15,116 @@ func TestConfigurationView(t *testing.T) {
1315
t.Run("configuration_view returns configuration", func(t *testing.T) {
1416
if err != nil {
1517
t.Fatalf("call tool failed %v", err)
16-
return
1718
}
1819
})
1920
var decoded *v1.Config
2021
err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded)
2122
t.Run("configuration_view has yaml content", func(t *testing.T) {
2223
if err != nil {
2324
t.Fatalf("invalid tool result content %v", err)
24-
return
2525
}
2626
})
2727
t.Run("configuration_view returns current-context", func(t *testing.T) {
2828
if decoded.CurrentContext != "fake-context" {
2929
t.Fatalf("fake-context not found: %v", decoded.CurrentContext)
30-
return
3130
}
3231
})
3332
t.Run("configuration_view returns context info", func(t *testing.T) {
3433
if len(decoded.Contexts) != 1 {
3534
t.Fatalf("invalid context count, expected 1, got %v", len(decoded.Contexts))
36-
return
3735
}
3836
if decoded.Contexts[0].Name != "fake-context" {
3937
t.Fatalf("fake-context not found: %v", decoded.Contexts)
40-
return
4138
}
4239
if decoded.Contexts[0].Context.Cluster != "fake" {
4340
t.Fatalf("fake-cluster not found: %v", decoded.Contexts)
44-
return
4541
}
4642
if decoded.Contexts[0].Context.AuthInfo != "fake" {
4743
t.Fatalf("fake-auth not found: %v", decoded.Contexts)
48-
return
4944
}
5045
})
5146
t.Run("configuration_view returns cluster info", func(t *testing.T) {
5247
if len(decoded.Clusters) != 1 {
5348
t.Fatalf("invalid cluster count, expected 1, got %v", len(decoded.Clusters))
54-
return
5549
}
5650
if decoded.Clusters[0].Name != "fake" {
5751
t.Fatalf("fake-cluster not found: %v", decoded.Clusters)
58-
return
5952
}
6053
if decoded.Clusters[0].Cluster.Server != "https://example.com" {
6154
t.Fatalf("fake-server not found: %v", decoded.Clusters)
62-
return
6355
}
6456
})
6557
t.Run("configuration_view returns auth info", func(t *testing.T) {
6658
if len(decoded.AuthInfos) != 1 {
6759
t.Fatalf("invalid auth info count, expected 1, got %v", len(decoded.AuthInfos))
68-
return
6960
}
7061
if decoded.AuthInfos[0].Name != "fake" {
7162
t.Fatalf("fake-auth not found: %v", decoded.AuthInfos)
72-
return
63+
}
64+
})
65+
})
66+
}
67+
68+
func TestConfigurationViewInCluster(t *testing.T) {
69+
kubernetes.InClusterConfig = func() (*rest.Config, error) {
70+
return &rest.Config{
71+
Host: "https://kubernetes.default.svc",
72+
BearerToken: "fake-token",
73+
}, nil
74+
}
75+
defer func() {
76+
kubernetes.InClusterConfig = rest.InClusterConfig
77+
}()
78+
testCase(t, func(c *mcpContext) {
79+
toolResult, err := c.callTool("configuration_view", map[string]interface{}{})
80+
t.Run("configuration_view returns configuration", func(t *testing.T) {
81+
if err != nil {
82+
t.Fatalf("call tool failed %v", err)
83+
}
84+
})
85+
var decoded *v1.Config
86+
err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded)
87+
t.Run("configuration_view has yaml content", func(t *testing.T) {
88+
if err != nil {
89+
t.Fatalf("invalid tool result content %v", err)
90+
}
91+
})
92+
t.Run("configuration_view returns current-context", func(t *testing.T) {
93+
if decoded.CurrentContext != "context" {
94+
t.Fatalf("context not found: %v", decoded.CurrentContext)
95+
}
96+
})
97+
t.Run("configuration_view returns context info", func(t *testing.T) {
98+
if len(decoded.Contexts) != 1 {
99+
t.Fatalf("invalid context count, expected 1, got %v", len(decoded.Contexts))
100+
}
101+
if decoded.Contexts[0].Name != "context" {
102+
t.Fatalf("context not found: %v", decoded.Contexts)
103+
}
104+
if decoded.Contexts[0].Context.Cluster != "cluster" {
105+
t.Fatalf("cluster not found: %v", decoded.Contexts)
106+
}
107+
if decoded.Contexts[0].Context.AuthInfo != "user" {
108+
t.Fatalf("user not found: %v", decoded.Contexts)
109+
}
110+
})
111+
t.Run("configuration_view returns cluster info", func(t *testing.T) {
112+
if len(decoded.Clusters) != 1 {
113+
t.Fatalf("invalid cluster count, expected 1, got %v", len(decoded.Clusters))
114+
}
115+
if decoded.Clusters[0].Name != "cluster" {
116+
t.Fatalf("cluster not found: %v", decoded.Clusters)
117+
}
118+
if decoded.Clusters[0].Cluster.Server != "https://kubernetes.default.svc" {
119+
t.Fatalf("server not found: %v", decoded.Clusters)
120+
}
121+
})
122+
t.Run("configuration_view returns auth info", func(t *testing.T) {
123+
if len(decoded.AuthInfos) != 1 {
124+
t.Fatalf("invalid auth info count, expected 1, got %v", len(decoded.AuthInfos))
125+
}
126+
if decoded.AuthInfos[0].Name != "user" {
127+
t.Fatalf("user not found: %v", decoded.AuthInfos)
73128
}
74129
})
75130
})

0 commit comments

Comments
 (0)