mirror of
https://github.com/containers/kubernetes-mcp-server.git
synced 2025-10-23 01:22:57 +03:00
feat: configuration view works in cluster
This commit is contained in:
@@ -6,9 +6,24 @@ import (
|
||||
)
|
||||
|
||||
func ConfigurationView() (string, error) {
|
||||
// TODO: consider in cluster run mode (current approach only shows kubeconfig)
|
||||
cfg, err := resolveConfig().RawConfig()
|
||||
if err != nil {
|
||||
var cfg clientcmdapi.Config
|
||||
var err error
|
||||
inClusterConfig, err := InClusterConfig()
|
||||
if err == nil && inClusterConfig != nil {
|
||||
cfg = *clientcmdapi.NewConfig()
|
||||
cfg.Clusters["cluster"] = &clientcmdapi.Cluster{
|
||||
Server: inClusterConfig.Host,
|
||||
InsecureSkipTLSVerify: inClusterConfig.Insecure,
|
||||
}
|
||||
cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{
|
||||
Token: inClusterConfig.BearerToken,
|
||||
}
|
||||
cfg.Contexts["context"] = &clientcmdapi.Context{
|
||||
Cluster: "cluster",
|
||||
AuthInfo: "user",
|
||||
}
|
||||
cfg.CurrentContext = "context"
|
||||
} else if cfg, err = resolveConfig().RawConfig(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = clientcmdapi.MinifyConfig(&cfg); err != nil {
|
||||
|
||||
@@ -13,6 +13,10 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// InClusterConfig is a variable that holds the function to get the in-cluster config
|
||||
// Exposed for testing
|
||||
var InClusterConfig = rest.InClusterConfig
|
||||
|
||||
type Kubernetes struct {
|
||||
cfg *rest.Config
|
||||
clientSet *kubernetes.Clientset
|
||||
@@ -71,24 +75,13 @@ func marshal(v any) (string, error) {
|
||||
|
||||
func resolveConfig() clientcmd.ClientConfig {
|
||||
pathOptions := clientcmd.NewDefaultPathOptions()
|
||||
//cfg, err := pathOptions.GetStartingConfig()
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//if err = clientcmdapi.MinifyConfig(cfg); err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//if err = clientcmdapi.FlattenConfig(cfg); err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//return cfg, nil
|
||||
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: pathOptions.GetDefaultFilename()},
|
||||
&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}})
|
||||
}
|
||||
|
||||
func resolveClientConfig() (*rest.Config, error) {
|
||||
inClusterConfig, err := rest.InClusterConfig()
|
||||
inClusterConfig, err := InClusterConfig()
|
||||
if err == nil && inClusterConfig != nil {
|
||||
return inClusterConfig, nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (s *Server) initConfiguration() []server.ServerTool {
|
||||
func configurationView(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
ret, err := kubernetes.ConfigurationView()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get configuration view: %v", err)
|
||||
err = fmt.Errorf("failed to get configuration: %v", err)
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/kubernetes"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"k8s.io/client-go/rest"
|
||||
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
"testing"
|
||||
@@ -13,7 +15,6 @@ func TestConfigurationView(t *testing.T) {
|
||||
t.Run("configuration_view returns configuration", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
var decoded *v1.Config
|
||||
@@ -21,55 +22,109 @@ func TestConfigurationView(t *testing.T) {
|
||||
t.Run("configuration_view has yaml content", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("invalid tool result content %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns current-context", func(t *testing.T) {
|
||||
if decoded.CurrentContext != "fake-context" {
|
||||
t.Fatalf("fake-context not found: %v", decoded.CurrentContext)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns context info", func(t *testing.T) {
|
||||
if len(decoded.Contexts) != 1 {
|
||||
t.Fatalf("invalid context count, expected 1, got %v", len(decoded.Contexts))
|
||||
return
|
||||
}
|
||||
if decoded.Contexts[0].Name != "fake-context" {
|
||||
t.Fatalf("fake-context not found: %v", decoded.Contexts)
|
||||
return
|
||||
}
|
||||
if decoded.Contexts[0].Context.Cluster != "fake" {
|
||||
t.Fatalf("fake-cluster not found: %v", decoded.Contexts)
|
||||
return
|
||||
}
|
||||
if decoded.Contexts[0].Context.AuthInfo != "fake" {
|
||||
t.Fatalf("fake-auth not found: %v", decoded.Contexts)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns cluster info", func(t *testing.T) {
|
||||
if len(decoded.Clusters) != 1 {
|
||||
t.Fatalf("invalid cluster count, expected 1, got %v", len(decoded.Clusters))
|
||||
return
|
||||
}
|
||||
if decoded.Clusters[0].Name != "fake" {
|
||||
t.Fatalf("fake-cluster not found: %v", decoded.Clusters)
|
||||
return
|
||||
}
|
||||
if decoded.Clusters[0].Cluster.Server != "https://example.com" {
|
||||
t.Fatalf("fake-server not found: %v", decoded.Clusters)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns auth info", func(t *testing.T) {
|
||||
if len(decoded.AuthInfos) != 1 {
|
||||
t.Fatalf("invalid auth info count, expected 1, got %v", len(decoded.AuthInfos))
|
||||
return
|
||||
}
|
||||
if decoded.AuthInfos[0].Name != "fake" {
|
||||
t.Fatalf("fake-auth not found: %v", decoded.AuthInfos)
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigurationViewInCluster(t *testing.T) {
|
||||
kubernetes.InClusterConfig = func() (*rest.Config, error) {
|
||||
return &rest.Config{
|
||||
Host: "https://kubernetes.default.svc",
|
||||
BearerToken: "fake-token",
|
||||
}, nil
|
||||
}
|
||||
defer func() {
|
||||
kubernetes.InClusterConfig = rest.InClusterConfig
|
||||
}()
|
||||
testCase(t, func(c *mcpContext) {
|
||||
toolResult, err := c.callTool("configuration_view", map[string]interface{}{})
|
||||
t.Run("configuration_view returns configuration", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
}
|
||||
})
|
||||
var decoded *v1.Config
|
||||
err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded)
|
||||
t.Run("configuration_view has yaml content", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("invalid tool result content %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns current-context", func(t *testing.T) {
|
||||
if decoded.CurrentContext != "context" {
|
||||
t.Fatalf("context not found: %v", decoded.CurrentContext)
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns context info", func(t *testing.T) {
|
||||
if len(decoded.Contexts) != 1 {
|
||||
t.Fatalf("invalid context count, expected 1, got %v", len(decoded.Contexts))
|
||||
}
|
||||
if decoded.Contexts[0].Name != "context" {
|
||||
t.Fatalf("context not found: %v", decoded.Contexts)
|
||||
}
|
||||
if decoded.Contexts[0].Context.Cluster != "cluster" {
|
||||
t.Fatalf("cluster not found: %v", decoded.Contexts)
|
||||
}
|
||||
if decoded.Contexts[0].Context.AuthInfo != "user" {
|
||||
t.Fatalf("user not found: %v", decoded.Contexts)
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns cluster info", func(t *testing.T) {
|
||||
if len(decoded.Clusters) != 1 {
|
||||
t.Fatalf("invalid cluster count, expected 1, got %v", len(decoded.Clusters))
|
||||
}
|
||||
if decoded.Clusters[0].Name != "cluster" {
|
||||
t.Fatalf("cluster not found: %v", decoded.Clusters)
|
||||
}
|
||||
if decoded.Clusters[0].Cluster.Server != "https://kubernetes.default.svc" {
|
||||
t.Fatalf("server not found: %v", decoded.Clusters)
|
||||
}
|
||||
})
|
||||
t.Run("configuration_view returns auth info", func(t *testing.T) {
|
||||
if len(decoded.AuthInfos) != 1 {
|
||||
t.Fatalf("invalid auth info count, expected 1, got %v", len(decoded.AuthInfos))
|
||||
}
|
||||
if decoded.AuthInfos[0].Name != "user" {
|
||||
t.Fatalf("user not found: %v", decoded.AuthInfos)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user