From 68619b57adffc2fda142225ff1588c592180c382 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:31:34 +0200 Subject: [PATCH 01/57] build(deps): bump sigs.k8s.io/controller-runtime from 0.22.2 to 0.22.3 (#374) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.2 to 0.22.3. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.2...v0.22.3) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.22.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c08f892e..78ed49c2 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( k8s.io/kubectl v0.34.1 k8s.io/metrics v0.34.1 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.22.2 + sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 4d36580c..2185419b 100644 --- a/go.sum +++ b/go.sum @@ -453,8 +453,8 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4= -sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664 h1:xC7x7FsPURJYhZnWHsWFd7nkdD/WRtQVWPC28FWt85Y= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664/go.mod h1:Cq9jUhwSYol5tNB0O/1vLYxNV9KqnhpvEa6HvJ1w0wY= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= From 1e154d7587bc78fdd3bf06eb0fb47d8bdee9933f Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 13 Oct 2025 13:12:29 +0200 Subject: [PATCH 02/57] test(kubernetes): refactor tests for Derived manager functionality to use testify (#369) Signed-off-by: Marc Nuri --- pkg/kubernetes/kubernetes_derived_test.go | 185 +++++++++++++ pkg/kubernetes/kubernetes_test.go | 316 ---------------------- 2 files changed, 185 insertions(+), 316 deletions(-) create mode 100644 pkg/kubernetes/kubernetes_derived_test.go delete mode 100644 pkg/kubernetes/kubernetes_test.go diff --git a/pkg/kubernetes/kubernetes_derived_test.go b/pkg/kubernetes/kubernetes_derived_test.go new file mode 100644 index 00000000..f45a2606 --- /dev/null +++ b/pkg/kubernetes/kubernetes_derived_test.go @@ -0,0 +1,185 @@ +package kubernetes + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/stretchr/testify/suite" +) + +type DerivedTestSuite struct { + suite.Suite +} + +func (s *DerivedTestSuite) TestKubeConfig() { + // Create a temporary kubeconfig file for testing + tempDir := s.T().TempDir() + kubeconfigPath := filepath.Join(tempDir, "config") + kubeconfigContent := ` +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://test-cluster.example.com + name: test-cluster +contexts: +- context: + cluster: test-cluster + user: test-user + name: test-context +current-context: test-context +users: +- name: test-user + user: + username: test-username + password: test-password +` + err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644) + s.Require().NoError(err, "failed to create kubeconfig file") + + s.Run("with no RequireOAuth (default) config", func() { + testStaticConfig := test.Must(config.ReadToml([]byte(` + kubeconfig = "` + strings.ReplaceAll(kubeconfigPath, `\`, `\\`) + `" + `))) + s.Run("without authorization header returns original manager", func() { + testManager, err := NewManager(testStaticConfig) + s.Require().NoErrorf(err, "failed to create test manager: %v", err) + s.T().Cleanup(testManager.Close) + + derived, err := testManager.Derived(s.T().Context()) + s.Require().NoErrorf(err, "failed to create derived manager: %v", err) + + s.Equal(derived.manager, testManager, "expected original manager, got different manager") + }) + + s.Run("with invalid authorization header returns original manager", func() { + testManager, err := NewManager(testStaticConfig) + s.Require().NoErrorf(err, "failed to create test manager: %v", err) + s.T().Cleanup(testManager.Close) + + ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "invalid-token") + derived, err := testManager.Derived(ctx) + s.Require().NoErrorf(err, "failed to create derived manager: %v", err) + + s.Equal(derived.manager, testManager, "expected original manager, got different manager") + }) + + s.Run("with valid bearer token creates derived manager with correct configuration", func() { + testManager, err := NewManager(testStaticConfig) + s.Require().NoErrorf(err, "failed to create test manager: %v", err) + s.T().Cleanup(testManager.Close) + + ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "Bearer aiTana-julIA") + derived, err := testManager.Derived(ctx) + s.Require().NoErrorf(err, "failed to create derived manager: %v", err) + + s.NotEqual(derived.manager, testManager, "expected new derived manager, got original manager") + s.Equal(derived.manager.staticConfig, testStaticConfig, "staticConfig not properly wired to derived manager") + + s.Run("RestConfig is correctly copied and sensitive fields are omitted", func() { + derivedCfg := derived.manager.cfg + s.Require().NotNil(derivedCfg, "derived config is nil") + + originalCfg := testManager.cfg + s.Equalf(originalCfg.Host, derivedCfg.Host, "expected Host %s, got %s", originalCfg.Host, derivedCfg.Host) + s.Equalf(originalCfg.APIPath, derivedCfg.APIPath, "expected APIPath %s, got %s", originalCfg.APIPath, derivedCfg.APIPath) + s.Equalf(originalCfg.QPS, derivedCfg.QPS, "expected QPS %f, got %f", originalCfg.QPS, derivedCfg.QPS) + s.Equalf(originalCfg.Burst, derivedCfg.Burst, "expected Burst %d, got %d", originalCfg.Burst, derivedCfg.Burst) + s.Equalf(originalCfg.Timeout, derivedCfg.Timeout, "expected Timeout %v, got %v", originalCfg.Timeout, derivedCfg.Timeout) + + s.Equalf(originalCfg.Insecure, derivedCfg.Insecure, "expected TLS Insecure %v, got %v", originalCfg.Insecure, derivedCfg.Insecure) + s.Equalf(originalCfg.ServerName, derivedCfg.ServerName, "expected TLS ServerName %s, got %s", originalCfg.ServerName, derivedCfg.ServerName) + s.Equalf(originalCfg.CAFile, derivedCfg.CAFile, "expected TLS CAFile %s, got %s", originalCfg.CAFile, derivedCfg.CAFile) + s.Equalf(string(originalCfg.CAData), string(derivedCfg.CAData), "expected TLS CAData %s, got %s", string(originalCfg.CAData), string(derivedCfg.CAData)) + + s.Equalf("aiTana-julIA", derivedCfg.BearerToken, "expected BearerToken %s, got %s", "aiTana-julIA", derivedCfg.BearerToken) + s.Equalf("kubernetes-mcp-server/bearer-token-auth", derivedCfg.UserAgent, "expected UserAgent \"kubernetes-mcp-server/bearer-token-auth\", got %s", derivedCfg.UserAgent) + + // Verify that sensitive fields are NOT copied to prevent credential leakage + // The derived config should only use the bearer token from the Authorization header + // and not inherit any authentication credentials from the original kubeconfig + s.Emptyf(derivedCfg.CertFile, "expected TLS CertFile to be empty, got %s", derivedCfg.CertFile) + s.Emptyf(derivedCfg.KeyFile, "expected TLS KeyFile to be empty, got %s", derivedCfg.KeyFile) + s.Emptyf(len(derivedCfg.CertData), "expected TLS CertData to be empty, got %v", derivedCfg.CertData) + s.Emptyf(len(derivedCfg.KeyData), "expected TLS KeyData to be empty, got %v", derivedCfg.KeyData) + + s.Emptyf(derivedCfg.Username, "expected Username to be empty, got %s", derivedCfg.Username) + s.Emptyf(derivedCfg.Password, "expected Password to be empty, got %s", derivedCfg.Password) + s.Nilf(derivedCfg.AuthProvider, "expected AuthProvider to be nil, got %v", derivedCfg.AuthProvider) + s.Nilf(derivedCfg.ExecProvider, "expected ExecProvider to be nil, got %v", derivedCfg.ExecProvider) + s.Emptyf(derivedCfg.BearerTokenFile, "expected BearerTokenFile to be empty, got %s", derivedCfg.BearerTokenFile) + s.Emptyf(derivedCfg.Impersonate.UserName, "expected Impersonate.UserName to be empty, got %s", derivedCfg.Impersonate.UserName) + + // Verify that the original manager still has the sensitive data + s.Falsef(originalCfg.Username == "" && originalCfg.Password == "", "original kubeconfig shouldn't be modified") + + }) + s.Run("derived manager has initialized clients", func() { + // Verify that the derived manager has proper clients initialized + s.NotNilf(derived.manager.accessControlClientSet, "expected accessControlClientSet to be initialized") + s.Equalf(testStaticConfig, derived.manager.accessControlClientSet.staticConfig, "staticConfig not properly wired to derived manager") + s.NotNilf(derived.manager.discoveryClient, "expected discoveryClient to be initialized") + s.NotNilf(derived.manager.accessControlRESTMapper, "expected accessControlRESTMapper to be initialized") + s.Equalf(testStaticConfig, derived.manager.accessControlRESTMapper.staticConfig, "staticConfig not properly wired to derived manager") + s.NotNilf(derived.manager.dynamicClient, "expected dynamicClient to be initialized") + }) + }) + }) + + s.Run("with RequireOAuth=true", func() { + testStaticConfig := test.Must(config.ReadToml([]byte(` + kubeconfig = "` + strings.ReplaceAll(kubeconfigPath, `\`, `\\`) + `" + require_oauth = true + `))) + + s.Run("with no authorization header returns oauth token required error", func() { + testManager, err := NewManager(testStaticConfig) + s.Require().NoErrorf(err, "failed to create test manager: %v", err) + s.T().Cleanup(testManager.Close) + + derived, err := testManager.Derived(s.T().Context()) + s.Require().Error(err, "expected error for missing oauth token, got nil") + s.EqualError(err, "oauth token required", "expected error 'oauth token required', got %s", err.Error()) + s.Nil(derived, "expected nil derived manager when oauth token required") + }) + + s.Run("with invalid authorization header returns oauth token required error", func() { + testManager, err := NewManager(testStaticConfig) + s.Require().NoErrorf(err, "failed to create test manager: %v", err) + s.T().Cleanup(testManager.Close) + + ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "invalid-token") + derived, err := testManager.Derived(ctx) + s.Require().Error(err, "expected error for invalid oauth token, got nil") + s.EqualError(err, "oauth token required", "expected error 'oauth token required', got %s", err.Error()) + s.Nil(derived, "expected nil derived manager when oauth token required") + }) + + s.Run("with valid bearer token creates derived manager", func() { + testManager, err := NewManager(testStaticConfig) + s.Require().NoErrorf(err, "failed to create test manager: %v", err) + s.T().Cleanup(testManager.Close) + + ctx := context.WithValue(s.T().Context(), HeaderKey("Authorization"), "Bearer aiTana-julIA") + derived, err := testManager.Derived(ctx) + s.Require().NoErrorf(err, "failed to create derived manager: %v", err) + + s.NotEqual(derived.manager, testManager, "expected new derived manager, got original manager") + s.Equal(derived.manager.staticConfig, testStaticConfig, "staticConfig not properly wired to derived manager") + + derivedCfg := derived.manager.cfg + s.Require().NotNil(derivedCfg, "derived config is nil") + + s.Equalf("aiTana-julIA", derivedCfg.BearerToken, "expected BearerToken %s, got %s", "aiTana-julIA", derivedCfg.BearerToken) + }) + }) +} + +func TestDerived(t *testing.T) { + suite.Run(t, new(DerivedTestSuite)) +} diff --git a/pkg/kubernetes/kubernetes_test.go b/pkg/kubernetes/kubernetes_test.go deleted file mode 100644 index 2051ed48..00000000 --- a/pkg/kubernetes/kubernetes_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package kubernetes - -import ( - "context" - "os" - "path" - "testing" - - "github.com/containers/kubernetes-mcp-server/pkg/config" -) - -func TestManager_Derived(t *testing.T) { - // Create a temporary kubeconfig file for testing - tempDir := t.TempDir() - kubeconfigPath := path.Join(tempDir, "config") - kubeconfigContent := ` -apiVersion: v1 -kind: Config -clusters: -- cluster: - server: https://test-cluster.example.com - name: test-cluster -contexts: -- context: - cluster: test-cluster - user: test-user - name: test-context -current-context: test-context -users: -- name: test-user - user: - username: test-username - password: test-password -` - if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil { - t.Fatalf("failed to create kubeconfig file: %v", err) - } - - t.Run("without authorization header returns original manager", func(t *testing.T) { - testStaticConfig := &config.StaticConfig{ - KubeConfig: kubeconfigPath, - DisabledTools: []string{"configuration_view"}, - DeniedResources: []config.GroupVersionKind{ - {Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - } - - testManager, err := NewManager(testStaticConfig) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - defer testManager.Close() - ctx := context.Background() - derived, err := testManager.Derived(ctx) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - - if derived.manager != testManager { - t.Errorf("expected original manager, got different manager") - } - }) - - t.Run("with invalid authorization header returns original manager", func(t *testing.T) { - testStaticConfig := &config.StaticConfig{ - KubeConfig: kubeconfigPath, - DisabledTools: []string{"configuration_view"}, - DeniedResources: []config.GroupVersionKind{ - {Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - } - - testManager, err := NewManager(testStaticConfig) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - defer testManager.Close() - ctx := context.WithValue(context.Background(), OAuthAuthorizationHeader, "invalid-token") - derived, err := testManager.Derived(ctx) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - - if derived.manager != testManager { - t.Errorf("expected original manager, got different manager") - } - }) - - t.Run("with valid bearer token creates derived manager with correct configuration", func(t *testing.T) { - testStaticConfig := &config.StaticConfig{ - KubeConfig: kubeconfigPath, - DisabledTools: []string{"configuration_view"}, - DeniedResources: []config.GroupVersionKind{ - {Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - } - - testManager, err := NewManager(testStaticConfig) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - defer testManager.Close() - testBearerToken := "test-bearer-token-123" - ctx := context.WithValue(context.Background(), OAuthAuthorizationHeader, "Bearer "+testBearerToken) - derived, err := testManager.Derived(ctx) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - - if derived.manager == testManager { - t.Errorf("expected new derived manager, got original manager") - } - - if derived.manager.staticConfig != testStaticConfig { - t.Errorf("staticConfig not properly wired to derived manager") - } - - derivedCfg := derived.manager.cfg - if derivedCfg == nil { - t.Fatalf("derived config is nil") - } - - originalCfg := testManager.cfg - if derivedCfg.Host != originalCfg.Host { - t.Errorf("expected Host %s, got %s", originalCfg.Host, derivedCfg.Host) - } - if derivedCfg.APIPath != originalCfg.APIPath { - t.Errorf("expected APIPath %s, got %s", originalCfg.APIPath, derivedCfg.APIPath) - } - if derivedCfg.QPS != originalCfg.QPS { - t.Errorf("expected QPS %f, got %f", originalCfg.QPS, derivedCfg.QPS) - } - if derivedCfg.Burst != originalCfg.Burst { - t.Errorf("expected Burst %d, got %d", originalCfg.Burst, derivedCfg.Burst) - } - if derivedCfg.Timeout != originalCfg.Timeout { - t.Errorf("expected Timeout %v, got %v", originalCfg.Timeout, derivedCfg.Timeout) - } - - if derivedCfg.Insecure != originalCfg.Insecure { - t.Errorf("expected TLS Insecure %v, got %v", originalCfg.Insecure, derivedCfg.Insecure) - } - if derivedCfg.ServerName != originalCfg.ServerName { - t.Errorf("expected TLS ServerName %s, got %s", originalCfg.ServerName, derivedCfg.ServerName) - } - if derivedCfg.CAFile != originalCfg.CAFile { - t.Errorf("expected TLS CAFile %s, got %s", originalCfg.CAFile, derivedCfg.CAFile) - } - if string(derivedCfg.CAData) != string(originalCfg.CAData) { - t.Errorf("expected TLS CAData %s, got %s", string(originalCfg.CAData), string(derivedCfg.CAData)) - } - - if derivedCfg.BearerToken != testBearerToken { - t.Errorf("expected BearerToken %s, got %s", testBearerToken, derivedCfg.BearerToken) - } - if derivedCfg.UserAgent != CustomUserAgent { - t.Errorf("expected UserAgent %s, got %s", CustomUserAgent, derivedCfg.UserAgent) - } - - // Verify that sensitive fields are NOT copied to prevent credential leakage - // The derived config should only use the bearer token from the Authorization header - // and not inherit any authentication credentials from the original kubeconfig - if derivedCfg.CertFile != "" { - t.Errorf("expected TLS CertFile to be empty, got %s", derivedCfg.CertFile) - } - if derivedCfg.KeyFile != "" { - t.Errorf("expected TLS KeyFile to be empty, got %s", derivedCfg.KeyFile) - } - if len(derivedCfg.CertData) != 0 { - t.Errorf("expected TLS CertData to be empty, got %v", derivedCfg.CertData) - } - if len(derivedCfg.KeyData) != 0 { - t.Errorf("expected TLS KeyData to be empty, got %v", derivedCfg.KeyData) - } - - if derivedCfg.Username != "" { - t.Errorf("expected Username to be empty, got %s", derivedCfg.Username) - } - if derivedCfg.Password != "" { - t.Errorf("expected Password to be empty, got %s", derivedCfg.Password) - } - if derivedCfg.AuthProvider != nil { - t.Errorf("expected AuthProvider to be nil, got %v", derivedCfg.AuthProvider) - } - if derivedCfg.ExecProvider != nil { - t.Errorf("expected ExecProvider to be nil, got %v", derivedCfg.ExecProvider) - } - if derivedCfg.BearerTokenFile != "" { - t.Errorf("expected BearerTokenFile to be empty, got %s", derivedCfg.BearerTokenFile) - } - if derivedCfg.Impersonate.UserName != "" { - t.Errorf("expected Impersonate.UserName to be empty, got %s", derivedCfg.Impersonate.UserName) - } - - // Verify that the original manager still has the sensitive data - if originalCfg.Username == "" && originalCfg.Password == "" { - t.Logf("original kubeconfig shouldn't be modified") - } - - // Verify that the derived manager has proper clients initialized - if derived.manager.accessControlClientSet == nil { - t.Error("expected accessControlClientSet to be initialized") - } - if derived.manager.accessControlClientSet.staticConfig != testStaticConfig { - t.Errorf("staticConfig not properly wired to derived manager") - } - if derived.manager.discoveryClient == nil { - t.Error("expected discoveryClient to be initialized") - } - if derived.manager.accessControlRESTMapper == nil { - t.Error("expected accessControlRESTMapper to be initialized") - } - if derived.manager.accessControlRESTMapper.staticConfig != testStaticConfig { - t.Errorf("staticConfig not properly wired to derived manager") - } - if derived.manager.dynamicClient == nil { - t.Error("expected dynamicClient to be initialized") - } - }) - - t.Run("with RequireOAuth=true and no authorization header returns oauth token required error", func(t *testing.T) { - testStaticConfig := &config.StaticConfig{ - KubeConfig: kubeconfigPath, - RequireOAuth: true, - DisabledTools: []string{"configuration_view"}, - DeniedResources: []config.GroupVersionKind{ - {Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - } - - testManager, err := NewManager(testStaticConfig) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - defer testManager.Close() - ctx := context.Background() - derived, err := testManager.Derived(ctx) - if err == nil { - t.Fatal("expected error for missing oauth token, got nil") - } - if err.Error() != "oauth token required" { - t.Fatalf("expected error 'oauth token required', got %s", err.Error()) - } - if derived != nil { - t.Error("expected nil derived manager when oauth token required") - } - }) - - t.Run("with RequireOAuth=true and invalid authorization header returns oauth token required error", func(t *testing.T) { - testStaticConfig := &config.StaticConfig{ - KubeConfig: kubeconfigPath, - RequireOAuth: true, - DisabledTools: []string{"configuration_view"}, - DeniedResources: []config.GroupVersionKind{ - {Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - } - - testManager, err := NewManager(testStaticConfig) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - defer testManager.Close() - ctx := context.WithValue(context.Background(), OAuthAuthorizationHeader, "invalid-token") - derived, err := testManager.Derived(ctx) - if err == nil { - t.Fatal("expected error for invalid oauth token, got nil") - } - if err.Error() != "oauth token required" { - t.Fatalf("expected error 'oauth token required', got %s", err.Error()) - } - if derived != nil { - t.Error("expected nil derived manager when oauth token required") - } - }) - - t.Run("with RequireOAuth=true and valid bearer token creates derived manager", func(t *testing.T) { - testStaticConfig := &config.StaticConfig{ - KubeConfig: kubeconfigPath, - RequireOAuth: true, - DisabledTools: []string{"configuration_view"}, - DeniedResources: []config.GroupVersionKind{ - {Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - } - - testManager, err := NewManager(testStaticConfig) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - defer testManager.Close() - testBearerToken := "test-bearer-token-123" - ctx := context.WithValue(context.Background(), OAuthAuthorizationHeader, "Bearer "+testBearerToken) - derived, err := testManager.Derived(ctx) - if err != nil { - t.Fatalf("failed to create manager: %v", err) - } - - if derived.manager == testManager { - t.Error("expected new derived manager, got original manager") - } - - if derived.manager.staticConfig != testStaticConfig { - t.Error("staticConfig not properly wired to derived manager") - } - - derivedCfg := derived.manager.cfg - if derivedCfg == nil { - t.Fatal("derived config is nil") - } - - if derivedCfg.BearerToken != testBearerToken { - t.Errorf("expected BearerToken %s, got %s", testBearerToken, derivedCfg.BearerToken) - } - }) -} From f3a446676fccc8d198b0383e2227e6f38978fb86 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 14 Oct 2025 15:25:49 +0200 Subject: [PATCH 03/57] refactor(kubernetes): keep Provider as only external Kubernetes interface (#372) * refactor(kubernetes): keep Provider as only external Kubernetes interface Initial phase to unify-merge the Provider interface with the Manager struct. - Renamed ManagerProvider to Provider (i.e. kubernets.Provider) - Moved Manager related logic to specific files - Exposed relevant method through Provider interface (GetDerivedKubernetes, IsOpenShift, VerifyToken) Signed-off-by: Marc Nuri * Update pkg/kubernetes/provider_kubeconfig.go Co-authored-by: Calum Murray Signed-off-by: Marc Nuri --------- Signed-off-by: Marc Nuri Co-authored-by: Calum Murray --- pkg/http/authorization.go | 4 +- pkg/kubernetes/configuration.go | 32 --- pkg/kubernetes/kubernetes.go | 184 +---------------- pkg/kubernetes/manager.go | 251 +++++++++++++++++++++++ pkg/kubernetes/provider.go | 13 +- pkg/kubernetes/provider_kubeconfig.go | 73 ++++--- pkg/kubernetes/provider_registry.go | 4 +- pkg/kubernetes/provider_registry_test.go | 10 +- pkg/kubernetes/provider_single.go | 38 ++-- pkg/kubernetes/provider_test.go | 16 +- pkg/kubernetes/token.go | 33 +-- pkg/mcp/m3labs.go | 10 +- pkg/mcp/mcp.go | 27 +-- pkg/mcp/tool_filter.go | 2 +- 14 files changed, 361 insertions(+), 336 deletions(-) create mode 100644 pkg/kubernetes/manager.go diff --git a/pkg/http/authorization.go b/pkg/http/authorization.go index 261fdb92..cded7f3e 100644 --- a/pkg/http/authorization.go +++ b/pkg/http/authorization.go @@ -23,7 +23,7 @@ import ( type KubernetesApiTokenVerifier interface { // KubernetesApiVerifyToken TODO: clarify proper implementation - KubernetesApiVerifyToken(ctx context.Context, token, audience, cluster string) (*authenticationapiv1.UserInfo, []string, error) + KubernetesApiVerifyToken(ctx context.Context, cluster, token, audience string) (*authenticationapiv1.UserInfo, []string, error) // GetTargetParameterName returns the parameter name used for target identification in MCP requests GetTargetParameterName() string } @@ -247,7 +247,7 @@ func (c *JWTClaims) ValidateWithProvider(ctx context.Context, audience string, p func (c *JWTClaims) ValidateWithKubernetesApi(ctx context.Context, audience, cluster string, verifier KubernetesApiTokenVerifier) error { if verifier != nil { - _, _, err := verifier.KubernetesApiVerifyToken(ctx, c.Token, audience, cluster) + _, _, err := verifier.KubernetesApiVerifyToken(ctx, cluster, c.Token, audience) if err != nil { return fmt.Errorf("kubernetes API token validation error: %v", err) } diff --git a/pkg/kubernetes/configuration.go b/pkg/kubernetes/configuration.go index ff521a2a..25602e32 100644 --- a/pkg/kubernetes/configuration.go +++ b/pkg/kubernetes/configuration.go @@ -47,42 +47,10 @@ func resolveKubernetesConfigurations(kubernetes *Manager) error { return err } -func (m *Manager) IsInCluster() bool { - if m.staticConfig.KubeConfig != "" { - return false - } - cfg, err := InClusterConfig() - return err == nil && cfg != nil -} - -func (m *Manager) configuredNamespace() string { - if ns, _, nsErr := m.clientCmdConfig.Namespace(); nsErr == nil { - return ns - } - return "" -} - -func (m *Manager) NamespaceOrDefault(namespace string) string { - if namespace == "" { - return m.configuredNamespace() - } - return namespace -} - func (k *Kubernetes) NamespaceOrDefault(namespace string) string { return k.manager.NamespaceOrDefault(namespace) } -// ToRESTConfig returns the rest.Config object (genericclioptions.RESTClientGetter) -func (m *Manager) ToRESTConfig() (*rest.Config, error) { - return m.cfg, nil -} - -// ToRawKubeConfigLoader returns the clientcmd.ClientConfig object (genericclioptions.RESTClientGetter) -func (m *Manager) ToRawKubeConfigLoader() clientcmd.ClientConfig { - return m.clientCmdConfig -} - // ConfigurationContextsDefault returns the current context name // TODO: Should be moved to the Provider level ? func (k *Kubernetes) ConfigurationContextsDefault() (string, error) { diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index 6cb770eb..3b5733e1 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -1,27 +1,10 @@ package kubernetes import ( - "context" - "errors" - "strings" - "k8s.io/apimachinery/pkg/runtime" - "github.com/fsnotify/fsnotify" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/memory" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/klog/v2" - - "github.com/containers/kubernetes-mcp-server/pkg/config" "github.com/containers/kubernetes-mcp-server/pkg/helm" + "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" ) @@ -47,174 +30,9 @@ func (k *Kubernetes) AccessControlClientset() *AccessControlClientset { return k.manager.accessControlClientSet } -type Manager struct { - cfg *rest.Config - clientCmdConfig clientcmd.ClientConfig - discoveryClient discovery.CachedDiscoveryInterface - accessControlClientSet *AccessControlClientset - accessControlRESTMapper *AccessControlRESTMapper - dynamicClient *dynamic.DynamicClient - - staticConfig *config.StaticConfig - CloseWatchKubeConfig CloseWatchKubeConfig -} - -var _ helm.Kubernetes = (*Manager)(nil) -var _ Openshift = (*Manager)(nil) - var Scheme = scheme.Scheme var ParameterCodec = runtime.NewParameterCodec(Scheme) -func NewManager(config *config.StaticConfig) (*Manager, error) { - k8s := &Manager{ - staticConfig: config, - } - if err := resolveKubernetesConfigurations(k8s); err != nil { - return nil, err - } - // TODO: Won't work because not all client-go clients use the shared context (e.g. discovery client uses context.TODO()) - //k8s.cfg.Wrap(func(original http.RoundTripper) http.RoundTripper { - // return &impersonateRoundTripper{original} - //}) - var err error - k8s.accessControlClientSet, err = NewAccessControlClientset(k8s.cfg, k8s.staticConfig) - if err != nil { - return nil, err - } - k8s.discoveryClient = memory.NewMemCacheClient(k8s.accessControlClientSet.DiscoveryClient()) - k8s.accessControlRESTMapper = NewAccessControlRESTMapper( - restmapper.NewDeferredDiscoveryRESTMapper(k8s.discoveryClient), - k8s.staticConfig, - ) - k8s.dynamicClient, err = dynamic.NewForConfig(k8s.cfg) - if err != nil { - return nil, err - } - return k8s, nil -} - -func (m *Manager) WatchKubeConfig(onKubeConfigChange func() error) { - if m.clientCmdConfig == nil { - return - } - kubeConfigFiles := m.clientCmdConfig.ConfigAccess().GetLoadingPrecedence() - if len(kubeConfigFiles) == 0 { - return - } - watcher, err := fsnotify.NewWatcher() - if err != nil { - return - } - for _, file := range kubeConfigFiles { - _ = watcher.Add(file) - } - go func() { - for { - select { - case _, ok := <-watcher.Events: - if !ok { - return - } - _ = onKubeConfigChange() - case _, ok := <-watcher.Errors: - if !ok { - return - } - } - } - }() - if m.CloseWatchKubeConfig != nil { - _ = m.CloseWatchKubeConfig() - } - m.CloseWatchKubeConfig = watcher.Close -} - -func (m *Manager) Close() { - if m.CloseWatchKubeConfig != nil { - _ = m.CloseWatchKubeConfig() - } -} - -func (m *Manager) GetAPIServerHost() string { - if m.cfg == nil { - return "" - } - return m.cfg.Host -} - -func (m *Manager) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { - return m.discoveryClient, nil -} - -func (m *Manager) ToRESTMapper() (meta.RESTMapper, error) { - return m.accessControlRESTMapper, nil -} - -func (m *Manager) Derived(ctx context.Context) (*Kubernetes, error) { - authorization, ok := ctx.Value(OAuthAuthorizationHeader).(string) - if !ok || !strings.HasPrefix(authorization, "Bearer ") { - if m.staticConfig.RequireOAuth { - return nil, errors.New("oauth token required") - } - return &Kubernetes{manager: m}, nil - } - klog.V(5).Infof("%s header found (Bearer), using provided bearer token", OAuthAuthorizationHeader) - derivedCfg := &rest.Config{ - Host: m.cfg.Host, - APIPath: m.cfg.APIPath, - // Copy only server verification TLS settings (CA bundle and server name) - TLSClientConfig: rest.TLSClientConfig{ - Insecure: m.cfg.Insecure, - ServerName: m.cfg.ServerName, - CAFile: m.cfg.CAFile, - CAData: m.cfg.CAData, - }, - BearerToken: strings.TrimPrefix(authorization, "Bearer "), - // pass custom UserAgent to identify the client - UserAgent: CustomUserAgent, - QPS: m.cfg.QPS, - Burst: m.cfg.Burst, - Timeout: m.cfg.Timeout, - Impersonate: rest.ImpersonationConfig{}, - } - clientCmdApiConfig, err := m.clientCmdConfig.RawConfig() - if err != nil { - if m.staticConfig.RequireOAuth { - klog.Errorf("failed to get kubeconfig: %v", err) - return nil, errors.New("failed to get kubeconfig") - } - return &Kubernetes{manager: m}, nil - } - clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo) - derived := &Kubernetes{manager: &Manager{ - clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil), - cfg: derivedCfg, - staticConfig: m.staticConfig, - }} - derived.manager.accessControlClientSet, err = NewAccessControlClientset(derived.manager.cfg, derived.manager.staticConfig) - if err != nil { - if m.staticConfig.RequireOAuth { - klog.Errorf("failed to get kubeconfig: %v", err) - return nil, errors.New("failed to get kubeconfig") - } - return &Kubernetes{manager: m}, nil - } - derived.manager.discoveryClient = memory.NewMemCacheClient(derived.manager.accessControlClientSet.DiscoveryClient()) - derived.manager.accessControlRESTMapper = NewAccessControlRESTMapper( - restmapper.NewDeferredDiscoveryRESTMapper(derived.manager.discoveryClient), - derived.manager.staticConfig, - ) - derived.manager.dynamicClient, err = dynamic.NewForConfig(derived.manager.cfg) - if err != nil { - if m.staticConfig.RequireOAuth { - klog.Errorf("failed to initialize dynamic client: %v", err) - return nil, errors.New("failed to initialize dynamic client") - } - return &Kubernetes{manager: m}, nil - } - return derived, nil -} - func (k *Kubernetes) NewHelm() *helm.Helm { // This is a derived Kubernetes, so it already has the Helm initialized return helm.NewHelm(k.manager) diff --git a/pkg/kubernetes/manager.go b/pkg/kubernetes/manager.go new file mode 100644 index 00000000..ea2741af --- /dev/null +++ b/pkg/kubernetes/manager.go @@ -0,0 +1,251 @@ +package kubernetes + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/containers/kubernetes-mcp-server/pkg/helm" + "github.com/fsnotify/fsnotify" + authenticationv1api "k8s.io/api/authentication/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/klog/v2" +) + +type Manager struct { + cfg *rest.Config + clientCmdConfig clientcmd.ClientConfig + discoveryClient discovery.CachedDiscoveryInterface + accessControlClientSet *AccessControlClientset + accessControlRESTMapper *AccessControlRESTMapper + dynamicClient *dynamic.DynamicClient + + staticConfig *config.StaticConfig + CloseWatchKubeConfig CloseWatchKubeConfig +} + +var _ helm.Kubernetes = (*Manager)(nil) +var _ Openshift = (*Manager)(nil) + +func NewManager(config *config.StaticConfig) (*Manager, error) { + k8s := &Manager{ + staticConfig: config, + } + if err := resolveKubernetesConfigurations(k8s); err != nil { + return nil, err + } + // TODO: Won't work because not all client-go clients use the shared context (e.g. discovery client uses context.TODO()) + //k8s.cfg.Wrap(func(original http.RoundTripper) http.RoundTripper { + // return &impersonateRoundTripper{original} + //}) + var err error + k8s.accessControlClientSet, err = NewAccessControlClientset(k8s.cfg, k8s.staticConfig) + if err != nil { + return nil, err + } + k8s.discoveryClient = memory.NewMemCacheClient(k8s.accessControlClientSet.DiscoveryClient()) + k8s.accessControlRESTMapper = NewAccessControlRESTMapper( + restmapper.NewDeferredDiscoveryRESTMapper(k8s.discoveryClient), + k8s.staticConfig, + ) + k8s.dynamicClient, err = dynamic.NewForConfig(k8s.cfg) + if err != nil { + return nil, err + } + return k8s, nil +} + +func (m *Manager) WatchKubeConfig(onKubeConfigChange func() error) { + if m.clientCmdConfig == nil { + return + } + kubeConfigFiles := m.clientCmdConfig.ConfigAccess().GetLoadingPrecedence() + if len(kubeConfigFiles) == 0 { + return + } + watcher, err := fsnotify.NewWatcher() + if err != nil { + return + } + for _, file := range kubeConfigFiles { + _ = watcher.Add(file) + } + go func() { + for { + select { + case _, ok := <-watcher.Events: + if !ok { + return + } + _ = onKubeConfigChange() + case _, ok := <-watcher.Errors: + if !ok { + return + } + } + } + }() + if m.CloseWatchKubeConfig != nil { + _ = m.CloseWatchKubeConfig() + } + m.CloseWatchKubeConfig = watcher.Close +} + +func (m *Manager) Close() { + if m.CloseWatchKubeConfig != nil { + _ = m.CloseWatchKubeConfig() + } +} + +func (m *Manager) GetAPIServerHost() string { + if m.cfg == nil { + return "" + } + return m.cfg.Host +} + +func (m *Manager) IsInCluster() bool { + if m.staticConfig.KubeConfig != "" { + return false + } + cfg, err := InClusterConfig() + return err == nil && cfg != nil +} + +func (m *Manager) configuredNamespace() string { + if ns, _, nsErr := m.clientCmdConfig.Namespace(); nsErr == nil { + return ns + } + return "" +} + +func (m *Manager) NamespaceOrDefault(namespace string) string { + if namespace == "" { + return m.configuredNamespace() + } + return namespace +} + +func (m *Manager) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + return m.discoveryClient, nil +} + +func (m *Manager) ToRESTMapper() (meta.RESTMapper, error) { + return m.accessControlRESTMapper, nil +} + +// ToRESTConfig returns the rest.Config object (genericclioptions.RESTClientGetter) +func (m *Manager) ToRESTConfig() (*rest.Config, error) { + return m.cfg, nil +} + +// ToRawKubeConfigLoader returns the clientcmd.ClientConfig object (genericclioptions.RESTClientGetter) +func (m *Manager) ToRawKubeConfigLoader() clientcmd.ClientConfig { + return m.clientCmdConfig +} + +func (m *Manager) VerifyToken(ctx context.Context, token, audience string) (*authenticationv1api.UserInfo, []string, error) { + tokenReviewClient, err := m.accessControlClientSet.TokenReview() + if err != nil { + return nil, nil, err + } + tokenReview := &authenticationv1api.TokenReview{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "authentication.k8s.io/v1", + Kind: "TokenReview", + }, + Spec: authenticationv1api.TokenReviewSpec{ + Token: token, + Audiences: []string{audience}, + }, + } + + result, err := tokenReviewClient.Create(ctx, tokenReview, metav1.CreateOptions{}) + if err != nil { + return nil, nil, fmt.Errorf("failed to create token review: %v", err) + } + + if !result.Status.Authenticated { + if result.Status.Error != "" { + return nil, nil, fmt.Errorf("token authentication failed: %s", result.Status.Error) + } + return nil, nil, fmt.Errorf("token authentication failed") + } + + return &result.Status.User, result.Status.Audiences, nil +} + +func (m *Manager) Derived(ctx context.Context) (*Kubernetes, error) { + authorization, ok := ctx.Value(OAuthAuthorizationHeader).(string) + if !ok || !strings.HasPrefix(authorization, "Bearer ") { + if m.staticConfig.RequireOAuth { + return nil, errors.New("oauth token required") + } + return &Kubernetes{manager: m}, nil + } + klog.V(5).Infof("%s header found (Bearer), using provided bearer token", OAuthAuthorizationHeader) + derivedCfg := &rest.Config{ + Host: m.cfg.Host, + APIPath: m.cfg.APIPath, + // Copy only server verification TLS settings (CA bundle and server name) + TLSClientConfig: rest.TLSClientConfig{ + Insecure: m.cfg.Insecure, + ServerName: m.cfg.ServerName, + CAFile: m.cfg.CAFile, + CAData: m.cfg.CAData, + }, + BearerToken: strings.TrimPrefix(authorization, "Bearer "), + // pass custom UserAgent to identify the client + UserAgent: CustomUserAgent, + QPS: m.cfg.QPS, + Burst: m.cfg.Burst, + Timeout: m.cfg.Timeout, + Impersonate: rest.ImpersonationConfig{}, + } + clientCmdApiConfig, err := m.clientCmdConfig.RawConfig() + if err != nil { + if m.staticConfig.RequireOAuth { + klog.Errorf("failed to get kubeconfig: %v", err) + return nil, errors.New("failed to get kubeconfig") + } + return &Kubernetes{manager: m}, nil + } + clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo) + derived := &Kubernetes{manager: &Manager{ + clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil), + cfg: derivedCfg, + staticConfig: m.staticConfig, + }} + derived.manager.accessControlClientSet, err = NewAccessControlClientset(derived.manager.cfg, derived.manager.staticConfig) + if err != nil { + if m.staticConfig.RequireOAuth { + klog.Errorf("failed to get kubeconfig: %v", err) + return nil, errors.New("failed to get kubeconfig") + } + return &Kubernetes{manager: m}, nil + } + derived.manager.discoveryClient = memory.NewMemCacheClient(derived.manager.accessControlClientSet.DiscoveryClient()) + derived.manager.accessControlRESTMapper = NewAccessControlRESTMapper( + restmapper.NewDeferredDiscoveryRESTMapper(derived.manager.discoveryClient), + derived.manager.staticConfig, + ) + derived.manager.dynamicClient, err = dynamic.NewForConfig(derived.manager.cfg) + if err != nil { + if m.staticConfig.RequireOAuth { + klog.Errorf("failed to initialize dynamic client: %v", err) + return nil, errors.New("failed to initialize dynamic client") + } + return &Kubernetes{manager: m}, nil + } + return derived, nil +} diff --git a/pkg/kubernetes/provider.go b/pkg/kubernetes/provider.go index 6ba0034b..1c1529e7 100644 --- a/pkg/kubernetes/provider.go +++ b/pkg/kubernetes/provider.go @@ -11,16 +11,23 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -type ManagerProvider interface { +type Provider interface { + // Openshift extends the Openshift interface to provide OpenShift specific functionality to toolset providers + // TODO: with the configurable toolset implementation and especially the multi-cluster approach + // extending this interface might not be a good idea anymore. + // For the kubecontext case, a user might be targeting both an OpenShift flavored cluster and a vanilla Kubernetes cluster. + // See: https://github.com/containers/kubernetes-mcp-server/pull/372#discussion_r2421592315 + Openshift + TokenVerifier GetTargets(ctx context.Context) ([]string, error) - GetManagerFor(ctx context.Context, target string) (*Manager, error) + GetDerivedKubernetes(ctx context.Context, target string) (*Kubernetes, error) GetDefaultTarget() string GetTargetParameterName() string WatchTargets(func() error) Close() } -func NewManagerProvider(cfg *config.StaticConfig) (ManagerProvider, error) { +func NewProvider(cfg *config.StaticConfig) (Provider, error) { m, err := NewManager(cfg) if err != nil { return nil, err diff --git a/pkg/kubernetes/provider_kubeconfig.go b/pkg/kubernetes/provider_kubeconfig.go index 1da46a58..3ae46143 100644 --- a/pkg/kubernetes/provider_kubeconfig.go +++ b/pkg/kubernetes/provider_kubeconfig.go @@ -5,13 +5,14 @@ import ( "fmt" "github.com/containers/kubernetes-mcp-server/pkg/config" + authenticationv1api "k8s.io/api/authentication/v1" ) // 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 +// kubeConfigClusterProvider implements Provider 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 { @@ -19,7 +20,7 @@ type kubeConfigClusterProvider struct { managers map[string]*Manager } -var _ ManagerProvider = &kubeConfigClusterProvider{} +var _ Provider = &kubeConfigClusterProvider{} func init() { RegisterProvider(config.ClusterProviderKubeConfig, newKubeConfigClusterProvider) @@ -27,7 +28,7 @@ func init() { // 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) { +func newKubeConfigClusterProvider(m *Manager, cfg *config.StaticConfig) (Provider, error) { // Handle in-cluster mode if m.IsInCluster() { return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments") @@ -56,26 +57,13 @@ func newKubeConfigClusterProvider(m *Manager, cfg *config.StaticConfig) (Manager }, 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] +func (p *kubeConfigClusterProvider) managerForContext(context string) (*Manager, error) { + m, ok := p.managers[context] if ok && m != nil { return m, nil } - baseManager := k.managers[k.defaultContext] + baseManager := p.managers[p.defaultContext] if baseManager.IsInCluster() { // In cluster mode, so context switching is not applicable @@ -87,23 +75,56 @@ func (k *kubeConfigClusterProvider) GetManagerFor(ctx context.Context, context s return nil, err } - k.managers[context] = m + p.managers[context] = m return m, nil } -func (k *kubeConfigClusterProvider) GetDefaultTarget() string { - return k.defaultContext +func (p *kubeConfigClusterProvider) IsOpenShift(ctx context.Context) bool { + return p.managers[p.defaultContext].IsOpenShift(ctx) +} + +func (p *kubeConfigClusterProvider) VerifyToken(ctx context.Context, context, token, audience string) (*authenticationv1api.UserInfo, []string, error) { + m, err := p.managerForContext(context) + if err != nil { + return nil, nil, err + } + return m.VerifyToken(ctx, token, audience) +} + +func (p *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) { + contextNames := make([]string, 0, len(p.managers)) + for contextName := range p.managers { + contextNames = append(contextNames, contextName) + } + + return contextNames, nil +} + +func (p *kubeConfigClusterProvider) GetTargetParameterName() string { + return KubeConfigTargetParameterName +} + +func (p *kubeConfigClusterProvider) GetDerivedKubernetes(ctx context.Context, context string) (*Kubernetes, error) { + m, err := p.managerForContext(context) + if err != nil { + return nil, err + } + return m.Derived(ctx) +} + +func (p *kubeConfigClusterProvider) GetDefaultTarget() string { + return p.defaultContext } -func (k *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) { - m := k.managers[k.defaultContext] +func (p *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) { + m := p.managers[p.defaultContext] m.WatchKubeConfig(onKubeConfigChanged) } -func (k *kubeConfigClusterProvider) Close() { - m := k.managers[k.defaultContext] +func (p *kubeConfigClusterProvider) Close() { + m := p.managers[p.defaultContext] m.Close() } diff --git a/pkg/kubernetes/provider_registry.go b/pkg/kubernetes/provider_registry.go index 67fa79b5..9af5a9ee 100644 --- a/pkg/kubernetes/provider_registry.go +++ b/pkg/kubernetes/provider_registry.go @@ -7,10 +7,10 @@ import ( "github.com/containers/kubernetes-mcp-server/pkg/config" ) -// ProviderFactory creates a new ManagerProvider instance for a given strategy. +// ProviderFactory creates a new Provider 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) +type ProviderFactory func(m *Manager, cfg *config.StaticConfig) (Provider, error) var providerFactories = make(map[string]ProviderFactory) diff --git a/pkg/kubernetes/provider_registry_test.go b/pkg/kubernetes/provider_registry_test.go index e52fbdfd..876e2bab 100644 --- a/pkg/kubernetes/provider_registry_test.go +++ b/pkg/kubernetes/provider_registry_test.go @@ -13,18 +13,18 @@ type ProviderRegistryTestSuite struct { func (s *ProviderRegistryTestSuite) TestRegisterProvider() { s.Run("With no pre-existing provider, registers the provider", func() { - RegisterProvider("test-strategy", func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) { + RegisterProvider("test-strategy", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { return nil, nil }) _, exists := providerFactories["test-strategy"] s.True(exists, "Provider should be registered") }) s.Run("With pre-existing provider, panics", func() { - RegisterProvider("test-pre-existent", func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) { + RegisterProvider("test-pre-existent", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { return nil, nil }) s.Panics(func() { - RegisterProvider("test-pre-existent", func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) { + RegisterProvider("test-pre-existent", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { return nil, nil }) }, "Registering a provider with an existing strategy should panic") @@ -39,10 +39,10 @@ func (s *ProviderRegistryTestSuite) TestGetRegisteredStrategies() { }) s.Run("With multiple registered providers, returns sorted list", func() { providerFactories = make(map[string]ProviderFactory) - RegisterProvider("foo-strategy", func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) { + RegisterProvider("foo-strategy", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { return nil, nil }) - RegisterProvider("bar-strategy", func(m *Manager, cfg *config.StaticConfig) (ManagerProvider, error) { + RegisterProvider("bar-strategy", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { return nil, nil }) strategies := GetRegisteredStrategies() diff --git a/pkg/kubernetes/provider_single.go b/pkg/kubernetes/provider_single.go index fe91f2a0..884ca09c 100644 --- a/pkg/kubernetes/provider_single.go +++ b/pkg/kubernetes/provider_single.go @@ -5,9 +5,10 @@ import ( "fmt" "github.com/containers/kubernetes-mcp-server/pkg/config" + authenticationv1api "k8s.io/api/authentication/v1" ) -// singleClusterProvider implements ManagerProvider for managing a single +// singleClusterProvider implements Provider for managing a single // Kubernetes cluster. Used for in-cluster deployments or when multi-cluster // support is disabled. type singleClusterProvider struct { @@ -15,7 +16,7 @@ type singleClusterProvider struct { manager *Manager } -var _ ManagerProvider = &singleClusterProvider{} +var _ Provider = &singleClusterProvider{} func init() { RegisterProvider(config.ClusterProviderInCluster, newSingleClusterProvider(config.ClusterProviderInCluster)) @@ -25,7 +26,7 @@ func init() { // 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) { + return func(m *Manager, cfg *config.StaticConfig) (Provider, error) { if strategy == config.ClusterProviderInCluster && !m.IsInCluster() { return nil, fmt.Errorf("server must be deployed in cluster for the in-cluster ClusterProviderStrategy") } @@ -37,30 +38,41 @@ func newSingleClusterProvider(strategy string) ProviderFactory { } } -func (s *singleClusterProvider) GetTargets(ctx context.Context) ([]string, error) { +func (p *singleClusterProvider) IsOpenShift(ctx context.Context) bool { + return p.manager.IsOpenShift(ctx) +} + +func (p *singleClusterProvider) VerifyToken(ctx context.Context, target, token, audience string) (*authenticationv1api.UserInfo, []string, error) { + if target != "" { + return nil, nil, fmt.Errorf("unable to get manager for other context/cluster with %s strategy", p.strategy) + } + return p.manager.VerifyToken(ctx, token, audience) +} + +func (p *singleClusterProvider) GetTargets(ctx context.Context) ([]string, error) { return []string{""}, nil } -func (s *singleClusterProvider) GetManagerFor(ctx context.Context, target string) (*Manager, error) { +func (p *singleClusterProvider) GetDerivedKubernetes(ctx context.Context, target string) (*Kubernetes, error) { if target != "" { - return nil, fmt.Errorf("unable to get manager for other context/cluster with %s strategy", s.strategy) + return nil, fmt.Errorf("unable to get manager for other context/cluster with %s strategy", p.strategy) } - return s.manager, nil + return p.manager.Derived(ctx) } -func (s *singleClusterProvider) GetDefaultTarget() string { +func (p *singleClusterProvider) GetDefaultTarget() string { return "" } -func (s *singleClusterProvider) GetTargetParameterName() string { +func (p *singleClusterProvider) GetTargetParameterName() string { return "" } -func (s *singleClusterProvider) WatchTargets(watch func() error) { - s.manager.WatchKubeConfig(watch) +func (p *singleClusterProvider) WatchTargets(watch func() error) { + p.manager.WatchKubeConfig(watch) } -func (s *singleClusterProvider) Close() { - s.manager.Close() +func (p *singleClusterProvider) Close() { + p.manager.Close() } diff --git a/pkg/kubernetes/provider_test.go b/pkg/kubernetes/provider_test.go index eca718f8..9691d246 100644 --- a/pkg/kubernetes/provider_test.go +++ b/pkg/kubernetes/provider_test.go @@ -43,7 +43,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { } s.Run("With no cluster_provider_strategy, returns single-cluster provider", func() { cfg := test.Must(config.ReadToml([]byte{})) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().NoError(err, "Expected no error for in-cluster provider") s.NotNil(provider, "Expected provider instance") s.IsType(&singleClusterProvider{}, provider, "Expected singleClusterProvider type") @@ -52,7 +52,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "in-cluster" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().NoError(err, "Expected no error for single-cluster strategy") s.NotNil(provider, "Expected provider instance") s.IsType(&singleClusterProvider{}, provider, "Expected singleClusterProvider type") @@ -61,7 +61,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "kubeconfig" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().Error(err, "Expected error for kubeconfig strategy") s.ErrorContains(err, "kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments") s.Nilf(provider, "Expected no provider instance, got %v", provider) @@ -70,7 +70,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "i-do-not-exist" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().Error(err, "Expected error for non-existent strategy") s.ErrorContains(err, "no provider registered for strategy 'i-do-not-exist'") s.Nilf(provider, "Expected no provider instance, got %v", provider) @@ -85,7 +85,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderLocal() { cfg := test.Must(config.ReadToml([]byte(` kubeconfig = "` + kubeconfigPath + `" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().NoError(err, "Expected no error for kubeconfig provider") s.NotNil(provider, "Expected provider instance") s.IsType(&kubeConfigClusterProvider{}, provider, "Expected kubeConfigClusterProvider type") @@ -95,7 +95,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderLocal() { kubeconfig = "` + kubeconfigPath + `" cluster_provider_strategy = "kubeconfig" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().NoError(err, "Expected no error for kubeconfig provider") s.NotNil(provider, "Expected provider instance") s.IsType(&kubeConfigClusterProvider{}, provider, "Expected kubeConfigClusterProvider type") @@ -105,7 +105,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderLocal() { kubeconfig = "` + kubeconfigPath + `" cluster_provider_strategy = "in-cluster" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().Error(err, "Expected error for in-cluster strategy") s.ErrorContains(err, "server must be deployed in cluster for the in-cluster ClusterProviderStrategy") s.Nilf(provider, "Expected no provider instance, got %v", provider) @@ -115,7 +115,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderLocal() { kubeconfig = "` + kubeconfigPath + `" cluster_provider_strategy = "i-do-not-exist" `))) - provider, err := NewManagerProvider(cfg) + provider, err := NewProvider(cfg) s.Require().Error(err, "Expected error for non-existent strategy") s.ErrorContains(err, "no provider registered for strategy 'i-do-not-exist'") s.Nilf(provider, "Expected no provider instance, got %v", provider) diff --git a/pkg/kubernetes/token.go b/pkg/kubernetes/token.go index d81f4135..f81c3a88 100644 --- a/pkg/kubernetes/token.go +++ b/pkg/kubernetes/token.go @@ -2,39 +2,10 @@ package kubernetes import ( "context" - "fmt" authenticationv1api "k8s.io/api/authentication/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (m *Manager) VerifyToken(ctx context.Context, token, audience string) (*authenticationv1api.UserInfo, []string, error) { - tokenReviewClient, err := m.accessControlClientSet.TokenReview() - if err != nil { - return nil, nil, err - } - tokenReview := &authenticationv1api.TokenReview{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "authentication.k8s.io/v1", - Kind: "TokenReview", - }, - Spec: authenticationv1api.TokenReviewSpec{ - Token: token, - Audiences: []string{audience}, - }, - } - - result, err := tokenReviewClient.Create(ctx, tokenReview, metav1.CreateOptions{}) - if err != nil { - return nil, nil, fmt.Errorf("failed to create token review: %v", err) - } - - if !result.Status.Authenticated { - if result.Status.Error != "" { - return nil, nil, fmt.Errorf("token authentication failed: %s", result.Status.Error) - } - return nil, nil, fmt.Errorf("token authentication failed") - } - - return &result.Status.User, result.Status.Audiences, nil +type TokenVerifier interface { + VerifyToken(ctx context.Context, cluster, token, audience string) (*authenticationv1api.UserInfo, []string, error) } diff --git a/pkg/mcp/m3labs.go b/pkg/mcp/m3labs.go index bae6aeb7..ade0f56b 100644 --- a/pkg/mcp/m3labs.go +++ b/pkg/mcp/m3labs.go @@ -39,15 +39,9 @@ func ServerToolToM3LabsServerTool(s *Server, tools []api.ServerTool) ([]server.S m3labTool.RawInputSchema = schema } m3labHandler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - // get the correct internalk8s.Manager for the target specified in the request + // get the correct derived Kubernetes client for the target specified in the request cluster := request.GetString(s.p.GetTargetParameterName(), s.p.GetDefaultTarget()) - m, err := s.p.GetManagerFor(ctx, cluster) - if err != nil { - return nil, err - } - - // derive the manager based on auth on top of the settings for the cluster - k, err := m.Derived(ctx) + k, err := s.p.GetDerivedKubernetes(ctx, cluster) if err != nil { return nil, err } diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index d8e91775..f64d4104 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -67,7 +67,7 @@ type Server struct { configuration *Configuration server *server.MCPServer enabledTools []string - p internalk8s.ManagerProvider + p internalk8s.Provider } func NewServer(configuration Configuration) (*Server, error) { @@ -101,7 +101,7 @@ func NewServer(configuration Configuration) (*Server, error) { func (s *Server) reloadKubernetesClusterProvider() error { ctx := context.Background() - p, err := internalk8s.NewManagerProvider(s.configuration.StaticConfig) + p, err := internalk8s.NewProvider(s.configuration.StaticConfig) if err != nil { return err } @@ -113,11 +113,6 @@ func (s *Server) reloadKubernetesClusterProvider() error { s.p = p - k, err := s.p.GetManagerFor(ctx, s.p.GetDefaultTarget()) - if err != nil { - return err - } - targets, err := p.GetTargets(ctx) if err != nil { return err @@ -136,7 +131,7 @@ func (s *Server) reloadKubernetesClusterProvider() error { applicableTools := make([]api.ServerTool, 0) for _, toolset := range s.configuration.Toolsets() { - for _, tool := range toolset.GetTools(k) { + for _, tool := range toolset.GetTools(p) { tool := mutator(tool) if !filter(tool) { continue @@ -182,23 +177,11 @@ func (s *Server) ServeHTTP(httpServer *http.Server) *server.StreamableHTTPServer // KubernetesApiVerifyToken verifies the given token with the audience by // sending an TokenReview request to API Server for the specified cluster. -func (s *Server) KubernetesApiVerifyToken(ctx context.Context, token string, audience string, cluster string) (*authenticationapiv1.UserInfo, []string, error) { +func (s *Server) KubernetesApiVerifyToken(ctx context.Context, cluster, token, audience string) (*authenticationapiv1.UserInfo, []string, error) { if s.p == nil { return nil, nil, fmt.Errorf("kubernetes cluster provider is not initialized") } - - // Use provided cluster or default - if cluster == "" { - cluster = s.p.GetDefaultTarget() - } - - // Get the cluster manager for the specified cluster - m, err := s.p.GetManagerFor(ctx, cluster) - if err != nil { - return nil, nil, err - } - - return m.VerifyToken(ctx, token, audience) + return s.p.VerifyToken(ctx, cluster, token, audience) } // GetTargetParameterName returns the parameter name used for target identification in MCP requests diff --git a/pkg/mcp/tool_filter.go b/pkg/mcp/tool_filter.go index c097132c..28678d96 100644 --- a/pkg/mcp/tool_filter.go +++ b/pkg/mcp/tool_filter.go @@ -32,7 +32,7 @@ func ShouldIncludeTargetListTool(targetName string, targets []string) ToolFilter // TODO: this check should be removed or make more generic when we have other if tool.Tool.Name == "configuration_contexts_list" && targetName != kubernetes.KubeConfigTargetParameterName { - // let's not include configuration_contexts_list if we aren't targeting contexts in our ManagerProvider + // let's not include configuration_contexts_list if we aren't targeting contexts in our Provider return false } From dfddf2382355624d23dd30c1a5b9407ff430cb69 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 15 Oct 2025 12:12:09 +0200 Subject: [PATCH 04/57] test(kubernetes): add unit tests for ProviderKubeconfig functionality (#375) Signed-off-by: Marc Nuri --- pkg/kubernetes/provider_kubeconfig_test.go | 142 +++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 pkg/kubernetes/provider_kubeconfig_test.go diff --git a/pkg/kubernetes/provider_kubeconfig_test.go b/pkg/kubernetes/provider_kubeconfig_test.go new file mode 100644 index 00000000..8a519e91 --- /dev/null +++ b/pkg/kubernetes/provider_kubeconfig_test.go @@ -0,0 +1,142 @@ +package kubernetes + +import ( + "fmt" + "net/http" + "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/stretchr/testify/suite" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +type ProviderKubeconfigTestSuite struct { + BaseProviderSuite + mockServer *test.MockServer + provider Provider +} + +func (s *ProviderKubeconfigTestSuite) SetupTest() { + s.mockServer = test.NewMockServer() + kubeconfig := s.mockServer.Kubeconfig() + for i := 0; i < 10; i++ { + // Add multiple fake contexts to force multi-cluster behavior + kubeconfig.Contexts[fmt.Sprintf("context-%d", i)] = clientcmdapi.NewContext() + } + provider, err := NewProvider(&config.StaticConfig{KubeConfig: test.KubeconfigFile(s.T(), kubeconfig)}) + s.Require().NoError(err, "Expected no error creating provider with kubeconfig") + s.provider = provider +} + +func (s *ProviderKubeconfigTestSuite) TearDownTest() { + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *ProviderKubeconfigTestSuite) TestType() { + s.IsType(&kubeConfigClusterProvider{}, s.provider) +} + +func (s *ProviderKubeconfigTestSuite) TestWithNonOpenShiftCluster() { + s.Run("IsOpenShift returns false", func() { + inOpenShift := s.provider.IsOpenShift(s.T().Context()) + s.False(inOpenShift, "Expected InOpenShift to return false") + }) +} + +func (s *ProviderKubeconfigTestSuite) TestWithOpenShiftCluster() { + s.mockServer.Handle(&test.InOpenShiftHandler{}) + s.Run("IsOpenShift returns true", func() { + inOpenShift := s.provider.IsOpenShift(s.T().Context()) + s.True(inOpenShift, "Expected InOpenShift to return true") + }) +} + +func (s *ProviderKubeconfigTestSuite) TestVerifyToken() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(` + { + "kind": "TokenReview", + "apiVersion": "authentication.k8s.io/v1", + "spec": {"token": "the-token"}, + "status": { + "authenticated": true, + "user": { + "username": "test-user", + "groups": ["system:authenticated"] + }, + "audiences": ["the-audience"] + } + }`)) + } + })) + s.Run("VerifyToken returns UserInfo for non-empty context", func() { + userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "fake-context", "some-token", "the-audience") + s.Require().NoError(err, "Expected no error from VerifyToken with empty target") + s.Require().NotNil(userInfo, "Expected UserInfo from VerifyToken with empty target") + s.Equalf(userInfo.Username, "test-user", "Expected username test-user, got: %s", userInfo.Username) + s.Containsf(userInfo.Groups, "system:authenticated", "Expected group system:authenticated in %v", userInfo.Groups) + s.Require().NotNil(audiences, "Expected audiences from VerifyToken with empty target") + s.Len(audiences, 1, "Expected audiences from VerifyToken with empty target") + s.Containsf(audiences, "the-audience", "Expected audience the-audience in %v", audiences) + }) + s.Run("VerifyToken returns UserInfo for empty context (default context", func() { + userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "", "the-token", "the-audience") + s.Require().NoError(err, "Expected no error from VerifyToken with empty target") + s.Require().NotNil(userInfo, "Expected UserInfo from VerifyToken with empty target") + s.Equalf(userInfo.Username, "test-user", "Expected username test-user, got: %s", userInfo.Username) + s.Containsf(userInfo.Groups, "system:authenticated", "Expected group system:authenticated in %v", userInfo.Groups) + s.Require().NotNil(audiences, "Expected audiences from VerifyToken with empty target") + s.Len(audiences, 1, "Expected audiences from VerifyToken with empty target") + s.Containsf(audiences, "the-audience", "Expected audience the-audience in %v", audiences) + }) +} + +func (s *ProviderKubeconfigTestSuite) TestGetTargets() { + s.Run("GetTargets returns all contexts defined in kubeconfig", func() { + targets, err := s.provider.GetTargets(s.T().Context()) + s.Require().NoError(err, "Expected no error from GetTargets") + s.Len(targets, 11, "Expected 11 targets from GetTargets") + s.Contains(targets, "fake-context", "Expected fake-context in targets from GetTargets") + for i := 0; i < 10; i++ { + s.Contains(targets, fmt.Sprintf("context-%d", i), "Expected context-%d in targets from GetTargets", i) + } + }) +} + +func (s *ProviderKubeconfigTestSuite) TestGetDerivedKubernetes() { + s.Run("GetDerivedKubernetes returns Kubernetes for valid context", func() { + k8s, err := s.provider.GetDerivedKubernetes(s.T().Context(), "fake-context") + s.Require().NoError(err, "Expected no error from GetDerivedKubernetes with valid context") + s.NotNil(k8s, "Expected Kubernetes from GetDerivedKubernetes with valid context") + }) + s.Run("GetDerivedKubernetes returns Kubernetes for empty context (default)", func() { + k8s, err := s.provider.GetDerivedKubernetes(s.T().Context(), "") + s.Require().NoError(err, "Expected no error from GetDerivedKubernetes with empty context") + s.NotNil(k8s, "Expected Kubernetes from GetDerivedKubernetes with empty context") + }) + s.Run("GetDerivedKubernetes returns error for invalid context", func() { + k8s, err := s.provider.GetDerivedKubernetes(s.T().Context(), "invalid-context") + s.Require().Error(err, "Expected error from GetDerivedKubernetes with invalid context") + s.ErrorContainsf(err, `context "invalid-context" does not exist`, "Expected context does not exist error, got: %v", err) + s.Nil(k8s, "Expected no Kubernetes from GetDerivedKubernetes with invalid context") + }) +} + +func (s *ProviderKubeconfigTestSuite) TestGetDefaultTarget() { + s.Run("GetDefaultTarget returns current-context defined in kubeconfig", func() { + s.Equal("fake-context", s.provider.GetDefaultTarget(), "Expected fake-context as default target") + }) +} + +func (s *ProviderKubeconfigTestSuite) TestGetTargetParameterName() { + s.Equal("context", s.provider.GetTargetParameterName(), "Expected context as target parameter name") +} + +func TestProviderKubeconfig(t *testing.T) { + suite.Run(t, new(ProviderKubeconfigTestSuite)) +} From 25032699dbe9b994bbcddaf5c4e2a3b13b4bd146 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 15 Oct 2025 17:16:55 +0200 Subject: [PATCH 05/57] test(kubernetes): add unit tests for ProviderSingle functionality (#376) Signed-off-by: Marc Nuri --- pkg/kubernetes/provider_kubeconfig_test.go | 4 +- pkg/kubernetes/provider_single_test.go | 133 +++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 pkg/kubernetes/provider_single_test.go diff --git a/pkg/kubernetes/provider_kubeconfig_test.go b/pkg/kubernetes/provider_kubeconfig_test.go index 8a519e91..2a9587eb 100644 --- a/pkg/kubernetes/provider_kubeconfig_test.go +++ b/pkg/kubernetes/provider_kubeconfig_test.go @@ -18,6 +18,8 @@ type ProviderKubeconfigTestSuite struct { } func (s *ProviderKubeconfigTestSuite) SetupTest() { + // Kubeconfig provider is used when the multi-cluster feature is enabled with the kubeconfig strategy. + // For this test suite we simulate a kubeconfig with multiple contexts. s.mockServer = test.NewMockServer() kubeconfig := s.mockServer.Kubeconfig() for i := 0; i < 10; i++ { @@ -84,7 +86,7 @@ func (s *ProviderKubeconfigTestSuite) TestVerifyToken() { s.Len(audiences, 1, "Expected audiences from VerifyToken with empty target") s.Containsf(audiences, "the-audience", "Expected audience the-audience in %v", audiences) }) - s.Run("VerifyToken returns UserInfo for empty context (default context", func() { + s.Run("VerifyToken returns UserInfo for empty context (default context)", func() { userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "", "the-token", "the-audience") s.Require().NoError(err, "Expected no error from VerifyToken with empty target") s.Require().NotNil(userInfo, "Expected UserInfo from VerifyToken with empty target") diff --git a/pkg/kubernetes/provider_single_test.go b/pkg/kubernetes/provider_single_test.go new file mode 100644 index 00000000..ff03e26c --- /dev/null +++ b/pkg/kubernetes/provider_single_test.go @@ -0,0 +1,133 @@ +package kubernetes + +import ( + "net/http" + "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/stretchr/testify/suite" + "k8s.io/client-go/rest" +) + +type ProviderSingleTestSuite struct { + BaseProviderSuite + mockServer *test.MockServer + originalIsInClusterConfig func() (*rest.Config, error) + provider Provider +} + +func (s *ProviderSingleTestSuite) SetupTest() { + // Single cluster provider is used when in-cluster or when the multi-cluster feature is disabled. + // For this test suite we simulate an in-cluster deployment. + s.originalIsInClusterConfig = InClusterConfig + s.mockServer = test.NewMockServer() + InClusterConfig = func() (*rest.Config, error) { + return s.mockServer.Config(), nil + } + provider, err := NewProvider(&config.StaticConfig{}) + s.Require().NoError(err, "Expected no error creating provider with kubeconfig") + s.provider = provider +} + +func (s *ProviderSingleTestSuite) TearDownTest() { + InClusterConfig = s.originalIsInClusterConfig + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *ProviderSingleTestSuite) TestType() { + s.IsType(&singleClusterProvider{}, s.provider) +} + +func (s *ProviderSingleTestSuite) TestWithNonOpenShiftCluster() { + s.Run("IsOpenShift returns false", func() { + inOpenShift := s.provider.IsOpenShift(s.T().Context()) + s.False(inOpenShift, "Expected InOpenShift to return false") + }) +} + +func (s *ProviderSingleTestSuite) TestWithOpenShiftCluster() { + s.mockServer.Handle(&test.InOpenShiftHandler{}) + s.Run("IsOpenShift returns true", func() { + inOpenShift := s.provider.IsOpenShift(s.T().Context()) + s.True(inOpenShift, "Expected InOpenShift to return true") + }) +} + +func (s *ProviderSingleTestSuite) TestVerifyToken() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(` + { + "kind": "TokenReview", + "apiVersion": "authentication.k8s.io/v1", + "spec": {"token": "the-token"}, + "status": { + "authenticated": true, + "user": { + "username": "test-user", + "groups": ["system:authenticated"] + }, + "audiences": ["the-audience"] + } + }`)) + } + })) + s.Run("VerifyToken returns UserInfo for empty target (default target)", func() { + userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "", "the-token", "the-audience") + s.Require().NoError(err, "Expected no error from VerifyToken with empty target") + s.Require().NotNil(userInfo, "Expected UserInfo from VerifyToken with empty target") + s.Equalf(userInfo.Username, "test-user", "Expected username test-user, got: %s", userInfo.Username) + s.Containsf(userInfo.Groups, "system:authenticated", "Expected group system:authenticated in %v", userInfo.Groups) + s.Require().NotNil(audiences, "Expected audiences from VerifyToken with empty target") + s.Len(audiences, 1, "Expected audiences from VerifyToken with empty target") + s.Containsf(audiences, "the-audience", "Expected audience the-audience in %v", audiences) + }) + s.Run("VerifyToken returns error for non-empty context", func() { + userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "non-empty", "the-token", "the-audience") + s.Require().Error(err, "Expected error from VerifyToken with non-empty target") + s.ErrorContains(err, "unable to get manager for other context/cluster with in-cluster strategy", "Expected error about trying to get other cluster") + s.Nil(userInfo, "Expected no UserInfo from VerifyToken with non-empty target") + s.Nil(audiences, "Expected no audiences from VerifyToken with non-empty target") + }) +} + +func (s *ProviderSingleTestSuite) TestGetTargets() { + s.Run("GetTargets returns single empty target", func() { + targets, err := s.provider.GetTargets(s.T().Context()) + s.Require().NoError(err, "Expected no error from GetTargets") + s.Len(targets, 1, "Expected 1 targets from GetTargets") + s.Contains(targets, "", "Expected empty target from GetTargets") + }) +} + +func (s *ProviderSingleTestSuite) TestGetDerivedKubernetes() { + s.Run("GetDerivedKubernetes returns Kubernetes for empty target", func() { + k8s, err := s.provider.GetDerivedKubernetes(s.T().Context(), "") + s.Require().NoError(err, "Expected no error from GetDerivedKubernetes with empty target") + s.NotNil(k8s, "Expected Kubernetes from GetDerivedKubernetes with empty target") + }) + s.Run("GetDerivedKubernetes returns error for non-empty target", func() { + k8s, err := s.provider.GetDerivedKubernetes(s.T().Context(), "non-empty-target") + s.Require().Error(err, "Expected error from GetDerivedKubernetes with non-empty target") + s.ErrorContains(err, "unable to get manager for other context/cluster with in-cluster strategy", "Expected error about trying to get other cluster") + s.Nil(k8s, "Expected no Kubernetes from GetDerivedKubernetes with non-empty target") + }) +} + +func (s *ProviderSingleTestSuite) TestGetDefaultTarget() { + s.Run("GetDefaultTarget returns empty string", func() { + s.Empty(s.provider.GetDefaultTarget(), "Expected fake-context as default target") + }) +} + +func (s *ProviderSingleTestSuite) TestGetTargetParameterName() { + s.Empty(s.provider.GetTargetParameterName(), "Expected empty string as target parameter name") +} + +func TestProviderSingle(t *testing.T) { + suite.Run(t, new(ProviderSingleTestSuite)) +} From 86628bb1bf6615b2c5052602c0f9f13a41b93305 Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Wed, 15 Oct 2025 15:00:09 -0400 Subject: [PATCH 06/57] feat(config): introduce provider-specific config registry Signed-off-by: Calum Murray --- pkg/config/config.go | 51 ++++++++++++++++++++++++++++++++--- pkg/config/provider_config.go | 33 +++++++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 pkg/config/provider_config.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 3fb2428e..5fe8e165 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,6 +1,8 @@ package config import ( + "bytes" + "fmt" "os" "github.com/BurntSushi/toml" @@ -59,8 +61,13 @@ type StaticConfig struct { // If set to "kubeconfig", the clusters will be loaded from those in the kubeconfig. // If set to "in-cluster", the server will use the in cluster config ClusterProviderStrategy string `toml:"cluster_provider_strategy,omitempty"` - // ClusterContexts is which context should be used for each cluster - ClusterContexts map[string]string `toml:"cluster_contexts"` + + // ClusterProvider-specific configurations + // This map holds raw TOML primitives that will be parsed by registered provider parsers + ClusterProviderConfigs map[string]toml.Primitive `toml:"cluster_provider_configs,omitempty"` + + // Internal: parsed provider configs (not exposed to TOML package) + parsedClusterProviderConfigs map[string]ProviderConfig } func Default() *StaticConfig { @@ -88,8 +95,46 @@ func Read(configPath string) (*StaticConfig, error) { // ReadToml reads the toml data and returns the StaticConfig. func ReadToml(configData []byte) (*StaticConfig, error) { config := Default() - if err := toml.Unmarshal(configData, config); err != nil { + md, err := toml.NewDecoder(bytes.NewReader(configData)).Decode(config) + if err != nil { + return nil, err + } + + if err := config.parseClusterProviderConfigs(md); err != nil { return nil, err } + return config, nil } + +func (c *StaticConfig) GetProviderConfig(strategy string) (ProviderConfig, bool) { + config, ok := c.parsedClusterProviderConfigs[strategy] + + return config, ok +} + +func (c *StaticConfig) parseClusterProviderConfigs(md toml.MetaData) error { + if c.parsedClusterProviderConfigs == nil { + c.parsedClusterProviderConfigs = make(map[string]ProviderConfig, len(c.ClusterProviderConfigs)) + } + + for strategy, primitive := range c.ClusterProviderConfigs { + parser, ok := getProviderConfigParser(strategy) + if !ok { + continue + } + + providerConfig, err := parser(primitive, md) + if err != nil { + return fmt.Errorf("failed to parse config for ClusterProvider '%s': %w", strategy, err) + } + + if err := providerConfig.Validate(); err != nil { + return fmt.Errorf("invalid config file for ClusterProvider '%s': %w", strategy, err) + } + + c.parsedClusterProviderConfigs[strategy] = providerConfig + } + + return nil +} diff --git a/pkg/config/provider_config.go b/pkg/config/provider_config.go new file mode 100644 index 00000000..23c5fffe --- /dev/null +++ b/pkg/config/provider_config.go @@ -0,0 +1,33 @@ +package config + +import ( + "fmt" + + "github.com/BurntSushi/toml" +) + +// ProviderConfig is the interface that all provider-specific configurations must implement. +// Each provider registers a factory function to parse its config from TOML primitives +type ProviderConfig interface { + Validate() error +} + +type ProviderConfigParser func(primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) + +var ( + providerConfigParsers = make(map[string]ProviderConfigParser) +) + +func RegisterProviderConfig(strategy string, parser ProviderConfigParser) { + if _, exists := providerConfigParsers[strategy]; exists { + panic(fmt.Sprintf("provider config parser already registered for strategy '%s'", strategy)) + } + + providerConfigParsers[strategy] = parser +} + +func getProviderConfigParser(strategy string) (ProviderConfigParser, bool) { + provider, ok := providerConfigParsers[strategy] + + return provider, ok +} From b66719ed8e81f8da069def50239c6973c3171c98 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 16 Oct 2025 12:35:47 +0200 Subject: [PATCH 07/57] test(config): add provider config tests Signed-off-by: Marc Nuri --- pkg/config/config_test.go | 28 ++--- pkg/config/provider_config_test.go | 157 +++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 pkg/config/provider_config_test.go diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b498548d..d0e87726 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -11,10 +11,25 @@ import ( "github.com/stretchr/testify/suite" ) -type ConfigSuite struct { +type BaseConfigSuite struct { suite.Suite } +func (s *BaseConfigSuite) writeConfig(content string) string { + s.T().Helper() + tempDir := s.T().TempDir() + path := filepath.Join(tempDir, "config.toml") + err := os.WriteFile(path, []byte(content), 0644) + if err != nil { + s.T().Fatalf("Failed to write config file %s: %v", path, err) + } + return path +} + +type ConfigSuite struct { + BaseConfigSuite +} + func (s *ConfigSuite) TestReadConfigMissingFile() { config, err := Read("non-existent-config.toml") s.Run("returns error for missing file", func() { @@ -159,17 +174,6 @@ func (s *ConfigSuite) TestReadConfigValidPreservesDefaultsForMissingFields() { }) } -func (s *ConfigSuite) writeConfig(content string) string { - s.T().Helper() - tempDir := s.T().TempDir() - path := filepath.Join(tempDir, "config.toml") - err := os.WriteFile(path, []byte(content), 0644) - if err != nil { - s.T().Fatalf("Failed to write config file %s: %v", path, err) - } - return path -} - func TestConfig(t *testing.T) { suite.Run(t, new(ConfigSuite)) } diff --git a/pkg/config/provider_config_test.go b/pkg/config/provider_config_test.go new file mode 100644 index 00000000..d933d894 --- /dev/null +++ b/pkg/config/provider_config_test.go @@ -0,0 +1,157 @@ +package config + +import ( + "errors" + "testing" + + "github.com/BurntSushi/toml" + "github.com/stretchr/testify/suite" +) + +type ProviderConfigSuite struct { + BaseConfigSuite + originalProviderConfigParsers map[string]ProviderConfigParser +} + +func (s *ProviderConfigSuite) SetupTest() { + s.originalProviderConfigParsers = make(map[string]ProviderConfigParser) + for k, v := range providerConfigParsers { + s.originalProviderConfigParsers[k] = v + } +} + +func (s *ProviderConfigSuite) TearDownTest() { + providerConfigParsers = make(map[string]ProviderConfigParser) + for k, v := range s.originalProviderConfigParsers { + providerConfigParsers[k] = v + } +} + +type ProviderConfigForTest struct { + BoolProp bool `toml:"bool_prop"` + StrProp string `toml:"str_prop"` + IntProp int `toml:"int_prop"` +} + +var _ ProviderConfig = (*ProviderConfigForTest)(nil) + +func (p *ProviderConfigForTest) Validate() error { + if p.StrProp == "force-error" { + return errors.New("validation error forced by test") + } + return nil +} + +func providerConfigForTestParser(primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { + var providerConfigForTest ProviderConfigForTest + if err := md.PrimitiveDecode(primitive, &providerConfigForTest); err != nil { + return nil, err + } + return &providerConfigForTest, nil +} + +func (s *ProviderConfigSuite) TestRegisterProviderConfig() { + s.Run("panics when registering duplicate provider config parser", func() { + s.Panics(func() { + RegisterProviderConfig("test", providerConfigForTestParser) + RegisterProviderConfig("test", providerConfigForTestParser) + }, "Expected panic when registering duplicate provider config parser") + }) +} + +func (s *ProviderConfigSuite) TestReadConfigValid() { + RegisterProviderConfig("test", providerConfigForTestParser) + validConfigPath := s.writeConfig(` + cluster_provider_strategy = "test" + [cluster_provider_configs.test] + bool_prop = true + str_prop = "a string" + int_prop = 42 + `) + + config, err := Read(validConfigPath) + s.Run("returns no error for valid file with registered provider config", func() { + s.Require().NoError(err, "Expected no error for valid file, got %v", err) + }) + s.Run("returns config for valid file with registered provider config", func() { + s.Require().NotNil(config, "Expected non-nil config for valid file") + }) + s.Run("parses provider config correctly", func() { + providerConfig, ok := config.GetProviderConfig("test") + s.Require().True(ok, "Expected to find provider config for strategy 'test'") + s.Require().NotNil(providerConfig, "Expected non-nil provider config for strategy 'test'") + testProviderConfig, ok := providerConfig.(*ProviderConfigForTest) + s.Require().True(ok, "Expected provider config to be of type *ProviderConfigForTest") + s.Equal(true, testProviderConfig.BoolProp, "Expected BoolProp to be true") + s.Equal("a string", testProviderConfig.StrProp, "Expected StrProp to be 'a string'") + s.Equal(42, testProviderConfig.IntProp, "Expected IntProp to be 42") + }) +} + +func (s *ProviderConfigSuite) TestReadConfigInvalidProviderConfig() { + RegisterProviderConfig("test", providerConfigForTestParser) + invalidConfigPath := s.writeConfig(` + cluster_provider_strategy = "test" + [cluster_provider_configs.test] + bool_prop = true + str_prop = "force-error" + int_prop = 42 + `) + + config, err := Read(invalidConfigPath) + s.Run("returns error for invalid provider config", func() { + s.Require().NotNil(err, "Expected error for invalid provider config, got nil") + s.ErrorContains(err, "validation error forced by test", "Expected validation error from provider config") + }) + s.Run("returns nil config for invalid provider config", func() { + s.Nil(config, "Expected nil config for invalid provider config") + }) +} + +func (s *ProviderConfigSuite) TestReadConfigUnregisteredProviderConfig() { + invalidConfigPath := s.writeConfig(` + cluster_provider_strategy = "unregistered" + [cluster_provider_configs.unregistered] + bool_prop = true + str_prop = "a string" + int_prop = 42 + `) + + config, err := Read(invalidConfigPath) + s.Run("returns no error for unregistered provider config", func() { + s.Require().NoError(err, "Expected no error for unregistered provider config, got %v", err) + }) + s.Run("returns config for unregistered provider config", func() { + s.Require().NotNil(config, "Expected non-nil config for unregistered provider config") + }) + s.Run("does not parse unregistered provider config", func() { + _, ok := config.GetProviderConfig("unregistered") + s.Require().False(ok, "Expected no provider config for unregistered strategy") + }) +} + +func (s *ProviderConfigSuite) TestReadConfigParserError() { + RegisterProviderConfig("test", func(primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { + return nil, errors.New("parser error forced by test") + }) + invalidConfigPath := s.writeConfig(` + cluster_provider_strategy = "test" + [cluster_provider_configs.test] + bool_prop = true + str_prop = "a string" + int_prop = 42 + `) + + config, err := Read(invalidConfigPath) + s.Run("returns error for provider config parser error", func() { + s.Require().NotNil(err, "Expected error for provider config parser error, got nil") + s.ErrorContains(err, "parser error forced by test", "Expected parser error from provider config") + }) + s.Run("returns nil config for provider config parser error", func() { + s.Nil(config, "Expected nil config for provider config parser error") + }) +} + +func TestProviderConfig(t *testing.T) { + suite.Run(t, new(ProviderConfigSuite)) +} From 9da29f4505f8740fbe78e3bd8fd8d69679993841 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 16 Oct 2025 21:35:21 +0200 Subject: [PATCH 08/57] refactor(kubernetes): streamline provider configuration and in-cluster detection (#378) * refactor(kubernetes): streamline provider configuration and in-cluster detection - Removed IsInCluster method from Manager and created function scoped to the runtime environment. As a method, the implementation was not correct. Removed GetAPIServerHost method from Manager which is no used. - **Temporarily** added an `inCluster` field to the Manager struct but should be eventually removed since it doesn't really make sense to hava a Manager in-cluster or out-of-cluster in the multi-cluster scenario. - Provider resolution (resolveStrategy) is now clearer, added complete coverage for all scenarios. - Added additional coverage for provider and manager. Signed-off-by: Marc Nuri * refactor(kubernetes): update NewManager to accept kubeconfig context and simplify manager creation - Removes Provider.newForContext(context string) method. Signed-off-by: Marc Nuri --------- Signed-off-by: Marc Nuri --- internal/test/env.go | 15 ++ pkg/kubernetes/configuration.go | 36 ++--- pkg/kubernetes/configuration_test.go | 155 -------------------- pkg/kubernetes/kubernetes_derived_test.go | 12 +- pkg/kubernetes/manager.go | 56 ++++--- pkg/kubernetes/manager_test.go | 163 +++++++++++++++++++++ pkg/kubernetes/provider.go | 68 ++------- pkg/kubernetes/provider_kubeconfig.go | 11 +- pkg/kubernetes/provider_kubeconfig_test.go | 7 + pkg/kubernetes/provider_single.go | 7 +- pkg/kubernetes/provider_test.go | 76 +++++++--- 11 files changed, 305 insertions(+), 301 deletions(-) create mode 100644 internal/test/env.go delete mode 100644 pkg/kubernetes/configuration_test.go create mode 100644 pkg/kubernetes/manager_test.go diff --git a/internal/test/env.go b/internal/test/env.go new file mode 100644 index 00000000..4d6afe7e --- /dev/null +++ b/internal/test/env.go @@ -0,0 +1,15 @@ +package test + +import ( + "os" + "strings" +) + +func RestoreEnv(originalEnv []string) { + os.Clearenv() + for _, env := range originalEnv { + if key, value, found := strings.Cut(env, "="); found { + _ = os.Setenv(key, value) + } + } +} diff --git a/pkg/kubernetes/configuration.go b/pkg/kubernetes/configuration.go index 25602e32..7b658acb 100644 --- a/pkg/kubernetes/configuration.go +++ b/pkg/kubernetes/configuration.go @@ -1,9 +1,9 @@ package kubernetes import ( + "github.com/containers/kubernetes-mcp-server/pkg/config" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/clientcmd/api/latest" ) @@ -22,29 +22,13 @@ var InClusterConfig = func() (*rest.Config, error) { return inClusterConfig, err } -// resolveKubernetesConfigurations resolves the required kubernetes configurations and sets them in the Kubernetes struct -func resolveKubernetesConfigurations(kubernetes *Manager) error { - // Always set clientCmdConfig - pathOptions := clientcmd.NewDefaultPathOptions() - if kubernetes.staticConfig.KubeConfig != "" { - pathOptions.LoadingRules.ExplicitPath = kubernetes.staticConfig.KubeConfig +func IsInCluster(cfg *config.StaticConfig) bool { + // Even if running in-cluster, if a kubeconfig is provided, we consider it as out-of-cluster + if cfg != nil && cfg.KubeConfig != "" { + return false } - kubernetes.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - pathOptions.LoadingRules, - &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}}) - var err error - if kubernetes.IsInCluster() { - kubernetes.cfg, err = InClusterConfig() - if err == nil && kubernetes.cfg != nil { - return nil - } - } - // Out of cluster - kubernetes.cfg, err = kubernetes.clientCmdConfig.ClientConfig() - if kubernetes.cfg != nil && kubernetes.cfg.UserAgent == "" { - kubernetes.cfg.UserAgent = rest.DefaultKubernetesUserAgent() - } - return err + restConfig, err := InClusterConfig() + return err == nil && restConfig != nil } func (k *Kubernetes) NamespaceOrDefault(namespace string) string { @@ -54,7 +38,7 @@ func (k *Kubernetes) NamespaceOrDefault(namespace string) string { // ConfigurationContextsDefault returns the current context name // TODO: Should be moved to the Provider level ? func (k *Kubernetes) ConfigurationContextsDefault() (string, error) { - if k.manager.IsInCluster() { + if k.manager.inCluster { return inClusterKubeConfigDefaultContext, nil } cfg, err := k.manager.clientCmdConfig.RawConfig() @@ -67,7 +51,7 @@ func (k *Kubernetes) ConfigurationContextsDefault() (string, error) { // ConfigurationContextsList returns the list of available context names // TODO: Should be moved to the Provider level ? func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) { - if k.manager.IsInCluster() { + if k.manager.inCluster { return map[string]string{inClusterKubeConfigDefaultContext: ""}, nil } cfg, err := k.manager.clientCmdConfig.RawConfig() @@ -93,7 +77,7 @@ func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) { func (k *Kubernetes) ConfigurationView(minify bool) (runtime.Object, error) { var cfg clientcmdapi.Config var err error - if k.manager.IsInCluster() { + if k.manager.inCluster { cfg = *clientcmdapi.NewConfig() cfg.Clusters["cluster"] = &clientcmdapi.Cluster{ Server: k.manager.cfg.Host, diff --git a/pkg/kubernetes/configuration_test.go b/pkg/kubernetes/configuration_test.go deleted file mode 100644 index 084b99d7..00000000 --- a/pkg/kubernetes/configuration_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package kubernetes - -import ( - "errors" - "os" - "path" - "runtime" - "strings" - "testing" - - "k8s.io/client-go/rest" - - "github.com/containers/kubernetes-mcp-server/pkg/config" -) - -func TestKubernetes_IsInCluster(t *testing.T) { - t.Run("with explicit kubeconfig", func(t *testing.T) { - m := Manager{ - staticConfig: &config.StaticConfig{ - KubeConfig: "kubeconfig", - }, - } - if m.IsInCluster() { - t.Errorf("expected not in cluster, got in cluster") - } - }) - t.Run("with empty kubeconfig and in cluster", func(t *testing.T) { - originalFunction := InClusterConfig - InClusterConfig = func() (*rest.Config, error) { - return &rest.Config{}, nil - } - defer func() { - InClusterConfig = originalFunction - }() - m := Manager{ - staticConfig: &config.StaticConfig{ - KubeConfig: "", - }, - } - if !m.IsInCluster() { - t.Errorf("expected in cluster, got not in cluster") - } - }) - t.Run("with empty kubeconfig and not in cluster (empty)", func(t *testing.T) { - originalFunction := InClusterConfig - InClusterConfig = func() (*rest.Config, error) { - return nil, nil - } - defer func() { - InClusterConfig = originalFunction - }() - m := Manager{ - staticConfig: &config.StaticConfig{ - KubeConfig: "", - }, - } - if m.IsInCluster() { - t.Errorf("expected not in cluster, got in cluster") - } - }) - t.Run("with empty kubeconfig and not in cluster (error)", func(t *testing.T) { - originalFunction := InClusterConfig - InClusterConfig = func() (*rest.Config, error) { - return nil, errors.New("error") - } - defer func() { - InClusterConfig = originalFunction - }() - m := Manager{ - staticConfig: &config.StaticConfig{ - KubeConfig: "", - }, - } - if m.IsInCluster() { - t.Errorf("expected not in cluster, got in cluster") - } - }) -} - -func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) { - t.Run("with missing file", func(t *testing.T) { - if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { - t.Skip("Skipping test on non-linux platforms") - } - tempDir := t.TempDir() - m := Manager{staticConfig: &config.StaticConfig{ - KubeConfig: path.Join(tempDir, "config"), - }} - err := resolveKubernetesConfigurations(&m) - if err == nil { - t.Errorf("expected error, got nil") - } - if !errors.Is(err, os.ErrNotExist) { - t.Errorf("expected file not found error, got %v", err) - } - if !strings.HasSuffix(err.Error(), ": no such file or directory") { - t.Errorf("expected file not found error, got %v", err) - } - }) - t.Run("with empty file", func(t *testing.T) { - tempDir := t.TempDir() - kubeconfigPath := path.Join(tempDir, "config") - if err := os.WriteFile(kubeconfigPath, []byte(""), 0644); err != nil { - t.Fatalf("failed to create kubeconfig file: %v", err) - } - m := Manager{staticConfig: &config.StaticConfig{ - KubeConfig: kubeconfigPath, - }} - err := resolveKubernetesConfigurations(&m) - if err == nil { - t.Errorf("expected error, got nil") - } - if !strings.Contains(err.Error(), "no configuration has been provided") { - t.Errorf("expected no kubeconfig error, got %v", err) - } - }) - t.Run("with valid file", func(t *testing.T) { - tempDir := t.TempDir() - kubeconfigPath := path.Join(tempDir, "config") - kubeconfigContent := ` -apiVersion: v1 -kind: Config -clusters: -- cluster: - server: https://example.com - name: example-cluster -contexts: -- context: - cluster: example-cluster - user: example-user - name: example-context -current-context: example-context -users: -- name: example-user - user: - token: example-token -` - if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil { - t.Fatalf("failed to create kubeconfig file: %v", err) - } - m := Manager{staticConfig: &config.StaticConfig{ - KubeConfig: kubeconfigPath, - }} - err := resolveKubernetesConfigurations(&m) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if m.cfg == nil { - t.Errorf("expected non-nil config, got nil") - } - if m.cfg.Host != "https://example.com" { - t.Errorf("expected host https://example.com, got %s", m.cfg.Host) - } - }) -} diff --git a/pkg/kubernetes/kubernetes_derived_test.go b/pkg/kubernetes/kubernetes_derived_test.go index f45a2606..5ad64db1 100644 --- a/pkg/kubernetes/kubernetes_derived_test.go +++ b/pkg/kubernetes/kubernetes_derived_test.go @@ -47,7 +47,7 @@ users: kubeconfig = "` + strings.ReplaceAll(kubeconfigPath, `\`, `\\`) + `" `))) s.Run("without authorization header returns original manager", func() { - testManager, err := NewManager(testStaticConfig) + testManager, err := NewManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -58,7 +58,7 @@ users: }) s.Run("with invalid authorization header returns original manager", func() { - testManager, err := NewManager(testStaticConfig) + testManager, err := NewManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -70,7 +70,7 @@ users: }) s.Run("with valid bearer token creates derived manager with correct configuration", func() { - testManager, err := NewManager(testStaticConfig) + testManager, err := NewManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -138,7 +138,7 @@ users: `))) s.Run("with no authorization header returns oauth token required error", func() { - testManager, err := NewManager(testStaticConfig) + testManager, err := NewManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -149,7 +149,7 @@ users: }) s.Run("with invalid authorization header returns oauth token required error", func() { - testManager, err := NewManager(testStaticConfig) + testManager, err := NewManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -161,7 +161,7 @@ users: }) s.Run("with valid bearer token creates derived manager", func() { - testManager, err := NewManager(testStaticConfig) + testManager, err := NewManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) diff --git a/pkg/kubernetes/manager.go b/pkg/kubernetes/manager.go index ea2741af..9a283a58 100644 --- a/pkg/kubernetes/manager.go +++ b/pkg/kubernetes/manager.go @@ -25,6 +25,7 @@ import ( type Manager struct { cfg *rest.Config clientCmdConfig clientcmd.ClientConfig + inCluster bool discoveryClient discovery.CachedDiscoveryInterface accessControlClientSet *AccessControlClientset accessControlRESTMapper *AccessControlRESTMapper @@ -37,18 +38,37 @@ type Manager struct { var _ helm.Kubernetes = (*Manager)(nil) var _ Openshift = (*Manager)(nil) -func NewManager(config *config.StaticConfig) (*Manager, error) { +func NewManager(config *config.StaticConfig, kubeconfigContext string) (*Manager, error) { k8s := &Manager{ staticConfig: config, } - if err := resolveKubernetesConfigurations(k8s); err != nil { - return nil, err + pathOptions := clientcmd.NewDefaultPathOptions() + if k8s.staticConfig.KubeConfig != "" { + pathOptions.LoadingRules.ExplicitPath = k8s.staticConfig.KubeConfig + } + k8s.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + pathOptions.LoadingRules, + &clientcmd.ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{Server: ""}, + CurrentContext: kubeconfigContext, + }) + var err error + if IsInCluster(k8s.staticConfig) { + k8s.cfg, err = InClusterConfig() + k8s.inCluster = true + } else { + k8s.cfg, err = k8s.clientCmdConfig.ClientConfig() + } + if err != nil || k8s.cfg == nil { + return nil, fmt.Errorf("failed to create kubernetes rest config: %v", err) + } + if k8s.cfg.UserAgent == "" { + k8s.cfg.UserAgent = rest.DefaultKubernetesUserAgent() } // TODO: Won't work because not all client-go clients use the shared context (e.g. discovery client uses context.TODO()) //k8s.cfg.Wrap(func(original http.RoundTripper) http.RoundTripper { // return &impersonateRoundTripper{original} //}) - var err error k8s.accessControlClientSet, err = NewAccessControlClientset(k8s.cfg, k8s.staticConfig) if err != nil { return nil, err @@ -107,21 +127,6 @@ func (m *Manager) Close() { } } -func (m *Manager) GetAPIServerHost() string { - if m.cfg == nil { - return "" - } - return m.cfg.Host -} - -func (m *Manager) IsInCluster() bool { - if m.staticConfig.KubeConfig != "" { - return false - } - cfg, err := InClusterConfig() - return err == nil && cfg != nil -} - func (m *Manager) configuredNamespace() string { if ns, _, nsErr := m.clientCmdConfig.Namespace(); nsErr == nil { return ns @@ -221,11 +226,14 @@ func (m *Manager) Derived(ctx context.Context) (*Kubernetes, error) { return &Kubernetes{manager: m}, nil } clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo) - derived := &Kubernetes{manager: &Manager{ - clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil), - cfg: derivedCfg, - staticConfig: m.staticConfig, - }} + derived := &Kubernetes{ + manager: &Manager{ + clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil), + inCluster: m.inCluster, + cfg: derivedCfg, + staticConfig: m.staticConfig, + }, + } derived.manager.accessControlClientSet, err = NewAccessControlClientset(derived.manager.cfg, derived.manager.staticConfig) if err != nil { if m.staticConfig.RequireOAuth { diff --git a/pkg/kubernetes/manager_test.go b/pkg/kubernetes/manager_test.go new file mode 100644 index 00000000..696e4f50 --- /dev/null +++ b/pkg/kubernetes/manager_test.go @@ -0,0 +1,163 @@ +package kubernetes + +import ( + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/stretchr/testify/suite" + "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +type ManagerTestSuite struct { + suite.Suite + originalEnv []string + originalInClusterConfig func() (*rest.Config, error) + mockServer *test.MockServer +} + +func (s *ManagerTestSuite) SetupTest() { + s.originalEnv = os.Environ() + s.originalInClusterConfig = InClusterConfig + s.mockServer = test.NewMockServer() +} + +func (s *ManagerTestSuite) TearDownTest() { + test.RestoreEnv(s.originalEnv) + InClusterConfig = s.originalInClusterConfig + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *ManagerTestSuite) TestNewManagerInCluster() { + InClusterConfig = func() (*rest.Config, error) { + return &rest.Config{}, nil + } + s.Run("with default StaticConfig (empty kubeconfig)", func() { + manager, err := NewManager(&config.StaticConfig{}, "") + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as in cluster", func() { + s.True(manager.inCluster, "expected in cluster, got not in cluster") + }) + s.Run("sets default user-agent", func() { + s.Contains(manager.cfg.UserAgent, "("+runtime.GOOS+"/"+runtime.GOARCH+")") + }) + }) + s.Run("with explicit kubeconfig", func() { + manager, err := NewManager(&config.StaticConfig{ + KubeConfig: s.mockServer.KubeconfigFile(s.T()), + }, "") + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + s.False(manager.inCluster, "expected not in cluster, got in cluster") + }) + }) +} + +func (s *ManagerTestSuite) TestNewManagerLocal() { + InClusterConfig = func() (*rest.Config, error) { + return nil, rest.ErrNotInCluster + } + s.Run("with valid kubeconfig in env", func() { + kubeconfig := s.mockServer.KubeconfigFile(s.T()) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfig)) + manager, err := NewManager(&config.StaticConfig{}, "") + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + s.False(manager.inCluster, "expected not in cluster, got in cluster") + }) + s.Run("loads correct config", func() { + s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfig, "expected kubeconfig path to match") + }) + s.Run("sets default user-agent", func() { + s.Contains(manager.cfg.UserAgent, "("+runtime.GOOS+"/"+runtime.GOARCH+")") + }) + s.Run("rest config host points to mock server", func() { + s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") + }) + }) + s.Run("with valid kubeconfig in env and explicit kubeconfig in config", func() { + kubeconfigInEnv := s.mockServer.KubeconfigFile(s.T()) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigInEnv)) + kubeconfigExplicit := s.mockServer.KubeconfigFile(s.T()) + manager, err := NewManager(&config.StaticConfig{ + KubeConfig: kubeconfigExplicit, + }, "") + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + s.False(manager.inCluster, "expected not in cluster, got in cluster") + }) + s.Run("loads correct config (explicit)", func() { + s.NotContains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigInEnv, "expected kubeconfig path to NOT match env") + s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigExplicit, "expected kubeconfig path to match explicit") + }) + s.Run("rest config host points to mock server", func() { + s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") + }) + }) + s.Run("with valid kubeconfig in env and explicit kubeconfig context (valid)", func() { + kubeconfig := s.mockServer.Kubeconfig() + kubeconfig.Contexts["not-the-mock-server"] = clientcmdapi.NewContext() + kubeconfig.Contexts["not-the-mock-server"].Cluster = "not-the-mock-server" + kubeconfig.Clusters["not-the-mock-server"] = clientcmdapi.NewCluster() + kubeconfig.Clusters["not-the-mock-server"].Server = "https://not-the-mock-server:6443" // REST configuration should point to mock server, not this + kubeconfig.CurrentContext = "not-the-mock-server" + kubeconfigFile := test.KubeconfigFile(s.T(), kubeconfig) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigFile)) + manager, err := NewManager(&config.StaticConfig{}, "fake-context") // fake-context is the one mock-server serves + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + s.False(manager.inCluster, "expected not in cluster, got in cluster") + }) + s.Run("loads correct config", func() { + s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigFile, "expected kubeconfig path to match") + }) + s.Run("rest config host points to mock server", func() { + s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") + }) + }) + s.Run("with valid kubeconfig in env and explicit kubeconfig context (invalid)", func() { + kubeconfigInEnv := s.mockServer.KubeconfigFile(s.T()) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigInEnv)) + manager, err := NewManager(&config.StaticConfig{}, "i-do-not-exist") + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.ErrorContains(err, `failed to create kubernetes rest config: context "i-do-not-exist" does not exist`) + }) + }) + s.Run("with invalid path kubeconfig in env", func() { + s.Require().NoError(os.Setenv("KUBECONFIG", "i-dont-exist")) + manager, err := NewManager(&config.StaticConfig{}, "") + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.ErrorContains(err, "failed to create kubernetes rest config") + }) + }) + s.Run("with empty kubeconfig in env", func() { + kubeconfigPath := filepath.Join(s.T().TempDir(), "config") + s.Require().NoError(os.WriteFile(kubeconfigPath, []byte(""), 0644)) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigPath)) + manager, err := NewManager(&config.StaticConfig{}, "") + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.ErrorContains(err, "no configuration has been provided") + }) + }) +} + +func TestManager(t *testing.T) { + suite.Run(t, new(ManagerTestSuite)) +} diff --git a/pkg/kubernetes/provider.go b/pkg/kubernetes/provider.go index 1c1529e7..26c8ff05 100644 --- a/pkg/kubernetes/provider.go +++ b/pkg/kubernetes/provider.go @@ -4,11 +4,6 @@ import ( "context" "github.com/containers/kubernetes-mcp-server/pkg/config" - "k8s.io/client-go/discovery/cached/memory" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - "k8s.io/client-go/tools/clientcmd" ) type Provider interface { @@ -28,76 +23,31 @@ type Provider interface { } func NewProvider(cfg *config.StaticConfig) (Provider, error) { - m, err := NewManager(cfg) - if err != nil { - return nil, err - } - - strategy := resolveStrategy(cfg, m) + strategy := resolveStrategy(cfg) factory, err := getProviderFactory(strategy) if err != nil { return nil, err } - return factory(m, cfg) -} - -func (m *Manager) newForContext(context string) (*Manager, error) { - pathOptions := clientcmd.NewDefaultPathOptions() - if m.staticConfig.KubeConfig != "" { - pathOptions.LoadingRules.ExplicitPath = m.staticConfig.KubeConfig - } - - clientCmdConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - pathOptions.LoadingRules, - &clientcmd.ConfigOverrides{ - CurrentContext: context, - }, - ) - - cfg, err := clientCmdConfig.ClientConfig() + m, err := NewManager(cfg, "") if err != nil { return nil, err } - if cfg.UserAgent == "" { - cfg.UserAgent = rest.DefaultKubernetesUserAgent() - } - - manager := &Manager{ - cfg: cfg, - clientCmdConfig: clientCmdConfig, - staticConfig: m.staticConfig, - } - - // Initialize clients for new manager - manager.accessControlClientSet, err = NewAccessControlClientset(manager.cfg, manager.staticConfig) - if err != nil { - return nil, err - } - - manager.discoveryClient = memory.NewMemCacheClient(manager.accessControlClientSet.DiscoveryClient()) - - manager.accessControlRESTMapper = NewAccessControlRESTMapper( - restmapper.NewDeferredDiscoveryRESTMapper(manager.discoveryClient), - manager.staticConfig, - ) - - manager.dynamicClient, err = dynamic.NewForConfig(manager.cfg) - if err != nil { - return nil, err - } - - return manager, nil + return factory(m, cfg) } -func resolveStrategy(cfg *config.StaticConfig, m *Manager) string { +func resolveStrategy(cfg *config.StaticConfig) string { if cfg.ClusterProviderStrategy != "" { return cfg.ClusterProviderStrategy } - if m.IsInCluster() { + if cfg.KubeConfig != "" { + return config.ClusterProviderKubeConfig + } + + if _, inClusterConfigErr := InClusterConfig(); inClusterConfigErr == nil { return config.ClusterProviderInCluster } diff --git a/pkg/kubernetes/provider_kubeconfig.go b/pkg/kubernetes/provider_kubeconfig.go index 3ae46143..21b64136 100644 --- a/pkg/kubernetes/provider_kubeconfig.go +++ b/pkg/kubernetes/provider_kubeconfig.go @@ -30,7 +30,7 @@ func init() { // via kubeconfig contexts. Returns an error if the manager is in-cluster mode. func newKubeConfigClusterProvider(m *Manager, cfg *config.StaticConfig) (Provider, error) { // Handle in-cluster mode - if m.IsInCluster() { + if IsInCluster(cfg) { return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments") } @@ -65,12 +65,7 @@ func (p *kubeConfigClusterProvider) managerForContext(context string) (*Manager, baseManager := p.managers[p.defaultContext] - if baseManager.IsInCluster() { - // In cluster mode, so context switching is not applicable - return baseManager, nil - } - - m, err := baseManager.newForContext(context) + m, err := NewManager(baseManager.staticConfig, context) if err != nil { return nil, err } @@ -92,7 +87,7 @@ func (p *kubeConfigClusterProvider) VerifyToken(ctx context.Context, context, to return m.VerifyToken(ctx, token, audience) } -func (p *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) { +func (p *kubeConfigClusterProvider) GetTargets(_ context.Context) ([]string, error) { contextNames := make([]string, 0, len(p.managers)) for contextName := range p.managers { contextNames = append(contextNames, contextName) diff --git a/pkg/kubernetes/provider_kubeconfig_test.go b/pkg/kubernetes/provider_kubeconfig_test.go index 2a9587eb..17984990 100644 --- a/pkg/kubernetes/provider_kubeconfig_test.go +++ b/pkg/kubernetes/provider_kubeconfig_test.go @@ -96,6 +96,13 @@ func (s *ProviderKubeconfigTestSuite) TestVerifyToken() { s.Len(audiences, 1, "Expected audiences from VerifyToken with empty target") s.Containsf(audiences, "the-audience", "Expected audience the-audience in %v", audiences) }) + s.Run("VerifyToken returns error for invalid context", func() { + userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "invalid-context", "some-token", "the-audience") + s.Require().Error(err, "Expected error from VerifyToken with invalid target") + s.ErrorContainsf(err, `context "invalid-context" does not exist`, "Expected context does not exist error, got: %v", err) + s.Nil(userInfo, "Expected no UserInfo from VerifyToken with invalid target") + s.Nil(audiences, "Expected no audiences from VerifyToken with invalid target") + }) } func (s *ProviderKubeconfigTestSuite) TestGetTargets() { diff --git a/pkg/kubernetes/provider_single.go b/pkg/kubernetes/provider_single.go index 884ca09c..a3de8b4f 100644 --- a/pkg/kubernetes/provider_single.go +++ b/pkg/kubernetes/provider_single.go @@ -27,7 +27,10 @@ func init() { // 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) (Provider, error) { - if strategy == config.ClusterProviderInCluster && !m.IsInCluster() { + if cfg != nil && cfg.KubeConfig != "" && strategy == config.ClusterProviderInCluster { + return nil, fmt.Errorf("kubeconfig file %s cannot be used with the in-cluster ClusterProviderStrategy", cfg.KubeConfig) + } + if strategy == config.ClusterProviderInCluster && !IsInCluster(cfg) { return nil, fmt.Errorf("server must be deployed in cluster for the in-cluster ClusterProviderStrategy") } @@ -49,7 +52,7 @@ func (p *singleClusterProvider) VerifyToken(ctx context.Context, target, token, return p.manager.VerifyToken(ctx, token, audience) } -func (p *singleClusterProvider) GetTargets(ctx context.Context) ([]string, error) { +func (p *singleClusterProvider) GetTargets(_ context.Context) ([]string, error) { return []string{""}, nil } diff --git a/pkg/kubernetes/provider_test.go b/pkg/kubernetes/provider_test.go index 9691d246..b178cb34 100644 --- a/pkg/kubernetes/provider_test.go +++ b/pkg/kubernetes/provider_test.go @@ -1,6 +1,7 @@ package kubernetes import ( + "os" "strings" "testing" @@ -31,13 +32,30 @@ func (s *BaseProviderSuite) TearDownTest() { type ProviderTestSuite struct { BaseProviderSuite + originalEnv []string + originalInClusterConfig func() (*rest.Config, error) + mockServer *test.MockServer + kubeconfigPath string } -func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { - originalIsInClusterConfig := InClusterConfig - s.T().Cleanup(func() { - InClusterConfig = originalIsInClusterConfig - }) +func (s *ProviderTestSuite) SetupTest() { + s.BaseProviderSuite.SetupTest() + s.originalEnv = os.Environ() + s.originalInClusterConfig = InClusterConfig + s.mockServer = test.NewMockServer() + s.kubeconfigPath = strings.ReplaceAll(s.mockServer.KubeconfigFile(s.T()), `\`, `\\`) +} + +func (s *ProviderTestSuite) TearDownTest() { + s.BaseProviderSuite.TearDownTest() + test.RestoreEnv(s.originalEnv) + InClusterConfig = s.originalInClusterConfig + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *ProviderTestSuite) TestNewProviderInCluster() { InClusterConfig = func() (*rest.Config, error) { return &rest.Config{}, nil } @@ -48,7 +66,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { s.NotNil(provider, "Expected provider instance") s.IsType(&singleClusterProvider{}, provider, "Expected singleClusterProvider type") }) - s.Run("With configured in-cluster cluster_provider_strategy, returns single-cluster provider", func() { + s.Run("With cluster_provider_strategy=in-cluster, returns single-cluster provider", func() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "in-cluster" `))) @@ -57,7 +75,7 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { s.NotNil(provider, "Expected provider instance") s.IsType(&singleClusterProvider{}, provider, "Expected singleClusterProvider type") }) - s.Run("With configured kubeconfig cluster_provider_strategy, returns error", func() { + s.Run("With cluster_provider_strategy=kubeconfig, returns error", func() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "kubeconfig" `))) @@ -66,7 +84,17 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { s.ErrorContains(err, "kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments") s.Nilf(provider, "Expected no provider instance, got %v", provider) }) - s.Run("With configured non-existent cluster_provider_strategy, returns error", func() { + s.Run("With cluster_provider_strategy=kubeconfig and kubeconfig set to valid path, returns kubeconfig provider", func() { + cfg := test.Must(config.ReadToml([]byte(` + cluster_provider_strategy = "kubeconfig" + kubeconfig = "` + s.kubeconfigPath + `" + `))) + provider, err := NewProvider(cfg) + s.Require().NoError(err, "Expected no error for kubeconfig strategy") + s.NotNil(provider, "Expected provider instance") + s.IsType(&kubeConfigClusterProvider{}, provider, "Expected kubeConfigClusterProvider type") + }) + s.Run("With cluster_provider_strategy=non-existent, returns error", func() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "i-do-not-exist" `))) @@ -77,22 +105,20 @@ func (s *ProviderTestSuite) TestNewManagerProviderInCluster() { }) } -func (s *ProviderTestSuite) TestNewManagerProviderLocal() { - mockServer := test.NewMockServer() - s.T().Cleanup(mockServer.Close) - kubeconfigPath := strings.ReplaceAll(mockServer.KubeconfigFile(s.T()), `\`, `\\`) +func (s *ProviderTestSuite) TestNewProviderLocal() { + InClusterConfig = func() (*rest.Config, error) { + return nil, rest.ErrNotInCluster + } + s.Require().NoError(os.Setenv("KUBECONFIG", s.kubeconfigPath)) s.Run("With no cluster_provider_strategy, returns kubeconfig provider", func() { - cfg := test.Must(config.ReadToml([]byte(` - kubeconfig = "` + kubeconfigPath + `" - `))) + cfg := test.Must(config.ReadToml([]byte{})) provider, err := NewProvider(cfg) s.Require().NoError(err, "Expected no error for kubeconfig provider") s.NotNil(provider, "Expected provider instance") s.IsType(&kubeConfigClusterProvider{}, provider, "Expected kubeConfigClusterProvider type") }) - s.Run("With configured kubeconfig cluster_provider_strategy, returns kubeconfig provider", func() { + s.Run("With cluster_provider_strategy=kubeconfig, returns kubeconfig provider", func() { cfg := test.Must(config.ReadToml([]byte(` - kubeconfig = "` + kubeconfigPath + `" cluster_provider_strategy = "kubeconfig" `))) provider, err := NewProvider(cfg) @@ -100,9 +126,8 @@ func (s *ProviderTestSuite) TestNewManagerProviderLocal() { s.NotNil(provider, "Expected provider instance") s.IsType(&kubeConfigClusterProvider{}, provider, "Expected kubeConfigClusterProvider type") }) - s.Run("With configured in-cluster cluster_provider_strategy, returns error", func() { + s.Run("With cluster_provider_strategy=in-cluster, returns error", func() { cfg := test.Must(config.ReadToml([]byte(` - kubeconfig = "` + kubeconfigPath + `" cluster_provider_strategy = "in-cluster" `))) provider, err := NewProvider(cfg) @@ -110,9 +135,18 @@ func (s *ProviderTestSuite) TestNewManagerProviderLocal() { s.ErrorContains(err, "server must be deployed in cluster for the in-cluster ClusterProviderStrategy") s.Nilf(provider, "Expected no provider instance, got %v", provider) }) - s.Run("With configured non-existent cluster_provider_strategy, returns error", func() { + s.Run("With cluster_provider_strategy=in-cluster and kubeconfig set to valid path, returns error", func() { + cfg := test.Must(config.ReadToml([]byte(` + kubeconfig = "` + s.kubeconfigPath + `" + cluster_provider_strategy = "in-cluster" + `))) + provider, err := NewProvider(cfg) + s.Require().Error(err, "Expected error for in-cluster strategy") + s.Regexp("kubeconfig file .+ cannot be used with the in-cluster ClusterProviderStrategy", err.Error()) + s.Nilf(provider, "Expected no provider instance, got %v", provider) + }) + s.Run("With configured cluster_provider_strategy=non-existent, returns error", func() { cfg := test.Must(config.ReadToml([]byte(` - kubeconfig = "` + kubeconfigPath + `" cluster_provider_strategy = "i-do-not-exist" `))) provider, err := NewProvider(cfg) From 7f4edfd07550e5c0158696a1acbd06c9c1273ee9 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 20 Oct 2025 16:59:21 +0200 Subject: [PATCH 09/57] refactor(kubernetes): Provider implementations deal with Manager instantiations (#379) * refactor(kubernetes): Provider implementations deal with Manager instantiations Removed `*Manager` parameter from `ProviderFactory`. Provider implementations should deal with the appropriate (base) Manager instantiation if needed at all. Manager creation function divided into two explicit functions: - NewKubeconfigManager: to be used when using KubeConfig files - NewInClusterManager: to be used inside a cluster New functions contain validations to ensure they are used in the expected places. This ensures that the right manager is used by the provider implementation. Fake kubeconfig for in-cluster Manager is now generated when the Manager is created. This kubeconfig has the "magic" strings (inClusterKubeConfigDefaultContext) that are used by the MCP server and tool mutators. Signed-off-by: Marc Nuri * review: Provider implementation refactor Signed-off-by: Marc Nuri --------- Signed-off-by: Marc Nuri --- pkg/kubernetes/configuration.go | 22 +- pkg/kubernetes/kubernetes_derived_test.go | 12 +- pkg/kubernetes/manager.go | 74 +++++-- pkg/kubernetes/manager_test.go | 255 +++++++++++++--------- pkg/kubernetes/provider.go | 7 +- pkg/kubernetes/provider_kubeconfig.go | 18 +- pkg/kubernetes/provider_registry.go | 2 +- pkg/kubernetes/provider_registry_test.go | 10 +- pkg/kubernetes/provider_single.go | 21 +- pkg/kubernetes/provider_test.go | 11 +- 10 files changed, 258 insertions(+), 174 deletions(-) diff --git a/pkg/kubernetes/configuration.go b/pkg/kubernetes/configuration.go index 7b658acb..71fd2dd2 100644 --- a/pkg/kubernetes/configuration.go +++ b/pkg/kubernetes/configuration.go @@ -38,9 +38,6 @@ func (k *Kubernetes) NamespaceOrDefault(namespace string) string { // ConfigurationContextsDefault returns the current context name // TODO: Should be moved to the Provider level ? func (k *Kubernetes) ConfigurationContextsDefault() (string, error) { - if k.manager.inCluster { - return inClusterKubeConfigDefaultContext, nil - } cfg, err := k.manager.clientCmdConfig.RawConfig() if err != nil { return "", err @@ -51,9 +48,6 @@ func (k *Kubernetes) ConfigurationContextsDefault() (string, error) { // ConfigurationContextsList returns the list of available context names // TODO: Should be moved to the Provider level ? func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) { - if k.manager.inCluster { - return map[string]string{inClusterKubeConfigDefaultContext: ""}, nil - } cfg, err := k.manager.clientCmdConfig.RawConfig() if err != nil { return nil, err @@ -77,21 +71,7 @@ func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) { func (k *Kubernetes) ConfigurationView(minify bool) (runtime.Object, error) { var cfg clientcmdapi.Config var err error - if k.manager.inCluster { - cfg = *clientcmdapi.NewConfig() - cfg.Clusters["cluster"] = &clientcmdapi.Cluster{ - Server: k.manager.cfg.Host, - InsecureSkipTLSVerify: k.manager.cfg.Insecure, - } - cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{ - Token: k.manager.cfg.BearerToken, - } - cfg.Contexts[inClusterKubeConfigDefaultContext] = &clientcmdapi.Context{ - Cluster: "cluster", - AuthInfo: "user", - } - cfg.CurrentContext = inClusterKubeConfigDefaultContext - } else if cfg, err = k.manager.clientCmdConfig.RawConfig(); err != nil { + if cfg, err = k.manager.clientCmdConfig.RawConfig(); err != nil { return nil, err } if minify { diff --git a/pkg/kubernetes/kubernetes_derived_test.go b/pkg/kubernetes/kubernetes_derived_test.go index 5ad64db1..69d4ef33 100644 --- a/pkg/kubernetes/kubernetes_derived_test.go +++ b/pkg/kubernetes/kubernetes_derived_test.go @@ -47,7 +47,7 @@ users: kubeconfig = "` + strings.ReplaceAll(kubeconfigPath, `\`, `\\`) + `" `))) s.Run("without authorization header returns original manager", func() { - testManager, err := NewManager(testStaticConfig, "") + testManager, err := NewKubeconfigManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -58,7 +58,7 @@ users: }) s.Run("with invalid authorization header returns original manager", func() { - testManager, err := NewManager(testStaticConfig, "") + testManager, err := NewKubeconfigManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -70,7 +70,7 @@ users: }) s.Run("with valid bearer token creates derived manager with correct configuration", func() { - testManager, err := NewManager(testStaticConfig, "") + testManager, err := NewKubeconfigManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -138,7 +138,7 @@ users: `))) s.Run("with no authorization header returns oauth token required error", func() { - testManager, err := NewManager(testStaticConfig, "") + testManager, err := NewKubeconfigManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -149,7 +149,7 @@ users: }) s.Run("with invalid authorization header returns oauth token required error", func() { - testManager, err := NewManager(testStaticConfig, "") + testManager, err := NewKubeconfigManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) @@ -161,7 +161,7 @@ users: }) s.Run("with valid bearer token creates derived manager", func() { - testManager, err := NewManager(testStaticConfig, "") + testManager, err := NewKubeconfigManager(testStaticConfig, "") s.Require().NoErrorf(err, "failed to create test manager: %v", err) s.T().Cleanup(testManager.Close) diff --git a/pkg/kubernetes/manager.go b/pkg/kubernetes/manager.go index 9a283a58..d09b8790 100644 --- a/pkg/kubernetes/manager.go +++ b/pkg/kubernetes/manager.go @@ -25,7 +25,6 @@ import ( type Manager struct { cfg *rest.Config clientCmdConfig clientcmd.ClientConfig - inCluster bool discoveryClient discovery.CachedDiscoveryInterface accessControlClientSet *AccessControlClientset accessControlRESTMapper *AccessControlRESTMapper @@ -38,33 +37,77 @@ type Manager struct { var _ helm.Kubernetes = (*Manager)(nil) var _ Openshift = (*Manager)(nil) -func NewManager(config *config.StaticConfig, kubeconfigContext string) (*Manager, error) { - k8s := &Manager{ - staticConfig: config, +var ( + ErrorKubeconfigInClusterNotAllowed = errors.New("kubeconfig manager cannot be used in in-cluster deployments") + ErrorInClusterNotInCluster = errors.New("in-cluster manager cannot be used outside of a cluster") +) + +func NewKubeconfigManager(config *config.StaticConfig, kubeconfigContext string) (*Manager, error) { + if IsInCluster(config) { + return nil, ErrorKubeconfigInClusterNotAllowed } + pathOptions := clientcmd.NewDefaultPathOptions() - if k8s.staticConfig.KubeConfig != "" { - pathOptions.LoadingRules.ExplicitPath = k8s.staticConfig.KubeConfig + if config.KubeConfig != "" { + pathOptions.LoadingRules.ExplicitPath = config.KubeConfig } - k8s.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientCmdConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( pathOptions.LoadingRules, &clientcmd.ConfigOverrides{ ClusterInfo: clientcmdapi.Cluster{Server: ""}, CurrentContext: kubeconfigContext, }) - var err error - if IsInCluster(k8s.staticConfig) { - k8s.cfg, err = InClusterConfig() - k8s.inCluster = true - } else { - k8s.cfg, err = k8s.clientCmdConfig.ClientConfig() + + restConfig, err := clientCmdConfig.ClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to create kubernetes rest config from kubeconfig: %v", err) } - if err != nil || k8s.cfg == nil { - return nil, fmt.Errorf("failed to create kubernetes rest config: %v", err) + + return newManager(config, restConfig, clientCmdConfig) +} + +func NewInClusterManager(config *config.StaticConfig) (*Manager, error) { + if config.KubeConfig != "" { + return nil, fmt.Errorf("kubeconfig file %s cannot be used with the in-cluster deployments: %v", config.KubeConfig, ErrorKubeconfigInClusterNotAllowed) + } + + if !IsInCluster(config) { + return nil, ErrorInClusterNotInCluster + } + + restConfig, err := InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to create in-cluster kubernetes rest config: %v", err) + } + + // Create a dummy kubeconfig clientcmdapi.Config for in-cluster config to be used in places where clientcmd.ClientConfig is required + clientCmdConfig := clientcmdapi.NewConfig() + clientCmdConfig.Clusters["cluster"] = &clientcmdapi.Cluster{ + Server: restConfig.Host, + InsecureSkipTLSVerify: restConfig.Insecure, + } + clientCmdConfig.AuthInfos["user"] = &clientcmdapi.AuthInfo{ + Token: restConfig.BearerToken, + } + clientCmdConfig.Contexts[inClusterKubeConfigDefaultContext] = &clientcmdapi.Context{ + Cluster: "cluster", + AuthInfo: "user", + } + clientCmdConfig.CurrentContext = inClusterKubeConfigDefaultContext + + return newManager(config, restConfig, clientcmd.NewDefaultClientConfig(*clientCmdConfig, nil)) +} + +func newManager(config *config.StaticConfig, restConfig *rest.Config, clientCmdConfig clientcmd.ClientConfig) (*Manager, error) { + k8s := &Manager{ + staticConfig: config, + cfg: restConfig, + clientCmdConfig: clientCmdConfig, } if k8s.cfg.UserAgent == "" { k8s.cfg.UserAgent = rest.DefaultKubernetesUserAgent() } + var err error // TODO: Won't work because not all client-go clients use the shared context (e.g. discovery client uses context.TODO()) //k8s.cfg.Wrap(func(original http.RoundTripper) http.RoundTripper { // return &impersonateRoundTripper{original} @@ -229,7 +272,6 @@ func (m *Manager) Derived(ctx context.Context) (*Kubernetes, error) { derived := &Kubernetes{ manager: &Manager{ clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil), - inCluster: m.inCluster, cfg: derivedCfg, staticConfig: m.staticConfig, }, diff --git a/pkg/kubernetes/manager_test.go b/pkg/kubernetes/manager_test.go index 696e4f50..63241fa9 100644 --- a/pkg/kubernetes/manager_test.go +++ b/pkg/kubernetes/manager_test.go @@ -34,126 +34,165 @@ func (s *ManagerTestSuite) TearDownTest() { } } -func (s *ManagerTestSuite) TestNewManagerInCluster() { - InClusterConfig = func() (*rest.Config, error) { - return &rest.Config{}, nil - } - s.Run("with default StaticConfig (empty kubeconfig)", func() { - manager, err := NewManager(&config.StaticConfig{}, "") - s.Require().NoError(err) - s.Require().NotNil(manager) - s.Run("behaves as in cluster", func() { - s.True(manager.inCluster, "expected in cluster, got not in cluster") - }) - s.Run("sets default user-agent", func() { - s.Contains(manager.cfg.UserAgent, "("+runtime.GOOS+"/"+runtime.GOARCH+")") - }) - }) - s.Run("with explicit kubeconfig", func() { - manager, err := NewManager(&config.StaticConfig{ - KubeConfig: s.mockServer.KubeconfigFile(s.T()), - }, "") - s.Require().NoError(err) - s.Require().NotNil(manager) - s.Run("behaves as NOT in cluster", func() { - s.False(manager.inCluster, "expected not in cluster, got in cluster") +func (s *ManagerTestSuite) TestNewInClusterManager() { + s.Run("In cluster", func() { + InClusterConfig = func() (*rest.Config, error) { + return &rest.Config{}, nil + } + s.Run("with default StaticConfig (empty kubeconfig)", func() { + manager, err := NewInClusterManager(&config.StaticConfig{}) + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as in cluster", func() { + rawConfig, err := manager.clientCmdConfig.RawConfig() + s.Require().NoError(err) + s.Equal("in-cluster", rawConfig.CurrentContext, "expected current context to be 'in-cluster'") + }) + s.Run("sets default user-agent", func() { + s.Contains(manager.cfg.UserAgent, "("+runtime.GOOS+"/"+runtime.GOARCH+")") + }) + }) + s.Run("with explicit kubeconfig", func() { + manager, err := NewInClusterManager(&config.StaticConfig{ + KubeConfig: s.mockServer.KubeconfigFile(s.T()), + }) + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.Regexp("kubeconfig file .+ cannot be used with the in-cluster deployments", err.Error()) + }) }) }) -} - -func (s *ManagerTestSuite) TestNewManagerLocal() { - InClusterConfig = func() (*rest.Config, error) { - return nil, rest.ErrNotInCluster - } - s.Run("with valid kubeconfig in env", func() { - kubeconfig := s.mockServer.KubeconfigFile(s.T()) - s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfig)) - manager, err := NewManager(&config.StaticConfig{}, "") - s.Require().NoError(err) - s.Require().NotNil(manager) - s.Run("behaves as NOT in cluster", func() { - s.False(manager.inCluster, "expected not in cluster, got in cluster") - }) - s.Run("loads correct config", func() { - s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfig, "expected kubeconfig path to match") - }) - s.Run("sets default user-agent", func() { - s.Contains(manager.cfg.UserAgent, "("+runtime.GOOS+"/"+runtime.GOARCH+")") - }) - s.Run("rest config host points to mock server", func() { - s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") - }) - }) - s.Run("with valid kubeconfig in env and explicit kubeconfig in config", func() { - kubeconfigInEnv := s.mockServer.KubeconfigFile(s.T()) - s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigInEnv)) - kubeconfigExplicit := s.mockServer.KubeconfigFile(s.T()) - manager, err := NewManager(&config.StaticConfig{ - KubeConfig: kubeconfigExplicit, - }, "") - s.Require().NoError(err) - s.Require().NotNil(manager) - s.Run("behaves as NOT in cluster", func() { - s.False(manager.inCluster, "expected not in cluster, got in cluster") - }) - s.Run("loads correct config (explicit)", func() { - s.NotContains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigInEnv, "expected kubeconfig path to NOT match env") - s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigExplicit, "expected kubeconfig path to match explicit") - }) - s.Run("rest config host points to mock server", func() { - s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") - }) - }) - s.Run("with valid kubeconfig in env and explicit kubeconfig context (valid)", func() { - kubeconfig := s.mockServer.Kubeconfig() - kubeconfig.Contexts["not-the-mock-server"] = clientcmdapi.NewContext() - kubeconfig.Contexts["not-the-mock-server"].Cluster = "not-the-mock-server" - kubeconfig.Clusters["not-the-mock-server"] = clientcmdapi.NewCluster() - kubeconfig.Clusters["not-the-mock-server"].Server = "https://not-the-mock-server:6443" // REST configuration should point to mock server, not this - kubeconfig.CurrentContext = "not-the-mock-server" - kubeconfigFile := test.KubeconfigFile(s.T(), kubeconfig) - s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigFile)) - manager, err := NewManager(&config.StaticConfig{}, "fake-context") // fake-context is the one mock-server serves - s.Require().NoError(err) - s.Require().NotNil(manager) - s.Run("behaves as NOT in cluster", func() { - s.False(manager.inCluster, "expected not in cluster, got in cluster") - }) - s.Run("loads correct config", func() { - s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigFile, "expected kubeconfig path to match") - }) - s.Run("rest config host points to mock server", func() { - s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") - }) - }) - s.Run("with valid kubeconfig in env and explicit kubeconfig context (invalid)", func() { - kubeconfigInEnv := s.mockServer.KubeconfigFile(s.T()) - s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigInEnv)) - manager, err := NewManager(&config.StaticConfig{}, "i-do-not-exist") + s.Run("Out of cluster", func() { + InClusterConfig = func() (*rest.Config, error) { + return nil, rest.ErrNotInCluster + } + manager, err := NewInClusterManager(&config.StaticConfig{}) s.Run("returns error", func() { s.Error(err) s.Nil(manager) - s.ErrorContains(err, `failed to create kubernetes rest config: context "i-do-not-exist" does not exist`) + s.ErrorIs(err, ErrorInClusterNotInCluster) + s.ErrorContains(err, "in-cluster manager cannot be used outside of a cluster") }) }) - s.Run("with invalid path kubeconfig in env", func() { - s.Require().NoError(os.Setenv("KUBECONFIG", "i-dont-exist")) - manager, err := NewManager(&config.StaticConfig{}, "") - s.Run("returns error", func() { - s.Error(err) - s.Nil(manager) - s.ErrorContains(err, "failed to create kubernetes rest config") +} + +func (s *ManagerTestSuite) TestNewKubeconfigManager() { + s.Run("Out of cluster", func() { + InClusterConfig = func() (*rest.Config, error) { + return nil, rest.ErrNotInCluster + } + s.Run("with valid kubeconfig in env", func() { + kubeconfig := s.mockServer.KubeconfigFile(s.T()) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfig)) + manager, err := NewKubeconfigManager(&config.StaticConfig{}, "") + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + rawConfig, err := manager.clientCmdConfig.RawConfig() + s.Require().NoError(err) + s.NotEqual("in-cluster", rawConfig.CurrentContext, "expected current context to NOT be 'in-cluster'") + s.Equal("fake-context", rawConfig.CurrentContext, "expected current context to be 'fake-context' as in kubeconfig") + }) + s.Run("loads correct config", func() { + s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfig, "expected kubeconfig path to match") + }) + s.Run("sets default user-agent", func() { + s.Contains(manager.cfg.UserAgent, "("+runtime.GOOS+"/"+runtime.GOARCH+")") + }) + s.Run("rest config host points to mock server", func() { + s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") + }) + }) + s.Run("with valid kubeconfig in env and explicit kubeconfig in config", func() { + kubeconfigInEnv := s.mockServer.KubeconfigFile(s.T()) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigInEnv)) + kubeconfigExplicit := s.mockServer.KubeconfigFile(s.T()) + manager, err := NewKubeconfigManager(&config.StaticConfig{ + KubeConfig: kubeconfigExplicit, + }, "") + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + rawConfig, err := manager.clientCmdConfig.RawConfig() + s.Require().NoError(err) + s.NotEqual("in-cluster", rawConfig.CurrentContext, "expected current context to NOT be 'in-cluster'") + s.Equal("fake-context", rawConfig.CurrentContext, "expected current context to be 'fake-context' as in kubeconfig") + }) + s.Run("loads correct config (explicit)", func() { + s.NotContains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigInEnv, "expected kubeconfig path to NOT match env") + s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigExplicit, "expected kubeconfig path to match explicit") + }) + s.Run("rest config host points to mock server", func() { + s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") + }) + }) + s.Run("with valid kubeconfig in env and explicit kubeconfig context (valid)", func() { + kubeconfig := s.mockServer.Kubeconfig() + kubeconfig.Contexts["not-the-mock-server"] = clientcmdapi.NewContext() + kubeconfig.Contexts["not-the-mock-server"].Cluster = "not-the-mock-server" + kubeconfig.Clusters["not-the-mock-server"] = clientcmdapi.NewCluster() + kubeconfig.Clusters["not-the-mock-server"].Server = "https://not-the-mock-server:6443" // REST configuration should point to mock server, not this + kubeconfig.CurrentContext = "not-the-mock-server" + kubeconfigFile := test.KubeconfigFile(s.T(), kubeconfig) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigFile)) + manager, err := NewKubeconfigManager(&config.StaticConfig{}, "fake-context") // fake-context is the one mock-server serves + s.Require().NoError(err) + s.Require().NotNil(manager) + s.Run("behaves as NOT in cluster", func() { + rawConfig, err := manager.clientCmdConfig.RawConfig() + s.Require().NoError(err) + s.NotEqual("in-cluster", rawConfig.CurrentContext, "expected current context to NOT be 'in-cluster'") + s.Equal("not-the-mock-server", rawConfig.CurrentContext, "expected current context to be 'not-the-mock-server' as in explicit context") + }) + s.Run("loads correct config", func() { + s.Contains(manager.clientCmdConfig.ConfigAccess().GetLoadingPrecedence(), kubeconfigFile, "expected kubeconfig path to match") + }) + s.Run("rest config host points to mock server", func() { + s.Equal(s.mockServer.Config().Host, manager.cfg.Host, "expected rest config host to match mock server") + }) + }) + s.Run("with valid kubeconfig in env and explicit kubeconfig context (invalid)", func() { + kubeconfigInEnv := s.mockServer.KubeconfigFile(s.T()) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigInEnv)) + manager, err := NewKubeconfigManager(&config.StaticConfig{}, "i-do-not-exist") + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.ErrorContains(err, `failed to create kubernetes rest config from kubeconfig: context "i-do-not-exist" does not exist`) + }) + }) + s.Run("with invalid path kubeconfig in env", func() { + s.Require().NoError(os.Setenv("KUBECONFIG", "i-dont-exist")) + manager, err := NewKubeconfigManager(&config.StaticConfig{}, "") + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.ErrorContains(err, "failed to create kubernetes rest config") + }) + }) + s.Run("with empty kubeconfig in env", func() { + kubeconfigPath := filepath.Join(s.T().TempDir(), "config") + s.Require().NoError(os.WriteFile(kubeconfigPath, []byte(""), 0644)) + s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigPath)) + manager, err := NewKubeconfigManager(&config.StaticConfig{}, "") + s.Run("returns error", func() { + s.Error(err) + s.Nil(manager) + s.ErrorContains(err, "no configuration has been provided") + }) }) }) - s.Run("with empty kubeconfig in env", func() { - kubeconfigPath := filepath.Join(s.T().TempDir(), "config") - s.Require().NoError(os.WriteFile(kubeconfigPath, []byte(""), 0644)) - s.Require().NoError(os.Setenv("KUBECONFIG", kubeconfigPath)) - manager, err := NewManager(&config.StaticConfig{}, "") + s.Run("In cluster", func() { + InClusterConfig = func() (*rest.Config, error) { + return &rest.Config{}, nil + } + manager, err := NewKubeconfigManager(&config.StaticConfig{}, "") s.Run("returns error", func() { s.Error(err) s.Nil(manager) - s.ErrorContains(err, "no configuration has been provided") + s.ErrorIs(err, ErrorKubeconfigInClusterNotAllowed) + s.ErrorContains(err, "kubeconfig manager cannot be used in in-cluster deployments") }) }) } diff --git a/pkg/kubernetes/provider.go b/pkg/kubernetes/provider.go index 26c8ff05..092c7de8 100644 --- a/pkg/kubernetes/provider.go +++ b/pkg/kubernetes/provider.go @@ -30,12 +30,7 @@ func NewProvider(cfg *config.StaticConfig) (Provider, error) { return nil, err } - m, err := NewManager(cfg, "") - if err != nil { - return nil, err - } - - return factory(m, cfg) + return factory(cfg) } func resolveStrategy(cfg *config.StaticConfig) string { diff --git a/pkg/kubernetes/provider_kubeconfig.go b/pkg/kubernetes/provider_kubeconfig.go index 21b64136..9ab055c8 100644 --- a/pkg/kubernetes/provider_kubeconfig.go +++ b/pkg/kubernetes/provider_kubeconfig.go @@ -2,6 +2,7 @@ package kubernetes import ( "context" + "errors" "fmt" "github.com/containers/kubernetes-mcp-server/pkg/config" @@ -27,11 +28,16 @@ func init() { } // 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) (Provider, error) { - // Handle in-cluster mode - if IsInCluster(cfg) { - return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments") +// via kubeconfig contexts. +// Internally, it leverages a KubeconfigManager for each context, initializing them +// lazily when requested. +func newKubeConfigClusterProvider(cfg *config.StaticConfig) (Provider, error) { + m, err := NewKubeconfigManager(cfg, "") + if err != nil { + if errors.Is(err, ErrorKubeconfigInClusterNotAllowed) { + return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments: %v", err) + } + return nil, err } rawConfig, err := m.clientCmdConfig.RawConfig() @@ -65,7 +71,7 @@ func (p *kubeConfigClusterProvider) managerForContext(context string) (*Manager, baseManager := p.managers[p.defaultContext] - m, err := NewManager(baseManager.staticConfig, context) + m, err := NewKubeconfigManager(baseManager.staticConfig, context) if err != nil { return nil, err } diff --git a/pkg/kubernetes/provider_registry.go b/pkg/kubernetes/provider_registry.go index 9af5a9ee..b9077f15 100644 --- a/pkg/kubernetes/provider_registry.go +++ b/pkg/kubernetes/provider_registry.go @@ -10,7 +10,7 @@ import ( // ProviderFactory creates a new Provider 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) (Provider, error) +type ProviderFactory func(cfg *config.StaticConfig) (Provider, error) var providerFactories = make(map[string]ProviderFactory) diff --git a/pkg/kubernetes/provider_registry_test.go b/pkg/kubernetes/provider_registry_test.go index 876e2bab..c94e1ec1 100644 --- a/pkg/kubernetes/provider_registry_test.go +++ b/pkg/kubernetes/provider_registry_test.go @@ -13,18 +13,18 @@ type ProviderRegistryTestSuite struct { func (s *ProviderRegistryTestSuite) TestRegisterProvider() { s.Run("With no pre-existing provider, registers the provider", func() { - RegisterProvider("test-strategy", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { + RegisterProvider("test-strategy", func(cfg *config.StaticConfig) (Provider, error) { return nil, nil }) _, exists := providerFactories["test-strategy"] s.True(exists, "Provider should be registered") }) s.Run("With pre-existing provider, panics", func() { - RegisterProvider("test-pre-existent", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { + RegisterProvider("test-pre-existent", func(cfg *config.StaticConfig) (Provider, error) { return nil, nil }) s.Panics(func() { - RegisterProvider("test-pre-existent", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { + RegisterProvider("test-pre-existent", func(cfg *config.StaticConfig) (Provider, error) { return nil, nil }) }, "Registering a provider with an existing strategy should panic") @@ -39,10 +39,10 @@ func (s *ProviderRegistryTestSuite) TestGetRegisteredStrategies() { }) s.Run("With multiple registered providers, returns sorted list", func() { providerFactories = make(map[string]ProviderFactory) - RegisterProvider("foo-strategy", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { + RegisterProvider("foo-strategy", func(cfg *config.StaticConfig) (Provider, error) { return nil, nil }) - RegisterProvider("bar-strategy", func(m *Manager, cfg *config.StaticConfig) (Provider, error) { + RegisterProvider("bar-strategy", func(cfg *config.StaticConfig) (Provider, error) { return nil, nil }) strategies := GetRegisteredStrategies() diff --git a/pkg/kubernetes/provider_single.go b/pkg/kubernetes/provider_single.go index a3de8b4f..3693d639 100644 --- a/pkg/kubernetes/provider_single.go +++ b/pkg/kubernetes/provider_single.go @@ -2,6 +2,7 @@ package kubernetes import ( "context" + "errors" "fmt" "github.com/containers/kubernetes-mcp-server/pkg/config" @@ -24,14 +25,26 @@ func init() { } // newSingleClusterProvider creates a provider that manages a single cluster. -// Validates that the manager is in-cluster when the in-cluster strategy is used. +// When used within a cluster or with an 'in-cluster' strategy, it uses an InClusterManager. +// Otherwise, it uses a KubeconfigManager. func newSingleClusterProvider(strategy string) ProviderFactory { - return func(m *Manager, cfg *config.StaticConfig) (Provider, error) { + return func(cfg *config.StaticConfig) (Provider, error) { if cfg != nil && cfg.KubeConfig != "" && strategy == config.ClusterProviderInCluster { return nil, fmt.Errorf("kubeconfig file %s cannot be used with the in-cluster ClusterProviderStrategy", cfg.KubeConfig) } - if strategy == config.ClusterProviderInCluster && !IsInCluster(cfg) { - return nil, fmt.Errorf("server must be deployed in cluster for the in-cluster ClusterProviderStrategy") + + var m *Manager + var err error + if strategy == config.ClusterProviderInCluster || IsInCluster(cfg) { + m, err = NewInClusterManager(cfg) + } else { + m, err = NewKubeconfigManager(cfg, "") + } + if err != nil { + if errors.Is(err, ErrorInClusterNotInCluster) { + return nil, fmt.Errorf("server must be deployed in cluster for the %s ClusterProviderStrategy: %v", strategy, err) + } + return nil, err } return &singleClusterProvider{ diff --git a/pkg/kubernetes/provider_test.go b/pkg/kubernetes/provider_test.go index b178cb34..32ea5668 100644 --- a/pkg/kubernetes/provider_test.go +++ b/pkg/kubernetes/provider_test.go @@ -126,6 +126,15 @@ func (s *ProviderTestSuite) TestNewProviderLocal() { s.NotNil(provider, "Expected provider instance") s.IsType(&kubeConfigClusterProvider{}, provider, "Expected kubeConfigClusterProvider type") }) + s.Run("With cluster_provider_strategy=disabled, returns single-cluster provider", func() { + cfg := test.Must(config.ReadToml([]byte(` + cluster_provider_strategy = "disabled" + `))) + provider, err := NewProvider(cfg) + s.Require().NoError(err, "Expected no error for disabled strategy") + s.NotNil(provider, "Expected provider instance") + s.IsType(&singleClusterProvider{}, provider, "Expected singleClusterProvider type") + }) s.Run("With cluster_provider_strategy=in-cluster, returns error", func() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "in-cluster" @@ -145,7 +154,7 @@ func (s *ProviderTestSuite) TestNewProviderLocal() { s.Regexp("kubeconfig file .+ cannot be used with the in-cluster ClusterProviderStrategy", err.Error()) s.Nilf(provider, "Expected no provider instance, got %v", provider) }) - s.Run("With configured cluster_provider_strategy=non-existent, returns error", func() { + s.Run("With cluster_provider_strategy=non-existent, returns error", func() { cfg := test.Must(config.ReadToml([]byte(` cluster_provider_strategy = "i-do-not-exist" `))) From 49afbad502e5d97248f3c0d4b7b324311a91d021 Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Mon, 20 Oct 2025 17:16:52 +0200 Subject: [PATCH 10/57] feat(http): add custom CA certificate support for OIDC providers --- pkg/http/authorization.go | 8 ++++++-- pkg/http/http.go | 6 +++--- pkg/http/http_test.go | 2 +- pkg/http/wellknown.go | 9 +++++++-- pkg/kubernetes-mcp-server/cmd/root.go | 5 +++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pkg/http/authorization.go b/pkg/http/authorization.go index cded7f3e..19f61709 100644 --- a/pkg/http/authorization.go +++ b/pkg/http/authorization.go @@ -108,7 +108,7 @@ func write401(w http.ResponseWriter, wwwAuthenticateHeader, errorType, message s // - If ValidateToken is set, the exchanged token is then used against the Kubernetes API Server for TokenReview. // // see TestAuthorizationOidcTokenExchange -func AuthorizationMiddleware(staticConfig *config.StaticConfig, oidcProvider *oidc.Provider, verifier KubernetesApiTokenVerifier) func(http.Handler) http.Handler { +func AuthorizationMiddleware(staticConfig *config.StaticConfig, oidcProvider *oidc.Provider, verifier KubernetesApiTokenVerifier, httpClient *http.Client) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == healthEndpoint || slices.Contains(WellKnownEndpoints, r.URL.EscapedPath()) { @@ -159,7 +159,11 @@ func AuthorizationMiddleware(staticConfig *config.StaticConfig, oidcProvider *oi if err == nil && sts.IsEnabled() { var exchangedToken *oauth2.Token // If the token is valid, we can exchange it for a new token with the specified audience and scopes. - exchangedToken, err = sts.ExternalAccountTokenExchange(r.Context(), &oauth2.Token{ + ctx := r.Context() + if httpClient != nil { + ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) + } + exchangedToken, err = sts.ExternalAccountTokenExchange(ctx, &oauth2.Token{ AccessToken: claims.Token, TokenType: "Bearer", }) diff --git a/pkg/http/http.go b/pkg/http/http.go index 3f74c09f..8001462c 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -24,11 +24,11 @@ const ( sseMessageEndpoint = "/message" ) -func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.StaticConfig, oidcProvider *oidc.Provider) error { +func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.StaticConfig, oidcProvider *oidc.Provider, httpClient *http.Client) error { mux := http.NewServeMux() wrappedMux := RequestMiddleware( - AuthorizationMiddleware(staticConfig, oidcProvider, mcpServer)(mux), + AuthorizationMiddleware(staticConfig, oidcProvider, mcpServer, httpClient)(mux), ) httpServer := &http.Server{ @@ -44,7 +44,7 @@ func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.Stat mux.HandleFunc(healthEndpoint, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - mux.Handle("/.well-known/", WellKnownHandler(staticConfig)) + mux.Handle("/.well-known/", WellKnownHandler(staticConfig, httpClient)) ctx, cancel := context.WithCancel(ctx) defer cancel() diff --git a/pkg/http/http_test.go b/pkg/http/http_test.go index 36e7f883..cac23314 100644 --- a/pkg/http/http_test.go +++ b/pkg/http/http_test.go @@ -89,7 +89,7 @@ func (c *httpContext) beforeEach(t *testing.T) { timeoutCtx, c.timeoutCancel = context.WithTimeout(t.Context(), 10*time.Second) group, gc := errgroup.WithContext(timeoutCtx) cancelCtx, c.StopServer = context.WithCancel(gc) - group.Go(func() error { return Serve(cancelCtx, mcpServer, c.StaticConfig, c.OidcProvider) }) + group.Go(func() error { return Serve(cancelCtx, mcpServer, c.StaticConfig, c.OidcProvider, nil) }) c.WaitForShutdown = group.Wait // Wait for HTTP server to start (using net) for i := 0; i < 10; i++ { diff --git a/pkg/http/wellknown.go b/pkg/http/wellknown.go index 0d80221e..6c065fa5 100644 --- a/pkg/http/wellknown.go +++ b/pkg/http/wellknown.go @@ -25,19 +25,24 @@ type WellKnown struct { authorizationUrl string scopesSupported []string disableDynamicClientRegistration bool + httpClient *http.Client } var _ http.Handler = &WellKnown{} -func WellKnownHandler(staticConfig *config.StaticConfig) http.Handler { +func WellKnownHandler(staticConfig *config.StaticConfig, httpClient *http.Client) http.Handler { authorizationUrl := staticConfig.AuthorizationURL if authorizationUrl != "" && strings.HasSuffix("authorizationUrl", "/") { authorizationUrl = strings.TrimSuffix(authorizationUrl, "/") } + if httpClient == nil { + httpClient = http.DefaultClient + } return &WellKnown{ authorizationUrl: authorizationUrl, disableDynamicClientRegistration: staticConfig.DisableDynamicClientRegistration, scopesSupported: staticConfig.OAuthScopes, + httpClient: httpClient, } } @@ -51,7 +56,7 @@ func (w WellKnown) ServeHTTP(writer http.ResponseWriter, request *http.Request) http.Error(writer, "Failed to create request: "+err.Error(), http.StatusInternalServerError) return } - resp, err := http.DefaultClient.Do(req.WithContext(request.Context())) + resp, err := w.httpClient.Do(req.WithContext(request.Context())) if err != nil { http.Error(writer, "Failed to perform request: "+err.Error(), http.StatusInternalServerError) return diff --git a/pkg/kubernetes-mcp-server/cmd/root.go b/pkg/kubernetes-mcp-server/cmd/root.go index 1e91d0c4..db1782ab 100644 --- a/pkg/kubernetes-mcp-server/cmd/root.go +++ b/pkg/kubernetes-mcp-server/cmd/root.go @@ -301,10 +301,11 @@ func (m *MCPServerOptions) Run() error { } var oidcProvider *oidc.Provider + var httpClient *http.Client if m.StaticConfig.AuthorizationURL != "" { ctx := context.Background() if m.StaticConfig.CertificateAuthority != "" { - httpClient := &http.Client{} + httpClient = &http.Client{} caCert, err := os.ReadFile(m.StaticConfig.CertificateAuthority) if err != nil { return fmt.Errorf("failed to read CA certificate from %s: %w", m.StaticConfig.CertificateAuthority, err) @@ -341,7 +342,7 @@ func (m *MCPServerOptions) Run() error { if m.StaticConfig.Port != "" { ctx := context.Background() - return internalhttp.Serve(ctx, mcpServer, m.StaticConfig, oidcProvider) + return internalhttp.Serve(ctx, mcpServer, m.StaticConfig, oidcProvider, httpClient) } if err := mcpServer.ServeStdio(); err != nil && !errors.Is(err, context.Canceled) { From ffc7b6c08d235e3339d2a179d6bc2cd14cdd7a42 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 21 Oct 2025 08:21:45 +0200 Subject: [PATCH 11/57] feat(ci): update release configuration for npm publishing using OIDC (#381) - https://github.blog/changelog/2025-09-29-strengthening-npm-security-important-changes-to-authentication-and-token-management/ - https://docs.npmjs.com/trusted-publishers Signed-off-by: Marc Nuri --- .github/workflows/release.yaml | 8 +++++++- Makefile | 6 ++---- npm/kubernetes-mcp-server-darwin-amd64/package.json | 4 ++++ npm/kubernetes-mcp-server-darwin-arm64/package.json | 4 ++++ npm/kubernetes-mcp-server-linux-amd64/package.json | 4 ++++ npm/kubernetes-mcp-server-linux-arm64/package.json | 4 ++++ npm/kubernetes-mcp-server-windows-amd64/package.json | 4 ++++ npm/kubernetes-mcp-server-windows-arm64/package.json | 4 ++++ 8 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f048e7a8..c035b3a8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,11 +12,11 @@ concurrency: env: GO_VERSION: 1.23 - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }} permissions: contents: write + id-token: write # Required for npmjs OIDC discussions: write jobs: @@ -39,6 +39,12 @@ jobs: files: | LICENSE kubernetes-mcp-server-* + # Ensure npm 11.5.1 or later is installed (required for https://docs.npmjs.com/trusted-publishers) + - name: Setup node + uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: 'https://registry.npmjs.org' - name: Publish npm run: make npm-publish diff --git a/Makefile b/Makefile index 7f4e4271..bca8ae17 100644 --- a/Makefile +++ b/Makefile @@ -71,16 +71,14 @@ npm-publish: npm-copy-binaries ## Publish the npm packages $(foreach os,$(OSES),$(foreach arch,$(ARCHS), \ DIRNAME="$(BINARY_NAME)-$(os)-$(arch)"; \ cd npm/$$DIRNAME; \ - echo '//registry.npmjs.org/:_authToken=$(NPM_TOKEN)' >> .npmrc; \ jq '.version = "$(NPM_VERSION)"' package.json > tmp.json && mv tmp.json package.json; \ - npm publish; \ + npm publish --tag latest; \ cd ../..; \ )) cp README.md LICENSE ./npm/kubernetes-mcp-server/ - echo '//registry.npmjs.org/:_authToken=$(NPM_TOKEN)' >> ./npm/kubernetes-mcp-server/.npmrc jq '.version = "$(NPM_VERSION)"' ./npm/kubernetes-mcp-server/package.json > tmp.json && mv tmp.json ./npm/kubernetes-mcp-server/package.json; \ jq '.optionalDependencies |= with_entries(.value = "$(NPM_VERSION)")' ./npm/kubernetes-mcp-server/package.json > tmp.json && mv tmp.json ./npm/kubernetes-mcp-server/package.json; \ - cd npm/kubernetes-mcp-server && npm publish + cd npm/kubernetes-mcp-server && npm publish --tag latest .PHONY: python-publish python-publish: ## Publish the python packages diff --git a/npm/kubernetes-mcp-server-darwin-amd64/package.json b/npm/kubernetes-mcp-server-darwin-amd64/package.json index f83bf58b..49e05004 100644 --- a/npm/kubernetes-mcp-server-darwin-amd64/package.json +++ b/npm/kubernetes-mcp-server-darwin-amd64/package.json @@ -2,6 +2,10 @@ "name": "kubernetes-mcp-server-darwin-amd64", "version": "0.0.0", "description": "Model Context Protocol (MCP) server for Kubernetes and OpenShift", + "repository": { + "type": "git", + "url": "git+https://github.com/containers/kubernetes-mcp-server.git" + }, "os": [ "darwin" ], diff --git a/npm/kubernetes-mcp-server-darwin-arm64/package.json b/npm/kubernetes-mcp-server-darwin-arm64/package.json index d8cbc618..f8e313c2 100644 --- a/npm/kubernetes-mcp-server-darwin-arm64/package.json +++ b/npm/kubernetes-mcp-server-darwin-arm64/package.json @@ -2,6 +2,10 @@ "name": "kubernetes-mcp-server-darwin-arm64", "version": "0.0.0", "description": "Model Context Protocol (MCP) server for Kubernetes and OpenShift", + "repository": { + "type": "git", + "url": "git+https://github.com/containers/kubernetes-mcp-server.git" + }, "os": [ "darwin" ], diff --git a/npm/kubernetes-mcp-server-linux-amd64/package.json b/npm/kubernetes-mcp-server-linux-amd64/package.json index deaa5364..1a519074 100644 --- a/npm/kubernetes-mcp-server-linux-amd64/package.json +++ b/npm/kubernetes-mcp-server-linux-amd64/package.json @@ -2,6 +2,10 @@ "name": "kubernetes-mcp-server-linux-amd64", "version": "0.0.0", "description": "Model Context Protocol (MCP) server for Kubernetes and OpenShift", + "repository": { + "type": "git", + "url": "git+https://github.com/containers/kubernetes-mcp-server.git" + }, "os": [ "linux" ], diff --git a/npm/kubernetes-mcp-server-linux-arm64/package.json b/npm/kubernetes-mcp-server-linux-arm64/package.json index ba2f6475..b861abeb 100644 --- a/npm/kubernetes-mcp-server-linux-arm64/package.json +++ b/npm/kubernetes-mcp-server-linux-arm64/package.json @@ -2,6 +2,10 @@ "name": "kubernetes-mcp-server-linux-arm64", "version": "0.0.0", "description": "Model Context Protocol (MCP) server for Kubernetes and OpenShift", + "repository": { + "type": "git", + "url": "git+https://github.com/containers/kubernetes-mcp-server.git" + }, "os": [ "linux" ], diff --git a/npm/kubernetes-mcp-server-windows-amd64/package.json b/npm/kubernetes-mcp-server-windows-amd64/package.json index 04b5d8ef..306e5047 100644 --- a/npm/kubernetes-mcp-server-windows-amd64/package.json +++ b/npm/kubernetes-mcp-server-windows-amd64/package.json @@ -2,6 +2,10 @@ "name": "kubernetes-mcp-server-windows-amd64", "version": "0.0.0", "description": "Model Context Protocol (MCP) server for Kubernetes and OpenShift", + "repository": { + "type": "git", + "url": "git+https://github.com/containers/kubernetes-mcp-server.git" + }, "os": [ "win32" ], diff --git a/npm/kubernetes-mcp-server-windows-arm64/package.json b/npm/kubernetes-mcp-server-windows-arm64/package.json index 38aa06f7..c30c4a30 100644 --- a/npm/kubernetes-mcp-server-windows-arm64/package.json +++ b/npm/kubernetes-mcp-server-windows-arm64/package.json @@ -2,6 +2,10 @@ "name": "kubernetes-mcp-server-windows-arm64", "version": "0.0.0", "description": "Model Context Protocol (MCP) server for Kubernetes and OpenShift", + "repository": { + "type": "git", + "url": "git+https://github.com/containers/kubernetes-mcp-server.git" + }, "os": [ "win32" ], From c3bc991237ab9f4aba7f642616f43cd970d27a53 Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Tue, 21 Oct 2025 03:09:55 -0400 Subject: [PATCH 12/57] chore(deps): bump golangci-lint from 2.2.2 to 2.5.0 to avoid panic (#383) Signed-off-by: Calum Murray --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bca8ae17..fe230f11 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ LD_FLAGS = -s -w \ COMMON_BUILD_ARGS = -ldflags "$(LD_FLAGS)" GOLANGCI_LINT = $(shell pwd)/_output/tools/bin/golangci-lint -GOLANGCI_LINT_VERSION ?= v2.2.2 +GOLANGCI_LINT_VERSION ?= v2.5.0 # NPM version should not append the -dirty flag NPM_VERSION ?= $(shell echo $(shell git describe --tags --always) | sed 's/^v//') From 0c78a1e89de742cf1e4b096342bae44f7c9bf3b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 08:38:28 +0200 Subject: [PATCH 13/57] build(deps): bump github.com/mark3labs/mcp-go from 0.41.1 to 0.42.0 (#388) Bumps [github.com/mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) from 0.41.1 to 0.42.0. - [Release notes](https://github.com/mark3labs/mcp-go/releases) - [Commits](https://github.com/mark3labs/mcp-go/compare/v0.41.1...v0.42.0) --- updated-dependencies: - dependency-name: github.com/mark3labs/mcp-go dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 78ed49c2..de49820d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/go-jose/go-jose/v4 v4.1.3 github.com/google/jsonschema-go v0.3.0 - github.com/mark3labs/mcp-go v0.41.1 + github.com/mark3labs/mcp-go v0.42.0 github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.1 diff --git a/go.sum b/go.sum index 2185419b..52a72c8b 100644 --- a/go.sum +++ b/go.sum @@ -187,8 +187,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA= -github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g= +github.com/mark3labs/mcp-go v0.42.0 h1:gk/8nYJh8t3yroCAOBhNbYsM9TCKvkM13I5t5Hfu6Ls= +github.com/mark3labs/mcp-go v0.42.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= From 7fe604e61da4854e0fada61fc1464c8a94bc486f Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Wed, 22 Oct 2025 14:42:36 +0200 Subject: [PATCH 14/57] feat(auth): add local development environment with Kind and Keycloak for OIDC (#354) * Initial KinD setup Signed-off-by: Matthias Wessendorf * Initial Keycloak container setup Signed-off-by: Matthias Wessendorf * Adding an initial realm setup Signed-off-by: Matthias Wessendorf * Adding OIDC issuer and realm updates, adding cert-manager and handling self-signed certificates Signed-off-by: Matthias Wessendorf * Updates to script b/c of invalid auth config Signed-off-by: Matthias Wessendorf * Adjusting ports and better support for mac/podman Signed-off-by: Matthias Wessendorf * Addressing review comments: * do not expose all internal tasks, just keep the important targets documents * remove the keycloak-forward * move binaries for dev tools to _output * generate a configuration TOML file into the _output folder Signed-off-by: Matthias Wessendorf --------- Signed-off-by: Matthias Wessendorf --- Makefile | 40 ++ build/keycloak.mk | 448 ++++++++++++++++++ build/kind.mk | 61 +++ build/tools.mk | 20 + .../cert-manager/selfsigned-issuer.yaml | 31 ++ dev/config/ingress/nginx-ingress.yaml | 386 +++++++++++++++ dev/config/keycloak/deployment.yaml | 71 +++ dev/config/keycloak/ingress.yaml | 34 ++ dev/config/keycloak/rbac.yaml | 20 + dev/config/kind/cluster.yaml | 30 ++ hack/generate-placeholder-ca.sh | 22 + 11 files changed, 1163 insertions(+) create mode 100644 build/keycloak.mk create mode 100644 build/kind.mk create mode 100644 build/tools.mk create mode 100644 dev/config/cert-manager/selfsigned-issuer.yaml create mode 100644 dev/config/ingress/nginx-ingress.yaml create mode 100644 dev/config/keycloak/deployment.yaml create mode 100644 dev/config/keycloak/ingress.yaml create mode 100644 dev/config/keycloak/rbac.yaml create mode 100644 dev/config/kind/cluster.yaml create mode 100755 hack/generate-placeholder-ca.sh diff --git a/Makefile b/Makefile index fe230f11..49730445 100644 --- a/Makefile +++ b/Makefile @@ -113,3 +113,43 @@ lint: golangci-lint ## Lint the code .PHONY: update-readme-tools update-readme-tools: ## Update the README.md file with the latest toolsets go run ./internal/tools/update-readme/main.go README.md + +##@ Tools + +.PHONY: tools +tools: ## Install all required tools (kind) to ./_output/bin/ + @echo "Checking and installing required tools to ./_output/bin/ ..." + @if [ -f _output/bin/kind ]; then echo "[OK] kind already installed"; else echo "Installing kind..."; $(MAKE) -s kind; fi + @echo "All tools ready!" + +##@ Local Development + +.PHONY: local-env-setup +local-env-setup: ## Setup complete local development environment with Kind cluster + @echo "=========================================" + @echo "Kubernetes MCP Server - Local Setup" + @echo "=========================================" + $(MAKE) tools + $(MAKE) kind-create-cluster + $(MAKE) keycloak-install + $(MAKE) build + @echo "" + @echo "=========================================" + @echo "Local environment ready!" + @echo "=========================================" + @echo "" + @echo "Configuration file generated:" + @echo " _output/config.toml" + @echo "" + @echo "Run the MCP server with:" + @echo " ./$(BINARY_NAME) --port 8080 --config _output/config.toml" + @echo "" + @echo "Or run with MCP inspector:" + @echo " npx @modelcontextprotocol/inspector@latest \$$(pwd)/$(BINARY_NAME) --config _output/config.toml" + +.PHONY: local-env-teardown +local-env-teardown: ## Tear down the local Kind cluster + $(MAKE) kind-delete-cluster + +# Include build configuration files +-include build/*.mk diff --git a/build/keycloak.mk b/build/keycloak.mk new file mode 100644 index 00000000..2a7a9296 --- /dev/null +++ b/build/keycloak.mk @@ -0,0 +1,448 @@ +# Keycloak IdP for development and testing + +KEYCLOAK_NAMESPACE = keycloak +KEYCLOAK_ADMIN_USER = admin +KEYCLOAK_ADMIN_PASSWORD = admin + +.PHONY: keycloak-install +keycloak-install: + @echo "Installing Keycloak (dev mode using official image)..." + @kubectl apply -f dev/config/keycloak/deployment.yaml + @echo "Applying Keycloak ingress (cert-manager will create TLS certificate)..." + @kubectl apply -f dev/config/keycloak/ingress.yaml + @echo "Extracting cert-manager CA certificate..." + @mkdir -p _output/cert-manager-ca + @kubectl get secret selfsigned-ca-secret -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 -d > _output/cert-manager-ca/ca.crt + @echo "✅ cert-manager CA certificate extracted to _output/cert-manager-ca/ca.crt (bind-mounted to API server)" + @echo "Restarting Kubernetes API server to pick up new CA..." + @docker exec kubernetes-mcp-server-control-plane pkill -f kube-apiserver || \ + podman exec kubernetes-mcp-server-control-plane pkill -f kube-apiserver + @echo "Waiting for API server to restart..." + @sleep 5 + @echo "Waiting for API server to be ready..." + @for i in $$(seq 1 30); do \ + if kubectl get --raw /healthz >/dev/null 2>&1; then \ + echo "✅ Kubernetes API server updated with cert-manager CA"; \ + break; \ + fi; \ + sleep 2; \ + done + @echo "Waiting for Keycloak to be ready..." + @kubectl wait --for=condition=ready pod -l app=keycloak -n $(KEYCLOAK_NAMESPACE) --timeout=120s || true + @echo "Waiting for Keycloak HTTP endpoint to be available..." + @for i in $$(seq 1 30); do \ + STATUS=$$(curl -sk -o /dev/null -w "%{http_code}" https://keycloak.127-0-0-1.sslip.io:8443/realms/master 2>/dev/null || echo "000"); \ + if [ "$$STATUS" = "200" ]; then \ + echo "✅ Keycloak HTTP endpoint ready"; \ + break; \ + fi; \ + echo " Attempt $$i/30: Waiting for Keycloak (status: $$STATUS)..."; \ + sleep 3; \ + done + @echo "" + @echo "Setting up OpenShift realm..." + @$(MAKE) -s keycloak-setup-realm + @echo "" + @echo "✅ Keycloak installed and configured!" + @echo "Access at: https://keycloak.127-0-0-1.sslip.io:8443" + +.PHONY: keycloak-uninstall +keycloak-uninstall: + @kubectl delete -f dev/config/keycloak/deployment.yaml 2>/dev/null || true + +.PHONY: keycloak-status +keycloak-status: ## Show Keycloak status and connection info + @if kubectl get svc -n $(KEYCLOAK_NAMESPACE) keycloak >/dev/null 2>&1; then \ + echo "========================================"; \ + echo "Keycloak Status"; \ + echo "========================================"; \ + echo ""; \ + echo "Status: Installed"; \ + echo ""; \ + echo "Admin Console:"; \ + echo " URL: https://keycloak.127-0-0-1.sslip.io:8443"; \ + echo " Username: $(KEYCLOAK_ADMIN_USER)"; \ + echo " Password: $(KEYCLOAK_ADMIN_PASSWORD)"; \ + echo ""; \ + echo "OIDC Endpoints (openshift realm):"; \ + echo " Discovery: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift/.well-known/openid-configuration"; \ + echo " Token: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift/protocol/openid-connect/token"; \ + echo " Authorize: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift/protocol/openid-connect/auth"; \ + echo " UserInfo: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift/protocol/openid-connect/userinfo"; \ + echo " JWKS: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift/protocol/openid-connect/certs"; \ + echo ""; \ + echo "========================================"; \ + else \ + echo "Keycloak is not installed. Run: make keycloak-install"; \ + fi + +.PHONY: keycloak-logs +keycloak-logs: ## Tail Keycloak logs + @kubectl logs -n $(KEYCLOAK_NAMESPACE) -l app=keycloak -f --tail=100 + +.PHONY: keycloak-setup-realm +keycloak-setup-realm: + @echo "=========================================" + @echo "Setting up OpenShift Realm for Token Exchange" + @echo "=========================================" + @echo "Using Keycloak at https://keycloak.127-0-0-1.sslip.io:8443" + @echo "" + @echo "Getting admin access token..." + @RESPONSE=$$(curl -sk -X POST "https://keycloak.127-0-0-1.sslip.io:8443/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=$(KEYCLOAK_ADMIN_USER)" \ + -d "password=$(KEYCLOAK_ADMIN_PASSWORD)" \ + -d "grant_type=password" \ + -d "client_id=admin-cli"); \ + TOKEN=$$(echo "$$RESPONSE" | jq -r '.access_token // empty' 2>/dev/null); \ + if [ -z "$$TOKEN" ] || [ "$$TOKEN" = "null" ]; then \ + echo "❌ Failed to get access token"; \ + echo "Response was: $$RESPONSE" | head -c 200; \ + echo ""; \ + echo "Check if:"; \ + echo " - Keycloak is running (make keycloak-install)"; \ + echo " - Keycloak is accessible at https://keycloak.127-0-0-1.sslip.io:8443"; \ + echo " - Admin credentials are correct: $(KEYCLOAK_ADMIN_USER)/$(KEYCLOAK_ADMIN_PASSWORD)"; \ + exit 1; \ + fi; \ + echo "✅ Successfully obtained access token"; \ + echo ""; \ + echo "Creating OpenShift realm..."; \ + REALM_RESPONSE=$$(curl -sk -w "%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"realm":"openshift","enabled":true}'); \ + REALM_CODE=$$(echo "$$REALM_RESPONSE" | tail -c 4); \ + if [ "$$REALM_CODE" = "201" ] || [ "$$REALM_CODE" = "409" ]; then \ + if [ "$$REALM_CODE" = "201" ]; then echo "✅ OpenShift realm created"; \ + else echo "✅ OpenShift realm already exists"; fi; \ + else \ + echo "❌ Failed to create OpenShift realm (HTTP $$REALM_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Configuring realm events..."; \ + EVENT_CONFIG_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X PUT "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"realm":"openshift","enabled":true,"eventsEnabled":true,"eventsListeners":["jboss-logging"],"adminEventsEnabled":true,"adminEventsDetailsEnabled":true}'); \ + EVENT_CONFIG_CODE=$$(echo "$$EVENT_CONFIG_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$EVENT_CONFIG_CODE" = "204" ]; then \ + echo "✅ User and admin event logging enabled"; \ + else \ + echo "⚠️ Could not configure event logging (HTTP $$EVENT_CONFIG_CODE)"; \ + fi; \ + echo ""; \ + echo "Creating mcp:openshift client scope..."; \ + SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"mcp:openshift","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \ + SCOPE_CODE=$$(echo "$$SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$SCOPE_CODE" = "201" ] || [ "$$SCOPE_CODE" = "409" ]; then \ + if [ "$$SCOPE_CODE" = "201" ]; then echo "✅ mcp:openshift client scope created"; \ + else echo "✅ mcp:openshift client scope already exists"; fi; \ + else \ + echo "❌ Failed to create mcp:openshift scope (HTTP $$SCOPE_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Adding audience mapper to mcp:openshift scope..."; \ + SCOPES_LIST=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + SCOPE_ID=$$(echo "$$SCOPES_LIST" | jq -r '.[] | select(.name == "mcp:openshift") | .id // empty' 2>/dev/null); \ + if [ -z "$$SCOPE_ID" ]; then \ + echo "❌ Failed to find mcp:openshift scope"; \ + exit 1; \ + fi; \ + MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$SCOPE_ID/protocol-mappers/models" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"openshift-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"openshift","id.token.claim":"true","access.token.claim":"true"}}'); \ + MAPPER_CODE=$$(echo "$$MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MAPPER_CODE" = "201" ] || [ "$$MAPPER_CODE" = "409" ]; then \ + if [ "$$MAPPER_CODE" = "201" ]; then echo "✅ Audience mapper added"; \ + else echo "✅ Audience mapper already exists"; fi; \ + else \ + echo "❌ Failed to create audience mapper (HTTP $$MAPPER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating groups client scope..."; \ + GROUPS_SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"groups","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \ + GROUPS_SCOPE_CODE=$$(echo "$$GROUPS_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$GROUPS_SCOPE_CODE" = "201" ] || [ "$$GROUPS_SCOPE_CODE" = "409" ]; then \ + if [ "$$GROUPS_SCOPE_CODE" = "201" ]; then echo "✅ groups client scope created"; \ + else echo "✅ groups client scope already exists"; fi; \ + else \ + echo "❌ Failed to create groups scope (HTTP $$GROUPS_SCOPE_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Adding group membership mapper to groups scope..."; \ + SCOPES_LIST=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + GROUPS_SCOPE_ID=$$(echo "$$SCOPES_LIST" | jq -r '.[] | select(.name == "groups") | .id // empty' 2>/dev/null); \ + if [ -z "$$GROUPS_SCOPE_ID" ]; then \ + echo "❌ Failed to find groups scope"; \ + exit 1; \ + fi; \ + GROUPS_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$GROUPS_SCOPE_ID/protocol-mappers/models" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"groups","protocol":"openid-connect","protocolMapper":"oidc-group-membership-mapper","config":{"claim.name":"groups","full.path":"false","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true"}}'); \ + GROUPS_MAPPER_CODE=$$(echo "$$GROUPS_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$GROUPS_MAPPER_CODE" = "201" ] || [ "$$GROUPS_MAPPER_CODE" = "409" ]; then \ + if [ "$$GROUPS_MAPPER_CODE" = "201" ]; then echo "✅ Group membership mapper added"; \ + else echo "✅ Group membership mapper already exists"; fi; \ + else \ + echo "❌ Failed to create group mapper (HTTP $$GROUPS_MAPPER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating mcp-server client scope..."; \ + MCP_SERVER_SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"mcp-server","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \ + MCP_SERVER_SCOPE_CODE=$$(echo "$$MCP_SERVER_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MCP_SERVER_SCOPE_CODE" = "201" ] || [ "$$MCP_SERVER_SCOPE_CODE" = "409" ]; then \ + if [ "$$MCP_SERVER_SCOPE_CODE" = "201" ]; then echo "✅ mcp-server client scope created"; \ + else echo "✅ mcp-server client scope already exists"; fi; \ + else \ + echo "❌ Failed to create mcp-server scope (HTTP $$MCP_SERVER_SCOPE_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Adding audience mapper to mcp-server scope..."; \ + SCOPES_LIST=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + MCP_SERVER_SCOPE_ID=$$(echo "$$SCOPES_LIST" | jq -r '.[] | select(.name == "mcp-server") | .id // empty' 2>/dev/null); \ + if [ -z "$$MCP_SERVER_SCOPE_ID" ]; then \ + echo "❌ Failed to find mcp-server scope"; \ + exit 1; \ + fi; \ + MCP_SERVER_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$MCP_SERVER_SCOPE_ID/protocol-mappers/models" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"mcp-server-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"mcp-server","id.token.claim":"true","access.token.claim":"true"}}'); \ + MCP_SERVER_MAPPER_CODE=$$(echo "$$MCP_SERVER_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MCP_SERVER_MAPPER_CODE" = "201" ] || [ "$$MCP_SERVER_MAPPER_CODE" = "409" ]; then \ + if [ "$$MCP_SERVER_MAPPER_CODE" = "201" ]; then echo "✅ mcp-server audience mapper added"; \ + else echo "✅ mcp-server audience mapper already exists"; fi; \ + else \ + echo "❌ Failed to create mcp-server audience mapper (HTTP $$MCP_SERVER_MAPPER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating openshift service client..."; \ + OPENSHIFT_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"clientId":"openshift","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups"],"optionalClientScopes":[]}'); \ + OPENSHIFT_CLIENT_CODE=$$(echo "$$OPENSHIFT_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$OPENSHIFT_CLIENT_CODE" = "201" ] || [ "$$OPENSHIFT_CLIENT_CODE" = "409" ]; then \ + if [ "$$OPENSHIFT_CLIENT_CODE" = "201" ]; then echo "✅ openshift client created"; \ + else echo "✅ openshift client already exists"; fi; \ + else \ + echo "❌ Failed to create openshift client (HTTP $$OPENSHIFT_CLIENT_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Adding username mapper to openshift client..."; \ + OPENSHIFT_CLIENTS_LIST=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + OPENSHIFT_CLIENT_ID=$$(echo "$$OPENSHIFT_CLIENTS_LIST" | jq -r '.[] | select(.clientId == "openshift") | .id // empty' 2>/dev/null); \ + OPENSHIFT_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$OPENSHIFT_CLIENT_ID/protocol-mappers/models" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \ + OPENSHIFT_USERNAME_MAPPER_CODE=$$(echo "$$OPENSHIFT_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "201" ] || [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "409" ]; then \ + if [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to openshift client"; \ + else echo "✅ Username mapper already exists on openshift client"; fi; \ + else \ + echo "❌ Failed to create username mapper (HTTP $$OPENSHIFT_USERNAME_MAPPER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating mcp-client public client..."; \ + MCP_PUBLIC_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"clientId":"mcp-client","enabled":true,"publicClient":true,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":false,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email"],"optionalClientScopes":["mcp-server"]}'); \ + MCP_PUBLIC_CLIENT_CODE=$$(echo "$$MCP_PUBLIC_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MCP_PUBLIC_CLIENT_CODE" = "201" ] || [ "$$MCP_PUBLIC_CLIENT_CODE" = "409" ]; then \ + if [ "$$MCP_PUBLIC_CLIENT_CODE" = "201" ]; then echo "✅ mcp-client public client created"; \ + else echo "✅ mcp-client public client already exists"; fi; \ + else \ + echo "❌ Failed to create mcp-client public client (HTTP $$MCP_PUBLIC_CLIENT_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Adding username mapper to mcp-client..."; \ + MCP_PUBLIC_CLIENTS_LIST=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + MCP_PUBLIC_CLIENT_ID=$$(echo "$$MCP_PUBLIC_CLIENTS_LIST" | jq -r '.[] | select(.clientId == "mcp-client") | .id // empty' 2>/dev/null); \ + MCP_PUBLIC_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_PUBLIC_CLIENT_ID/protocol-mappers/models" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \ + MCP_PUBLIC_USERNAME_MAPPER_CODE=$$(echo "$$MCP_PUBLIC_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "201" ] || [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "409" ]; then \ + if [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to mcp-client"; \ + else echo "✅ Username mapper already exists on mcp-client"; fi; \ + else \ + echo "❌ Failed to create username mapper (HTTP $$MCP_PUBLIC_USERNAME_MAPPER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating mcp-server client with token exchange..."; \ + MCP_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false"}}'); \ + MCP_CLIENT_CODE=$$(echo "$$MCP_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MCP_CLIENT_CODE" = "201" ] || [ "$$MCP_CLIENT_CODE" = "409" ]; then \ + if [ "$$MCP_CLIENT_CODE" = "201" ]; then echo "✅ mcp-server client created"; \ + else echo "✅ mcp-server client already exists"; fi; \ + else \ + echo "❌ Failed to create mcp-server client (HTTP $$MCP_CLIENT_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Enabling standard token exchange for mcp-server..."; \ + CLIENTS_LIST=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + MCP_CLIENT_ID=$$(echo "$$CLIENTS_LIST" | jq -r '.[] | select(.clientId == "mcp-server") | .id // empty' 2>/dev/null); \ + if [ -z "$$MCP_CLIENT_ID" ]; then \ + echo "❌ Failed to find mcp-server client"; \ + exit 1; \ + fi; \ + UPDATE_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X PUT "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false","standard.token.exchange.enabled":"true"}}'); \ + UPDATE_CLIENT_CODE=$$(echo "$$UPDATE_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$UPDATE_CLIENT_CODE" = "204" ]; then \ + echo "✅ Standard token exchange enabled for mcp-server client"; \ + else \ + echo "⚠️ Could not enable token exchange (HTTP $$UPDATE_CLIENT_CODE)"; \ + fi; \ + echo ""; \ + echo "Getting mcp-server client secret..."; \ + SECRET_RESPONSE=$$(curl -sk -X GET "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID/client-secret" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Accept: application/json"); \ + CLIENT_SECRET=$$(echo "$$SECRET_RESPONSE" | jq -r '.value // empty' 2>/dev/null); \ + if [ -z "$$CLIENT_SECRET" ]; then \ + echo "❌ Failed to get client secret"; \ + else \ + echo "✅ Client secret retrieved"; \ + fi; \ + echo ""; \ + echo "Adding username mapper to mcp-server client..."; \ + MCP_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID/protocol-mappers/models" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \ + MCP_USERNAME_MAPPER_CODE=$$(echo "$$MCP_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ + if [ "$$MCP_USERNAME_MAPPER_CODE" = "201" ] || [ "$$MCP_USERNAME_MAPPER_CODE" = "409" ]; then \ + if [ "$$MCP_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to mcp-server client"; \ + else echo "✅ Username mapper already exists on mcp-server client"; fi; \ + else \ + echo "❌ Failed to create username mapper (HTTP $$MCP_USERNAME_MAPPER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Creating test user mcp/mcp..."; \ + USER_RESPONSE=$$(curl -sk -w "%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/users" \ + -H "Authorization: Bearer $$TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"username":"mcp","email":"mcp@example.com","firstName":"MCP","lastName":"User","enabled":true,"emailVerified":true,"credentials":[{"type":"password","value":"mcp","temporary":false}]}'); \ + USER_CODE=$$(echo "$$USER_RESPONSE" | tail -c 4); \ + if [ "$$USER_CODE" = "201" ] || [ "$$USER_CODE" = "409" ]; then \ + if [ "$$USER_CODE" = "201" ]; then echo "✅ mcp user created"; \ + else echo "✅ mcp user already exists"; fi; \ + else \ + echo "❌ Failed to create mcp user (HTTP $$USER_CODE)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "Setting up RBAC for mcp user..."; \ + kubectl apply -f dev/config/keycloak/rbac.yaml; \ + echo "✅ RBAC binding created for mcp user"; \ + echo ""; \ + echo "🎉 OpenShift realm setup complete!"; \ + echo ""; \ + echo "========================================"; \ + echo "Configuration Summary"; \ + echo "========================================"; \ + echo "Realm: openshift"; \ + echo "Authorization URL: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift"; \ + echo "Issuer URL (for config.toml): https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift"; \ + echo ""; \ + echo "Test User:"; \ + echo " Username: mcp"; \ + echo " Password: mcp"; \ + echo " Email: mcp@example.com"; \ + echo " RBAC: cluster-admin (full cluster access)"; \ + echo ""; \ + echo "Clients:"; \ + echo " mcp-client (public, for browser-based auth)"; \ + echo " Client ID: mcp-client"; \ + echo " Optional Scopes: mcp-server"; \ + echo " mcp-server (confidential, token exchange enabled)"; \ + echo " Client ID: mcp-server"; \ + echo " Client Secret: $$CLIENT_SECRET"; \ + echo " openshift (service account)"; \ + echo " Client ID: openshift"; \ + echo ""; \ + echo "Client Scopes:"; \ + echo " mcp-server (default) - Audience: mcp-server"; \ + echo " mcp:openshift (optional) - Audience: openshift"; \ + echo " groups (default) - Group membership mapper"; \ + echo ""; \ + echo "TOML Configuration (config.toml):"; \ + echo " require_oauth = true"; \ + echo " oauth_audience = \"mcp-server\""; \ + echo " oauth_scopes = [\"openid\", \"mcp-server\"]"; \ + echo " validate_token = false"; \ + echo " authorization_url = \"https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift\""; \ + echo " sts_client_id = \"mcp-server\""; \ + echo " sts_client_secret = \"$$CLIENT_SECRET\""; \ + echo " sts_audience = \"openshift\""; \ + echo " sts_scopes = [\"mcp:openshift\"]"; \ + echo " certificate_authority = \"_output/cert-manager-ca/ca.crt\""; \ + echo "========================================"; \ + echo ""; \ + echo "Note: The Kubernetes API server is configured with:"; \ + echo " --oidc-issuer-url=https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift"; \ + echo ""; \ + echo "Important: The cert-manager CA certificate was extracted to:"; \ + echo " _output/cert-manager-ca/ca.crt"; \ + echo ""; \ + echo "Writing configuration to _output/config.toml..."; \ + mkdir -p _output; \ + printf '%s\n' \ + 'require_oauth = true' \ + 'oauth_audience = "mcp-server"' \ + 'oauth_scopes = ["openid", "mcp-server"]' \ + 'validate_token = false' \ + 'authorization_url = "https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift"' \ + 'sts_client_id = "mcp-server"' \ + "sts_client_secret = \"$$CLIENT_SECRET\"" \ + 'sts_audience = "openshift"' \ + 'sts_scopes = ["mcp:openshift"]' \ + 'certificate_authority = "_output/cert-manager-ca/ca.crt"' \ + > _output/config.toml; \ + echo "✅ Configuration written to _output/config.toml" diff --git a/build/kind.mk b/build/kind.mk new file mode 100644 index 00000000..fe83f1ab --- /dev/null +++ b/build/kind.mk @@ -0,0 +1,61 @@ +# Kind cluster management + +KIND_CLUSTER_NAME ?= kubernetes-mcp-server + +# Detect container engine (docker or podman) +CONTAINER_ENGINE ?= $(shell command -v docker 2>/dev/null || command -v podman 2>/dev/null) + +.PHONY: kind-create-certs +kind-create-certs: + @if [ ! -f _output/cert-manager-ca/ca.crt ]; then \ + echo "Creating placeholder CA certificate for bind mount..."; \ + ./hack/generate-placeholder-ca.sh; \ + else \ + echo "✅ Placeholder CA already exists"; \ + fi + +.PHONY: kind-create-cluster +kind-create-cluster: kind kind-create-certs + @# Set KIND provider for podman on Linux + @if [ "$(shell uname -s)" != "Darwin" ] && echo "$(CONTAINER_ENGINE)" | grep -q "podman"; then \ + export KIND_EXPERIMENTAL_PROVIDER=podman; \ + fi; \ + if $(KIND) get clusters 2>/dev/null | grep -q "^$(KIND_CLUSTER_NAME)$$"; then \ + echo "Kind cluster '$(KIND_CLUSTER_NAME)' already exists, skipping creation"; \ + else \ + echo "Creating Kind cluster '$(KIND_CLUSTER_NAME)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER_NAME) --config dev/config/kind/cluster.yaml; \ + echo "Adding ingress-ready label to control-plane node..."; \ + kubectl label node $(KIND_CLUSTER_NAME)-control-plane ingress-ready=true --overwrite; \ + echo "Installing nginx ingress controller..."; \ + kubectl apply -f dev/config/ingress/nginx-ingress.yaml; \ + echo "Waiting for ingress controller to be ready..."; \ + kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=90s; \ + echo "✅ Ingress controller ready"; \ + echo "Installing cert-manager..."; \ + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml; \ + echo "Waiting for cert-manager to be ready..."; \ + kubectl wait --namespace cert-manager --for=condition=available deployment/cert-manager --timeout=120s; \ + kubectl wait --namespace cert-manager --for=condition=available deployment/cert-manager-cainjector --timeout=120s; \ + kubectl wait --namespace cert-manager --for=condition=available deployment/cert-manager-webhook --timeout=120s; \ + echo "✅ cert-manager ready"; \ + echo "Creating cert-manager ClusterIssuer..."; \ + sleep 5; \ + kubectl apply -f dev/config/cert-manager/selfsigned-issuer.yaml; \ + echo "✅ ClusterIssuer created"; \ + echo "Adding /etc/hosts entry for Keycloak in control plane..."; \ + if command -v docker >/dev/null 2>&1 && docker ps --filter "name=$(KIND_CLUSTER_NAME)-control-plane" --format "{{.Names}}" | grep -q "$(KIND_CLUSTER_NAME)-control-plane"; then \ + docker exec $(KIND_CLUSTER_NAME)-control-plane bash -c 'grep -q "keycloak.127-0-0-1.sslip.io" /etc/hosts || echo "127.0.0.1 keycloak.127-0-0-1.sslip.io" >> /etc/hosts'; \ + elif command -v podman >/dev/null 2>&1 && podman ps --filter "name=$(KIND_CLUSTER_NAME)-control-plane" --format "{{.Names}}" | grep -q "$(KIND_CLUSTER_NAME)-control-plane"; then \ + podman exec $(KIND_CLUSTER_NAME)-control-plane bash -c 'grep -q "keycloak.127-0-0-1.sslip.io" /etc/hosts || echo "127.0.0.1 keycloak.127-0-0-1.sslip.io" >> /etc/hosts'; \ + fi; \ + echo "✅ /etc/hosts entry added"; \ + fi + +.PHONY: kind-delete-cluster +kind-delete-cluster: kind + @# Set KIND provider for podman on Linux + @if [ "$(shell uname -s)" != "Darwin" ] && echo "$(CONTAINER_ENGINE)" | grep -q "podman"; then \ + export KIND_EXPERIMENTAL_PROVIDER=podman; \ + fi; \ + $(KIND) delete cluster --name $(KIND_CLUSTER_NAME) diff --git a/build/tools.mk b/build/tools.mk new file mode 100644 index 00000000..9c9945a8 --- /dev/null +++ b/build/tools.mk @@ -0,0 +1,20 @@ +# Tools + +# Platform detection +OS := $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH := $(shell uname -m | tr '[:upper:]' '[:lower:]') +ifeq ($(ARCH),x86_64) + ARCH = amd64 +endif +ifeq ($(ARCH),aarch64) + ARCH = arm64 +endif + +KIND = _output/bin/kind +KIND_VERSION = v0.30.0 +$(KIND): + @mkdir -p _output/bin + GOBIN=$(PWD)/_output/bin go install sigs.k8s.io/kind@$(KIND_VERSION) + +.PHONY: kind +kind: $(KIND) ## Download kind locally if necessary diff --git a/dev/config/cert-manager/selfsigned-issuer.yaml b/dev/config/cert-manager/selfsigned-issuer.yaml new file mode 100644 index 00000000..8bb27f7a --- /dev/null +++ b/dev/config/cert-manager/selfsigned-issuer.yaml @@ -0,0 +1,31 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: selfsigned-ca + namespace: cert-manager +spec: + isCA: true + commonName: selfsigned-ca + secretName: selfsigned-ca-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-ca-issuer +spec: + ca: + secretName: selfsigned-ca-secret diff --git a/dev/config/ingress/nginx-ingress.yaml b/dev/config/ingress/nginx-ingress.yaml new file mode 100644 index 00000000..8405740d --- /dev/null +++ b/dev/config/ingress/nginx-ingress.yaml @@ -0,0 +1,386 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: ingress-nginx +data: + allow-snippet-annotations: "true" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + name: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + - namespaces + verbs: + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + name: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx +subjects: + - kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - ingress-nginx-leader + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx +subjects: + - kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + type: NodePort + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + appProtocol: http + - name: https + port: 443 + protocol: TCP + targetPort: https + appProtocol: https + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + replicas: 1 + revisionHistoryLimit: 10 + minReadySeconds: 0 + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + spec: + dnsPolicy: ClusterFirst + containers: + - name: controller + image: registry.k8s.io/ingress-nginx/controller:v1.11.1 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + args: + - /nginx-ingress-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --watch-ingress-without-class=true + securityContext: + runAsNonRoot: true + runAsUser: 101 + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - name: http + containerPort: 80 + protocol: TCP + hostPort: 80 + - name: https + containerPort: 443 + protocol: TCP + hostPort: 443 + - name: https-alt + containerPort: 443 + protocol: TCP + hostPort: 8443 + - name: webhook + containerPort: 8443 + protocol: TCP + resources: + requests: + cpu: 100m + memory: 90Mi + nodeSelector: + ingress-ready: "true" + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 0 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Equal + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Equal +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + name: nginx +spec: + controller: k8s.io/ingress-nginx diff --git a/dev/config/keycloak/deployment.yaml b/dev/config/keycloak/deployment.yaml new file mode 100644 index 00000000..efcb7e0f --- /dev/null +++ b/dev/config/keycloak/deployment.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: keycloak +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:26.4 + args: ["start-dev"] + env: + - name: KC_BOOTSTRAP_ADMIN_USERNAME + value: "admin" + - name: KC_BOOTSTRAP_ADMIN_PASSWORD + value: "admin" + - name: KC_HOSTNAME + value: "https://keycloak.127-0-0-1.sslip.io:8443" + - name: KC_HTTP_ENABLED + value: "true" + - name: KC_HEALTH_ENABLED + value: "true" + - name: KC_PROXY_HEADERS + value: "xforwarded" + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: /health/ready + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health/live + port: 9000 + initialDelaySeconds: 60 + periodSeconds: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app: keycloak + type: ClusterIP diff --git a/dev/config/keycloak/ingress.yaml b/dev/config/keycloak/ingress.yaml new file mode 100644 index 00000000..d172e091 --- /dev/null +++ b/dev/config/keycloak/ingress.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak + annotations: + cert-manager.io/cluster-issuer: "selfsigned-ca-issuer" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + # Required for Keycloak 26.2.0+ to include port in issuer URLs + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Port 8443; + proxy_set_header X-Forwarded-Host $host:8443; +spec: + ingressClassName: nginx + tls: + - hosts: + - keycloak.127-0-0-1.sslip.io + secretName: keycloak-tls-cert + rules: + - host: keycloak.127-0-0-1.sslip.io + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: keycloak + port: + number: 80 diff --git a/dev/config/keycloak/rbac.yaml b/dev/config/keycloak/rbac.yaml new file mode 100644 index 00000000..6f3f8c75 --- /dev/null +++ b/dev/config/keycloak/rbac.yaml @@ -0,0 +1,20 @@ +# RBAC ClusterRoleBinding for mcp user with OIDC authentication +# +# IMPORTANT: This requires Kubernetes API server to be configured with OIDC: +# --oidc-issuer-url=https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift +# --oidc-username-claim=preferred_username +# +# Without OIDC configuration, this binding will not work. +# +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: oidc-mcp-cluster-admin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: User + name: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift#mcp diff --git a/dev/config/kind/cluster.yaml b/dev/config/kind/cluster.yaml new file mode 100644 index 00000000..d78e802f --- /dev/null +++ b/dev/config/kind/cluster.yaml @@ -0,0 +1,30 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + extraMounts: + - hostPath: ./_output/cert-manager-ca/ca.crt + containerPath: /etc/kubernetes/pki/keycloak-ca.crt + readOnly: true + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + + kind: ClusterConfiguration + apiServer: + extraArgs: + oidc-issuer-url: https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift + oidc-client-id: openshift + oidc-username-claim: preferred_username + oidc-groups-claim: groups + oidc-ca-file: /etc/kubernetes/pki/keycloak-ca.crt + extraPortMappings: + - containerPort: 80 + hostPort: 8080 + protocol: TCP + - containerPort: 443 + hostPort: 8443 + protocol: TCP diff --git a/hack/generate-placeholder-ca.sh b/hack/generate-placeholder-ca.sh new file mode 100755 index 00000000..5428304d --- /dev/null +++ b/hack/generate-placeholder-ca.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Generate a placeholder self-signed CA certificate for KIND cluster startup +# This will be replaced with the real cert-manager CA after the cluster is created + +CERT_DIR="_output/cert-manager-ca" +CA_CERT="$CERT_DIR/ca.crt" +CA_KEY="$CERT_DIR/ca.key" + +mkdir -p "$CERT_DIR" + +# Generate a self-signed CA certificate (valid placeholder) +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout "$CA_KEY" \ + -out "$CA_CERT" \ + -days 365 \ + -subj "/CN=placeholder-ca" \ + 2>/dev/null + +echo "✅ Placeholder CA certificate created at $CA_CERT" +echo "⚠️ This will be replaced with cert-manager CA after cluster creation" From 75eeaac3d2d92613a673053560491c769a3d0590 Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Thu, 23 Oct 2025 00:14:05 -0400 Subject: [PATCH 15/57] fix(dev): do not use in-use port in example run command (#394) Signed-off-by: Calum Murray --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 49730445..04ff1ac0 100644 --- a/Makefile +++ b/Makefile @@ -142,7 +142,7 @@ local-env-setup: ## Setup complete local development environment with Kind clust @echo " _output/config.toml" @echo "" @echo "Run the MCP server with:" - @echo " ./$(BINARY_NAME) --port 8080 --config _output/config.toml" + @echo " ./$(BINARY_NAME) --port 8008 --config _output/config.toml" @echo "" @echo "Or run with MCP inspector:" @echo " npx @modelcontextprotocol/inspector@latest \$$(pwd)/$(BINARY_NAME) --config _output/config.toml" From aab94419e349cb20017a4a2c7ef03a93a3394a39 Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Thu, 23 Oct 2025 03:16:35 -0400 Subject: [PATCH 16/57] cleanup: refactor all the keycloak setup to json files (#390) Signed-off-by: Calum Murray --- build/keycloak.mk | 32 +++++++++---------- dev/config/keycloak/client-scopes/groups.json | 8 +++++ .../keycloak/client-scopes/mcp-openshift.json | 8 +++++ .../keycloak/client-scopes/mcp-server.json | 8 +++++ dev/config/keycloak/clients/mcp-client.json | 12 +++++++ .../keycloak/clients/mcp-server-update.json | 19 +++++++++++ dev/config/keycloak/clients/mcp-server.json | 18 +++++++++++ dev/config/keycloak/clients/openshift.json | 12 +++++++ .../keycloak/mappers/groups-membership.json | 12 +++++++ .../keycloak/mappers/mcp-server-audience.json | 10 ++++++ .../keycloak/mappers/openshift-audience.json | 10 ++++++ dev/config/keycloak/mappers/username.json | 13 ++++++++ dev/config/keycloak/realm/realm-create.json | 4 +++ .../keycloak/realm/realm-events-config.json | 8 +++++ dev/config/keycloak/users/mcp.json | 15 +++++++++ 15 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 dev/config/keycloak/client-scopes/groups.json create mode 100644 dev/config/keycloak/client-scopes/mcp-openshift.json create mode 100644 dev/config/keycloak/client-scopes/mcp-server.json create mode 100644 dev/config/keycloak/clients/mcp-client.json create mode 100644 dev/config/keycloak/clients/mcp-server-update.json create mode 100644 dev/config/keycloak/clients/mcp-server.json create mode 100644 dev/config/keycloak/clients/openshift.json create mode 100644 dev/config/keycloak/mappers/groups-membership.json create mode 100644 dev/config/keycloak/mappers/mcp-server-audience.json create mode 100644 dev/config/keycloak/mappers/openshift-audience.json create mode 100644 dev/config/keycloak/mappers/username.json create mode 100644 dev/config/keycloak/realm/realm-create.json create mode 100644 dev/config/keycloak/realm/realm-events-config.json create mode 100644 dev/config/keycloak/users/mcp.json diff --git a/build/keycloak.mk b/build/keycloak.mk index 2a7a9296..d541c8b4 100644 --- a/build/keycloak.mk +++ b/build/keycloak.mk @@ -111,7 +111,7 @@ keycloak-setup-realm: REALM_RESPONSE=$$(curl -sk -w "%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"realm":"openshift","enabled":true}'); \ + -d @dev/config/keycloak/realm/realm-create.json); \ REALM_CODE=$$(echo "$$REALM_RESPONSE" | tail -c 4); \ if [ "$$REALM_CODE" = "201" ] || [ "$$REALM_CODE" = "409" ]; then \ if [ "$$REALM_CODE" = "201" ]; then echo "✅ OpenShift realm created"; \ @@ -125,7 +125,7 @@ keycloak-setup-realm: EVENT_CONFIG_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X PUT "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"realm":"openshift","enabled":true,"eventsEnabled":true,"eventsListeners":["jboss-logging"],"adminEventsEnabled":true,"adminEventsDetailsEnabled":true}'); \ + -d @dev/config/keycloak/realm/realm-events-config.json); \ EVENT_CONFIG_CODE=$$(echo "$$EVENT_CONFIG_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$EVENT_CONFIG_CODE" = "204" ]; then \ echo "✅ User and admin event logging enabled"; \ @@ -137,7 +137,7 @@ keycloak-setup-realm: SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"mcp:openshift","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \ + -d @dev/config/keycloak/client-scopes/mcp-openshift.json); \ SCOPE_CODE=$$(echo "$$SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$SCOPE_CODE" = "201" ] || [ "$$SCOPE_CODE" = "409" ]; then \ if [ "$$SCOPE_CODE" = "201" ]; then echo "✅ mcp:openshift client scope created"; \ @@ -159,7 +159,7 @@ keycloak-setup-realm: MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$SCOPE_ID/protocol-mappers/models" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"openshift-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"openshift","id.token.claim":"true","access.token.claim":"true"}}'); \ + -d @dev/config/keycloak/mappers/openshift-audience.json); \ MAPPER_CODE=$$(echo "$$MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MAPPER_CODE" = "201" ] || [ "$$MAPPER_CODE" = "409" ]; then \ if [ "$$MAPPER_CODE" = "201" ]; then echo "✅ Audience mapper added"; \ @@ -173,7 +173,7 @@ keycloak-setup-realm: GROUPS_SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"groups","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \ + -d @dev/config/keycloak/client-scopes/groups.json); \ GROUPS_SCOPE_CODE=$$(echo "$$GROUPS_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$GROUPS_SCOPE_CODE" = "201" ] || [ "$$GROUPS_SCOPE_CODE" = "409" ]; then \ if [ "$$GROUPS_SCOPE_CODE" = "201" ]; then echo "✅ groups client scope created"; \ @@ -195,7 +195,7 @@ keycloak-setup-realm: GROUPS_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$GROUPS_SCOPE_ID/protocol-mappers/models" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"groups","protocol":"openid-connect","protocolMapper":"oidc-group-membership-mapper","config":{"claim.name":"groups","full.path":"false","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true"}}'); \ + -d @dev/config/keycloak/mappers/groups-membership.json); \ GROUPS_MAPPER_CODE=$$(echo "$$GROUPS_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$GROUPS_MAPPER_CODE" = "201" ] || [ "$$GROUPS_MAPPER_CODE" = "409" ]; then \ if [ "$$GROUPS_MAPPER_CODE" = "201" ]; then echo "✅ Group membership mapper added"; \ @@ -209,7 +209,7 @@ keycloak-setup-realm: MCP_SERVER_SCOPE_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"mcp-server","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}'); \ + -d @dev/config/keycloak/client-scopes/mcp-server.json); \ MCP_SERVER_SCOPE_CODE=$$(echo "$$MCP_SERVER_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MCP_SERVER_SCOPE_CODE" = "201" ] || [ "$$MCP_SERVER_SCOPE_CODE" = "409" ]; then \ if [ "$$MCP_SERVER_SCOPE_CODE" = "201" ]; then echo "✅ mcp-server client scope created"; \ @@ -231,7 +231,7 @@ keycloak-setup-realm: MCP_SERVER_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/client-scopes/$$MCP_SERVER_SCOPE_ID/protocol-mappers/models" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"mcp-server-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"mcp-server","id.token.claim":"true","access.token.claim":"true"}}'); \ + -d @dev/config/keycloak/mappers/mcp-server-audience.json); \ MCP_SERVER_MAPPER_CODE=$$(echo "$$MCP_SERVER_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MCP_SERVER_MAPPER_CODE" = "201" ] || [ "$$MCP_SERVER_MAPPER_CODE" = "409" ]; then \ if [ "$$MCP_SERVER_MAPPER_CODE" = "201" ]; then echo "✅ mcp-server audience mapper added"; \ @@ -245,7 +245,7 @@ keycloak-setup-realm: OPENSHIFT_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"clientId":"openshift","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups"],"optionalClientScopes":[]}'); \ + -d @dev/config/keycloak/clients/openshift.json); \ OPENSHIFT_CLIENT_CODE=$$(echo "$$OPENSHIFT_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$OPENSHIFT_CLIENT_CODE" = "201" ] || [ "$$OPENSHIFT_CLIENT_CODE" = "409" ]; then \ if [ "$$OPENSHIFT_CLIENT_CODE" = "201" ]; then echo "✅ openshift client created"; \ @@ -263,7 +263,7 @@ keycloak-setup-realm: OPENSHIFT_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$OPENSHIFT_CLIENT_ID/protocol-mappers/models" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{ "name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \ + -d @dev/config/keycloak/mappers/username.json); \ OPENSHIFT_USERNAME_MAPPER_CODE=$$(echo "$$OPENSHIFT_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "201" ] || [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "409" ]; then \ if [ "$$OPENSHIFT_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to openshift client"; \ @@ -277,7 +277,7 @@ keycloak-setup-realm: MCP_PUBLIC_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"clientId":"mcp-client","enabled":true,"publicClient":true,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":false,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email"],"optionalClientScopes":["mcp-server"]}'); \ + -d @dev/config/keycloak/clients/mcp-client.json); \ MCP_PUBLIC_CLIENT_CODE=$$(echo "$$MCP_PUBLIC_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MCP_PUBLIC_CLIENT_CODE" = "201" ] || [ "$$MCP_PUBLIC_CLIENT_CODE" = "409" ]; then \ if [ "$$MCP_PUBLIC_CLIENT_CODE" = "201" ]; then echo "✅ mcp-client public client created"; \ @@ -295,7 +295,7 @@ keycloak-setup-realm: MCP_PUBLIC_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_PUBLIC_CLIENT_ID/protocol-mappers/models" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \ + -d @dev/config/keycloak/mappers/username.json); \ MCP_PUBLIC_USERNAME_MAPPER_CODE=$$(echo "$$MCP_PUBLIC_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "201" ] || [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "409" ]; then \ if [ "$$MCP_PUBLIC_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to mcp-client"; \ @@ -309,7 +309,7 @@ keycloak-setup-realm: MCP_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false"}}'); \ + -d @dev/config/keycloak/clients/mcp-server.json); \ MCP_CLIENT_CODE=$$(echo "$$MCP_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MCP_CLIENT_CODE" = "201" ] || [ "$$MCP_CLIENT_CODE" = "409" ]; then \ if [ "$$MCP_CLIENT_CODE" = "201" ]; then echo "✅ mcp-server client created"; \ @@ -331,7 +331,7 @@ keycloak-setup-realm: UPDATE_CLIENT_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X PUT "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["profile","email","groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false","standard.token.exchange.enabled":"true"}}'); \ + -d @dev/config/keycloak/clients/mcp-server-update.json); \ UPDATE_CLIENT_CODE=$$(echo "$$UPDATE_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$UPDATE_CLIENT_CODE" = "204" ]; then \ echo "✅ Standard token exchange enabled for mcp-server client"; \ @@ -354,7 +354,7 @@ keycloak-setup-realm: MCP_USERNAME_MAPPER_RESPONSE=$$(curl -sk -w "HTTPCODE:%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/clients/$$MCP_CLIENT_ID/protocol-mappers/models" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"name":"username","protocol":"openid-connect","protocolMapper":"oidc-usermodel-property-mapper","config":{"userinfo.token.claim":"true","user.attribute":"username","id.token.claim":"true","access.token.claim":"true","claim.name":"preferred_username","jsonType.label":"String"}}'); \ + -d @dev/config/keycloak/mappers/username.json); \ MCP_USERNAME_MAPPER_CODE=$$(echo "$$MCP_USERNAME_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2); \ if [ "$$MCP_USERNAME_MAPPER_CODE" = "201" ] || [ "$$MCP_USERNAME_MAPPER_CODE" = "409" ]; then \ if [ "$$MCP_USERNAME_MAPPER_CODE" = "201" ]; then echo "✅ Username mapper added to mcp-server client"; \ @@ -368,7 +368,7 @@ keycloak-setup-realm: USER_RESPONSE=$$(curl -sk -w "%{http_code}" -X POST "https://keycloak.127-0-0-1.sslip.io:8443/admin/realms/openshift/users" \ -H "Authorization: Bearer $$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"username":"mcp","email":"mcp@example.com","firstName":"MCP","lastName":"User","enabled":true,"emailVerified":true,"credentials":[{"type":"password","value":"mcp","temporary":false}]}'); \ + -d @dev/config/keycloak/users/mcp.json); \ USER_CODE=$$(echo "$$USER_RESPONSE" | tail -c 4); \ if [ "$$USER_CODE" = "201" ] || [ "$$USER_CODE" = "409" ]; then \ if [ "$$USER_CODE" = "201" ]; then echo "✅ mcp user created"; \ diff --git a/dev/config/keycloak/client-scopes/groups.json b/dev/config/keycloak/client-scopes/groups.json new file mode 100644 index 00000000..4eb20b74 --- /dev/null +++ b/dev/config/keycloak/client-scopes/groups.json @@ -0,0 +1,8 @@ +{ + "name": "groups", + "protocol": "openid-connect", + "attributes": { + "display.on.consent.screen": "false", + "include.in.token.scope": "true" + } +} diff --git a/dev/config/keycloak/client-scopes/mcp-openshift.json b/dev/config/keycloak/client-scopes/mcp-openshift.json new file mode 100644 index 00000000..39f55e7b --- /dev/null +++ b/dev/config/keycloak/client-scopes/mcp-openshift.json @@ -0,0 +1,8 @@ +{ + "name": "mcp:openshift", + "protocol": "openid-connect", + "attributes": { + "display.on.consent.screen": "false", + "include.in.token.scope": "true" + } +} diff --git a/dev/config/keycloak/client-scopes/mcp-server.json b/dev/config/keycloak/client-scopes/mcp-server.json new file mode 100644 index 00000000..5ac0440b --- /dev/null +++ b/dev/config/keycloak/client-scopes/mcp-server.json @@ -0,0 +1,8 @@ +{ + "name": "mcp-server", + "protocol": "openid-connect", + "attributes": { + "display.on.consent.screen": "false", + "include.in.token.scope": "true" + } +} diff --git a/dev/config/keycloak/clients/mcp-client.json b/dev/config/keycloak/clients/mcp-client.json new file mode 100644 index 00000000..7e353e80 --- /dev/null +++ b/dev/config/keycloak/clients/mcp-client.json @@ -0,0 +1,12 @@ +{ + "clientId": "mcp-client", + "enabled": true, + "publicClient": true, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "authorizationServicesEnabled": false, + "redirectUris": ["*"], + "defaultClientScopes": ["profile", "email"], + "optionalClientScopes": ["mcp-server"] +} diff --git a/dev/config/keycloak/clients/mcp-server-update.json b/dev/config/keycloak/clients/mcp-server-update.json new file mode 100644 index 00000000..8af21f11 --- /dev/null +++ b/dev/config/keycloak/clients/mcp-server-update.json @@ -0,0 +1,19 @@ +{ + "clientId": "mcp-server", + "enabled": true, + "publicClient": false, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": false, + "redirectUris": ["*"], + "defaultClientScopes": ["profile", "email", "groups", "mcp-server"], + "optionalClientScopes": ["mcp:openshift"], + "attributes": { + "oauth2.device.authorization.grant.enabled": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false", + "standard.token.exchange.enabled": "true" + } +} diff --git a/dev/config/keycloak/clients/mcp-server.json b/dev/config/keycloak/clients/mcp-server.json new file mode 100644 index 00000000..fcabacbe --- /dev/null +++ b/dev/config/keycloak/clients/mcp-server.json @@ -0,0 +1,18 @@ +{ + "clientId": "mcp-server", + "enabled": true, + "publicClient": false, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": false, + "redirectUris": ["*"], + "defaultClientScopes": ["profile", "email", "groups", "mcp-server"], + "optionalClientScopes": ["mcp:openshift"], + "attributes": { + "oauth2.device.authorization.grant.enabled": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + } +} diff --git a/dev/config/keycloak/clients/openshift.json b/dev/config/keycloak/clients/openshift.json new file mode 100644 index 00000000..0c603175 --- /dev/null +++ b/dev/config/keycloak/clients/openshift.json @@ -0,0 +1,12 @@ +{ + "clientId": "openshift", + "enabled": true, + "publicClient": false, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": false, + "redirectUris": ["*"], + "defaultClientScopes": ["profile", "email", "groups"], + "optionalClientScopes": [] +} diff --git a/dev/config/keycloak/mappers/groups-membership.json b/dev/config/keycloak/mappers/groups-membership.json new file mode 100644 index 00000000..266a66e9 --- /dev/null +++ b/dev/config/keycloak/mappers/groups-membership.json @@ -0,0 +1,12 @@ +{ + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "config": { + "claim.name": "groups", + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } +} diff --git a/dev/config/keycloak/mappers/mcp-server-audience.json b/dev/config/keycloak/mappers/mcp-server-audience.json new file mode 100644 index 00000000..37b7e969 --- /dev/null +++ b/dev/config/keycloak/mappers/mcp-server-audience.json @@ -0,0 +1,10 @@ +{ + "name": "mcp-server-audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "mcp-server", + "id.token.claim": "true", + "access.token.claim": "true" + } +} diff --git a/dev/config/keycloak/mappers/openshift-audience.json b/dev/config/keycloak/mappers/openshift-audience.json new file mode 100644 index 00000000..74b84b71 --- /dev/null +++ b/dev/config/keycloak/mappers/openshift-audience.json @@ -0,0 +1,10 @@ +{ + "name": "openshift-audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "openshift", + "id.token.claim": "true", + "access.token.claim": "true" + } +} diff --git a/dev/config/keycloak/mappers/username.json b/dev/config/keycloak/mappers/username.json new file mode 100644 index 00000000..d76ccfa2 --- /dev/null +++ b/dev/config/keycloak/mappers/username.json @@ -0,0 +1,13 @@ +{ + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } +} diff --git a/dev/config/keycloak/realm/realm-create.json b/dev/config/keycloak/realm/realm-create.json new file mode 100644 index 00000000..d651e7dd --- /dev/null +++ b/dev/config/keycloak/realm/realm-create.json @@ -0,0 +1,4 @@ +{ + "realm": "openshift", + "enabled": true +} diff --git a/dev/config/keycloak/realm/realm-events-config.json b/dev/config/keycloak/realm/realm-events-config.json new file mode 100644 index 00000000..72b07a5b --- /dev/null +++ b/dev/config/keycloak/realm/realm-events-config.json @@ -0,0 +1,8 @@ +{ + "realm": "openshift", + "enabled": true, + "eventsEnabled": true, + "eventsListeners": ["jboss-logging"], + "adminEventsEnabled": true, + "adminEventsDetailsEnabled": true +} diff --git a/dev/config/keycloak/users/mcp.json b/dev/config/keycloak/users/mcp.json new file mode 100644 index 00000000..b84bc3f2 --- /dev/null +++ b/dev/config/keycloak/users/mcp.json @@ -0,0 +1,15 @@ +{ + "username": "mcp", + "email": "mcp@example.com", + "firstName": "MCP", + "lastName": "User", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "mcp", + "temporary": false + } + ] +} From 3072d19420923c5f1612e1f0a09dce3bac6c05ac Mon Sep 17 00:00:00 2001 From: Boris Lublinsky <25286171+blublinsky@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:41:04 +0100 Subject: [PATCH 17/57] add support for nodes query (#384) * feat(http): add custom CA certificate support for OIDC providers add support for nodes logs Signed-off-by: blublinsky * removed some tools Signed-off-by: blublinsky --------- Signed-off-by: blublinsky Co-authored-by: Matthias Wessendorf --- pkg/kubernetes/nodes.go | 48 +++++++++++ pkg/mcp/nodes_test.go | 66 ++++++++++++++++ pkg/mcp/testdata/toolsets-core-tools.json | 34 ++++++++ ...toolsets-full-tools-multicluster-enum.json | 42 ++++++++++ .../toolsets-full-tools-multicluster.json | 38 +++++++++ .../toolsets-full-tools-openshift.json | 34 ++++++++ pkg/mcp/testdata/toolsets-full-tools.json | 34 ++++++++ pkg/toolsets/core/nodes.go | 79 +++++++++++++++++++ pkg/toolsets/core/toolset.go | 1 + 9 files changed, 376 insertions(+) create mode 100644 pkg/kubernetes/nodes.go create mode 100644 pkg/mcp/nodes_test.go create mode 100644 pkg/toolsets/core/nodes.go diff --git a/pkg/kubernetes/nodes.go b/pkg/kubernetes/nodes.go new file mode 100644 index 00000000..c6bb58fe --- /dev/null +++ b/pkg/kubernetes/nodes.go @@ -0,0 +1,48 @@ +package kubernetes + +import ( + "context" + "fmt" +) + +func (k *Kubernetes) NodeLog(ctx context.Context, name string, logPath string, tail int64) (string, error) { + // Use the node proxy API to access logs from the kubelet + // Common log paths: + // - /var/log/kubelet.log - kubelet logs + // - /var/log/kube-proxy.log - kube-proxy logs + // - /var/log/containers/ - container logs + + if logPath == "" { + logPath = "kubelet.log" + } + + // Build the URL for the node proxy logs endpoint + url := []string{"api", "v1", "nodes", name, "proxy", "logs", logPath} + + // Query parameters for tail + params := make(map[string]string) + if tail > 0 { + params["tailLines"] = fmt.Sprintf("%d", tail) + } + + req := k.manager.discoveryClient.RESTClient(). + Get(). + AbsPath(url...) + + // Add tail parameter if specified + for key, value := range params { + req.Param(key, value) + } + + result := req.Do(ctx) + if result.Error() != nil { + return "", fmt.Errorf("failed to get node logs: %w", result.Error()) + } + + rawData, err := result.Raw() + if err != nil { + return "", fmt.Errorf("failed to read node log response: %w", err) + } + + return string(rawData), nil +} diff --git a/pkg/mcp/nodes_test.go b/pkg/mcp/nodes_test.go new file mode 100644 index 00000000..b9915778 --- /dev/null +++ b/pkg/mcp/nodes_test.go @@ -0,0 +1,66 @@ +package mcp + +import ( + "strings" + "testing" + + "github.com/mark3labs/mcp-go/mcp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestNodeLog(t *testing.T) { + testCase(t, func(c *mcpContext) { + c.withEnvTest() + + // Create test node + kubernetesAdmin := c.newKubernetesClient() + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node-log", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "192.168.1.10"}, + }, + }, + } + + _, _ = kubernetesAdmin.CoreV1().Nodes().Create(c.ctx, node, metav1.CreateOptions{}) + + // Test node_log tool + toolResult, err := c.callTool("node_log", map[string]interface{}{ + "name": "test-node-log", + }) + + t.Run("node_log returns successfully or with expected error", func(t *testing.T) { + if err != nil { + t.Fatalf("call tool failed: %v", err) + } + // Node logs might not be available in test environment + // We just check that the tool call completes + if toolResult.IsError { + content := toolResult.Content[0].(mcp.TextContent).Text + // Expected error messages in test environment + if !strings.Contains(content, "failed to get node logs") && + !strings.Contains(content, "not logged any message yet") { + t.Logf("tool returned error (expected in test environment): %v", content) + } + } + }) + }) +} + +func TestNodeLogMissingArguments(t *testing.T) { + testCase(t, func(c *mcpContext) { + c.withEnvTest() + + t.Run("node_log requires name", func(t *testing.T) { + toolResult, err := c.callTool("node_log", map[string]interface{}{}) + + if err == nil && !toolResult.IsError { + t.Fatal("expected error when name is missing") + } + }) + }) +} diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index 43680dae..5039cf24 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -33,6 +33,40 @@ }, "name": "namespaces_list" }, + { + "annotations": { + "title": "Node: Log", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", + "inputSchema": { + "type": "object", + "properties": { + "log_path": { + "default": "kubelet.log", + "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", + "type": "string" + }, + "name": { + "description": "Name of the node to get logs from", + "type": "string" + }, + "tail": { + "default": 100, + "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name" + ] + }, + "name": "node_log" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index 97af6fb5..cec61b95 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -195,6 +195,48 @@ }, "name": "namespaces_list" }, + { + "annotations": { + "title": "Node: Log", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", + "inputSchema": { + "type": "object", + "properties": { + "context": { + "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", + "enum": [ + "extra-cluster", + "fake-context" + ], + "type": "string" + }, + "log_path": { + "default": "kubelet.log", + "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", + "type": "string" + }, + "name": { + "description": "Name of the node to get logs from", + "type": "string" + }, + "tail": { + "default": 100, + "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name" + ] + }, + "name": "node_log" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index 861a1b5a..7f1f91da 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -175,6 +175,44 @@ }, "name": "namespaces_list" }, + { + "annotations": { + "title": "Node: Log", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", + "inputSchema": { + "type": "object", + "properties": { + "context": { + "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", + "type": "string" + }, + "log_path": { + "default": "kubelet.log", + "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", + "type": "string" + }, + "name": { + "description": "Name of the node to get logs from", + "type": "string" + }, + "tail": { + "default": 100, + "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name" + ] + }, + "name": "node_log" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index b5018945..066eee0b 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -139,6 +139,40 @@ }, "name": "namespaces_list" }, + { + "annotations": { + "title": "Node: Log", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", + "inputSchema": { + "type": "object", + "properties": { + "log_path": { + "default": "kubelet.log", + "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", + "type": "string" + }, + "name": { + "description": "Name of the node to get logs from", + "type": "string" + }, + "tail": { + "default": 100, + "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name" + ] + }, + "name": "node_log" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index 7b9f471d..17b5e7e2 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -139,6 +139,40 @@ }, "name": "namespaces_list" }, + { + "annotations": { + "title": "Node: Log", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", + "inputSchema": { + "type": "object", + "properties": { + "log_path": { + "default": "kubelet.log", + "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", + "type": "string" + }, + "name": { + "description": "Name of the node to get logs from", + "type": "string" + }, + "tail": { + "default": 100, + "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "name" + ] + }, + "name": "node_log" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/toolsets/core/nodes.go b/pkg/toolsets/core/nodes.go new file mode 100644 index 00000000..5516dce6 --- /dev/null +++ b/pkg/toolsets/core/nodes.go @@ -0,0 +1,79 @@ +package core + +import ( + "errors" + "fmt" + + "github.com/google/jsonschema-go/jsonschema" + "k8s.io/utils/ptr" + + "github.com/containers/kubernetes-mcp-server/pkg/api" +) + +func initNodes() []api.ServerTool { + return []api.ServerTool{ + {Tool: api.Tool{ + Name: "node_log", + Description: "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "name": { + Type: "string", + Description: "Name of the node to get logs from", + }, + "log_path": { + Type: "string", + Description: "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", + Default: api.ToRawMessage("kubelet.log"), + }, + "tail": { + Type: "integer", + Description: "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", + Default: api.ToRawMessage(100), + Minimum: ptr.To(float64(0)), + }, + }, + Required: []string{"name"}, + }, + Annotations: api.ToolAnnotations{ + Title: "Node: Log", + ReadOnlyHint: ptr.To(true), + DestructiveHint: ptr.To(false), + IdempotentHint: ptr.To(false), + OpenWorldHint: ptr.To(true), + }, + }, Handler: nodesLog}, + } +} + +func nodesLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + name := params.GetArguments()["name"] + if name == nil { + return api.NewToolCallResult("", errors.New("failed to get node log, missing argument name")), nil + } + logPath := params.GetArguments()["log_path"] + if logPath == nil { + logPath = "kubelet.log" + } + tail := params.GetArguments()["tail"] + var tailInt int64 + if tail != nil { + // Convert to int64 - safely handle both float64 (JSON number) and int types + switch v := tail.(type) { + case float64: + tailInt = int64(v) + case int: case int64: + tailInt = int64(v) + default: + return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tail)), nil + } + } + ret, err := params.NodeLog(params, name.(string), logPath.(string), tailInt) + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to get node log for %s: %v", name, err)), nil + } else if ret == "" { + ret = fmt.Sprintf("The node %s has not logged any message yet or the log file is empty", name) + } + return api.NewToolCallResult(ret, nil), nil +} diff --git a/pkg/toolsets/core/toolset.go b/pkg/toolsets/core/toolset.go index 9f88c7aa..dfd61f42 100644 --- a/pkg/toolsets/core/toolset.go +++ b/pkg/toolsets/core/toolset.go @@ -24,6 +24,7 @@ func (t *Toolset) GetTools(o internalk8s.Openshift) []api.ServerTool { return slices.Concat( initEvents(), initNamespaces(o), + initNodes(), initPods(), initResources(o), ) From b8981276c61594eb26179ceeb8beb2a888cbe8c2 Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Thu, 23 Oct 2025 09:55:31 +0200 Subject: [PATCH 18/57] fix(dev): change kind host port to 8000 (#398) Signed-off-by: Matthias Wessendorf --- dev/config/kind/cluster.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/config/kind/cluster.yaml b/dev/config/kind/cluster.yaml index d78e802f..fda11689 100644 --- a/dev/config/kind/cluster.yaml +++ b/dev/config/kind/cluster.yaml @@ -23,7 +23,7 @@ nodes: oidc-ca-file: /etc/kubernetes/pki/keycloak-ca.crt extraPortMappings: - containerPort: 80 - hostPort: 8080 + hostPort: 8000 protocol: TCP - containerPort: 443 hostPort: 8443 From acf465b5ed75c792df534b4ed6dc1e0f5d169d6c Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 10:06:55 +0200 Subject: [PATCH 19/57] feat(ai): add CLAUDE.md instructions and update AGENTS.md (#397) Adds a softlink to AGENTS.md so that CLAUDE can reuse the information. Signed-off-by: Marc Nuri --- AGENTS.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 58 insertions(+) create mode 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 854cfe5d..485ac1ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,12 +11,15 @@ This MCP server enables AI assistants (like Claude, Gemini, Cursor, and others) - Go package layout follows the standard Go conventions: - `cmd/kubernetes-mcp-server/` – main application entry point using Cobra CLI framework. - `pkg/` – libraries grouped by domain. + - `api/` - API-related functionality, tool definitions, and toolset interfaces. - `config/` – configuration management. - `helm/` - Helm chart operations integration. - `http/` - HTTP server and authorization middleware. - `kubernetes/` - Kubernetes client management, authentication, and access control. - `mcp/` - Model Context Protocol (MCP) server implementation with tool registration and STDIO/HTTP support. - `output/` - output formatting and rendering. + - `toolsets/` - Toolset registration and management for MCP tools. + - `version/` - Version information management. - `.github/` – GitHub-related configuration (Actions workflows, issue templates...). - `docs/` – documentation files. - `npm/` – Node packages that wraps the compiled binaries for distribution through npmjs.com. @@ -30,6 +33,21 @@ Implement new functionality in the Go sources under `cmd/` and `pkg/`. The JavaScript (`npm/`) and Python (`python/`) directories only wrap the compiled binary for distribution (npm and PyPI). Most changes will not require touching them unless the version or packaging needs to be updated. +### Adding new MCP tools + +The project uses a toolset-based architecture for organizing MCP tools: + +- **Tool definitions** are created in `pkg/api/` using the `ServerTool` struct. +- **Toolsets** group related tools together (e.g., config tools, core Kubernetes tools, Helm tools). +- **Registration** happens in `pkg/toolsets/` where toolsets are registered at initialization. +- Each toolset lives in its own subdirectory under `pkg/toolsets/` (e.g., `pkg/toolsets/config/`, `pkg/toolsets/core/`, `pkg/toolsets/helm/`). + +When adding a new tool: +1. Define the tool handler function that implements the tool's logic. +2. Create a `ServerTool` struct with the tool definition and handler. +3. Add the tool to an appropriate toolset (or create a new toolset if needed). +4. Register the toolset in `pkg/toolsets/` if it's a new toolset. + ## Building Use the provided Makefile targets: @@ -105,6 +123,45 @@ make lint The `lint` target downloads the specified `golangci-lint` version if it is not already present under `_output/tools/bin/`. +## Additional Makefile targets + +Beyond the basic build, test, and lint targets, the Makefile provides additional utilities: + +**Local Development:** +```bash +# Setup a complete local development environment with Kind cluster +make local-env-setup + +# Tear down the local Kind cluster +make local-env-teardown + +# Show Keycloak status and connection info (for OIDC testing) +make keycloak-status + +# Tail Keycloak logs +make keycloak-logs + +# Install required development tools (like Kind) to ./_output/bin/ +make tools +``` + +**Distribution and Publishing:** +```bash +# Copy compiled binaries to each npm package +make npm-copy-binaries + +# Publish the npm packages +make npm-publish + +# Publish the Python packages +make python-publish + +# Update README.md with the latest toolsets +make update-readme-tools +``` + +Run `make help` to see all available targets with descriptions. + ## Dependencies When introducing new modules run `make tidy` so that `go.mod` and `go.sum` remain tidy. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000..47dc3e3d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 07783a4ad318f7590912040204ae7d3c9cc8c76d Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 15:31:22 +0200 Subject: [PATCH 20/57] feat(config): default configuration with merge support for downstream overrides (#396) * feat(config): default configuration with merge support for downstream overrides Creates the required infrastructure for downstream forks to be able to provide default config overrides without modifying the original source code. Downstream forks should be able to create merge/rebase scripts that automatically accepted downstream merge conflicts in config_default_overrides.go since this file will never change upstream. Example usage for downstream forks: To customize defaults, simply populate fields in the returned StaticConfig: ```go func defaultOverrides() *StaticConfig { return &StaticConfig{ ListOutput: "json", // Override default list output format Toolsets: []string{"core"}, // Override default enabled toolsets Port: "9000", // Override default port } } ``` Any fields specified here will override the base defaults defined in config_default.go. Fields not specified will preserve their base default values. Signed-off-by: Marc Nuri * test: skip downstream toolset (full) tests Skips toolset metadata tests in case there are config overrides. This is useful in downstream forks where the default toolsets might be different to those upstream. Signed-off-by: Marc Nuri --------- Signed-off-by: Marc Nuri --- pkg/config/config.go | 9 +----- pkg/config/config_default.go | 43 ++++++++++++++++++++++++++ pkg/config/config_default_overrides.go | 8 +++++ pkg/config/config_test.go | 43 ++++++++++++++++++++++++++ pkg/mcp/toolsets_test.go | 12 +++++++ 5 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 pkg/config/config_default.go create mode 100644 pkg/config/config_default_overrides.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 5fe8e165..5bd00ff3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,7 +19,7 @@ const ( type StaticConfig struct { DeniedResources []GroupVersionKind `toml:"denied_resources"` - LogLevel int `toml:"log_level,omitempty"` + LogLevel int `toml:"log_level,omitzero"` Port string `toml:"port,omitempty"` SSEBaseURL string `toml:"sse_base_url,omitempty"` KubeConfig string `toml:"kubeconfig,omitempty"` @@ -70,13 +70,6 @@ type StaticConfig struct { parsedClusterProviderConfigs map[string]ProviderConfig } -func Default() *StaticConfig { - return &StaticConfig{ - ListOutput: "table", - Toolsets: []string{"core", "config", "helm"}, - } -} - type GroupVersionKind struct { Group string `toml:"group"` Version string `toml:"version"` diff --git a/pkg/config/config_default.go b/pkg/config/config_default.go new file mode 100644 index 00000000..febea70c --- /dev/null +++ b/pkg/config/config_default.go @@ -0,0 +1,43 @@ +package config + +import ( + "bytes" + + "github.com/BurntSushi/toml" +) + +func Default() *StaticConfig { + defaultConfig := StaticConfig{ + ListOutput: "table", + Toolsets: []string{"core", "config", "helm"}, + } + overrides := defaultOverrides() + mergedConfig := mergeConfig(defaultConfig, overrides) + return &mergedConfig +} + +// HasDefaultOverrides indicates whether the internal defaultOverrides function +// provides any overrides or an empty StaticConfig. +func HasDefaultOverrides() bool { + overrides := defaultOverrides() + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Encode(overrides); err != nil { + // If marshaling fails, assume no overrides + return false + } + return len(bytes.TrimSpace(buf.Bytes())) > 0 +} + +// mergeConfig applies non-zero values from override to base using TOML serialization +// and returns the merged StaticConfig. +// In case of any error during marshalling or unmarshalling, it returns the base config unchanged. +func mergeConfig(base, override StaticConfig) StaticConfig { + var overrideBuffer bytes.Buffer + if err := toml.NewEncoder(&overrideBuffer).Encode(override); err != nil { + // If marshaling fails, return base unchanged + return base + } + + _, _ = toml.NewDecoder(&overrideBuffer).Decode(&base) + return base +} diff --git a/pkg/config/config_default_overrides.go b/pkg/config/config_default_overrides.go new file mode 100644 index 00000000..70d065bc --- /dev/null +++ b/pkg/config/config_default_overrides.go @@ -0,0 +1,8 @@ +package config + +func defaultOverrides() StaticConfig { + return StaticConfig{ + // IMPORTANT: this file is used to override default config values in downstream builds. + // This is intentionally left blank. + } +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index d0e87726..afdde191 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -174,6 +174,49 @@ func (s *ConfigSuite) TestReadConfigValidPreservesDefaultsForMissingFields() { }) } +func (s *ConfigSuite) TestMergeConfig() { + base := StaticConfig{ + ListOutput: "table", + Toolsets: []string{"core", "config", "helm"}, + Port: "8080", + } + s.Run("merges override values on top of base", func() { + override := StaticConfig{ + ListOutput: "json", + Port: "9090", + } + + result := mergeConfig(base, override) + + s.Equal("json", result.ListOutput, "ListOutput should be overridden") + s.Equal("9090", result.Port, "Port should be overridden") + }) + + s.Run("preserves base values when override is empty", func() { + override := StaticConfig{} + + result := mergeConfig(base, override) + + s.Equal("table", result.ListOutput, "ListOutput should be preserved from base") + s.Equal([]string{"core", "config", "helm"}, result.Toolsets, "Toolsets should be preserved from base") + s.Equal("8080", result.Port, "Port should be preserved from base") + }) + + s.Run("handles partial overrides", func() { + override := StaticConfig{ + Toolsets: []string{"custom"}, + ReadOnly: true, + } + + result := mergeConfig(base, override) + + s.Equal("table", result.ListOutput, "ListOutput should be preserved from base") + s.Equal([]string{"custom"}, result.Toolsets, "Toolsets should be overridden") + s.Equal("8080", result.Port, "Port should be preserved from base since override doesn't specify it") + s.True(result.ReadOnly, "ReadOnly should be overridden to true") + }) +} + func TestConfig(t *testing.T) { suite.Run(t, new(ConfigSuite)) } diff --git a/pkg/mcp/toolsets_test.go b/pkg/mcp/toolsets_test.go index 527b1e22..d81392a5 100644 --- a/pkg/mcp/toolsets_test.go +++ b/pkg/mcp/toolsets_test.go @@ -65,6 +65,9 @@ func (s *ToolsetsSuite) TestNoToolsets() { } func (s *ToolsetsSuite) TestDefaultToolsetsTools() { + if configuration.HasDefaultOverrides() { + s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)") + } s.Run("Default configuration toolsets", func() { s.InitMcpClient() tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) @@ -82,6 +85,9 @@ func (s *ToolsetsSuite) TestDefaultToolsetsTools() { } func (s *ToolsetsSuite) TestDefaultToolsetsToolsInOpenShift() { + if configuration.HasDefaultOverrides() { + s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)") + } s.Run("Default configuration toolsets in OpenShift", func() { s.Handle(&test.InOpenShiftHandler{}) s.InitMcpClient() @@ -100,6 +106,9 @@ func (s *ToolsetsSuite) TestDefaultToolsetsToolsInOpenShift() { } func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiCluster() { + if configuration.HasDefaultOverrides() { + s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)") + } s.Run("Default configuration toolsets in multi-cluster (with 11 clusters)", func() { kubeconfig := s.Kubeconfig() for i := 0; i < 10; i++ { @@ -123,6 +132,9 @@ func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiCluster() { } func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiClusterEnum() { + if configuration.HasDefaultOverrides() { + s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)") + } s.Run("Default configuration toolsets in multi-cluster (with 2 clusters)", func() { kubeconfig := s.Kubeconfig() // Add additional cluster to force multi-cluster behavior with enum parameter From b1e47570449853d49e257a447cd71d9e3c0c5b02 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 15:52:15 +0200 Subject: [PATCH 21/57] test(mcp):update mcp processing tests to use testify and improve readability (#401) Signed-off-by: Marc Nuri --- pkg/mcp/mcp_middleware_test.go | 68 +++++++++ pkg/mcp/mcp_tools_test.go | 254 +++++++++++++-------------------- 2 files changed, 170 insertions(+), 152 deletions(-) create mode 100644 pkg/mcp/mcp_middleware_test.go diff --git a/pkg/mcp/mcp_middleware_test.go b/pkg/mcp/mcp_middleware_test.go new file mode 100644 index 00000000..987bfe4f --- /dev/null +++ b/pkg/mcp/mcp_middleware_test.go @@ -0,0 +1,68 @@ +package mcp + +import ( + "regexp" + "strings" + "testing" + + "github.com/mark3labs/mcp-go/client/transport" +) + +func TestToolCallLogging(t *testing.T) { + testCaseWithContext(t, &mcpContext{logLevel: 5}, func(c *mcpContext) { + _, _ = c.callTool("configuration_view", map[string]interface{}{ + "minified": false, + }) + t.Run("Logs tool name", func(t *testing.T) { + expectedLog := "mcp tool call: configuration_view(" + if !strings.Contains(c.logBuffer.String(), expectedLog) { + t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String()) + } + }) + t.Run("Logs tool call arguments", func(t *testing.T) { + expected := `"mcp tool call: configuration_view\((.+)\)"` + m := regexp.MustCompile(expected).FindStringSubmatch(c.logBuffer.String()) + if len(m) != 2 { + t.Fatalf("Expected log entry to contain arguments, got %s", c.logBuffer.String()) + } + if m[1] != "map[minified:false]" { + t.Errorf("Expected log arguments to be 'map[minified:false]', got %s", m[1]) + } + }) + }) + before := func(c *mcpContext) { + c.clientOptions = append(c.clientOptions, transport.WithHeaders(map[string]string{ + "Accept-Encoding": "gzip", + "Authorization": "Bearer should-not-be-logged", + "authorization": "Bearer should-not-be-logged", + "a-loggable-header": "should-be-logged", + })) + } + testCaseWithContext(t, &mcpContext{logLevel: 7, before: before}, func(c *mcpContext) { + _, _ = c.callTool("configuration_view", map[string]interface{}{ + "minified": false, + }) + t.Run("Logs tool call headers", func(t *testing.T) { + expectedLog := "mcp tool call headers: A-Loggable-Header: should-be-logged" + if !strings.Contains(c.logBuffer.String(), expectedLog) { + t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String()) + } + }) + sensitiveHeaders := []string{ + "Authorization:", + // TODO: Add more sensitive headers as needed + } + t.Run("Does not log sensitive headers", func(t *testing.T) { + for _, header := range sensitiveHeaders { + if strings.Contains(c.logBuffer.String(), header) { + t.Errorf("Log should not contain sensitive header '%s', got: %s", header, c.logBuffer.String()) + } + } + }) + t.Run("Does not log sensitive header values", func(t *testing.T) { + if strings.Contains(c.logBuffer.String(), "should-not-be-logged") { + t.Errorf("Log should not contain sensitive header value 'should-not-be-logged', got: %s", c.logBuffer.String()) + } + }) + }) +} diff --git a/pkg/mcp/mcp_tools_test.go b/pkg/mcp/mcp_tools_test.go index 196b93e2..f6b8a8be 100644 --- a/pkg/mcp/mcp_tools_test.go +++ b/pkg/mcp/mcp_tools_test.go @@ -1,180 +1,130 @@ package mcp import ( - "regexp" - "strings" "testing" - "github.com/mark3labs/mcp-go/client/transport" + "github.com/BurntSushi/toml" "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" "k8s.io/utils/ptr" - - "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/containers/kubernetes-mcp-server/pkg/config" ) -func TestUnrestricted(t *testing.T) { - testCase(t, func(c *mcpContext) { - tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{}) - t.Run("ListTools returns tools", func(t *testing.T) { - if err != nil { - t.Fatalf("call ListTools failed %v", err) - } - }) - t.Run("Destructive tools ARE NOT read only", func(t *testing.T) { - for _, tool := range tools.Tools { - readOnly := ptr.Deref(tool.Annotations.ReadOnlyHint, false) - destructive := ptr.Deref(tool.Annotations.DestructiveHint, false) - if readOnly && destructive { - t.Errorf("Tool %s is read-only and destructive, which is not allowed", tool.Name) - } - } - }) +// McpToolProcessingSuite tests MCP tool processing (isToolApplicable) +type McpToolProcessingSuite struct { + BaseMcpSuite +} + +func (s *McpToolProcessingSuite) TestUnrestricted() { + s.InitMcpClient() + + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NotNil(tools) + + s.Run("ListTools returns tools", func() { + s.NoError(err, "call ListTools failed") + s.NotNilf(tools, "list tools failed") + }) + + s.Run("Destructive tools ARE NOT read only", func() { + for _, tool := range tools.Tools { + readOnly := ptr.Deref(tool.Annotations.ReadOnlyHint, false) + destructive := ptr.Deref(tool.Annotations.DestructiveHint, false) + s.Falsef(readOnly && destructive, "Tool %s is read-only and destructive, which is not allowed", tool.Name) + } }) } -func TestReadOnly(t *testing.T) { - readOnlyServer := func(c *mcpContext) { c.staticConfig = &config.StaticConfig{ReadOnly: true} } - testCaseWithContext(t, &mcpContext{before: readOnlyServer}, func(c *mcpContext) { - tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{}) - t.Run("ListTools returns tools", func(t *testing.T) { - if err != nil { - t.Fatalf("call ListTools failed %v", err) - } - }) - t.Run("ListTools returns only read-only tools", func(t *testing.T) { - for _, tool := range tools.Tools { - if tool.Annotations.ReadOnlyHint == nil || !*tool.Annotations.ReadOnlyHint { - t.Errorf("Tool %s is not read-only but should be", tool.Name) - } - if tool.Annotations.DestructiveHint != nil && *tool.Annotations.DestructiveHint { - t.Errorf("Tool %s is destructive but should not be in read-only mode", tool.Name) - } - } - }) +func (s *McpToolProcessingSuite) TestReadOnly() { + s.Require().NoError(toml.Unmarshal([]byte(` + read_only = true + `), s.Cfg), "Expected to parse read only server config") + s.InitMcpClient() + + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NotNil(tools) + + s.Run("ListTools returns tools", func() { + s.NoError(err, "call ListTools failed") + s.NotNilf(tools, "list tools failed") + }) + + s.Run("ListTools returns only read-only tools", func() { + for _, tool := range tools.Tools { + s.Falsef(tool.Annotations.ReadOnlyHint == nil || !*tool.Annotations.ReadOnlyHint, + "Tool %s is not read-only but should be", tool.Name) + s.Falsef(tool.Annotations.DestructiveHint != nil && *tool.Annotations.DestructiveHint, + "Tool %s is destructive but should not be in read-only mode", tool.Name) + } }) } -func TestDisableDestructive(t *testing.T) { - disableDestructiveServer := func(c *mcpContext) { c.staticConfig = &config.StaticConfig{DisableDestructive: true} } - testCaseWithContext(t, &mcpContext{before: disableDestructiveServer}, func(c *mcpContext) { - tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{}) - t.Run("ListTools returns tools", func(t *testing.T) { - if err != nil { - t.Fatalf("call ListTools failed %v", err) - } - }) - t.Run("ListTools does not return destructive tools", func(t *testing.T) { - for _, tool := range tools.Tools { - if tool.Annotations.DestructiveHint != nil && *tool.Annotations.DestructiveHint { - t.Errorf("Tool %s is destructive but should not be", tool.Name) - } - } - }) +func (s *McpToolProcessingSuite) TestDisableDestructive() { + s.Require().NoError(toml.Unmarshal([]byte(` + disable_destructive = true + `), s.Cfg), "Expected to parse disable destructive server config") + s.InitMcpClient() + + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NotNil(tools) + + s.Run("ListTools returns tools", func() { + s.NoError(err, "call ListTools failed") + s.NotNilf(tools, "list tools failed") + }) + + s.Run("ListTools does not return destructive tools", func() { + for _, tool := range tools.Tools { + s.Falsef(tool.Annotations.DestructiveHint != nil && *tool.Annotations.DestructiveHint, + "Tool %s is destructive but should not be in disable_destructive mode", tool.Name) + } }) } -func TestEnabledTools(t *testing.T) { - enabledToolsServer := test.Must(config.ReadToml([]byte(` +func (s *McpToolProcessingSuite) TestEnabledTools() { + s.Require().NoError(toml.Unmarshal([]byte(` enabled_tools = [ "namespaces_list", "events_list" ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: enabledToolsServer}, func(c *mcpContext) { - tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{}) - t.Run("ListTools returns tools", func(t *testing.T) { - if err != nil { - t.Fatalf("call ListTools failed %v", err) - } - }) - t.Run("ListTools returns only explicitly enabled tools", func(t *testing.T) { - if len(tools.Tools) != 2 { - t.Fatalf("ListTools should return 2 tools, got %d", len(tools.Tools)) - } - for _, tool := range tools.Tools { - if tool.Name != "namespaces_list" && tool.Name != "events_list" { - t.Errorf("Tool %s is not enabled but should be", tool.Name) - } - } - }) + `), s.Cfg), "Expected to parse enabled tools server config") + s.InitMcpClient() + + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NotNil(tools) + + s.Run("ListTools returns tools", func() { + s.NoError(err, "call ListTools failed") + s.NotNilf(tools, "list tools failed") }) -} -func TestDisabledTools(t *testing.T) { - testCaseWithContext(t, &mcpContext{ - staticConfig: &config.StaticConfig{ - DisabledTools: []string{"namespaces_list", "events_list"}, - }, - }, func(c *mcpContext) { - tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{}) - t.Run("ListTools returns tools", func(t *testing.T) { - if err != nil { - t.Fatalf("call ListTools failed %v", err) - } - }) - t.Run("ListTools does not return disabled tools", func(t *testing.T) { - for _, tool := range tools.Tools { - if tool.Name == "namespaces_list" || tool.Name == "events_list" { - t.Errorf("Tool %s is not disabled but should be", tool.Name) - } - } - }) + s.Run("ListTools returns only explicitly enabled tools", func() { + s.Len(tools.Tools, 2, "ListTools should return exactly 2 tools") + for _, tool := range tools.Tools { + s.Falsef(tool.Name != "namespaces_list" && tool.Name != "events_list", + "Tool %s is not enabled but should be", tool.Name) + } }) } -func TestToolCallLogging(t *testing.T) { - testCaseWithContext(t, &mcpContext{logLevel: 5}, func(c *mcpContext) { - _, _ = c.callTool("configuration_view", map[string]interface{}{ - "minified": false, - }) - t.Run("Logs tool name", func(t *testing.T) { - expectedLog := "mcp tool call: configuration_view(" - if !strings.Contains(c.logBuffer.String(), expectedLog) { - t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String()) - } - }) - t.Run("Logs tool call arguments", func(t *testing.T) { - expected := `"mcp tool call: configuration_view\((.+)\)"` - m := regexp.MustCompile(expected).FindStringSubmatch(c.logBuffer.String()) - if len(m) != 2 { - t.Fatalf("Expected log entry to contain arguments, got %s", c.logBuffer.String()) - } - if m[1] != "map[minified:false]" { - t.Errorf("Expected log arguments to be 'map[minified:false]', got %s", m[1]) - } - }) +func (s *McpToolProcessingSuite) TestDisabledTools() { + s.Require().NoError(toml.Unmarshal([]byte(` + disabled_tools = [ "namespaces_list", "events_list" ] + `), s.Cfg), "Expected to parse disabled tools server config") + s.InitMcpClient() + + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NotNil(tools) + + s.Run("ListTools returns tools", func() { + s.NoError(err, "call ListTools failed") + s.NotNilf(tools, "list tools failed") }) - before := func(c *mcpContext) { - c.clientOptions = append(c.clientOptions, transport.WithHeaders(map[string]string{ - "Accept-Encoding": "gzip", - "Authorization": "Bearer should-not-be-logged", - "authorization": "Bearer should-not-be-logged", - "a-loggable-header": "should-be-logged", - })) - } - testCaseWithContext(t, &mcpContext{logLevel: 7, before: before}, func(c *mcpContext) { - _, _ = c.callTool("configuration_view", map[string]interface{}{ - "minified": false, - }) - t.Run("Logs tool call headers", func(t *testing.T) { - expectedLog := "mcp tool call headers: A-Loggable-Header: should-be-logged" - if !strings.Contains(c.logBuffer.String(), expectedLog) { - t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String()) - } - }) - sensitiveHeaders := []string{ - "Authorization:", - // TODO: Add more sensitive headers as needed + + s.Run("ListTools does not return disabled tools", func() { + for _, tool := range tools.Tools { + s.Falsef(tool.Name == "namespaces_list" || tool.Name == "events_list", + "Tool %s is not disabled but should be", tool.Name) } - t.Run("Does not log sensitive headers", func(t *testing.T) { - for _, header := range sensitiveHeaders { - if strings.Contains(c.logBuffer.String(), header) { - t.Errorf("Log should not contain sensitive header '%s', got: %s", header, c.logBuffer.String()) - } - } - }) - t.Run("Does not log sensitive header values", func(t *testing.T) { - if strings.Contains(c.logBuffer.String(), "should-not-be-logged") { - t.Errorf("Log should not contain sensitive header value 'should-not-be-logged', got: %s", c.logBuffer.String()) - } - }) }) } + +func TestMcpToolProcessing(t *testing.T) { + suite.Run(t, new(McpToolProcessingSuite)) +} From 56f7edece8c2b6b7e8356f386949f2a8c1f2252f Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 16:10:28 +0200 Subject: [PATCH 22/57] fix(nodes): reviewed kubernetes.nodes implementation (#399) * fix(nodes): reviewed kubernetes.nodes implementation - Changed node_log tool name to nodes_log for consistency - Added access control check for nodes and configured denied resources - Migrated tests to testify - Added complete test coverage for nodes_log https://kubernetes.io/docs/concepts/cluster-administration/system-logs/#log-query Signed-off-by: Marc Nuri * test(nodes): add test for nodes_log tool with negative tail argument Signed-off-by: Marc Nuri --------- Signed-off-by: Marc Nuri --- pkg/kubernetes/accesscontrol_clientset.go | 16 ++ pkg/kubernetes/nodes.go | 22 +- pkg/mcp/events_test.go | 1 + pkg/mcp/nodes_test.go | 231 ++++++++++++++---- pkg/mcp/testdata/toolsets-core-tools.json | 2 +- ...toolsets-full-tools-multicluster-enum.json | 2 +- .../toolsets-full-tools-multicluster.json | 2 +- .../toolsets-full-tools-openshift.json | 2 +- pkg/mcp/testdata/toolsets-full-tools.json | 2 +- pkg/toolsets/core/nodes.go | 17 +- 10 files changed, 221 insertions(+), 76 deletions(-) diff --git a/pkg/kubernetes/accesscontrol_clientset.go b/pkg/kubernetes/accesscontrol_clientset.go index ed875c64..0ce64c49 100644 --- a/pkg/kubernetes/accesscontrol_clientset.go +++ b/pkg/kubernetes/accesscontrol_clientset.go @@ -39,6 +39,22 @@ func (a *AccessControlClientset) DiscoveryClient() discovery.DiscoveryInterface return a.discoveryClient } +func (a *AccessControlClientset) NodesLogs(ctx context.Context, name, logPath string) (*rest.Request, error) { + gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"} + if !isAllowed(a.staticConfig, gvk) { + return nil, isNotAllowedError(gvk) + } + + if _, err := a.delegate.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{}); err != nil { + return nil, fmt.Errorf("failed to get node %s: %w", name, err) + } + + url := []string{"api", "v1", "nodes", name, "proxy", "logs", logPath} + return a.delegate.CoreV1().RESTClient(). + Get(). + AbsPath(url...), nil +} + func (a *AccessControlClientset) Pods(namespace string) (corev1.PodInterface, error) { gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"} if !isAllowed(a.staticConfig, gvk) { diff --git a/pkg/kubernetes/nodes.go b/pkg/kubernetes/nodes.go index c6bb58fe..76d9cc92 100644 --- a/pkg/kubernetes/nodes.go +++ b/pkg/kubernetes/nodes.go @@ -5,33 +5,21 @@ import ( "fmt" ) -func (k *Kubernetes) NodeLog(ctx context.Context, name string, logPath string, tail int64) (string, error) { +func (k *Kubernetes) NodesLog(ctx context.Context, name string, logPath string, tail int64) (string, error) { // Use the node proxy API to access logs from the kubelet // Common log paths: // - /var/log/kubelet.log - kubelet logs // - /var/log/kube-proxy.log - kube-proxy logs // - /var/log/containers/ - container logs - if logPath == "" { - logPath = "kubelet.log" + req, err := k.AccessControlClientset().NodesLogs(ctx, name, logPath) + if err != nil { + return "", err } - // Build the URL for the node proxy logs endpoint - url := []string{"api", "v1", "nodes", name, "proxy", "logs", logPath} - // Query parameters for tail - params := make(map[string]string) if tail > 0 { - params["tailLines"] = fmt.Sprintf("%d", tail) - } - - req := k.manager.discoveryClient.RESTClient(). - Get(). - AbsPath(url...) - - // Add tail parameter if specified - for key, value := range params { - req.Param(key, value) + req.Param("tailLines", fmt.Sprintf("%d", tail)) } result := req.Do(ctx) diff --git a/pkg/mcp/events_test.go b/pkg/mcp/events_test.go index 6d771bca..68ca85a8 100644 --- a/pkg/mcp/events_test.go +++ b/pkg/mcp/events_test.go @@ -126,6 +126,7 @@ func (s *EventsSuite) TestEventsListDenied() { s.InitMcpClient() s.Run("events_list (denied)", func() { toolResult, err := s.CallTool("events_list", map[string]interface{}{}) + s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("has error", func() { s.Truef(toolResult.IsError, "call tool should fail") s.Nilf(err, "call tool should not return error object") diff --git a/pkg/mcp/nodes_test.go b/pkg/mcp/nodes_test.go index b9915778..ce2cbc7e 100644 --- a/pkg/mcp/nodes_test.go +++ b/pkg/mcp/nodes_test.go @@ -1,66 +1,205 @@ package mcp import ( - "strings" + "net/http" "testing" + "github.com/BurntSushi/toml" + "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/mark3labs/mcp-go/mcp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/stretchr/testify/suite" ) -func TestNodeLog(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - - // Create test node - kubernetesAdmin := c.newKubernetesClient() - node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-node-log", - }, - Status: corev1.NodeStatus{ - Addresses: []corev1.NodeAddress{ - {Type: corev1.NodeInternalIP, Address: "192.168.1.10"}, - }, - }, - } +type NodesSuite struct { + BaseMcpSuite + mockServer *test.MockServer +} - _, _ = kubernetesAdmin.CoreV1().Nodes().Create(c.ctx, node, metav1.CreateOptions{}) +func (s *NodesSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) +} - // Test node_log tool - toolResult, err := c.callTool("node_log", map[string]interface{}{ - "name": "test-node-log", - }) +func (s *NodesSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} - t.Run("node_log returns successfully or with expected error", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed: %v", err) - } - // Node logs might not be available in test environment - // We just check that the tool call completes - if toolResult.IsError { - content := toolResult.Content[0].(mcp.TextContent).Text - // Expected error messages in test environment - if !strings.Contains(content, "failed to get node logs") && - !strings.Contains(content, "not logged any message yet") { - t.Logf("tool returned error (expected in test environment): %v", content) +func (s *NodesSuite) TestNodesLog() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Get Node response + if req.URL.Path == "/api/v1/nodes/existing-node" { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "existing-node" } + }`)) + return + } + // Get Empty Log response + if req.URL.Path == "/api/v1/nodes/existing-node/proxy/logs/empty.log" { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + return + } + // Get Kubelet Log response + if req.URL.Path == "/api/v1/nodes/existing-node/proxy/logs/kubelet.log" { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + logContent := "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n" + if req.URL.Query().Get("tailLines") != "" { + logContent = "Line 4\nLine 5\n" } + _, _ = w.Write([]byte(logContent)) + return + } + w.WriteHeader(http.StatusNotFound) + })) + s.InitMcpClient() + s.Run("nodes_log(name=nil)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{}) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes missing name", func() { + expectedMessage := "failed to get node log, missing argument name" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_log(name=inexistent-node)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "inexistent-node", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes missing node", func() { + expectedMessage := "failed to get node log for inexistent-node: failed to get node inexistent-node: the server could not find the requested resource (get nodes inexistent-node)" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_log(name=existing-node, log_path=missing.log)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "existing-node", + "log_path": "missing.log", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes missing log file", func() { + expectedMessage := "failed to get node log for existing-node: failed to get node logs: the server could not find the requested resource" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_log(name=existing-node, log_path=empty.log)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "existing-node", + "log_path": "empty.log", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes empty log", func() { + expectedMessage := "The node existing-node has not logged any message yet or the log file is empty" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive message '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_log(name=existing-node, log_path=kubelet.log)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "existing-node", + "log_path": "kubelet.log", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns full log", func() { + expectedMessage := "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected log content '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) + for _, tailCase := range []interface{}{2, int64(2), float64(2)} { + s.Run("nodes_log(name=existing-node, log_path=kubelet.log, tail=2)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "existing-node", + "log_path": "kubelet.log", + "tail": tailCase, + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns tail log", func() { + expectedMessage := "Line 4\nLine 5\n" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected log content '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_log(name=existing-node, log_path=kubelet.log, tail=-1)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "existing-node", + "log_path": "kubelet.log", + "tail": -1, + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns full log", func() { + expectedMessage := "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected log content '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + } } -func TestNodeLogMissingArguments(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - - t.Run("node_log requires name", func(t *testing.T) { - toolResult, err := c.callTool("node_log", map[string]interface{}{}) - - if err == nil && !toolResult.IsError { - t.Fatal("expected error when name is missing") - } +func (s *NodesSuite) TestNodesLogDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` + denied_resources = [ { version = "v1", kind = "Node" } ] + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("nodes_log (denied)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "does-not-matter", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { + expectedMessage := "failed to get node log for does-not-matter: resource not allowed: /v1, Kind=Node" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) } + +func TestNodes(t *testing.T) { + suite.Run(t, new(NodesSuite)) +} diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index 5039cf24..37345100 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -65,7 +65,7 @@ "name" ] }, - "name": "node_log" + "name": "nodes_log" }, { "annotations": { diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index cec61b95..7041bd3f 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -235,7 +235,7 @@ "name" ] }, - "name": "node_log" + "name": "nodes_log" }, { "annotations": { diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index 7f1f91da..a454f1ef 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -211,7 +211,7 @@ "name" ] }, - "name": "node_log" + "name": "nodes_log" }, { "annotations": { diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index 066eee0b..5e5fa4ea 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -171,7 +171,7 @@ "name" ] }, - "name": "node_log" + "name": "nodes_log" }, { "annotations": { diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index 17b5e7e2..56a160ed 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -171,7 +171,7 @@ "name" ] }, - "name": "node_log" + "name": "nodes_log" }, { "annotations": { diff --git a/pkg/toolsets/core/nodes.go b/pkg/toolsets/core/nodes.go index 5516dce6..6c669398 100644 --- a/pkg/toolsets/core/nodes.go +++ b/pkg/toolsets/core/nodes.go @@ -13,7 +13,7 @@ import ( func initNodes() []api.ServerTool { return []api.ServerTool{ {Tool: api.Tool{ - Name: "node_log", + Name: "nodes_log", Description: "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", InputSchema: &jsonschema.Schema{ Type: "object", @@ -48,12 +48,12 @@ func initNodes() []api.ServerTool { } func nodesLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) { - name := params.GetArguments()["name"] - if name == nil { + name, ok := params.GetArguments()["name"].(string) + if !ok || name == "" { return api.NewToolCallResult("", errors.New("failed to get node log, missing argument name")), nil } - logPath := params.GetArguments()["log_path"] - if logPath == nil { + logPath, ok := params.GetArguments()["log_path"].(string) + if !ok || logPath == "" { logPath = "kubelet.log" } tail := params.GetArguments()["tail"] @@ -63,13 +63,14 @@ func nodesLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) { switch v := tail.(type) { case float64: tailInt = int64(v) - case int: case int64: - tailInt = int64(v) + case int: + case int64: + tailInt = v default: return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tail)), nil } } - ret, err := params.NodeLog(params, name.(string), logPath.(string), tailInt) + ret, err := params.NodesLog(params, name, logPath, tailInt) if err != nil { return api.NewToolCallResult("", fmt.Errorf("failed to get node log for %s: %v", name, err)), nil } else if ret == "" { From 9b3deb4b57f2327a43390e2d8d05a16f12a086a4 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 16:42:10 +0200 Subject: [PATCH 23/57] test(pods): update PodsExec tests to use testify and improve readability (#400) * test(pods): update PodsExec tests to use testify and improve readability Signed-off-by: Marc Nuri * review: enhance namespace="" test description for pods_exec clarity Signed-off-by: Marc Nuri --------- Signed-off-by: Marc Nuri --- internal/test/mcp.go | 4 +- pkg/mcp/pods_exec_test.go | 179 ++++++++++++++++++++------------------ 2 files changed, 95 insertions(+), 88 deletions(-) diff --git a/internal/test/mcp.go b/internal/test/mcp.go index 8daaae40..b82e3194 100644 --- a/internal/test/mcp.go +++ b/internal/test/mcp.go @@ -1,12 +1,12 @@ package test import ( + "net/http" "net/http/httptest" "testing" "github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/mcp" - "github.com/mark3labs/mcp-go/server" "github.com/stretchr/testify/require" "golang.org/x/net/context" ) @@ -17,7 +17,7 @@ type McpClient struct { *client.Client } -func NewMcpClient(t *testing.T, mcpHttpServer *server.StreamableHTTPServer) *McpClient { +func NewMcpClient(t *testing.T, mcpHttpServer http.Handler) *McpClient { require.NotNil(t, mcpHttpServer, "McpHttpServer must be provided") var err error ret := &McpClient{ctx: t.Context()} diff --git a/pkg/mcp/pods_exec_test.go b/pkg/mcp/pods_exec_test.go index dac6883c..c39cc8d6 100644 --- a/pkg/mcp/pods_exec_test.go +++ b/pkg/mcp/pods_exec_test.go @@ -7,125 +7,132 @@ import ( "strings" "testing" + "github.com/BurntSushi/toml" "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/containers/kubernetes-mcp-server/pkg/config" ) -func TestPodsExec(t *testing.T) { - testCase(t, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec/exec" { - return - } - var stdin, stdout bytes.Buffer - ctx, err := test.CreateHTTPStreams(w, req, &test.StreamOptions{ - Stdin: &stdin, - Stdout: &stdout, - }) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - return - } - defer func(conn io.Closer) { _ = conn.Close() }(ctx.Closer) - _, _ = io.WriteString(ctx.StdoutStream, "command:"+strings.Join(req.URL.Query()["command"], " ")+"\n") - _, _ = io.WriteString(ctx.StdoutStream, "container:"+strings.Join(req.URL.Query()["container"], " ")+"\n") - })) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec" { - return - } - test.WriteObject(w, &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "pod-to-exec", - }, - Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container-to-exec"}}}, - }) - })) - podsExecNilNamespace, err := c.callTool("pods_exec", map[string]interface{}{ +type PodsExecSuite struct { + BaseMcpSuite + mockServer *test.MockServer +} + +func (s *PodsExecSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) +} + +func (s *PodsExecSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *PodsExecSuite) TestPodsExec() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec/exec" { + return + } + var stdin, stdout bytes.Buffer + ctx, err := test.CreateHTTPStreams(w, req, &test.StreamOptions{ + Stdin: &stdin, + Stdout: &stdout, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(err.Error())) + return + } + defer func(conn io.Closer) { _ = conn.Close() }(ctx.Closer) + _, _ = io.WriteString(ctx.StdoutStream, "command:"+strings.Join(req.URL.Query()["command"], " ")+"\n") + _, _ = io.WriteString(ctx.StdoutStream, "container:"+strings.Join(req.URL.Query()["container"], " ")+"\n") + })) + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec" { + return + } + test.WriteObject(w, &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "pod-to-exec", + }, + Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container-to-exec"}}}, + }) + })) + s.InitMcpClient() + + s.Run("pods_exec(name=pod-to-exec, namespace=nil, command=[ls -l]), uses configured namespace", func() { + result, err := s.CallTool("pods_exec", map[string]interface{}{ "name": "pod-to-exec", "command": []interface{}{"ls", "-l"}, }) - t.Run("pods_exec with name and nil namespace returns command output", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if podsExecNilNamespace.IsError { - t.Fatalf("call tool failed: %v", podsExecNilNamespace.Content) - } - if !strings.Contains(podsExecNilNamespace.Content[0].(mcp.TextContent).Text, "command:ls -l\n") { - t.Errorf("unexpected result %v", podsExecNilNamespace.Content[0].(mcp.TextContent).Text) - } + s.Require().NotNil(result) + s.Run("returns command output", func() { + s.NoError(err, "call tool failed %v", err) + s.Falsef(result.IsError, "call tool failed: %v", result.Content) + s.Contains(result.Content[0].(mcp.TextContent).Text, "command:ls -l\n", "unexpected result %v", result.Content[0].(mcp.TextContent).Text) }) - podsExecInNamespace, err := c.callTool("pods_exec", map[string]interface{}{ + }) + s.Run("pods_exec(name=pod-to-exec, namespace=default, command=[ls -l])", func() { + result, err := s.CallTool("pods_exec", map[string]interface{}{ "namespace": "default", "name": "pod-to-exec", "command": []interface{}{"ls", "-l"}, }) - t.Run("pods_exec with name and namespace returns command output", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if podsExecInNamespace.IsError { - t.Fatalf("call tool failed: %v", podsExecInNamespace.Content) - } - if !strings.Contains(podsExecInNamespace.Content[0].(mcp.TextContent).Text, "command:ls -l\n") { - t.Errorf("unexpected result %v", podsExecInNamespace.Content[0].(mcp.TextContent).Text) - } + s.Require().NotNil(result) + s.Run("returns command output", func() { + s.NoError(err, "call tool failed %v", err) + s.Falsef(result.IsError, "call tool failed: %v", result.Content) + s.Contains(result.Content[0].(mcp.TextContent).Text, "command:ls -l\n", "unexpected result %v", result.Content[0].(mcp.TextContent).Text) }) - podsExecInNamespaceAndContainer, err := c.callTool("pods_exec", map[string]interface{}{ + }) + s.Run("pods_exec(name=pod-to-exec, namespace=default, command=[ls -l], container=a-specific-container)", func() { + result, err := s.CallTool("pods_exec", map[string]interface{}{ "namespace": "default", "name": "pod-to-exec", "command": []interface{}{"ls", "-l"}, "container": "a-specific-container", }) - t.Run("pods_exec with name, namespace, and container returns command output", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if podsExecInNamespaceAndContainer.IsError { - t.Fatalf("call tool failed") - } - if !strings.Contains(podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text, "command:ls -l\n") { - t.Errorf("unexpected result %v", podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text) - } - if !strings.Contains(podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text, "container:a-specific-container\n") { - t.Errorf("expected container name not found %v", podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text) - } + s.Require().NotNil(result) + s.Run("returns command output", func() { + s.NoError(err, "call tool failed %v", err) + s.Falsef(result.IsError, "call tool failed: %v", result.Content) + s.Contains(result.Content[0].(mcp.TextContent).Text, "command:ls -l\n", "unexpected result %v", result.Content[0].(mcp.TextContent).Text) }) }) } -func TestPodsExecDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsExecSuite) TestPodsExecDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsRun, _ := c.callTool("pods_exec", map[string]interface{}{ + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("pods_exec (denied)", func() { + toolResult, err := s.CallTool("pods_exec", map[string]interface{}{ "namespace": "default", "name": "pod-to-exec", "command": []interface{}{"ls", "-l"}, "container": "a-specific-container", }) - t.Run("pods_exec has error", func(t *testing.T) { - if !podsRun.IsError { - t.Fatalf("call tool should fail") - } + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("pods_exec describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to exec in pod pod-to-exec in namespace default: resource not allowed: /v1, Kind=Pod" - if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) } + +func TestPodsExec(t *testing.T) { + suite.Run(t, new(PodsExecSuite)) +} From 3d4dcabcc8e69be3238edc2104b070323a875cd4 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 17:23:16 +0200 Subject: [PATCH 24/57] test(pods): update PodsTop tests to use testify and improve readability (#402) Signed-off-by: Marc Nuri --- pkg/mcp/pods_top_test.go | 427 +++++++++++++++++++-------------------- 1 file changed, 213 insertions(+), 214 deletions(-) diff --git a/pkg/mcp/pods_top_test.go b/pkg/mcp/pods_top_test.go index 9fd218bb..92f6505a 100644 --- a/pkg/mcp/pods_top_test.go +++ b/pkg/mcp/pods_top_test.go @@ -5,247 +5,246 @@ import ( "regexp" "testing" + "github.com/BurntSushi/toml" "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/mark3labs/mcp-go/mcp" - - "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/stretchr/testify/suite" ) -func TestPodsTopMetricsUnavailable(t *testing.T) { - testCase(t, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json") - // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) - if req.URL.Path == "/api" { - _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":[],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Groups) - if req.URL.Path == "/apis" { - _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) - return - } - })) - podsTopMetricsApiUnavailable, err := c.callTool("pods_top", map[string]interface{}{}) - t.Run("pods_top with metrics API not available", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if !podsTopMetricsApiUnavailable.IsError { - t.Errorf("call tool should have returned an error") - } - if podsTopMetricsApiUnavailable.Content[0].(mcp.TextContent).Text != "failed to get pods top: metrics API is not available" { - t.Errorf("call tool returned unexpected content: %s", podsTopMetricsApiUnavailable.Content[0].(mcp.TextContent).Text) - } - }) +type PodsTopSuite struct { + BaseMcpSuite + mockServer *test.MockServer +} + +func (s *PodsTopSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) +} + +func (s *PodsTopSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *PodsTopSuite) TestPodsTopMetricsUnavailable() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":[],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) + return + } + })) + s.InitMcpClient() + + s.Run("pods_top with metrics API not available", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{}) + s.NoError(err, "call tool failed %v", err) + s.Require().NoError(err) + s.True(result.IsError, "call tool should have returned an error") + s.Equalf("failed to get pods top: metrics API is not available", result.Content[0].(mcp.TextContent).Text, + "call tool returned unexpected content: %s", result.Content[0].(mcp.TextContent).Text) }) } -func TestPodsTopMetricsAvailable(t *testing.T) { - testCase(t, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - println("Request received:", req.Method, req.URL.Path) // TODO: REMOVE LINE - w.Header().Set("Content-Type", "application/json") - // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) - if req.URL.Path == "/api" { - _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Groups) - if req.URL.Path == "/apis" { - _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Resources) - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { - _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) - return - } - // Pod Metrics from all namespaces - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/pods" { - if req.URL.Query().Get("labelSelector") == "app=pod-ns-5-42" { - _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-ns-5-42","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"42m","memory":"42Mi","swap":"42Mi"}}]}` + - `]}`)) - } else { - _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"100m","memory":"200Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"200m","memory":"300Mi","swap":"37Mi"}}]},` + - `{"metadata":{"name":"pod-2","namespace":"ns-1"},"containers":[{"name":"container-1-ns-1","usage":{"cpu":"300m","memory":"400Mi","swap":"42Mi"}}]}` + - `]}`)) - - } - return - } - // Pod Metrics from configured namespace - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" { +func (s *PodsTopSuite) TestPodsTopMetricsAvailable() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Resources) + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { + _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) + return + } + // Pod Metrics from all namespaces + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/pods" { + if req.URL.Query().Get("labelSelector") == "app=pod-ns-5-42" { _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"30m","memory":"40Mi","swap":"37Mi"}}]}` + + `{"metadata":{"name":"pod-ns-5-42","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"42m","memory":"42Mi","swap":"42Mi"}}]}` + `]}`)) - return - } - // Pod Metrics from ns-5 namespace - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods" { + } else { _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-ns-5-1","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"42Mi"}}]}` + + `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"100m","memory":"200Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"200m","memory":"300Mi","swap":"37Mi"}}]},` + + `{"metadata":{"name":"pod-2","namespace":"ns-1"},"containers":[{"name":"container-1-ns-1","usage":{"cpu":"300m","memory":"400Mi","swap":"42Mi"}}]}` + `]}`)) - return - } - // Pod Metrics from ns-5 namespace with pod-ns-5-5 pod name - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods/pod-ns-5-5" { - _, _ = w.Write([]byte(`{"kind":"PodMetrics","apiVersion":"metrics.k8s.io/v1beta1",` + - `"metadata":{"name":"pod-ns-5-5","namespace":"ns-5"},` + - `"containers":[{"name":"container-1","usage":{"cpu":"13m","memory":"37Mi","swap":"42Mi"}}]` + - `}`)) - } - })) - podsTopDefaults, err := c.callTool("pods_top", map[string]interface{}{}) - t.Run("pods_top defaults returns pod metrics from all namespaces", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopDefaults.Content[0].(mcp.TextContent).Text - if podsTopDefaults.IsError { - t.Fatalf("call tool failed %s", textContent) - } - expectedHeaders := regexp.MustCompile(`(?m)^\s*NAMESPACE\s+POD\s+NAME\s+CPU\(cores\)\s+MEMORY\(bytes\)\s+SWAP\(bytes\)\s*$`) - if !expectedHeaders.MatchString(textContent) { - t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders.String(), textContent) - } - expectedRows := []string{ - "default\\s+pod-1\\s+container-1\\s+100m\\s+200Mi\\s+13Mi", - "default\\s+pod-1\\s+container-2\\s+200m\\s+300Mi\\s+37Mi", - "ns-1\\s+pod-2\\s+container-1-ns-1\\s+300m\\s+400Mi\\s+42Mi", - } - for _, row := range expectedRows { - if !regexp.MustCompile(row).MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", row, textContent) - } - } - expectedTotal := regexp.MustCompile(`(?m)^\s+600m\s+900Mi\s+92Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + } - }) - podsTopConfiguredNamespace, err := c.callTool("pods_top", map[string]interface{}{ + return + } + // Pod Metrics from configured namespace + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" { + _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + + `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"30m","memory":"40Mi","swap":"37Mi"}}]}` + + `]}`)) + return + } + // Pod Metrics from ns-5 namespace + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods" { + _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + + `{"metadata":{"name":"pod-ns-5-1","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"42Mi"}}]}` + + `]}`)) + return + } + // Pod Metrics from ns-5 namespace with pod-ns-5-5 pod name + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods/pod-ns-5-5" { + _, _ = w.Write([]byte(`{"kind":"PodMetrics","apiVersion":"metrics.k8s.io/v1beta1",` + + `"metadata":{"name":"pod-ns-5-5","namespace":"ns-5"},` + + `"containers":[{"name":"container-1","usage":{"cpu":"13m","memory":"37Mi","swap":"42Mi"}}]` + + `}`)) + } + })) + s.InitMcpClient() + + s.Run("pods_top(defaults) returns pod metrics from all namespaces", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{}) + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedHeaders := regexp.MustCompile(`(?m)^\s*NAMESPACE\s+POD\s+NAME\s+CPU\(cores\)\s+MEMORY\(bytes\)\s+SWAP\(bytes\)\s*$`) + s.Regexpf(expectedHeaders, textContent, "expected headers '%s' not found in output:\n%s", expectedHeaders.String(), textContent) + expectedRows := []string{ + "default\\s+pod-1\\s+container-1\\s+100m\\s+200Mi\\s+13Mi", + "default\\s+pod-1\\s+container-2\\s+200m\\s+300Mi\\s+37Mi", + "ns-1\\s+pod-2\\s+container-1-ns-1\\s+300m\\s+400Mi\\s+42Mi", + } + + for _, row := range expectedRows { + s.Regexpf(row, textContent, "expected row '%s' not found in output:\n%s", row, textContent) + } + + expectedTotal := regexp.MustCompile(`(?m)^\s+600m\s+900Mi\s+92Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top(allNamespaces=false) returns pod metrics from configured namespace", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "all_namespaces": false, }) - t.Run("pods_top[allNamespaces=false] returns pod metrics from configured namespace", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopConfiguredNamespace.Content[0].(mcp.TextContent).Text - expectedRows := []string{ - "default\\s+pod-1\\s+container-1\\s+10m\\s+20Mi\\s+13Mi", - "default\\s+pod-1\\s+container-2\\s+30m\\s+40Mi\\s+37Mi", - } - for _, row := range expectedRows { - if !regexp.MustCompile(row).MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", row, textContent) - } - } - expectedTotal := regexp.MustCompile(`(?m)^\s+40m\s+60Mi\s+50Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) - podsTopNamespace, err := c.callTool("pods_top", map[string]interface{}{ + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRows := []string{ + "default\\s+pod-1\\s+container-1\\s+10m\\s+20Mi\\s+13Mi", + "default\\s+pod-1\\s+container-2\\s+30m\\s+40Mi\\s+37Mi", + } + for _, row := range expectedRows { + s.Regexpf(row, textContent, "expected row '%s' not found in output:\n%s", row, textContent) + } + + expectedTotal := regexp.MustCompile(`(?m)^\s+40m\s+60Mi\s+50Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top(namespace=ns-5) returns pod metrics from provided namespace", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "namespace": "ns-5", }) - t.Run("pods_top[namespace=ns-5] returns pod metrics from provided namespace", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopNamespace.Content[0].(mcp.TextContent).Text - expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-1\s+container-1\s+10m\s+20Mi\s+42Mi`) - if !expectedRow.MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) - } - expectedTotal := regexp.MustCompile(`(?m)^\s+10m\s+20Mi\s+42Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) - podsTopNamespaceName, err := c.callTool("pods_top", map[string]interface{}{ + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-1\s+container-1\s+10m\s+20Mi\s+42Mi`) + s.Regexpf(expectedRow, textContent, "expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) + + expectedTotal := regexp.MustCompile(`(?m)^\s+10m\s+20Mi\s+42Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top(namespace=ns-5,name=pod-ns-5-5) returns pod metrics from provided namespace and name", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "namespace": "ns-5", "name": "pod-ns-5-5", }) - t.Run("pods_top[namespace=ns-5,name=pod-ns-5-5] returns pod metrics from provided namespace and name", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopNamespaceName.Content[0].(mcp.TextContent).Text - expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-5\s+container-1\s+13m\s+37Mi\s+42Mi`) - if !expectedRow.MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) - } - expectedTotal := regexp.MustCompile(`(?m)^\s+13m\s+37Mi\s+42Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) - podsTopNamespaceLabelSelector, err := c.callTool("pods_top", map[string]interface{}{ + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-5\s+container-1\s+13m\s+37Mi\s+42Mi`) + s.Regexpf(expectedRow, textContent, "expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) + + expectedTotal := regexp.MustCompile(`(?m)^\s+13m\s+37Mi\s+42Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top[label_selector=app=pod-ns-5-42] returns pod metrics from pods matching selector", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "label_selector": "app=pod-ns-5-42", }) - t.Run("pods_top[label_selector=app=pod-ns-5-42] returns pod metrics from pods matching selector", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopNamespaceLabelSelector.Content[0].(mcp.TextContent).Text - expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-42\s+container-1\s+42m\s+42Mi`) - if !expectedRow.MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) - } - expectedTotal := regexp.MustCompile(`(?m)^\s+42m\s+42Mi\s+42Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-42\s+container-1\s+42m\s+42Mi`) + s.Regexpf(expectedRow, textContent, "expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) + + expectedTotal := regexp.MustCompile(`(?m)^\s+42m\s+42Mi\s+42Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) }) } -func TestPodsTopDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsTopSuite) TestPodsTopDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { group = "metrics.k8s.io", version = "v1beta1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json") - // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) - if req.URL.Path == "/api" { - _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Groups) - if req.URL.Path == "/apis" { - _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Resources) - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { - _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) - return - } - })) - podsTop, _ := c.callTool("pods_top", map[string]interface{}{}) - t.Run("pods_run has error", func(t *testing.T) { - if !podsTop.IsError { - t.Fatalf("call tool should fail") - } + `), s.Cfg), "Expected to parse denied resources config") + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Resources) + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { + _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) + return + } + })) + s.InitMcpClient() + + s.Run("pods_top (denied)", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{}) + s.Require().NotNil(result, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(result.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("pods_run describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to get pods top: resource not allowed: metrics.k8s.io/v1beta1, Kind=PodMetrics" - if podsTop.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsTop.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, result.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, result.Content[0].(mcp.TextContent).Text) }) }) } + +func TestPodsTop(t *testing.T) { + suite.Run(t, new(PodsTopSuite)) +} From 54f7e7ffdab518b83f445f2641acc4d86e55b692 Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Fri, 24 Oct 2025 07:00:14 -0400 Subject: [PATCH 25/57] fix(dev): do not require cors-disabled browsers for dev auth flow (#393) Signed-off-by: Calum Murray --- dev/config/keycloak/clients/mcp-client.json | 1 + dev/config/keycloak/clients/mcp-server-update.json | 1 + dev/config/keycloak/clients/mcp-server.json | 1 + dev/config/keycloak/clients/openshift.json | 1 + 4 files changed, 4 insertions(+) diff --git a/dev/config/keycloak/clients/mcp-client.json b/dev/config/keycloak/clients/mcp-client.json index 7e353e80..7f2c596e 100644 --- a/dev/config/keycloak/clients/mcp-client.json +++ b/dev/config/keycloak/clients/mcp-client.json @@ -7,6 +7,7 @@ "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "redirectUris": ["*"], + "webOrigins": ["*"], "defaultClientScopes": ["profile", "email"], "optionalClientScopes": ["mcp-server"] } diff --git a/dev/config/keycloak/clients/mcp-server-update.json b/dev/config/keycloak/clients/mcp-server-update.json index 8af21f11..2709e75c 100644 --- a/dev/config/keycloak/clients/mcp-server-update.json +++ b/dev/config/keycloak/clients/mcp-server-update.json @@ -7,6 +7,7 @@ "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "redirectUris": ["*"], + "webOrigins": ["*"], "defaultClientScopes": ["profile", "email", "groups", "mcp-server"], "optionalClientScopes": ["mcp:openshift"], "attributes": { diff --git a/dev/config/keycloak/clients/mcp-server.json b/dev/config/keycloak/clients/mcp-server.json index fcabacbe..873fa5ce 100644 --- a/dev/config/keycloak/clients/mcp-server.json +++ b/dev/config/keycloak/clients/mcp-server.json @@ -7,6 +7,7 @@ "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "redirectUris": ["*"], + "webOrigins": ["*"], "defaultClientScopes": ["profile", "email", "groups", "mcp-server"], "optionalClientScopes": ["mcp:openshift"], "attributes": { diff --git a/dev/config/keycloak/clients/openshift.json b/dev/config/keycloak/clients/openshift.json index 0c603175..2905c437 100644 --- a/dev/config/keycloak/clients/openshift.json +++ b/dev/config/keycloak/clients/openshift.json @@ -7,6 +7,7 @@ "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "redirectUris": ["*"], + "webOrigins": ["*"], "defaultClientScopes": ["profile", "email", "groups"], "optionalClientScopes": [] } From e86686a10233567367244498cf9f3b819d0e5942 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Fri, 24 Oct 2025 14:41:41 +0200 Subject: [PATCH 26/57] fix(http): well-known mitm propagates original headers (#406) Signed-off-by: Marc Nuri --- pkg/http/http_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++ pkg/http/wellknown.go | 7 ++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/pkg/http/http_test.go b/pkg/http/http_test.go index cac23314..ab531813 100644 --- a/pkg/http/http_test.go +++ b/pkg/http/http_test.go @@ -390,6 +390,122 @@ func TestWellKnownReverseProxy(t *testing.T) { }) } +func TestWellKnownHeaderPropagation(t *testing.T) { + cases := []string{ + ".well-known/oauth-authorization-server", + ".well-known/oauth-protected-resource", + ".well-known/openid-configuration", + } + var receivedRequestHeaders http.Header + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.EscapedPath(), "/.well-known/") { + http.NotFound(w, r) + return + } + // Capture headers received from the proxy + receivedRequestHeaders = r.Header.Clone() + // Set response headers that should be propagated back + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "https://example.com") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("X-Custom-Backend-Header", "backend-value") + _, _ = w.Write([]byte(`{"issuer": "https://example.com"}`)) + })) + t.Cleanup(testServer.Close) + staticConfig := &config.StaticConfig{ + AuthorizationURL: testServer.URL, + RequireOAuth: true, + ValidateToken: true, + ClusterProviderStrategy: config.ClusterProviderKubeConfig, + } + testCaseWithContext(t, &httpContext{StaticConfig: staticConfig}, func(ctx *httpContext) { + for _, path := range cases { + receivedRequestHeaders = nil + req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/%s", ctx.HttpAddress, path), nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + // Add various headers to test propagation + req.Header.Set("Origin", "https://example.com") + req.Header.Set("User-Agent", "Test-Agent/1.0") + req.Header.Set("Accept", "application/json") + req.Header.Set("Accept-Language", "en-US") + req.Header.Set("X-Custom-Header", "custom-value") + req.Header.Set("Referer", "https://example.com/page") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Failed to get %s endpoint: %v", path, err) + } + t.Cleanup(func() { _ = resp.Body.Close() }) + + t.Run("Well-known proxy propagates Origin header to backend for "+path, func(t *testing.T) { + if receivedRequestHeaders == nil { + t.Fatal("Backend did not receive any headers") + } + if receivedRequestHeaders.Get("Origin") != "https://example.com" { + t.Errorf("Expected Origin header 'https://example.com', got '%s'", receivedRequestHeaders.Get("Origin")) + } + }) + + t.Run("Well-known proxy propagates User-Agent header to backend for "+path, func(t *testing.T) { + if receivedRequestHeaders.Get("User-Agent") != "Test-Agent/1.0" { + t.Errorf("Expected User-Agent header 'Test-Agent/1.0', got '%s'", receivedRequestHeaders.Get("User-Agent")) + } + }) + + t.Run("Well-known proxy propagates Accept header to backend for "+path, func(t *testing.T) { + if receivedRequestHeaders.Get("Accept") != "application/json" { + t.Errorf("Expected Accept header 'application/json', got '%s'", receivedRequestHeaders.Get("Accept")) + } + }) + + t.Run("Well-known proxy propagates Accept-Language header to backend for "+path, func(t *testing.T) { + if receivedRequestHeaders.Get("Accept-Language") != "en-US" { + t.Errorf("Expected Accept-Language header 'en-US', got '%s'", receivedRequestHeaders.Get("Accept-Language")) + } + }) + + t.Run("Well-known proxy propagates custom headers to backend for "+path, func(t *testing.T) { + if receivedRequestHeaders.Get("X-Custom-Header") != "custom-value" { + t.Errorf("Expected X-Custom-Header 'custom-value', got '%s'", receivedRequestHeaders.Get("X-Custom-Header")) + } + }) + + t.Run("Well-known proxy propagates Referer header to backend for "+path, func(t *testing.T) { + if receivedRequestHeaders.Get("Referer") != "https://example.com/page" { + t.Errorf("Expected Referer header 'https://example.com/page', got '%s'", receivedRequestHeaders.Get("Referer")) + } + }) + + t.Run("Well-known proxy returns Access-Control-Allow-Origin from backend for "+path, func(t *testing.T) { + if resp.Header.Get("Access-Control-Allow-Origin") != "https://example.com" { + t.Errorf("Expected Access-Control-Allow-Origin header 'https://example.com', got '%s'", resp.Header.Get("Access-Control-Allow-Origin")) + } + }) + + t.Run("Well-known proxy returns Access-Control-Allow-Methods from backend for "+path, func(t *testing.T) { + if resp.Header.Get("Access-Control-Allow-Methods") != "GET, POST, OPTIONS" { + t.Errorf("Expected Access-Control-Allow-Methods header 'GET, POST, OPTIONS', got '%s'", resp.Header.Get("Access-Control-Allow-Methods")) + } + }) + + t.Run("Well-known proxy returns Cache-Control from backend for "+path, func(t *testing.T) { + if resp.Header.Get("Cache-Control") != "no-cache" { + t.Errorf("Expected Cache-Control header 'no-cache', got '%s'", resp.Header.Get("Cache-Control")) + } + }) + + t.Run("Well-known proxy returns custom response headers from backend for "+path, func(t *testing.T) { + if resp.Header.Get("X-Custom-Backend-Header") != "backend-value" { + t.Errorf("Expected X-Custom-Backend-Header 'backend-value', got '%s'", resp.Header.Get("X-Custom-Backend-Header")) + } + }) + } + }) +} + func TestWellKnownOverrides(t *testing.T) { cases := []string{ ".well-known/oauth-authorization-server", diff --git a/pkg/http/wellknown.go b/pkg/http/wellknown.go index 6c065fa5..01ff3092 100644 --- a/pkg/http/wellknown.go +++ b/pkg/http/wellknown.go @@ -32,7 +32,7 @@ var _ http.Handler = &WellKnown{} func WellKnownHandler(staticConfig *config.StaticConfig, httpClient *http.Client) http.Handler { authorizationUrl := staticConfig.AuthorizationURL - if authorizationUrl != "" && strings.HasSuffix("authorizationUrl", "/") { + if authorizationUrl != "" && strings.HasSuffix(authorizationUrl, "/") { authorizationUrl = strings.TrimSuffix(authorizationUrl, "/") } if httpClient == nil { @@ -56,6 +56,11 @@ func (w WellKnown) ServeHTTP(writer http.ResponseWriter, request *http.Request) http.Error(writer, "Failed to create request: "+err.Error(), http.StatusInternalServerError) return } + for key, values := range request.Header { + for _, value := range values { + req.Header.Add(key, value) + } + } resp, err := w.httpClient.Do(req.WithContext(request.Context())) if err != nil { http.Error(writer, "Failed to perform request: "+err.Error(), http.StatusInternalServerError) From 42a067441f656c1ff075ca17026f16626d36be48 Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Mon, 27 Oct 2025 15:34:16 +0100 Subject: [PATCH 27/57] chore(docs): adding first set of user documentation (#411) * Adding inital README and instructions for setup of server with Kubernetes, as well as with 'claude code CLI' Signed-off-by: Matthias Wessendorf * Review: * note on recommendation about use SA versus exciting (default) kubeconfig * adding a collapsible section * removed incorrect args Signed-off-by: Matthias Wessendorf --------- Signed-off-by: Matthias Wessendorf --- README.md | 9 ++ docs/GETTING_STARTED_CLAUDE_CODE.md | 99 ++++++++++++ docs/GETTING_STARTED_KUBERNETES.md | 242 ++++++++++++++++++++++++++++ docs/README.md | 21 +++ 4 files changed, 371 insertions(+) create mode 100644 docs/GETTING_STARTED_CLAUDE_CODE.md create mode 100644 docs/GETTING_STARTED_KUBERNETES.md create mode 100644 docs/README.md diff --git a/README.md b/README.md index 600f4981..eab469c6 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,15 @@ If you're using the native binaries you don't need to have Node or Python instal - Access to a Kubernetes cluster. +
+Claude Code + +Follow the [dedicated Claude Code getting started guide](docs/GETTING_STARTED_CLAUDE_CODE.md) in our [user documentation](docs/). + +For a secure production setup with dedicated ServiceAccount and read-only access, also review the [Kubernetes setup guide](docs/GETTING_STARTED_KUBERNETES.md). + +
+ ### Claude Desktop #### Using npx diff --git a/docs/GETTING_STARTED_CLAUDE_CODE.md b/docs/GETTING_STARTED_CLAUDE_CODE.md new file mode 100644 index 00000000..b8e2985a --- /dev/null +++ b/docs/GETTING_STARTED_CLAUDE_CODE.md @@ -0,0 +1,99 @@ +# Using Kubernetes MCP Server with Claude Code CLI + +This guide shows you how to configure the Kubernetes MCP Server with Claude Code CLI. + +> **Prerequisites:** Complete the [Getting Started with Kubernetes](GETTING_STARTED_KUBERNETES.md) guide first to create a ServiceAccount and kubeconfig file. + +## Quick Setup + +Add the MCP server using the `claude mcp add-json` command: + +```bash +claude mcp add-json kubernetes-mcp-server \ + '{"command":"npx","args":["-y","kubernetes-mcp-server@latest","--read-only"],"env":{"KUBECONFIG":"'${HOME}'/.kube/mcp-viewer.kubeconfig"}}' \ + -s user +``` + +**What this does:** +- Adds the Kubernetes MCP Server to your Claude Code configuration +- Uses `npx` to automatically download and run the latest version +- Enables read-only mode for safety +- Uses the kubeconfig file you created in the Getting Started guide +- `-s user` makes it available in all your projects + +## Manual Configuration (Alternative) + +If you prefer to edit the config file manually: + +**Location:** `~/.config/claude-code/config.toml` + +```toml +[[mcp_servers]] +name = "kubernetes-mcp-server" +command = "npx" +args = [ + "-y", + "kubernetes-mcp-server@latest", + "--read-only" +] + +[mcp_servers.env] +KUBECONFIG = "/home/YOUR_USERNAME/.kube/mcp-viewer.kubeconfig" +``` + +**Important:** Replace `/home/YOUR_USERNAME/` with your actual home directory path. + +## Verify Connection + +After adding the MCP server, verify it's connected: + +```bash +claude mcp list +``` + +Expected output: +``` +Checking MCP server health... + +kubernetes-mcp-server: npx -y kubernetes-mcp-server@latest --read-only - ✓ Connected +``` + +## Using the MCP Server + +Once connected, interact with your Kubernetes cluster using natural language! Claude will use the Kubernetes MCP Server +to fetch and display the relevant information from your cluster: + +```bash +> List all namespaces in my cluster + +● I'll list all the namespaces in your Kubernetes cluster. + ⎿ APIVERSION KIND NAME STATUS AGE LABELS + v1 Namespace default Active 77m kubernetes.io/metadata.name=default + v1 Namespace kube-node-lease Active 77m kubernetes.io/metadata.name=kube-node-lease + … +4 lines (ctrl+o to expand) + +● Your cluster has 6 namespaces: + + 1. default - The default namespace for resources without a specified namespace + 2. kube-node-lease - Used for node heartbeat/lease objects + 3. kube-public - Publicly accessible namespace, typically for cluster information + 4. kube-system - System namespace for Kubernetes control plane components + 5. local-path-storage - Likely used for local storage provisioning + 6. mcp - Custom namespace (created ~75 minutes ago) + + All namespaces are in Active status and the cluster appears to be running for about 77 minutes. +``` + +## Configuration Options + +Common command-line flags you can add to the `args` array: + +| Flag | Description | +|------|-------------| +| `--read-only` | Enable read-only mode (recommended) | +| `--kubeconfig ` | Path to kubeconfig file (or use `KUBECONFIG` env var) | + +## Next Steps + +- Review the [Getting Started with Kubernetes](GETTING_STARTED_KUBERNETES.md) guide for more details on ServiceAccount setup +- Explore the [main README](../README.md) for more MCP server capabilities diff --git a/docs/GETTING_STARTED_KUBERNETES.md b/docs/GETTING_STARTED_KUBERNETES.md new file mode 100644 index 00000000..9f3613bf --- /dev/null +++ b/docs/GETTING_STARTED_KUBERNETES.md @@ -0,0 +1,242 @@ +# Getting Started with Kubernetes MCP Server + +This guide walks you through the foundational setup for using the Kubernetes MCP Server with your Kubernetes cluster. You'll create a dedicated, read-only ServiceAccount and generate a secure kubeconfig file that can be used with various MCP clients. + +> **Note:** This setup is **recommended for production use** but not strictly required. The MCP Server can use your existing kubeconfig file (e.g., `~/.kube/config`), but using a dedicated ServiceAccount with limited permissions follows the principle of least privilege and is more secure. + +> **Next:** After completing this guide, continue with the [Claude Code CLI guide](GETTING_STARTED_CLAUDE_CODE.md). See the [docs README](README.md) for all available guides. + +## What You'll Create + +By the end of this guide, you'll have: +- A dedicated `mcp-viewer` ServiceAccount with read-only cluster access +- A secure, time-bound authentication token +- A dedicated kubeconfig file (`~/.kube/mcp-viewer.kubeconfig`) + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` CLI installed and configured +- Cluster admin permissions to create ServiceAccounts and RBAC bindings + +## 1. Create a Read-Only ServiceAccount and RBAC + +A ServiceAccount represents a non-human identity. Binding it to a read-only role lets tools query the cluster safely without using administrator credentials. + +### Step 1.1: Create the Namespace and ServiceAccount + +First, create a Namespace for the ServiceAccount: + +```bash +# Create or pick a Namespace for the ServiceAccount +kubectl create namespace mcp + +# Create the ServiceAccount +kubectl create serviceaccount mcp-viewer -n mcp +``` + +### Step 1.2: Grant Read-Only Access (RBAC) + +Use a ClusterRoleBinding or RoleBinding to grant read-only permissions. + +#### Option A: Cluster-Wide Read-Only (Most Common) + +This binds the ServiceAccount to the built-in `view` ClusterRole, which provides read-only access across the whole cluster. + +```bash +# Binds the view ClusterRole to the ServiceAccount +kubectl create clusterrolebinding mcp-viewer-crb \ + --clusterrole=view \ + --serviceaccount=mcp:mcp-viewer +``` + +#### Option B: Namespace-Scoped Only (Tighter Scope) + +This limits read access to the `mcp` namespace only, using the built-in `view` Role. + +```bash +# Binds the view role to the ServiceAccount within the 'mcp' namespace +kubectl create rolebinding mcp-viewer-rb \ + --role=view \ + --serviceaccount=mcp:mcp-viewer \ + -n mcp +``` + +### Quick Verification (Optional) + +Verify the permissions granted to the ServiceAccount: + +```bash +# Check if the ServiceAccount can list pods cluster-wide +# Expect 'yes' if you used the view ClusterRole (Option A) +kubectl auth can-i list pods --as=system:serviceaccount:mcp:mcp-viewer --all-namespaces +``` + +## 2. Mint a ServiceAccount Token + +Tools authenticate via a bearer token. We use the TokenRequest API (`kubectl create token`) to generate a secure, short-lived token. + +```bash +# Create a time-bound token (choose a duration, e.g., 2 hours) +TOKEN="$(kubectl create token mcp-viewer --duration=2h -n mcp)" + +# Verify the token was generated (Optional) +echo "$TOKEN" +``` + +**Note:** The `kubectl create token` command requires Kubernetes v1.24+. For older versions, you'll need to extract the token from the ServiceAccount's secret. + +## 3. Build a Dedicated Kubeconfig + +A dedicated kubeconfig file isolates this ServiceAccount's credentials from your personal admin credentials, making it easy to point external tools at. + +### Step 3.1: Get Cluster Details + +Get the API server address and certificate authority from your current active context: + +```bash +# 1. Get the current cluster API server address +API_SERVER="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')" + +# 2. Get the cluster's Certificate Authority (CA) file path or data +# First, try to get the CA file path +CA_FILE="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.certificate-authority}')" + +# If CA file is not set, extract the CA data and write it to a temp file +if [ -z "$CA_FILE" ]; then + CA_FILE="/tmp/k8s-ca-$$.crt" + kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > "$CA_FILE" +fi + +# 3. Define the desired context name +CONTEXT_NAME="mcp-viewer-context" +KUBECONFIG_FILE="$HOME/.kube/mcp-viewer.kubeconfig" +``` + +### Step 3.2: Create and Configure the Kubeconfig File + +Create the new kubeconfig file by defining the cluster, user (the ServiceAccount), and context: + +```bash +# Create a new kubeconfig file with cluster configuration +kubectl config --kubeconfig="$KUBECONFIG_FILE" set-cluster mcp-viewer-cluster \ + --server="$API_SERVER" \ + --certificate-authority="$CA_FILE" \ + --embed-certs=true + +# Set the ServiceAccount token as the user credential +kubectl config --kubeconfig="$KUBECONFIG_FILE" set-credentials mcp-viewer \ + --token="$TOKEN" + +# Define the context (links the cluster and user) +kubectl config --kubeconfig="$KUBECONFIG_FILE" set-context "$CONTEXT_NAME" \ + --cluster=mcp-viewer-cluster \ + --user=mcp-viewer + +# Set the new context as current +kubectl config --kubeconfig="$KUBECONFIG_FILE" use-context "$CONTEXT_NAME" + +# Secure the file permissions +chmod 600 "$KUBECONFIG_FILE" + +# Clean up temporary CA file if we created one +if [[ "$CA_FILE" == /tmp/k8s-ca-*.crt ]]; then + rm -f "$CA_FILE" +fi +``` + +### Quick Sanity Check + +You can now use this new file to verify access: + +```bash +# Run a command using the dedicated kubeconfig file +kubectl --kubeconfig="$KUBECONFIG_FILE" get pods -A +``` + +This command should successfully list all Pods if you chose **Option A: Cluster-Wide Read-Only**, proving the ServiceAccount and its token are correctly configured. + +## 4. Use with Kubernetes MCP Server + +Now that you have a dedicated kubeconfig file, you can use it with the Kubernetes MCP Server: + +```bash +# Run the MCP server with the dedicated kubeconfig +./kubernetes-mcp-server --kubeconfig="$HOME/.kube/mcp-viewer.kubeconfig" + +# Or use npx +npx -y kubernetes-mcp-server@latest --kubeconfig="$HOME/.kube/mcp-viewer.kubeconfig" + +# Or use uvx +uvx kubernetes-mcp-server@latest --kubeconfig="$HOME/.kube/mcp-viewer.kubeconfig" +``` + +Alternatively, you can set the `KUBECONFIG` environment variable: + +```bash +export KUBECONFIG="$HOME/.kube/mcp-viewer.kubeconfig" +./kubernetes-mcp-server +``` + +## Token Expiration and Renewal + +The token created in Step 2 has a limited lifetime (2 hours in the example). When it expires, you'll need to: + +1. Generate a new token: + ```bash + TOKEN="$(kubectl create token mcp-viewer --duration=2h -n mcp)" + ``` + +2. Update the kubeconfig file: + ```bash + kubectl config --kubeconfig="$KUBECONFIG_FILE" set-credentials mcp-viewer --token="$TOKEN" + ``` + +For long-running applications, consider: +- Using a longer token duration (up to the cluster's maximum, typically 24h) +- Implementing automatic token renewal in your application +- Using a different authentication method (e.g., client certificates) + +## Cleanup + +To remove the ServiceAccount and associated RBAC bindings: + +```bash +# Delete the ClusterRoleBinding (if using Option A) +kubectl delete clusterrolebinding mcp-viewer-crb + +# Delete the RoleBinding (if using Option B) +kubectl delete rolebinding mcp-viewer-rb -n mcp + +# Delete the ServiceAccount +kubectl delete serviceaccount mcp-viewer -n mcp + +# Delete the namespace (optional - only if you created it specifically for this) +kubectl delete namespace mcp + +# Remove the kubeconfig file +rm "$HOME/.kube/mcp-viewer.kubeconfig" +``` + +## Troubleshooting + +### kubectl create token: command not found + +This command requires Kubernetes v1.24+. For older versions, you'll need to extract the token from the ServiceAccount's secret manually. + +### Permission denied errors + +Ensure you're using the correct kubeconfig file and that the ServiceAccount has the necessary RBAC permissions. Verify with: + +```bash +kubectl auth can-i list pods --as=system:serviceaccount:mcp:mcp-viewer --all-namespaces +``` + +## Next Steps + +Now that you have a working kubeconfig with read-only access, configure Claude Code CLI: + +- **[Using with Claude Code CLI](GETTING_STARTED_CLAUDE_CODE.md)** - Configure the MCP server with Claude Code CLI + +You can also: +- Explore the [main README](../README.md) for more MCP server capabilities diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..b6918bff --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# Kubernetes MCP Server Documentation + +Welcome to the Kubernetes MCP Server documentation! This directory contains guides to help you set up and use the Kubernetes MCP Server with your Kubernetes cluster and Claude Code CLI. + +## Getting Started Guides + +Choose the guide that matches your needs: + +| Guide | Description | Best For | +|-------|-------------|----------| +| **[Getting Started with Kubernetes](GETTING_STARTED_KUBERNETES.md)** | Base setup: Create ServiceAccount, token, and kubeconfig | Everyone - **start here first** | +| **[Using with Claude Code CLI](GETTING_STARTED_CLAUDE_CODE.md)** | Configure MCP server with Claude Code CLI | Claude Code CLI users | + +## Recommended Workflow + +1. **Complete the base setup**: Start with [Getting Started with Kubernetes](GETTING_STARTED_KUBERNETES.md) to create a ServiceAccount and kubeconfig file +2. **Configure Claude Code**: Then follow the [Claude Code CLI guide](GETTING_STARTED_CLAUDE_CODE.md) + +## Additional Documentation + +- **[Main README](../README.md)** - Project overview and general information From e420f161b10bbcd2d03b69361b7825efb0dc21a8 Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Mon, 27 Oct 2025 16:16:18 +0100 Subject: [PATCH 28/57] =?UTF-8?q?=F0=9F=94=A7=20Developer=20doc=20for=20ke?= =?UTF-8?q?ycloak=20environment=20and=20testing=20with=20MCP=20Inspector?= =?UTF-8?q?=20(#413)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matthias Wessendorf --- docs/KEYCLOAK_OIDC_SETUP.md | 217 ++++++++++++++++++ docs/README.md | 1 + docs/images/keycloak-login-page.png | Bin 0 -> 512052 bytes .../images/keycloak-mcp-inspector-connect.png | Bin 0 -> 172961 bytes .../images/keycloak-mcp-inspector-results.png | Bin 0 -> 343570 bytes 5 files changed, 218 insertions(+) create mode 100644 docs/KEYCLOAK_OIDC_SETUP.md create mode 100644 docs/images/keycloak-login-page.png create mode 100644 docs/images/keycloak-mcp-inspector-connect.png create mode 100644 docs/images/keycloak-mcp-inspector-results.png diff --git a/docs/KEYCLOAK_OIDC_SETUP.md b/docs/KEYCLOAK_OIDC_SETUP.md new file mode 100644 index 00000000..2d6d53c6 --- /dev/null +++ b/docs/KEYCLOAK_OIDC_SETUP.md @@ -0,0 +1,217 @@ +# Keycloak OIDC Setup for Kubernetes MCP Server + +This guide shows you how to set up a local development environment with Keycloak for OIDC authentication testing. + +## Overview + +The local development environment includes: +- **Kind cluster** with OIDC-enabled API server +- **Keycloak** (deployed in the cluster) for OIDC provider +- **Kubernetes MCP Server** configured for OAuth/OIDC authentication + +## Quick Start + +Set up the complete environment with one command: + +```bash +make local-env-setup +``` + +This will: +1. Install required tools (kind) to `./_output/bin/` +2. Create a Kind cluster with OIDC configuration +3. Deploy Keycloak in the cluster +4. Configure Keycloak realm and clients +5. Build the MCP server binary +6. Generate a configuration file at `_output/config.toml` + +## Running the MCP Server + +After setup completes, run the server: + +```bash +# Start the server +./kubernetes-mcp-server --port 8008 --config _output/config.toml +``` + +Or use the MCP Inspector for testing: + +```bash +npx @modelcontextprotocol/inspector@latest $(pwd)/kubernetes-mcp-server --config _output/config.toml +``` + +## Quick Walkthrough + +### 1. Start MCP Inspector and Connect + +After running the inspector, in the `Authentication`'s **OAuth 2.0 Flow** set the `Client ID` to be `mcp-client` and the `Scope` to `mcp-server`, afterwards click the "Connect" button. + + + MCP Inspector Connect Button + + +### 2. Login to Keycloak + +You'll be redirected to Keycloak. Enter the test credentials: +- Username: `mcp` +- Password: `mcp` + + + Keycloak Login Page + + +### 3. Use MCP Tools + +After authentication, you can use the **Tools** from the Kubernetes-MCP-Server from the MCP Inspector, like below where we run the `pods_list` tool, to list all pods in the current cluster from all namespaces. + + + MCP Inspector Tool Results + + +## Architecture + +### Keycloak Deployment +- Runs as a StatefulSet in the `keycloak` namespace +- Exposed via Ingress with TLS at `https://keycloak.127-0-0-1.sslip.io:8443` +- Uses cert-manager for TLS certificates +- Accessible from both host and cluster pods + +### Kind Cluster with OIDC +- Kubernetes API server configured with OIDC authentication +- Points to Keycloak's `openshift` realm as the OIDC issuer +- Validates bearer tokens against Keycloak's JWKS endpoint +- API server trusts the cert-manager CA certificate + +### Authentication Flow + +``` +User Browser + | + | 1. OAuth login (https://keycloak.127-0-0-1.sslip.io:8443) + v +Keycloak + | + | 2. ID Token (aud: mcp-server) + v +MCP Server + | + | 3. Token Exchange (aud: openshift) + v +Keycloak + | + | 4. Exchanged Access Token + v +MCP Server + | + | 5. Bearer Token in API request + v +Kubernetes API Server + | + | 6. Validate token via OIDC + v +Keycloak JWKS + | + | 7. Token valid, execute tool + v +MCP Server → User +``` + +## Keycloak Configuration + +The setup automatically configures: + +### Realm: `openshift` +- Token lifespan: 30 minutes +- Session idle timeout: 30 minutes + +### Clients + +1. **mcp-client** (public) + - Public client for browser-based OAuth login + - PKCE required for security + - Valid redirect URIs: `*` + +2. **mcp-server** (confidential) + - Confidential client with client secret + - Standard token exchange enabled + - Can exchange tokens with `aud: openshift` + - Default scopes: `openid`, `groups`, `mcp-server` + - Optional scopes: `mcp:openshift` + +3. **openshift** (confidential) + - Target client for token exchange + - Accepts exchanged tokens from `mcp-server` + - Used by Kubernetes API server for OIDC validation + +### Client Scopes +- **mcp-server**: Default scope with audience mapper +- **mcp:openshift**: Optional scope for token exchange with audience mapper +- **groups**: Group membership mapper (included in tokens) + +### Default User +- **Username**: `mcp` +- **Password**: `mcp` +- **Email**: `mcp@example.com` +- **RBAC**: `cluster-admin` (full cluster access) + +## MCP Server Configuration + +The generated `_output/config.toml` includes: + +```toml +require_oauth = true +oauth_audience = "mcp-server" +oauth_scopes = ["openid", "mcp-server"] +validate_token = false # Validation done by K8s API server +authorization_url = "https://keycloak.127-0-0-1.sslip.io:8443/realms/openshift" + +sts_client_id = "mcp-server" +sts_client_secret = "..." # Auto-generated +sts_audience = "openshift" # Triggers token exchange +sts_scopes = ["mcp:openshift"] + +certificate_authority = "_output/cert-manager-ca/ca.crt" # For HTTPS validation +``` + +## Useful Commands + +### Check Keycloak Status + +```bash +make keycloak-status +``` + +Shows: +- Keycloak pod status +- Service endpoints +- Access URL +- Admin credentials + +### View Keycloak Logs + +```bash +make keycloak-logs +``` + +### Access Keycloak Admin Console + +Open your browser to: +``` +https://keycloak.127-0-0-1.sslip.io:8443 +``` + +**Admin credentials:** +- Username: `admin` +- Password: `admin` + +Navigate to the `openshift` realm to view/modify the configuration. + +## Teardown + +Remove the local environment: + +```bash +make local-env-teardown +``` + +This deletes the Kind cluster (Keycloak is removed with it). diff --git a/docs/README.md b/docs/README.md index b6918bff..0eaa634e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,4 +18,5 @@ Choose the guide that matches your needs: ## Additional Documentation +- **[Keycloak OIDC Setup](KEYCLOAK_OIDC_SETUP.md)** - Developer guide for local Keycloak environment and testing with MCP Inspector - **[Main README](../README.md)** - Project overview and general information diff --git a/docs/images/keycloak-login-page.png b/docs/images/keycloak-login-page.png new file mode 100644 index 0000000000000000000000000000000000000000..2e35d4033a37b9498d59158c1b09e3118ae25419 GIT binary patch literal 512052 zcmZ5|2RN2t-}h}MJJ~Xmgfg2>GJdGSAD9kz z6x4Cx%M-^W6#h-+D68$LX7kk1#n9dqWoB(-WqQ-W*xuCC+QHn$aRZ}K94@+vTqJF8 zYUudP#+pI>nUyI@3I4((z@TjG$iTzJ!^gnIBh1Ss46mptF-Xg*TS_dKqfiVe1sN%I z*QC`+H&69n^%9#sLS~iBamlNGm?U=@F#R$8FfVJ4S*H0O_t&b|kLVZ3ezF>RSTLGj z;asCr_55-Do&~!OnJNb^t4Q#*vLG+Ki!olw-<`WRh^E~HytI)D=>fXiPA!L@a z{XO<}$6?aOHcIBdzA#b5{*!*Ujvo7j{`Q{(!J zcRi>xuz4kS9rc^P>^FSacGjP=mj3rcWxNVWtfEO?R3U zVLTNvtN9PQE~oaj4}Lv0cJU-o*2T&$vhE(Y+Z%fH{{>w26N2d#CEIa6j*3f399qJr zLHBa30v}9Vq`RYjVI6%ig>YiU>hAJdm&Wn>(b$Bf#+EeAf88dApF|nA8Cq63@z5l3 z{nQW9-RTkHiitldnrSUc)nh_GNyP#)Q-=4S3QkXb`iigeZ~QLa>zlCruI9R&cQiPu zv}fcsLOiGux=f~zp{_NQIu)3fK{370eAj81Q>*jS7gYSep%sT*z;A*Jo1^>ylsQkZ~e^C44-!SB}YmD~XS)PqZC=)L831P4oXh z#%zz}_eZwlhh}Ds7I?nGs7MrtE}L@DEyMgDFWH^X#&_);2_Mwm+Sd*mPZUzI5+xW| z;ox1XwM6%QMytQ8JMLU5p?tb@6Fc|crFG2O)Z+jB%=L+KeEv<;9%>ZbKtE?j-InB4 zi$dr!hsy5Tg6l1lPjVJX-qq5SjXcsGKYUF%^FSfC?4g=kg{rEmG#|Cndj=e8RZ@>X zuK%uGs5$DTDOGsJoBJ$k((olKD;v~jQSln%fZcZ>nN9iE1L>n$o!{0EGz1UBizN1$ zCD(*M6qO$;*4HSZZKghZ_AGsCYik}$O675(rgotwYdE^>k}AXffA3xfBa{J$7!xg) zSt{$H)N9l2AUuk1-N_;&*fZBqBFUIYK>pSUN@O6@_1SwG`otz*S+~o{qE12AI|X+} zUPwqt!O+lfi6!ph)ABKi9B%e0o^_zqc&oXf-NjU}k0( z78V|VsH>ZdWx(mn!kMV@#B(}+pwHclK*j6r_Q&V87RN^_IyyS{OiX@K1~kw7GG_ia zurk=gZpvunsSia)Mk=8AMaI7CN{(j=N!Jso=t8b_#neB{nZQ(tz1Kf5aGf$zE^IrA z|Ammer84WZurW(rsp-pwtisrB55%^%pO6Id@xuHzbd6q=77lQXiS zRvaj#tieV$Xu&6=8ps+>jA@gq$5i{Uqs)?iWYUs9fa2XhRTWl_EImD48twif<*w$E z{bi8`=PnMi>-v+Idap@3)%t}P45?kSSa?i|s%OAa6(hic_vNJ~P-i2&B(SNV!Zu z`0g;dqo^1$JyE4oSfb5Q;)-LwW|+^>ir!?-gT(VSg&1ZtwP%+c&ALkvs6%f~0A z{`l}-W}js7$Z>^_j0`^nH@vsGwe?;n7Cl6!CBFy$gUM336>Sa`g;=lZfBI~OxJf6a zv`CqJjhcZ4p4u!+>y9&}{?5-ARXH`vb(-nI9NT+88}DV^F)=YoO?_1r^ihm9wqMij zp3Go^I$OMc6d!p3*_xbjam#@9T1C|blQ)Kc1G?7U$>fEeS?sNF$@;k)$*bcr<+QH1 ziSlO@wb{dsBtu#&)_O)thMgstG9*lH=k%=+yTgr^TIisp?PoCsVpgPN3w-Z+Is{hf^@{PAnWEt+XDH+B^ zjVXjV6OaE>F4|VC0)~{CYRe9Fg4jCkQ$uq2S<0z~^}gRw`dhhv`jzANYq*Z@dfQ&g z7d}~4LSN)Ka2&p5V{IK0MJjs|a^|-p{#M^3|B)J#gw(oVpYGRadf2&B&Ln!+`M2_hmVJpP zC*oPdVG+4%@`gz;(JU%z<<|;^Ebhi)O8WXo`Kqg4@zx~83g@U>!?>h7#!DTkg_hr8 z^5-}yCxw;vc*vpxZIbhEL=w19VP5f zoy_Gth*c2o+yMW8m|V@ng!e=&t(_D00zmfQ;2530bVTJ<5tWY-X$?bjODtfvzz{UpYZW?HlE0muvN-orPg!uC1ff3!US7PpohPA(}I&uU=wzHXrP9Gd~s6?k6%_>S@Nl98-a>zwe2hA=m zacO@TWtMOGVSz@%!zi1}ed_Kp7tbPi}fdwO!R z-BUlcuj?|274gMC_55TZ_rr&eoYC+M#?fNIi8c*X6IC|yUS49*?L$S@t6Ntpk6Rpi z#3%+&<2I7&pLTR~pnNNyj~I2ml;PCLC0{sWg=S>UZYxh)1VT4KLJY)FZqL*=I`t$2c*VA1syS>#%C(BxmZBzBf zt5<8GVxJ#GAKRYpju>xD)oV9+%IdqcPlOK?O}kjN{!(ys0SIs-z0P*vty5F(u0| znoEDnMKCW-s{LZE_-SPg03yv~cT3+78jrx7Mwii?Zy}c@fBO|a? zSBmTRs4J}}!zuJV`x**03%+E?MsS&aBmLagmQhOEIXDg>hef-F)uAyPrlOlGoAaTBACq7an-M*LIwYX@0vZU-&c5aDxl;ZZA z>Q#I^8j9)a)l%0jqo2QiF|x2=FZ86gzrOi+b15g){q)Mh!h-SH$>CjP<&Zw}&o8ga zZ_b9$&p|5hSy@IKw+0g+pGeN3?aL?~oK{&$4mtbzc%v~oKc6kc`IQRuLwKh(0d;+K1dN=>Ro?9 zb(_m(N)(@p2Q!v=wm(a8w9FJw{On-fI{GFD2mbkC>iJS-S9kFvXJ`KP#&hrE+Q-&4 zSXDpl@uIE>`9s~PD*j?{HJk~uP|MAQP}QdK)R{xO1eI`0cX=TH_>9M=jHC14$sb!V zpxHNqg41xc#L#uU#%$|gp1t(>$gSKVwv#^zjh|mo4KU)McPAI%s3(Y;#^{l`IFAoA z-j5e9v>h^E7}Rw9Q$M=I;#3os+|Y*c<_ZNx!eYJ)Ojmm1QJqh_lfr&=CS2v^C56bf zs@koZ_7+*MS)hAT>v++uCuKL!%Eci%f5;+t$f9^$K|!JU!&v!C32_PnO*Hyso0g7R z^1G!sC|^?sWl5`3UP_lvy4PH(c~Oo%O|8HHr|&A~eMysC!dZ8*9e1%ickX!RFS4_- z-LbG>>rR!JzA01q;e*VhM^x_;6aAvnc0{HR31M^((XDevLqWyXEP4{=?~l$aAdvat z1Abp$AM34K!5p2TVPVbh_?{c^yj|VbBE<>{3SwnvZ-vl^dHp*0*+8~b&VZ0tq;R63 z9aAt7eU#J6aC${Wg_f00p=Oe(8;MRKIi#`ce562$-Mk#~id^O;z!|Q8I<9j&uWCbl z&i1BD-@SXc>Ep8jF8iN%Ehj3wjCZHXOgnA&CtaiqHIdtaUxKCpchm;4v$h>FP8XtC zAnWZd{_Weh&0h)n=(G+W$;ru$CMvC9_`4w7GSE)vfsciSCFrroK}au-Uq1FCDd`F+ zTyng(wzr?NCe-=;yUg9YI1ML%6rnGrr=_9b^3h5wn(^^*->A(U2vAuM4-tOrAEEUg z`{Svd-@e^bS63JOC<*(2nd5HVp>{2N~Ge@m7Wl zTjlA!0?W#HJh!_9Zs|uf=N1+c)VXZ5eGa?)G9kfCLe7a$S*uWIXud|)>IZEO2f1P~ zn@T%W0dC{Ancmq}LB~av=!r{5eJ8_z=b6FV0FvOHn%^rN;t&zZd!NauYv?w`UW+J& zR>B(Y-`_t8m2PBhOie#GFRun=K;gR_)vLCCFn&1fM3v+TjXbTT{hO*X>vGJILp zADV7A!qJ67w>Qbi$QW`d>}s< z#u!Q6<+(eg1(~;?^5n^j#?v($G54G9 z{n=xda?hUKXp5w|fD*L-87lqjce2Y=*w?RjkxJgu9B^T+6LLD-wDa|B?Mjh@PW-dD z`1qh}imwfz{JG8sQLGMmO9$gOe~V|gJ3rl@Z@xeu*kMvOef`Fbi^}4M%-=dY(NKx} zRxRt3bz7UQM8+Ml*O0LFjau-6ntw34;cZ4#-p8G@rtEX1_QST7!1eG~0_~B*`VXN` zP`&n2dr~E$J=SY3j6885I6Lg|L8mpmjuJim8JAm7fPu37QGE&GIdvdyZSG6Ne{M*k z=N%v3{tpMD@bK`>v*QhyiH7WKY(74|Jt0-(&HDQv9=mHn{nF6YjnpX6oZV`tZ}-Q- z<8t3I$EO$nVKbX|y_v3IB)f4Z;O{Kz-fhW0uYayXlzaC=I1CD*lz5floKoTEue zU=kBmy_B7}nx1c^J#O%prvfFNkzu=Ogu$Ow5Zr7YM4AvxTJQVP9#3aN$I#NIT{aVI znt+e#5l?2#ByJ?b{5Pp{&$T>k#FT=`hs^MK3yPh9HE>Hf6*ojd5(pSI*a z9n3`lWt6iC>pbExopWEx*f>+#H9V^3=5q^+q^X6kFR$*UU8dErm5;h2rL9dFj88SE zc7B}S_2v!b3mO5!tgI};*&C=&=V!;c#l`Z5hUpXh;Smu~tEb!+XhYH;!rPQsPYTP( z$}&nwOwWq9N70r8Sb*PyvE=i?*8Ey$_Jic`aIPTBiznxclkoH@s z>-a93)b}(zy2fGQPQeg5YJ6g1D@5W14dQ%p__tZA*YA&AgM;bsxD;*1x{Rd-W6j?});*JUv{b8=2x8CxMYXfcQBt%C66|xerh!>~HON>?kGjOBHI)7uBt|AhQ9q z&=+)K)TmA9_f!zg%c^zjM@yZ?T$K3cBhL)DKBBSlG2NDzrl*aPDa7v^4<%QlF;T^e zCq<|1is97AtXFOMvg5dT?V^Q3t>}3kQ(e(j=giX5O9fiRZOcOi+$LX%kv@)uxOMd` z_WSJYIb;$`o>C^21(Ic^$q(bfNUFHk_%qjMG{Kd(C%6hLd%LyHtZ$2n(Ppp;e^(nH z8!I<^nV5*DN~){*J^TIpw5+Tz;;U9>?a}mA8)i_z_kLC8_Y~z96ugX!!*Il(PJHtQ zMpW|KyAn@;$vo+ayW#IrkkE4H9-Lb-v02Iv_EFzITSKP6bNo+tdCDnSqPu?ywXirZ4eSMu3K94b5+h-G_% zM*L^a3Rje9rYwyo9uQ+5@4;jQ&utw-ydH)}ackzKJYDn8jyPE4h@U@AemD*&X@7F% z*7MrcNZ7zwu7MJ1xYVBoScjk8SFQtO%E3;YcaS zW($fN*ZEC87N7nzO8Y6jHqBLMJ2@IrHXi@@4CN~wLdYmC-e4>^owN1%a^vUW!U=RQ zy=rCW-#w@bfqA_BcUIMXuxJ=I^}Cu?v+@C^$9qESF&mX2>sKSt*4bb9;JZxd2?Fd>`bLa~9<0HbE-eLARb@ ze{J=en$iq*-w5v#gs&us$K47BzDW`(_9rKP2B1Z?P__IuGJ!j?c}PaBTyj_60{ z=CXPouT|}tdhD;uLMH$K|Et8x(UJG;pj~3_ccRVSEY(33jLh?4*HdhBU%=E8)Mt)= zSE0Uq#HfV&_EY@V(DMOFd${5@(Pi-S@)|&7d*j_mUgB&v^;x>`mpIvdqN787?_L9j z6mf~w1pmozfu78a40IZP%g=b6CxpNWAd_ySz!c?I>s|h6N4Hc-0+}l%w-xsjQUix-gJfcuhSZ^riv zy0ju5vw}YZj`_1^Tkfgy(R5$NE1s9y&fXC{KiM9I<$d?=UE07>OhQ5kvT)-Soom|_ zfb0=g;Gi$sJ1|+qB?G2KAg3rLyhWqv#KHm3NQg;$EkL%vk?;|>hGarUYIq1qSy`+p zz!WDVKBp9PUOTFuF#d5-M}|g6x5KEZ3KPWslt+(M-Qy~2Svxy!miupfk&$9^wiWn% znCfASY_HpBh&VcO4>`Uf=1wXw?Rgskk#X0RjQ}{`RZxJIeDyzrn=`ZnIXykP_1cwN z&>_bcn7SYn@85qhU?F9LV3{};rIyReqK3ti-QT{o#4t*44nMZp%~jH`cE5?83B_Wz zOJMplpr47tJ2hoxB+6pDq%hhod=&nn4{Mf1k6$r)h>r2MF;c(z{`5A?`nEfZ+$X0S z=aN=dgjdoe>NbU5ERm6!pUb*^u>AFwIa6HkQ!<966Y{y5)&s3Bp}Q7*)HKwq z@g5EC5TJH{ek$C5$9oY(5poZ{9KWS{mnrlW!G!bVUkK*EcT_%?NbFh4;#(F+>t5!=6F zUGs|^>Kn$=(voDmX!%CN@hyJK(O#?L;*J!gVg} zx-F8*5)$+v(5R9^55t9l{9d$l6)ZIkNDtZ{%`eGCU3oS=vQpd_6-gto_kZ`K0B~E) z6Rk&&#%^lw3aFCSrxMgfPa`4dYfSee(FJI0X#&a_^9v`*o$_oXzQWAjnPf+5`U4op?zH0uv&q*%nVtQnZi zCceYAHOs0mnZM#*@$WEDUtnQjG1{8@qFrU3+*(o!IQZ+gZ+D!W_*&I6yzdukwp#G+ zEjGYL0ZqBSd-l&gp?~Ex)X{$*FSh;$Bo)nqT^k$s(dY&sbo-{Jyxks1N>yJ8|Aw3P zgf2r14F-ME0;+Hh5zLxq%5gzHJ~Bw~H==Gl{8r~sh z6mc4E|C8MOsG7uoJ{0?%?jXP18T=ut@>cN(97 zz&vyZLT3SUViL^e`7K;p>7{qypH{W&`|K$cKEyK7|2x__ep!j*y6Kvga*GllyO^E# zw9AAiy}o{O-nsch%r&O6tvziMnr{Ncf{y?4?2f0mE~=Aab#-;=X=ohA4Oz%KIFxH? zsIh4<$cwWu*>@)xzhqWW+21Nv%+%CRnScBDk<3vmE@*7RE#KSP+I-h~5_&abnKjc$ zR2yejL^B~X3;GV~@2G=L#!F2{^LM$;RTitv>Of_&c>dgwm-^UI0$2~|OL{~!ANtIX zK1#^QOf7e8-GyJ|qi)m)&lkS)=w<8PUy`?vp|7|-Bi@usNlB^KoVRyn*PE8Q-=ZQT zD+`Myt|R9-C@}D#kVr2dgc-YgLtf_ep0>7jbd=d* zuAD}}kc!I_btZOP(nrDl+#lT*)-X|b+U>OFRO;TQ--$$_5~zrOxSKv##!RR4FBbhz^uXd;+ueR*kKqGSmJJs>R$c39_Yf|`t0E(tJb4f zqLO^VLeJ=_sl$6?h=E(w^dY&ldkJ#>VICsAt`_|xz&aW8CmQm+?HwOq=2gRgm6jb3 z56vP>Eu4Jo&E#JQ0H2e=pawe(>L4DTw3@?h=&=3|-%@`>@xh~T_w^CWa%=1U_b+&} zGKpRR{lsEr*?>P0qjn?hJ&3gA(niKqDSyKl?&zhOMa7YQHY*O=q)pT1`wFpK%>4Yl zb^qn{$Up;a4z@!$?HJ$Hm%3=A{LepS1vc>$2{kotvL;Q*DVAslJ^=y2&aJ=620u4P z!7z?D+Mtd$p>{zb7QJ4PsmkP0@R%j?4b{q&3W`y924>y!f1(wI+n3?R7ZFL2Wobw0rd0ElO0jI zblR8H{o{kec$2{jGVosN#KeSOll8fgY@6+>|?mz$pb_MGzr2&%?Jqjw5Y*6R$K^6 z11?Jc;R}Dh8TN=U%KgYbb68_cb+$GSFDrxZdiMvVE7@;T{u30cK8E~>eWRnJ3l7l$#oF5B zf^z}_*LFX4l`49LT;e0p0VNsgo}L%#qCcuahX4wQDk;>t&Y26B9=&GlEb}sQd=UEJ zf48#vX{k0WN3YrT<0Jd1By2)i)!zPouvkE8pkH0b*TGkL)@uP${XsL5CJ;)Ia+gcI zRW~1TkbkVG*ve=B-)F;Rd-S@mzkhCj*3+tWk4KdD6Rddp!#o)7{U_t4!lV!S)2`y6 zBa2d@qiYRn1n~1285xu*dVUZi@IH`#eb=>||DqMQPD}pGnwqgjqp}L1 z+qAznHKk>%WSkGTWR_G`VPV&=H|v$()Kq^?q}1XO5t&{4TeO5A_YD?anLd(x{P=P4 z_~P2>GXtK)E|6TC*W1!sctojrA*AuK0|uP@8Ms8RMZ*%nRy55L{=Yj9#X;E)X=tIL z?kOr_lu1C`BtIC*bQn7$?0edi5?N5(KusfH{n-!WLQGQ9i<1*i2;I3JpR?QSy2o!T zEBWD{*q)7u-dkKu@90p<1NYo&&U;6dG-u_Gw6u^VryDa4I&1?q&^`Zz>GB15djd=@ zNsa#)NuZnT9yXkP5Qp*l%hhO6bX8KIc*>G@uCZ}En1#OhgIk(8o80HNjJ$f<0`X~7L7ZW*kBTi{+2y&d4!_n>*VRDf zwYYQW0_ME$*qvi{zkk0Ema!x;k6}YMdwU*`0YSi+P1}uRrGyTJ{1buHV^fmOS#(;k zQ8A!JUzLxlKAA8rQOi?D6lmw>SO%QCN=mYT+@okke%qV8?0({9t?GiY<io z>6uFW(gvWAik23|sk`^8Ag)m0DzXL)7iKSX3Ae>Bt+S^ z2HLrr?KfCY;73N?alUhsAv65ds+f84O@%cis9z(bwBMvu(U>_Sj*e`-{_v z?gf-KP}i{LUe>Kz4!^%kDJWb_@!ER?Xby$)-cFom0DM6ty6LNbrtrtbV^MHS%w_$F ziJ93|cwYFdR#^X1vmUyt{W#`}5+}1n=>R>Io(&sqhS0rL$*xY- zPgsepi&quNjOhD_J$(3Z^LYAvYU#+}w0k$Wd_SC>NaBPG@uwhv8l2L+@ZS=D3rOns z_qPGS(~|*Kv#wGCrM4vq58qFVE(9D-xBa`TRgts)=a$VE$?z91J`X5MM1lf6mzR@bUuX63A<25f zvGoVtI&xsi2(;;sMa+t;I8!sd7(rn~n0QDlsGhz+PkNlBW)@=|Z(!+*Z-1IywXyZt z2=K+iBNy~MZ=Eur*29v04dtxo9w@=HdfXRRYLExNF`&P_@|94L+kt(SwXe<opE>3u+sQ~@*z4j=4~HbvrBz-0I!XSXwIx>>4ZqKTDou_A!7w2}B(h3TAl*5fK`0Zf>dNyyxIzq7 zX`E~?teOg6NcEwkqN4iR-R=A)4%yTm@na5PuR?&=O8@wg6pG_#HD$3N5XaptALk4( z!W-GyP0?c@=>~~Me*5WnNjEnkga$$yS>y5K#ww#++5~XItv%j*w?Al?qQiU1Iy*Zz z6}^jyjD){cwl1^*?_qm(;?{U7HoLJmH$T5MY8-7i_c;vFxScnqBB6$!Y=%gD9?Vml zoZ1H!v=vmXmoHx$o=cC!Lzzc>4{+l^32s!LLHBCAS@pvoRFTymHJ3qkWMyM($&41q z1>9wIT+|*fNiFUvNOD<>zWDgt8*XFUxz86 z^j`uD1)Sk!iSxrAB%KkyEL!5Mx^w3g-=}M) zZ1?o+Od;%mSi`y7h@D?uEhq~9jyJr|Vh48pTvG*)>%*d=J~G~Sf=Z@12|>6x>s_V@?IBgSx3i0Ecx)Bfs) z8gMNbl|K3ysy=kkiRQt8mYn3q-0@h$q5d~4W-a4nTdaZH- zHuMBdG2s@$w==6*4+hZowe;MuRz`~3CC;{pqAApgcmwj$E5P~%jUZV}l-9g;9t;?U z(+yr=fRmPM`!#E9MaI?q`sGVMP%bk-%=0;2(XZPrfQL>LuxSJ7ywvm1KJ`px?}C=} zrvnoIn^Y3yF3P*>sV#%KcTe`}@V4~ny~4*eH`?Q;T^dN-ZgdRVJi(vukYc{l{}f0*-zd@KWI@)(a;?RrVs2tPicEW{w2zX6JUBd9 z?uoetHtL14g1=5|NmT|3qXl2TaYmm7&#ZdkJlU&;ou11TI%^}0oR*Fjd~Mmx#&;7W zDuWkyDHCJyby4j(8*E_JfWigdBu8z!)ysncUt+H*Zk}%V(5L#G3GJ;tYb@M57hmkWx8tz_JXs58;ZB}%I({? zRYCmhN)(uf0l3`DO;z>M`lsy&q-11*CnqEOwf8d`NmK=8WqPhwe8RzLA+rs#WueEC%gc8{BtI#;Z+oagMgfcj zOUvL{;~8j-|FKfMdtS7)WW(?$C1;=DFhr-YO+!mJsPJ*soX^>U4+`b-!|uY$>_14aS^8T~NA{oDfD@yn8JG!HdK*|7qPH|tp0oEKw z9v%|J2g2>NJc#BZMWH)}P~RZJayA^GXRywVKQI0QHn9>Ak9=3BM;a4P4$l>&q|kt6 z0hpVld15p5i3<9uoS`A3_|amf-Wog9^+gKpgMkz=4=%eo8N~laOCf>`AO|b@=gR}R zcJ%-^ll6M1C_snC;Eyy;BY1~$N&~;ZDW8lku;`?+&9Is4AMr)NOrRI(40ZVhB{x&r zse_YjP!*bm=A9&ISDm537Zm)KZGYa5Fyv+G>~Mmn$y_RUwc$*`^!pADJTDO|dA(2->H;suS7J_|dpxR7>KmGgRVx|*x&%KM9N zB!uz$($9-(3oR_#Qu%G#Qyij<-%X2h6K%JfF!HZXYxxP#L>>$+&}>=#`c=ZqD0-#w z3D*U_LaT$R^Rvm>GsU*(FCSFj9&~m;{4nL=W}YDblc5hztk(eznms+1QURieiaa-QZ(4x&2C29B@=X1zP>FlQJ z{kt^b1tB?TDC?A@XqEsp8|#TM7HN_78*EKW?}S$c-EgO=QX@L9_S@xWllx3p&`?k( z)~-oNh!NbDkG|t6BUni*5ynXvpV=`pX*-Z97kS6c&8;dwcXibgCP-INDKT(J0=zH; zkig2xc|n*B3oHYOSpwW{^E1r>2QvXaL=K+d&&jN;9A4TKnm(S|M<_6^@4vy8hL{UR zN41j#?a)&_|AcAHFSmmM^Zk3wO!;UzE32E8mSb}}pQY5*5hp^-+qV}hbRs9~T;|Jr z#HF7*}|Xgh-f z=r_480c;~O9fBvRp3a^Vh&33%n*n02`dt?;x;G78NW}9zqUS-Nq%=8g<~61Xw}WUT z0T+mlct1abt_i8<2hIOgLV__o{$!CsbW~IuI4==<02CGjz&dmxw-N!bA|nM1&FCmZ z9K4~HJ!9i@O!I&Jr2wdn;7V{i)VH&r)K;3sy?q-7E+RxJ7lb)L{B%bxNpRLpd==^& zSaF3<3W4lU)zgcDLM0u`oFn~X+HE1(+{)?;Vu*u+IBLcB6D%t+F)_Z^Q(V#6wTf0N z^;AhcLE;7nM`r6EaNW&;s^;jfsxdPE4O%Utlp=QEKR-L5wY7qIajh{2Ch9L=zI^TK z3gYNQ1|cwLOs~1z24Np;Os!xo?ECTK1#E^f*udxt#3*j+1cECS@md`1ECFTG0#?Qn zP~QN48^BNxGG{Xko?sLRw$`9=ucw1|kp^$+E#2BcWJ|$@^nkwVURK%pWz)-abWtFA zFy6j>8Jy_|-q>8LnijTHgKz=v`NQKX!o$PE>Z9cXLoUkAAuat`n8zmfeswu$!WF>V zAXmW6zX9(g=$9p4$Ic+xeM=WpC0#3y_$w0LSM)u%f=+)rfm#m41b*^^5X3_Q zb~o7N7p=A{>+92xfT}~RtPoXj$OV}+kY}&0H>-DxuAW~m7rk~33z>0&O<-eVD*^u> zNJ7G1MTA!beV-TAV1t_gv0NSPt|V)6$wFWae|SX6ZG;KOuWVOGxkDJw!6wKJAQBl& zvB65NcepoUvjv8MI?rOqr!*ugIRl0Z-N`_b>>kxD=5IfDI@);#2D`qc`9C}Fj{a1W z!!Cneb9K6r9@0np=~I>{dWk4#VhzZRxN=LQTrkfSVb zZ(nxm(7pGF=UG4VF)0chMe@LD00~7qb-8=$I_OewfHUat?*|p6)aTsWa_ZAqI(3UP zzCNPpnRx-e1Ziy(ZdJNq$o-^VN|gk2h>wwspK%EVChG3Z;lM2T(5nlmd$zj=n7| zBv-XF*-X1+ux8W`s7v`XNWM13@t^sq>(aPgRDI;NJ5uTaj`6P@9cVv1z3a-%`ypN)Mc1`nRa?KPb1F z-L9+PQ=fAm->B)Fj0~ob1=nVOEC3B(p!{B`T#GQSYXr^f^XJcCouLN1G}MwZA6hYY zUKkC)Fo-5ajHI8@)F-#Qii&U^69XueHT{*96;&;*g%LwXFR|Z;<;x}DJ6^0lXnRyw ziE{c;Z3m2_?BmB9;g7K6PLKEJiDh0n7db@p)M*5~UN2#t3ePPJc&w5^?r?y0@r zS{~MQb^XnEAl1z5>F_&AT4hY*o;ovKqR3@7)z4b(%IQ0nSi$Xb#?HfcIRFiS8Ti9g z6@6WgMw3y+Ty1HlVdb|2*K@|NwmvbQj_dd@LL-|i)yztEe?9|%Zn(bql9ft?8r@f` z^2wRBnbZS*>$h)0_{*uD6JBWxd#6K+uiOgb&DLVYZQJwd^RRcXJAEv>Xp(~_Sd=6a zJ6=>==(Ia%xpw~neX#$fXWBwO!wO=Jfgq)u# zuZnqeH*AFb{@&CTx2-zR?|;OPLD%3+zFw1!l5pKVw{4hrYdl`B;|Ax{$=QCRi{RTU z&?q2Db1pYVQ{BdfgmXG>26A%5=0~3*9jAV?oNnQ39vO zRe=4!CyP*AzW!j9Jbc&bF=zcw>_~JobPS<`t!N*_ijT}4;IBq(#hWm2kJBwaefrc0 zOmxr{5r+)e@en@&k@x|Mv+<;?vl0&%Hzq#b)W^EKtgOVOg9I{<<4H9!IQH(q)P)p5 zsE-JLWIHiZw3ePJeAsZj9so2u^58&-B2M#oEj_#H>ZY-cjV#3kWcY)6DtLkzhtrXv zZ($y33g8+4&taRXp|BdDDG=8VA~XPUPO^3Y1|;V?){j$yf!%5zpo%@JlVFH~Mspk= z3p|TF8Lr*RKHA z83X6cK+i}6&i&%Wi*NX?sQ=@YE)c8t>SlS66bcJ~Pb-X2>xf?lSx30bS(nD+js;uQMj)J$( zo4Vg$XVB3K>lfymF$fasdc4|>Z%-AdT?fd84*t!Oj~@ezi#gqQmt|j8p`oEI_GjUF zpP#ynj*quYPSS!6W5&Mb{g53*RvxjPvvaUENA{=8d^g2J;cy5zI7U+IyoT#1DcLbZ zLqQRemc|e&!vr%+1`w_$$*`O)gM*~&>+3VNf-~EPwk|F?f!{3FkMqYHpbJ6dE>2W& zW@KiTIY@WHXF{3d)A`^z?zj z!A7vc_wHrEk-yg7USeoOwzjsC;G#A#H1yN=J`7q-1;859VM57e;QLVjQJr()w}v}h zz}UXHB1G~qkM!ELYrvkF+YyR>sdPWuxzWIpj1o$=&zYBcVn&4Gdfc&l_NjbkT&g8t!63jd_iP zvWc;=NAo9zU{1gRPjc)ZEoEgQSmq!IX&I(V;0EHhoss0>QC4JJZh}QlNl5{FSa4O9 zz>mqvmoORvc{mSIR#IB(*Vrgg>$D>GsBYkGW7yH$_8|@~F6f;YpN+UZJw1Di^(6pv zM7_4lGn)MPzFwm%)A2W%@LJcb?acZ*p?IqD*<(Agcqk}93L>%61Vs!vsVBt}r|h9*KP0DBe~5zS{G@-FIb5HUmt^FoQeoAnSp z_g8_#g2Gl(UM_8IeJfAWihZm|@n~-?q9g8lK^LFcW3(>mi`O45@_>)O2)oT)e!9S_}Z8=;xD5 zw6u}%?5$8WAgSP>Cs$HZQd)7bvK;C0OaPIJs;VE%g7`H2_(0Bspz#G{A<1FC=x8FO zaw2DZ;B`a>W)R>7AkYF7oNAf&dnp|b7Ak0j^CMCdWvj~}-V4Ut*$C)$q=rw|`% z^SPzc!Wpz+Hhcfd5$4==l~>lWR^+)C+MTmKKhV+z5Je|{Ue%AvkQ+?X2-SDmtiF=; z>6}=V&U&+ABXmj1Z`57E$yF9bN#Bccg`cKc z1n8G~?&-0rWRa|X$`=~07MCZ|< z9?z5Z)Vn@=ErPS(TRFZH63D$sxFC1nI@`|9nIh0OO@S&oD~_l>T9ow>ON|eplBy>j z>mq2@6`r@d*GZP^lWk@=O^{#phX2<9w$nFEr8hKG=`svWX}gJNsN|bBOB~vm?-Q&C zy#H`z^+9cT#M?1JVgcF{SPqYK*)Tp$ zT~N77nqE*yFdRkaM`nZH{P?ktZ*J+OTbi#ar>_&&-O~T!LZSEbS_v0_%hX#n?%cZT z&br=xJ;F}Qn8wD&j*Gp1fACvc_Rda^U-cY%ibHUGeR3fmhW2amIUq@5|D+3xAisIX z)btuTr`~-wjqA`w0Y=RN)~)tB=7$j|0I(Jl8yjxp*%7;24;a6{n(!d#EoMQ3ipu~6 z0TTkDdxx2%xw#n)gwN&)o5o1ESkW})0tN;KG7g~op^yWzApRo7*KWQI5rN?F3Ulhp z{5%GKqLO>q9q*WGm>1z-)Pp647uDy;2~IP!5Ey)7H}crwZ0O9=QW$(retqy}{wKF> z7OM?4R#%JMGA20ecInr|~rewu;&COX6Lq4bzNF##@@H05ox#754_*|oqsF>Im@HsQ!fc z22D{>5t*>Sr%~;5F7DP;XmLUZ*7Jsj2E^VDgDE3PSZ|$+4X_yn>T+-p$N(m)YKH|v z7+$^xm^qzHpP$lOSXg{*E&mNY(>EvxYjt&%7|Ib$K3N1|EHy^v=AkgOsFMGH+Td4F z!H4kAi30jEdbUnZ|DmGb$Z|}2+^70_q^?5)OW6^R0$M2W;^mozg^>?crHP3&Ku`pH z`Xow8O>G2Q2+|Y@q7+ZMOhaL8o}Q*Jf7;cOCKd42_6vvy*n(FGLEu#0%S2`?myIbw z=T*utN28sc5g8>gh=BeUBFWGM$Ekp~$GMdd0o3BBtu`9qv@%)op`|jA;v&x648Z!? z+1VhyVI#Bq1tPkbd%Cqw*lxM+!7jqnt^?CnspS|CjA*Y!87>kK1l84vG#t#~J?wV% z@IY!VRJT{Qsb^xV&d^q3@r+Qxv)hBvn@d-Wo0e-8Uz9V@LqyW%cNkJF|@IXhIx|#-e2)GM>DiiTc~RNZ=~$J z)3&`zwejJJkRA)lxXA=Y6Z|5gEhGLM&i{2=4;nxL2H%M)un|zLp=1Kw!1|Tvp;mRo!#9I7aeH7H7;S`%>gVD z1ZVH=ozn~2GC&CC5B?v%z5|>K_TO8RN=B4QD3Vo4DJ9t;2r%bI$c%?>Xl^uKxe4r{uTp`+I-CpLJ_txix(=&dkgNb-Ul9 zqsZrs(@YHfmHGMkk3m8E`THMlmQsVhC9JKgt!+2qSOA^eUS6=3+rO>NJZ;2@oH|&_ zToq?8T%h~?`!`ft5;DVeepQAAdMB4>rC*Q-~@csLr* z{=DFsbX)3IPtSdv>{17Jlzsk8MeLYZWn*yG^JPqazj*>ZCq33We2TzKf+uD(_1^w| zH`@`<9Y0tlW8UM47Lu5wf8#`}ZE2|h7z)bh2*eM;w(UDyOaM2a6Wtf430WcGM?k=y z>fE|p@WH`J9L&&xP>4nc!W+9D$XEp`Sfsg-;&LC>?M@Nm4D)#spXi!CbB zJ=fQe%2#OAH*OCo?|z$=n9D+F&I%EoV?`71cfDC_MFl0nud$*D;&mbON3JPDs>wd; zTq1!2DYRCniru`g2i*4+EJ`AY!KZ}AG`3#+(YME1d55(;GmU1UauNL2*+$30vem%A zV2`Nt&VOSC?&A~&2pX2Tm5OSw|ExLyKDl7ZAU;mOy6b$T*P=%t+8*?ibHLO9LQw!_ zB(N{(+F(URL6UG_?8!p>z9)wQI^8FqHFd@EhOy>bzO( zmLpCxH|w=Ef-%{*7rQXypHtD$ctu6Af=YZbadXlrKt=C!Rd}wNwRKc{JcqE&fIDo? z0IT?eY->JYvy)5_JDv%>pVLgci=uRuG&UJslRS_+C2F?qM>+_KbVBZ#XUB z$0ID$7z`bF8$GhK1<=y~k&~9;Q5ETgV}1)HMwg9@HaIys5f|vjjT;GG1xZGpriGBK zlOz4pCr?rZ|Bhl@LHG4y{4JX3o|2G9k90D`!5Mo-L@*Jy00ZwYBrWWOy-IYxhXI3j zP&?(u_3M8@9U!TsM1zG&iOP+26pQL+4@>|J?_~=MrQL6yowD}wOCWsm!HYTfL%DE> z*~4v3rbHZBYtXL{b;7T1+n+qa>PZS;8a0Bph-b&QNhg&c~hwgA5@$m3i6GLYw zwOvulB~NIm)V)?CCav^CJGDmu&B=%-TU`QJEB(N=K(jz~rwys`u_|jl!8}I$}W!;&P(<+%6=}fLYM?r6899rWurr_J< z{9u#zfsoi+oy7!8LOt(~c*+3JAJ^Hg zj6NPqJ+yEQ1s@;Z<0nse^pqRdW#3j$Jh?wT7G0V{#`EXT>uu4{(^0J#ey62i@{~!Q zH|#19vrscsn0;aLJVx#L=8-a^YKn+0@SA#*0dp)`d-q?zE@PRSCW^Bhy+*Zv+a{6* z;p@vZF^gH9kRC7b+Q>8j2OdSEUneRt;VedrCTTicX7wf}K@dLjru9AlnZCSG~(zNY7m#IY}QN>={2hno+9(oikk#=L8n z1bgs>=5yXOEUFWty_53;-?KS5Wevo|DNrZi-MVgdh;zq7via(t7N=U>1d)@FtN}NY z-AJ3~R^}6=Fn%v_E77#wIB&0VkbyoejH*?xh~uf>IBFg$CwG&fy!>X4(tN}l!EHM=HK<0$G*Z2yqod_-IhmKR(a}+$ zn8>45zy$9FPhCz)enOz&*`~)3D0r*$a?;N2rDZNbpO9`)!itI&Dxd*;0R=KhJ_@FB zLdn^Kxvh(rrEM>AU?BKUZR|SH)g|kyE|aTQ*{>QJ(zCKEow{FQH}bTq7?q7FZ*$|C zbsNPG?l8o(c0&wj4)C^Q8C?{!Nw%$~=GyTCe zP^<-Yfzf&0caGa(XWn5{O@I`9282KE(UCn6LfqIyYf<<5mH{DQ0D=U`C-&?Updcg+ zK2-ux9g@=3SnfdeukNfZEEGmyzy=Eo3rj02)y-vRNb80c z3PdpZL{vY&`4YjeHuJ6RvrM5C3&qOPq_UO1g^9^~@HTV+Ljc~W@_gUnl4AN;k`$_%^VVXr_is%U6j?>6TO1y*nv72@D5H<1gDI3*J+burbPpc%b= zeODoK4jrXhzqbCB26Nk0H@ZE0&KI*~2l69yX13EUE41cKMn8uh+7S=52aF`eWN`Zu z`WSA9HS&q7(2!sl2(Eh%+@3)7dBI;+4iQBqc2!1kVCJ*;=M@zRWtfmqWGP;7XOtC( zlNSKq32F(=eOT|lRh@YyNzPh}*(R*apX`9+6(y+YrMu`U9I$+R7Ry8LsV>WCcV zDIa$lv>*FZeljNv+O3=E;}vfkG&;OLpO)|ObAv`yvUtiwd%lW)GE`nwnL->CKzeTwGjBI1}W1%?@9S_V%W{cJxWrh6=PuN5C`e z+_^J|@|vTgBLSKTRE=K7W~6O_FH}X_KgNwjeA(< z2z5$rc1Lj9#(NvLst&rKYuA<^*nSt2=>^Fff@gHS?sidUU!Izs)q1js&a~ts6e+0< zPI>pSb;!X+octC)3qAe(s1t(@qtVuWW4X`n&vy^#HTELD4~PFD8lN~`w{K*z0(3@| zY&-g~x_T=lIP&u|QDSZZsBS6;2Q~aV={aP65mXU9#qIeCBN4ZiXVlO8M@AG|ix%eQ zVz9dr?IZV!p8f0BuO#FKM_zE`cHyP@{%C@pV6?AAktgz4aNRLIB`Ta+jGfu0%Xmz5 ze<1||o6esrIP4I z@>9O8U+eK`ToRI-Q+8|iLUMvPxfD`ZKr&mg7`joddk$O(PMUc1=5?AJXNJVjqO-{X zjAB~%E4pJr_VbpNGR$MX)obFtkT+kPl&>{K(U>2YBH8rV8rvVv`_MN1y>u}#G49l@ z=fKv)Pf<(O_5Jzd2jS+UK@lL|U8AGQ1sLG?c-nR9wJ;}-^;9x&8nvP>hE>f%=~QSc&dbaUI65PI=Pz}zT!LUUgA*IOYEa>f`s=@Hp2Dp# z{B!|JVq#)CdGaI<^$qB_%paXdPE|R4dvB8sn|u#zk?V4yj#Nt0qJ{Z24!r`JoO@1#gomzSYXxo@FY$MXs}$<$!ltfJz+HvJ|?z1wG!2Zbd&3JVIl&@bg0 z1)o^XRE`!UhHj8JWXOl?ft-_QX$jg4JPHD3cbp&3{T%Mj1pPeZQPKSYeQ)pga|#M- zsxf~%-Z7*7Y#!RB*kTWpk9_yD@BMUUVuh1J9%*^Ly?3rxN*>uu4$ zNGK}$EB%Q4TO?UqKhxaMP=!Z|?!kq0lWdgVV>RFt)} zFyYMCO5LeJ-Rsi-jfHd@yIBHtq!ieD@9HZ9nBjcB0h1`jojZ33J#_WW^XO>hbJ@M4qk#yUQM>k3``{t#-Y!jD+=^*`7u0|S!$5Je0eY0bDoV`f|4^pL5XMSztc$+ zH#c<@Hcv$g6VU!bo58g{(s9=J(jux$bUZc}XhzB19qH@yf^tGjnDPdo(nFkS!NK&q z0#1sFl_=h)(|cekf)<%qNHT4p=>^lCJ$tyr?aY1R!rrhS(&+i=IKfD;>YZ)|=SNuF zafoO;|6y#7F1er8u`>?dI7(Gj`rkv;T6_xBlQZ|DibRGAEcIU8J`g8jFx|8__4V=7A*|%ao z{QyQO@oqhEJdt)&d7versk{FE4Zxa{m*WRG3%0}F!GXsmD>pYZXHLX*>12oVq$2to z5;{k6+5lxah?Tw#&ZK>Np zFgT7r{pg-4KoNWCs4(JmT%T-6lhkly^pUP@Xt?L&vuWg+3`VXSw4(5W3LY*#wTA{G z0ZWv#?YdFggNDB>fBw50jiAI1J+MZ&xFV_|Hc!wE=<=*BRkLBm$U! zg3_J!NIRD)==nVX9@r2UAMpbr`*6hIe1p7hCk8ODt;`!wMEZm37jc@Qc3Yli@}jP* zF3Jc1N=F#52e3*2s{7Yw=8f4%t#pU&Xa@`0o=~Zcgf0eiY`!Lk6-(%@R@eZZDEj0e*Oo?$Iv8)he|A|@u?rY*-;^Y-R!2N z{-;{sa#S{7c)+Xfe~L9Q*-lxq$3ji|r^sf{d!np?l&E5T*&02V6XiG0hEHv`?G3NI zCT}ESGZ6GhHZU&Rvv5{5fzw4Y!q6R??EQOjqvw4+JwYRxsb>b`(xQQLuOox@zh0}=ZL8r z!|K}T8d*rfi9rOQ_=Jnkz4D}#qrN7akjwZO=DNZ=qXAH_L;OIAw}tnN-=9lx0 zo`nyN+hlSy&-+dU1RShS>U@XR<=RfYV7c|^TKw3=Sn(Mj5fb8Raef-rxvfpg?)7@b ziPssSj5aZ+-564aXD@v3*Dtg=*Bcdh@^Oo;Q^ON2DxJ0tKtiOqg|6c+4mfrQ?{G|f zLcA|gNM0HH^@01~!H;;`415IAwiLvpJ=c0oCKUl~*o&Yiv|L?rfhrtz$$!SWR%UVC z;fNO}wg&v3Gs&8li@?Z10#XR*)HO1))y>U~)9@DN%bm>39ET41Qw#jm%so(eRY5>d z@FV04buU^Ts@nmoCX58nsg&xkUrD{4Xy=Ew3N4QMu6-$qDUb5D`P^6My`-bFcH+VH zr$>$-*BD%*yi>64`}d$K8I4(0_0VmCXM_$=TjZ+PGnleQI+|_M|AsxBJDTV_^XD|W z6>(HbNP2lvS@bDarZ=m9*2~=Y+RhK@JQxG?O*wB^(^aVfIfpx;NWGV*$EOB)b%dk= z26IE?t9QF_75E^4bR&I(FO>E^D8K6K87j=Gr5=}}BkriOaJtQ~srJK|wLSexUOW$* znYZk{hL2X=I<8-&wmsn7=IkV}%gpPn;Z=UIcK$e>SgkFdDGU#uxt>ch!`9-jhOs|8o6ea573y^9rH#Zd}%^r8~=4ceeEe=MDxzYGY*T?O7 z3RMKint{gDkVl@$85xK0D3$A?pl*gXq-c5ON2n!78qFqv1tfBFZgKISz(`DmpO>;S zaC90X$_*k3qLDf7n2(iW^3~<(^XC~oo{3cSlGQ(cM8>t>U837=WyIC9MNV-t?eJ)m zDk5o=y&@;3ramGD+nt100;BIKJprXww9Cw{y|-4uWnF=a6G^;Wi-+~VOD3q7;FZ() zNauzn2hFOKTfX##OWq{+qphVSq#kNdCLh?df$b(p9ZuDr-;2;*Unq11C{74IA|D6X zFt@O9x2)^eL;m6mh52r)D+8S+Vq`u=VI@5ZP6st%`w00&`-z@SYuBx-ta%#3r?`*i zHc{Egze!C3Ic*5^mN4RI%qpcAj~#n;LMuj+HEsZ*QtNsmV=%26Lh%IG+=1;?5Q=F` z^kslR@h8s2^F=weM*mYD;xTqo7Dh&i z2}>7j%|V>4@f78PN@Gu>;u%q!vuNh3a>nMJIXOcQ^gd0>k8@&qE?TDpW^wP1hhY;{ zsl3K!G}?{RAw;$s8%v0<*sni8a{?olhmGT#b&}Le9VKNZ9n%9 zC3)-PDVdmc=`964$a9lL!rC@5Ia&7YS2*G9vCJA*&Tw$toATuxY5~|0FMB^}_@=q}nhjFY zg*gu_N{$^r?&4*t zxSXvGhdx)$+NtTVn&YVVdPN?0$pGRFasfe3Vd3(jYoL4-HqPJD)Xcz}7HpmQa{@o5;lw@UO zWKI8TM}3Q$;)d_vQ*2o03Tsz5UizQB{d+y>wH`lyycbD^XDLUr)+&Z2`<{DkutPd( z-Dm6S6dj6f2)DWXPv9Y*PyElKxkk^yM6+`Biw1#bE0lg;oE9tfy%4p{M(~(tUZq&+ zOa-6N*pKJUo+Cwzxm3o-az6Br*M6y#7#wYx(7zdUV0t26`=zw~n-Aa_)&^4vv6X0> zJmlmbditgLp8m15Ij_!ArZdyu%PU*0WLEo8>%Qe8|3aktg5>;@i>kg?CAJDboe+pQ z$66EH5%pcvmCuj0XfP6tS<+l^23YRXvr`Pm)cEDWPIWujfNVxUG3pLp)V?)PxC*Kru z)(HK0rzYzxe3gotktS_LzD8Q&Ea$U>E8~|9v$f?7ze6vB30gkn_-*kFr8+|G2g}_@ zzFaIc-mtQ9FtJJh-KMec8t~ez&{y3U&n#_hq@lG2SI+6|Db4-?VPv(*%4~@_Twvtj zTl+O`A9`p1-U++ohrF?np?|lW>{C=#dICW4^y$brk-Y3|l>&)d*jX4M5Wve%%nK0c zlR(hk-R#mddeqd^WV|n{t0VcP7AFk4AsC9y%F4>~-unT7G?71|cdt6|sG^%R9`=}% zM_w`)7HL@vW@sV00y5SJ>cGtfaZ~7&38+kp)Ad&{qNzi9_oyi8&6~&SZ?^4A+#X%G zN3rZ?>yIBCd-sZ0hU6EBBA0^t^y$+u5?>HygRlcEsAX@}_K8TdbfE<9t#to2aQh0Qi;?A#CG8_<`%B1_Rh zf1sGW_SSZ2>gu6brBf33Z~FQA#yKnX*cUp@9xfNwQ&tWU4dZ#9k%p1fZukeC^bsxIO<_23^uMXk+@;mSZ{_^Z^o?Yy596=2bqKv1xz zuWVsXaHcMo$?nvT_4V5zKS|hk`_j}tYisNC&rSyauAH13Hv|q;_Vp_@#x{%qIL2fy zUygYsaT^2zVHYlnnMvxEc(jN7Mdco7&R|AcO%r<|B~->Erl7Ds>FYM1^>AR`aAVxc zh4YD^+F&3q^sEQ*4;R$uphs}XU;FzpsRcn)acOD3BGSox4+myW$6lbqD6#7j5_VvV z;(#C!@Bv|4CM+ZVKIW?3&-O#o&TQzIO|X0-Tu>UP?9|Uv@g6?h{GH3DH%}gQd;oe# zqjdcESRCx6QK(t8`3Dd;zrfkDDbee<@Hoi*la5o|5W#>RkPBP^G;YhomuH^b2c8Vt9*EXNepq8*%^%IfPM%@p_u>pX1^@~AS? zc(`h(+^D7EAWt?jUKjPwBNPPp0ilFfe@=l4i{s7Pw}z-2M~)sXg|ez`>PRe)2&=Nu z06sBaUfZ1*PM4V_{^3qa=fo(4YQTp zIcQBk*tIb;igh#Z>Zv#1?dEw!1AIQNPLFu5(R;f$V{F%->S`QNYf3-K?1^Z30%e>K z8&iz3v1}1jGuuABXCJP-BF}Wuv*wo>?vH2!6J4vhxv?~j7c<6v$2a@3{+!D3Rk*;L z@}YZzDf|(~vePdsO&W`y-jJw5&oGTSdwXg--ScZBGQ2I7?@*5o_~s^izFD60T-#Lt zai@T&{t=<0AG3H;gX9ti274&(Tw?yCv{12@8~0=o6%*t>Kng!x&4yZ4Nu{372D3em zoocUo9&YaUaK(8NwF*$O3If88v>nHuHH4B2;uo$kV-%5rmMk_H1eGh9No@PqXQvp5 zwjH=fsyipj3L7Cs$MH9K`kJ7CaNL8 zdF09t>~oBJd!vcu0aHCtt{5zKz^9PKNWXHqg41ve{XJ3jfz-|UgJ&xX%mVSbaZ{pc zMDqgS5^M+?plvz2=JW@UqB35R`>dinV2af6tpxA+3jrbJpvlIew_Q8&>?!(x9jC^j z3H_%gI$1}bP-E^VN)m_^RSXPh!HM7Z^z6caB^YZWVi=aMUDy19l(ZOtMIkCgTHAH>IeC(2ex; zBpihf-7vWy1hQnbS>$L%4n|PR36>IMExfTC*?srkJrM2CD+3N(D9>Og8WZV<-wa= zd5`z=_S@zg_Duq9Mqa79i2WD^I)ss;4Nq+oh)(l2`N)ag0%=z$c60Od`}Xdoz$Goo zJ6pj#5|9=&9pK%wSj;1%xUWU6Z3UAUj;fuoqyZHEe5WNT5V#3O(|8aqkc&!dXoR&1 z+yQ$!HQYiA4W^vDydMgE0< zrLfzO+%PsX^9lQ9BhG4^6zeG|AuPMs9?yn`lt>Cl{0Qi@i9!;Ovev^r{QP|{S1HTE zdQS4j<2}Rnc-kf>YBS_(@#HK!{YZX<-2S=F)fI1&6@mB>LVF{1Y2()2_rb*ldxv1< zf+jxJD-1ML)MbGXGzf-bDYza(dWN^;YAL7(&_$P_uMB?rlzeu`I(Sqig@zDn9^V?Zlmx1WiAKK|jW5oH>E=~c95UmK2jiPm<0{)9vqIQ&*Yvv^stt8dt z`t_u$;+Wy(HRHwjk)%bQn3#YLYy;-}p5wBw10eXxwi#3B%^uzzud@$#d?+is_vbWGbri5-W8QoCP~k<(o2M}^K60Vw z%n)tZPpvWX=MOT_ly=V%W)RdNHr@bGDDWUCNgf!+6B~DpkcvhGf~p;lBO{3qj8tT7 zf;AMY2$&*943ZH=fGWHhKBz4FAT-2<4eX<&2(G6IX zcwvxzL#oER4(LpvkR}ZK+}zw{z$*khg;z~yN#sQ2&&cwOOLL zr>+n0j)(LJU_*C>1QCfu+l3ci#l@njA}+0`l$&nDRqeO%~-$Vci8l_w*A!pW|}|=<1N;SC10Ta2iTb0khf(z#b>w5 zI@j~X9<=PChdxBLG+$ZCD(Qai>QY6QOUoyY|-=!>vLun6d|6eTgz`35#Sj>I;8Z zSxY-iYp@tVWo364|E#YhX4qPd$cehM`?uvina!1Je~`7hv}H1`bZq*K%J)d; zt(R5`AG|o_aPl_S*7&uKSJlOpzn-?-sb#HyzxS)Xrr%d%PVMDBBwNHw`^LW>z52y~ zsd7>O>(@_(YQ{&zc&S}_M+`LhKhiRVWOx4-lc&GhbyjW5!i8^Bt!gqM z0LHr|0Mev%6G0|+2T`XGF|8L3i!a1T#rAH&RJcRxP;bn7D^D2!MyOnJhC9zbSU(0a zI{UjA5HptOapX_d5JhU`z0H#(LudJMo3f8OLLUHt=+^yQ{cI)NAod3^A;AN0IF1E zihXQhPW24P`DN@o!T=EksW@wgF_v?#kgxe|enr8I793$DY=vmKVbx#%d6B#Vq-aRG z-YWOqPSolU@~$cHPy_)>Muww}4MbqXz;F-L`Ul8}!Ki9o3P3MH?(|DIYlBmd(5=4P z9&yJfi2}6ChT)vJPzh%%Y{L!d-eH4cKe)P8PKYX2@b~zdpPFAJM;~ zm!>%!x9*S|h=}1GQR(M&e(hjl>Q0V!W#2B)R<->uSfzMG(!ub}_+o|lSYd{9cP=7C zGBOX|qXk&wkTL@Lo#XJ~0IL#A&4l?PARoDDDzVJcycNtFDX@^Ru9+yt801M@2O_n# z2`4*nD*$=~G;k7Ck!TOmi>g%8UEpTC3KbDit?xR_i@-57MG5v->r#)I6E{C#4)h=X z7?>%cD{8 z&3JF)=P-6+Wx$IFe*WB_s7YRozPPan*vuJ(N@#7n&EjY9Y+b{{4{-+uv8~`X9+lI5 zczemmL}ZSrfiUs9tMism54VsUu*BoGz~8}r1P29)yRPE3Y97reQc!X|4jd&!WfJtI z2YqddhNBrV^lN76`(uq0=>XJLbela%R2Ce4sZHv>#{xBbk zP61ekMq0Kx11lXcb(mifbk)VHD^8d_!$KKw`j+FZI6t#%twwiJq6xF2d*;mqj+RX% zzzu-#qpshO;1eaNytb3xEPzQ~qEsZ?u;n<$c4GG{lEtTi$s-WsL{N)_h2R_8Va>LJ zts2wJ>>`JI^jR>X5=*6&RoY!8y}`7viu=BCbvOhHX1glQgirKZ1wQ=Jjr(# zXTB)?x|4o*0T8hvRGRM2O5gL$m;eqyTz3TU6yf$~9+$w_vyplyX!Q zRTTx(1=YsdZ+)=5v%%gzK>KR>fLF zT8*jM5)HzP{WB%NE}rKPI3waw^wp1p2t zhiZkk0hPwXH#!(^0b92Gaw5gV?r+OUU0GKvy6k|F+qhdU-m`5mos3SpKuKa zN!aeXwEiJ2+r7w)Gj^okYMyNOBdKX-F!$Y|U z8jKtdhZ7~ti+?y7k{f6f98%Uw|9s^e!V z!R+I5h@&Fjg=P@ktpQ^T&D9R`y{rBZp}i?6kbrj=IA6xlYGF~t2|9DG9S;%Xnq+sc z4VV*jd=T1F9yw0TT{xUQUbi17Q(^d_d4&nCTKm2Gh2*x3_~r!w4>webYEw zn^gfbaIqG+w zCyCZG78-uTunt=k=CbUnys0F`NF-$WU|iaPKfI=`rFhEWfuq3DqmmXDnN@NE8RgI| z(a_L9{&LHfwI22sGG0Ktw0rib&5ztecry22O+GXcz#DYqT>&_uUofj<0?Zp`fx^QE z(g=KW^$Y`z_d1B2EKlwcahOPG9d88e0VdJ(PyEwQicS);C(=TOgQA7}SWfQ%B!i13 zc46)XL{yh9n{jxN4!k551K|i&l!Jh zINOpF`M4w_JzcOb9rA;Zuxbi+d;tOj@z&?hpG{twsIwmHKYy8=I^VBlHQ0 z6o4c3MMd%9e^h8pl$4Zsc|0R)@Qz%DMGuSMdgroHAUsI&18<(|>gf?(G{&SIXU#8o z9-gI4o5PiZ1h^tpp1hmcmevm3;QEpRAP6@P&j;`aVa;;znv+-UsT1-#BG)JBylo#4L*L;+|q|2egUP1JxkMv!fNqLT$-IMd?H?GAzfQprb zi%qN&30=Ie&O-SXnYjVd5sifK458X6)_@s}{m-%+q%lUJ1>|yNj zZ*p6Q_hv>PU=O^mch6vdk#uALU5zOs zo+A6z6?R->50)EYmmlAK_Dj?1=KDE=8gC;`)S90zudT~S1c(H+j__LpTnF;1;MQ>@ zCAAN2bu^=YSbpP&0h3kY%a=iqTGDv{wJ|D(&qzOSx(!kOWfPO{H#^O4-jqfJL56Wy zL_~wI`UZdD2Cwj{&Suq%7tiq;f#&JlK4fm@%wkVAB6@g0lZciPW*=r&pyfeev5Gs*ch}7 z(q5Gf_fOa+W`50pmcDK%-X=?m`D-PFxGx@Dv-u>VXLII+&k4qd<&0UqNwujZ`P2~| zssrCT@eN#yHwNAibDoV#a{CMH(|4{8t%mAMIopISlJg()+h&Uq*)^IFoSjln_(_zq zi;GLx61_Sm(X!>kwR~es$XqMPQfZ7bK0rtW>&(B^830HQ=Oodt)3S*sJa<~wc#ocLxV9$42tCNP z_5Ss$3*U5BH$G626S{w^!LnGfY~jSR3iYJf)|kMMkdU7W_R3zXRPa(IuHA0ctL?;f z;riTc?f379avR#w-(<>1{bo-)AGfq!FXj zznk5@{kq%LTiRubn`Y1dP#Y_uQj;~m2P6crrtOXs0Ulkp?$y_chnJRB#3aJGJBuCY zE&wBP_ZeEY)aLjHseE3myg&f~NeVvUlU3Xqo>lkfOk*(U6+#wcTgQ;HVd>LE@6<_vQ(pvcWAh|4XF zoRQUDsi~%K#k6?CFzv_b)l?F<9Q7|@_oJRcMMJLYo&%hr6uZOLWSP^bd#xTGvWne? zmgD`$j|qBTK91esVBCykmc=R_Rqb+%Zhc@T=i@L6&S+XE|n8F^54FS z)X{d>>~;++>pG&cbCm;U{Mh_bgq~l^wgbENmzizCB_0qx5_tOpRQ%MCiQv~B8cGyj z$O=aEHN9qFsfJ7hS{{$_lSYV9_%C1fgSjV0Q-!g}_isD*wlC?Li2+sl>2CA(6Gt|y zd$sI9-V2O|u{fezrEBo?v7+#6L{k)+mjCvJj{*;l@4fu$#{umWXq_>qV9fu~vqH^J zE9uat>Ei9Nsq4$wQz>qhT{Wuz?FZlwytFo;TmegbU9k3RZEeuFs&n<;g@CB2jxUF+ z%(ljh8_g*b2{8i$!}Ul1<;DE_pZ2D7gZ&_|+D4whk`6_eA)`gs8mNiGRxY?F7{n&=uz;;|FozfFQS zOcK8igb2uM{7-M_#*E9aS|LBe{}W9!c0B-G1H0@{{J{|c0phMk_lJqP`{vZ<54~2E z#bx*PTph^AzbcY7wEq)c$^ZQtTmAE{CSbZSfd4?j(7LYbTLxe0uEK9`S{m1YB ze?N_~Lso3eZBcAxwl1*ffB)XC+yAnp{O@nToALK!EGetQtIMm!t2L*RyH7^VJ+uBk z$C{jZUjsyz3(G`DS?@(gv>%7y04X381PT6wJAmvr2lrd(}YHThQQME)>T zDNR@X5Pdh2%gG|{pVhjUrmYe<>yo8^O!#o?>@wp>>RB^4=@F+N`MINus*Dq4H0pHTOQ1IIOv<+SWlgJ3X+ydNpjRdh_^>#%|b2nEAEmA4C9+4pS2=OLPr)-QiEmSm#s0uvK?H z-`udlv$GQy7mg+RP&+?oQl4fj5jX29pb)QLm+5+>lcQo}$;M^n2@SRIe!b+Jr;4HV z9>x|wW|ob68M4``tlhLb6^fF6>X_bN*7MMOqy(zvl*+IkqDoE<8h@~{a_1|rTx>ZkL!oem4=Mv zY4p}|skh(o>Ef7oh2G=WRi{|Rm|jb5wRerRbMvW~#|ODk7oGp@V(7%dFpmivizkuB z;Va^!?EQ&mMt@Biw$S#D_$)V2Tx$4eqZQxyDX`+|hCNvU;v=VYqiUvJZ?fLiQBym% zhs8t4M4>I;w`8lz;ON%6)o|4XHn&CGKf%dt@bR28-}2#v+Z!Zyn>h2uz2;Efaw zuVTXGQx&g=T$F#+KkEpt5AI>oO}u>g(T%pa<1XJK(!aIPO)u#LQLDF|ihoyfR&4Lw zTIQ)nJ=(W^F>Oa38`rd0sHB7y2z5r8zaLykarAwEFuLQBa$Dy&?h2FDpgnd6@)s7V zd?~*g--y%~yEfgTCHsOlVvgHBK+BT((t&Lgh8|D%pJsh~xM-wGCqG&&v{zpwm$pOV zt^-Awfroyo$nKv_b2ayhj-=>ymbNUty~CXK;9_Ba*9*Z1H)`&;+2&WDvrXwuT~V0k zsw|i{q&Z}MFp>WZEx*m(pB`Et(sL|FQY#6X>tWNR1R=c__Iw-@T$YAm#5)}hipQtX#%9~~_v6LFfD|7fo?_AkrU~m0Q z^+wW}Su3xyM5hCOM~hy+evK0(>_iuX^vk0K`zu&zm1sL@&1=6n2~*czTG%AIwPd?! zSB1-?O=8jc{L6X@w^s%yk7V?E_r=?|=@dyd)vLNR4JrS28i4y{O!;AkxT);VdQnZ@ z`8E6{KW@~#V_Z4dd#h`Fl+%swkhfXF@S2#}G3GJ#!{d2V$3<5BhwhAWt!j(j8kDnI z-9H{a(bdU5-?5Nv%5_bs{dlNjRl#svR5a_0u-hV^T2&O5-nW`E?p=L3QP|wUsOjQU zw8kv_x!!G z4tD9#9nWCRx^g=yf7xs1l&Gjz-P%8GVm-&UWT)V0 zEPH@)_1p5fw+fxC{Dy;_{L-s6q4mgqTLtQ-7k?r3i1>=8S7^zd{Jgu?BITRkK3*$r zsOfU{jMkZ$lPfuoQv$@=Vw3MKUmUt&Bt6u(Yx!f9L&ui4@o`h>*ST)px@EDhpK9?> zw!tmES`nwDm9VK78*7|rdkk$ue;0(M7`d|vtf81w>Zk8_oUPGaw0}k~v@U++K&eyAU4iqE%hvw6CosDppT6R!B6wzWetb#kcwn=|bC{1<1~A z-pyci#Une(EvuZ8-bVH<|E+}(i$Kp?$0}r5$E(k$|EjSRTgF8@O9niNt6Yh&jx9)s zuDG08v6H@HoR%wfJaJd_*fMUCZgKlS;~mdDe?qkPihIOF&2~?Y6;0_3(RbYX4jOS6 zw3shuoFC>FF6r2*Qgf`~lby`y7vIsDhGUXl8`RE;`g$+1^-v#6%i2@((=CR{MAhN8 zBWKjB=ts{)X`@>;s{)UgsPiW*JnUl_+$rGiEP7b1$?Yb`vok%VcjgC|54mcWMNFu~ zs}``E=8p2UMqV@6J#^8vaz%bc;l-W3ZW1Lk3H>tEt1EWClkEcLR30k$~Q3uG)WFKFf|@5DUGPYEac* zy0k~j^-uKp^y6f=;|?;n8-`r+d(1RH%km|T44dks$#5bs~D7vU@D8{iW<6L^D zYFEmp3_8=;f;;6B-M+3j-)B*o|44V&n5bL(?2b;dPE>qqLvikG3wM4hYsbR*h0`0B z-`SNU(c}$Y>QPZI9dN#&bE%b4k4ri|DomkQot}*FIq{@Zx8$CCSQ1w6xFL7wkIC0PS$_u3 z=d-xv*oM0JOOHHxBr3l6=PTErP$V8we=(2W&5vUvlDwVD$j6tB%V%F_B*J&K5_n&GrQfzx5eIBU83vqUX~^a`9F(l zXdHWI#Lwfdp~PZ&BI=6fZ`!gaiH~nBCl!aXp84W&dNNc$us5;)(&;zpa~~hgay?G4 z%N!3ajqaf`3w>L{xoVscHPX7^Lwnv}kXpTn;i~ka+`<-ay$CsXB`rCtS;NKBneSy~ z@-KVnYUOLeD>bc+BRUysId{s5?$FiKmi-9v2FE=l4 zYW+hu*_6U`W6;$0$Gw0QA^9sUk_loXA0!SarqA?`JC9C{r_JzvQr6r&FfO*M{KZpr zF>wc8-|f3|Am4m(#hHIaT2JQm8IdodTQXKy7G^J%teagg@lmq&8S(`Fs3^4_c{nXZ>6*j(CvnE<=-kZbVoT|w!2XK7>FZsWrUOH` zCd#RllolnFS^4*;hh;@XOh}n#wTFaH&6+zYEY{f_NDGVhKhM4Ld@;l>;^{Z>Wmmaw ziaXaiGn?ICW#4eg?fb$fKJ#~ugDXY4W6-ZlgFf}`i?*|yUi3bR%A>1m|z{VoEg!2dZa_-?!3p)H00vC!E=$C@3=0pEyzG@<=4krQY4T zK&RF$zGG}m!=d!aE54>Hf-(zR#m$|orYAQIhP2F;m6p7Fmq=7ooPws)MYV#7=8OGX zntl!*tQ4%P2^?TzUkGfwZ=Io^TJ*_m@kQ{sDPPf4{`G$2vWs7~B`O~m8}(4>SLie; z3$Tkg@Vn&O?o~Z{U1x?aCgA0+pVm1jEROH2VR2*0i*t}T8Tpqi^es|18gcjEax^{XR>VCMp(4oM$=ciZr>@)YPVK{` z?}-O1Q&+@Ae{t^W=i0x#PJ4dqi1?)3trN7)reP{?Dbl=iLX;Wdu@b0O(T{5rZLZcMlFif!(fxG_2# z({8O}d&l@&!EpYIy=jNy^Js1u1a6}sH6MZfWM~=>)bpCYp1M=Lx2kcxi{U<@K%T*Dgiv%FB_iESRZne1R7Dqx8GU zWX^wE;FiDM%0{n6l)o4RA2=FFxrNVBAbw9}%<7q<+CTFmV=yibl|cIZcc) zb5(d8>9}~$fj7e5+|@+vQej8Eo8Y*^qNrieaLImswGL^UXA{q8rau?-8#ean>MOKJ z*Q?aT_TFFNtcsR*pF0%GB+zV=A#!$fS*P|@jLei+v#UlNMTYZ6{??Fxds{z8nhixK zggyUL3ox+nmpH>s`?#$azHl`iYm1oqJvbkFJU=;{QvQxW-7%F!Jte-{rJc0DpC5aZ zadfCNTZHyc$-CZ+7Q?SSiT3i3N_baSRxAoT%$%irmAkJWU0pi?U8&tOz28DMcE z4#!25#TzaK2Rv?>TUt6#x$wKxqmNTot(N)kY}pmp<5tBdZbVg@b@1?X>pHSjB+Ik? zmN+y&&aFH+uX&21jp3S3RGUY5%glU+NMa+aU9JgTrs*!B*&(GZVQsagW|@hGRF2iH z!JEoUtljGWsy6IO+3hL+*^*~9gPO)zQ0MmX4Jlpez5Z3Nh2M+))8TdwrQ{9j97xZd z&f{gbUG%eiB^gDV^MBZS^M5EC_W$1!uBcqf5?LxqNGgnOqo z`<$wdvZ^we<@q*b2we+#e(u{~+f=-(cdNP{TTw^K$8^NPUaF2#To9{};+Ph^a zqo6b|LrBRqkKYjsSr6w>TEqm&v`;qz27BwtHFlN~2n2<>1Ktdq=Cqe?U(!s}c3GM5 z!jNbkFTU}npJJr)5ayd@&i38`J4pxCM97{Qyj80Bexw@CTjUV-K_0u7cG)`oyryLH z;SbweNl!z9+$-Gwjk}6qt07HS-#2v#?v0fhk1YGpj>}c|hkwZLEpk`(=D3x&?DHfk z85W2lOWTXQ^nHokbr!=h)=Ebg`&YeTL9H!+=et!u81()5?0S!-pUela7hz^MId6ZF zZrZGo#*5~@4<0j`dT9ZQkeJ_Oe}JLlVbB>r$jN|Z+l0x)v@wt6TgFb)lBfmbN`@T* z1!(wuYtk)rQK%e50~Y_1kPLJ0Gb#yoegDAHGOQX?lRmoom(03@`Swi&s2ed{QJSa4 zGuEHWdAWPL;xx)R)(l}+ON6ehHlZ##M}+@SSd24g81E6TQ!}Ac+&r!!;ki>k98a_S zPqGv;vKWh!x;roz!wUY&=Huo13SJ~Wb)W+%b-FBIf^dhr{Wze?H6?Q>_$ZzEr&;A@ zt0pkwLJA&CTfO&ICERl1B|kf@SMOua{bEdee)mGltg|SCL(>0%a4Fk-l0fUI3cH^N z>)M6xQ>PKqF|;-t16oV!pbjwqlp~S8e)g^QD>9gLanhFBq8?16cPS541 zt~TzS@d|rFvYya$j50e)V~%Q`C|>>Y2J*8d6Kl4dG#cw9ZLoe>#;NV^QstT^1+V`C zVZfTRIh5h5;rc`cT669kyRKGOUQu4?I*)q*D>g<93I1h7P%DaH;tAt3dX> z5Ys~6AlWrfh2cvV!)Ig$Byn0w5s~ny$yZg4)4iDM5b8DSAO*jTk}mtVfP`}FDIf*5 zKT=I&r>XQ{soq+lfq!HMYCPD}`6iRCgBK4T9-+3Y`HCy$EE-f5*-gI!EH+i3(g)9c z4+&t!2D67xmeZx94o28GgtO1KmcolYRd@L(Li%;yJ#!>3SH_jV+@6QM@oiZi@#A(V zcTNnwHn!ZpeHkZ+?QIElx%6FG@GJnrv(=PYEuZU*con~U!FAJ0PZ1{Z!86*NRoM$f>JXV0&)o;?nJLH8OK z!BMdv*RPlizqUiZ%>@=I2*^c2JT`==j5sj01WDk$OP_3F$L*y(4<(s29%F)xNpBXy zQ_3rPY6|FbyrJ^cd#NlYgQgb2^;$T+r%g*2GI7jg1bUax?ATJ%)q-v@ z%kTrqh;23u+lpdFfTFBgob$Pkv)s3Nnc@xD&Uz{D0IZ=`lG3+(8}%O2c$3E}V2Vw{ zO3p*%y8o`>)1scTzqjr0gJi0-?`riqO^w2!0^h0zhVT1YLJmMJ^}*i755*G47XyycF`iI&9*0fWj)rX;1j3)UuNHU0B41{%%=r#dkcd zJ2NVR+%3;2FG$=3OV=2J^r!xtpsyDEhoG$(vg&PStg^7mAdB&rp}xtmJ-+*-eYL|!_7G6lRXQDKyXlR4ZFu5ntHk&ZsQxFD)Usj%~uTf zjfUP#xCV~0X>v|Zd4gny(#uA!d2wuDB3e?j#Wev7A4_HZ2us^3?6h9%IbEk!Aaw76 z23(TXEi|&J)-S=lFRD(j%XRsPvM~pOSEdaH-=#korc{#-=40C16g=L=EU+cP4@B+v zcs%B@XyOJ?M3iP8M%W(gu-HGM&i=Aqv5XYey4(%EwqW8pQ3|NNuHB?Zxb97J^VW!O zn7T$w9#W|>1d}vAu4NhUYZqJc3yIseX27N1--|t^#jN^WO*AlX^*4H%o#B7jw{-t< z2=bTKfMUevLj%1&KT?^CbLg=4qPN-d4%w!$I$-~+b)!i)enPP7KnD4$iu|46N#&u_ z9{@G5>k&HzWe9G>%si?p@&6dwi!moQJXxzP{^d>6OTpo3ZrnUFpWmTqqF%TARKfE> z2KJ{ieNJ;PMIJla*kN)ap!vOzGgnAEzieCiQ!`?F$N&$@!5QHG=*ag{vq8^n=n_jK zy@7W34Qqh0;R7!J7Lw6&GQbDR4W(M)PYE)jF5iPa4fy`_b!#uGT&KTa9;#`fAvYdE z{Q;5Qq8m;OXHPczwOf3N4pI`OR;+EXTOF~=l11Dw`#RREeE4$T850YR$<_dTlPgTT z|6H;IW&rV+eH*0**b!BEC$eLG{jx%tfbTR(6#NJR3cDmsp8V=9_rf@3RTT5=4T<`i zW7BkZDiuyU0@`~;9&MYIV|$@r&dx8-%XwEeS6n{%6*+W4+gBdd~<+a^O z(`^YjaA)0^CDiBOb)X-*JGBTUZ5_+Bbvy~Nzl-WsLX~Y=E8O7JZnfRk)vGJoj(60% zck9Hm=*Vif9wkpZ@LEsTzeYeam7NHV*M+sRdmm$-!-3v-k4E z%LsT7HA8z)r*tigxqthL?hlCbI%KlG9K>cbXx=M}73h`t_hty!Ch$jvU{h;(Bchu1 z8;%p)7&vU^Kc%aSiaGO(zye+aC*mFAF%G}I*=Jvc>YW4*GDVE^_-#HaeAUbWMbpoz z{u`TLo01$~b9FipcX~3s*ZW=GL$w;+IShO$74Z#^|-2Vg1y6;cWIAI3YhF1@AxDIG%k;EnUDnUC=vyr_xpQJ%85yhhTN~ z*{h;kn)dNW2*RGsUkUjzZRKLRI3qe~PMZZ)H#|yp zt@%0dal$(7&js%d>59s&V3;i|#&B`*|A&~Z!sT`9HzA9QP`}m>TH)(O9f5?b&~O80 z+bhkJSV+JXJ$Xo8{nl=hN%r42J)(2TADZzGKEEKRpI%71l-H_UlP?zc)py39u4^zj zihf?M->ELX`q-CU0dL=wf#%)NW+|RC5hS&zN^ z{W9B?s|Y(qxJG$#yvEFzM^Rr_hPu91=9}+06SFrwc4S60`VmKYST{9@*cKKQDR*>b*qX@!m>Fzsm$Y*Wbs@C$igfzlY1hPPq z(?zQ*oi5$0N#6h@p_Gr3Wb47mwiOZZp{ug{^{vv*rwo=5a3NoJyS=2e^$VB1Rqoca zJSd7ig-jmg_RIozG`*maw(Ia!!Q;p`_q8EUg^91W6KXpid;j5OWRhr8^ComG)hS zi5taFT^PR@Kd1CwUr`DLmWOm&*RZ_Si`3DODJSy8wHXfB_2uJu=<|%73SW_;^)B-^ zZ+cmqInykg*i>E7c~Eg3YOG+Rs=)>V!F_A<&8oYco8Nr-!+f306}~p=kwW3VuGMP_ z%>yf%CL;qc7TXs?pwtx0gwLBnQeNDPwAXtGpA;5Dc=hSeA~0}MrWrNqfpom_uDhB4 zYp;20K>3$cW;I3WXpK*1c6g=hq*?J(|fBp=isfH=4#T@icxu=)=B1F*+)kU7gK7O#lIKN^PJZ~y$@%Xt6g<_jQC zi$mPL=gOBk2@CC3ss%?ya!SXRDqWDjWdcgjl1}66o zJA@n8v@Y><@PA>+|CgW)adn19i71ZY2*V@dadWda|B1(vFn3kkzc;^4G|Qt{oFy_-3o7@qH(g`+GKs<1Ewx zL;cj+!<%y?)M-R^Lmvma6lNarg=4Q9v2jx#wvy8KGK0L@Y3uYpO<^6`<(=2szjvW6 zt@9KP>aKVs0kOy~`Bhq`Y1O7MsMGFK(}9Be%Y*kr)G=`>e!SWi&cfLa#g&~?vV@~x z|01P-uvw9OOaG=CHvE~-hY>?XDYNu5=dRgt?lieSXQ#m3G3WS>e`qGWS1C3@n+&32 zPs@NO6#_LhPVQOvu^9`iU z2IRBO+Eq)*lR#>OWls#@*_?|z-ul%U`nC1YC7l#1_j2$mw=nb^W81^2(`M~7G1F!4 z;Cqb;1AFGp^~}_Wq_@Dl&FjJT&>fCCB(>J4vhC(l zq7aCDZ5k0kyS1n6gwFeU-^30?IENwCQA}7L92i23H&;!kvErwH*v!%nqlK#DF*++V z4Gx7h2yl3wc<}V30+ghAX8x06eh%?P0r&i_C~YeQ7rtT&l~CJ3YZdNu0$a3t z`}|6)m(=nr3QOfyU_#p3RmICgbIM4H-E6VSn56tu*Qz=vMoR#Ou?4s6Ff;E9KDnyc zYiNQ~%5rGV{c#_yZFrJk@RI6(qvaftlXS;))0;j_#VdM*w0SRGGm5(>v7OSF5K|{U z!x(S%5$MGwX8{X+0?+hW4ly9qgZ4=5Rr@|(_uD`pe zp3z_rkKZg*Lq$0i|6$1O>A^$P0|&kL($~&Q@EP@i#u(yWz&rBahsYkGTQt-n%n_x~ zGj}nwR;VSbma~|Whaex#Yo+#TvvG;RD1WDGT-5@LmHOb*7-W*6s9ALZx=K8rw$B9` zK*U45l2=TitcJ{Tj}~CZISK#j=kgCfw`JY`)!z--N;NPI$TN;dUfm2ND|YoBJff=2 zT44U2VcRAaTfS&puV^3t`$*1`+&IF@jOO;0(AS_mf{J?MGyPXYB|SRzrvV!PpZK9l z0^q%wIT>T{!6RSI}U2bKw`4yb=tz( z^K$E9U_fb}Y-k=Zxo;)_$3^E1BPpNSg#Q}y)#%o5-VzsFLp-uU7(Y<=vvbJ4CIk8G z_|=>ep;PVgdr*>p1>gP!dR1Mo;O(tDj=vo_;1|Q|niCdwDA73A^Di5m^j3J^e>PN9 zWB8)^onEBzVI682C~z@9IlCaJI|HC_#F*+9M)9k2`x5RVOW; zK@Hhja297U26Jl?ex>h2=Zr*NI=vIhlYVnWg|Ihgu2^-qxuknOFx?daoVag^$*sn#+;i3UTL!y}LxnA1b*~->_Z{-)JfrRW z4?YJPE;256Yt|*S?f43M(IhNt=mleAyMM`uRnMk8R~tf-iHjEpy_LY;LXuMqSju#_ zv{QdP6#981J_1+A8HeR};y1 z@YwVrDcSCublV{p!=V>-_}dj_h_%u!q4geI+~wrz%a7ZF+S|t+Rq0uAzj%zf{2X$B z?e_cW@$`u_ae^oHf>SMPXiC|jExoVa2Pk+oY%g&z%5dR=6PiE=!YsbKnE|Ni|A(M2 zj`I#q05d~t4bRfJR*#JzKCtyE{FJUs~)|b{{_y@)Osvs?JO1-p%st|KTSH@C&WZfn$L=2b)pk(OW=*v zIP`_Iay!MW^Ue((>>{*nIQ+W?m2AhItXiUvkQ>zXntK)0i;0`>AqA(kmb|&HiyK$w zlvm9TExZZsylCE@W<5Z-xxCnSSPi7oD!BSoWcuoIhwpW1^YkXTo)zFf%FiD^~7F+Jryy&qq= z&RBDaO5JjiqVT2QO}uXKv`CkIq!#$Y)TLs8psf1u*b&h?3`#UrhhD zln2oH8uYOFuwxr;wIXk>h3piL5-`}QjUL@z&IoKxWZW>9hc22phvuPkT6d3Yyd^De zG%|0HxCRWa#=KObTw?&La@&fNk_jETX49%c`CYm=#?_crtxWAxfh;sic~u*R{Y*6k zQA~LgWz{won4Y{F)q)2>4%?>BpF0HmH^&Y#k^_dfq%v9`>T6HD*0|~IF7(69K)+xV z1hZ<2=LSBoYb%HrPY zwFnmIp8y#@h&((sJ)OmtEsR5KhM|-UeuDZLWBsWH59dPr$UjF^Xf?#DaW2XFD{DBjboJiP?>&Jt-)wnc!AHvR`S-ktymnL2j?S}FjQu0vu;g%*y0D=o z*NuS|O%Bs@t0I=tr)>5@2d={mcJv``brlZHn~uKAVuVjulbql<#$x57;$*yGL#9Rz zAUDomb;BWGQT9~C6mr{SPTLabmwVKuE&i*`>TAgH|0U!LC$vQVNXp=v=G^_Usj%4n zXDsj)vj-suyjO#juCHuMuXkw@gy_wu){~rvu$DaAKOoodVhTT?>^Bt}+utbHSpPWI z$L{gCeVN5FmNQ@gQoSVKT0wtFK$iTNE8X1o2^6^>qj?vX?S1M`O@3=+x^nF-TaoA_QM(@SLG7$@12U-f=EeZ#ZB)-z? z9?9xNge_=KLkfbU5|LWlegN))YiSH?DUOo4JxBVy@<%v+rSZ4oB`ZH{P(3R_{Ez!$ zs5dbDV>G%;>7oXws5)Fe`g#@ucVD6h&W7W#s-Vq+)WEf2#z%y(EKFbE$EL&6_tAGW3=IwM>=9=jG+57m{gua>W1Q>p zpM3mH`hh&Ht_To)dXVEWm&%O=O+?=)Wvoa8U7}J(-F%(MZq{~`pU+xD=PpbUkT-mC z)_xC7f#_md@stY$ipYP^Sae0VmOm+#|81)Y&VlPz?#a-!B0bINP;xT#rG*G&9xXhL zzQJwz8m_{%=(uiNGfn;tLc8&@220LEu~(X-_2`>}A-}}2`78EYB%q%X_?{}jOpFp`zC%=ICofcI{G9d_;-)vFwd5LvIS+iw@GE%)Sd zX59DT37_i`>1&!7HNZZeQ47*SbS;&saa~2i1eyElx$0c=5H|)Aj#MohQ`YTGmBTgPeDmfIw$t%- zfci30==;3L0j2zy2d7VkdCLKQU2}W@H73=;j<6|YYl!SukCn<(`1Kyr2e8&y!&YAY zFnMaI3xYqJ|Hi;g=JK7vUpp+Q@*VH+DqG=7xIyStZh+V-6GO=T;n+Sy+WL z247ZL*G(4HGkX|}Op@}bVJJD;M4wSzp~&{ zrA?~U>-gmuyQu*d*Gg1CRPh=c()@&FnR8E=RBfiRxflx6n_B$W)HE9a)DDk-|Aod%t(}%;wEY}!qEjmlRu^hk zY4Bwe{~%>w>)U76v+833FtxYm`i%caR{s4TSs7|vEC@F9lWcYh)3y@$fp?QUav@4W z%nExe(sA{%M7hwvkk}U`>V5+anV48@UegTt+!i;hr%ppwMG6*|t{Q)uP4pV8wV$pk zD#6sSq~-rUr4=RKa(AEA9EJx&$?0>^$jP&P`uu(M(ZJ-6rCmMgHFTGzQ$()+hKfL6 zlO6oY)#P3WWL@7)_5M-)mP6-XbEl}h++t1O%E#`}E7P?AH1)XKX4s9S;VY}5AMpe) z(#ra@aIJyaC5QJo8$edZv2-(yXAt${6-1LG4lp>}66tGfRqY@XCJri0_6>hmcyOOu zOx?X|41$|*^^{;_5i7Cp_3Uek?qWI_$Wfq;@rAcNcZ;75z`AIVimjn=ixhpC4Tb{j zi5sa1$1ma=VTkvgPx>p|`0jF&ri(9bPtn7@?+(9wR^ps9;qI5^Fq@6AY&^|-Mu}BB zLNK=PHzUgfJBW#2(YXuc9AmY;2Ns+k=DJYd(tfy$feFz~RrKQu6ic|-nJl=MR8UP#=G8u9EK6%V5nsbcu>)V3e}Ipy5> zDCp5dcTR;G7OzH*hY4DLY#l{ly0fUZ#ckSQ=crk^do^S+@dl1`WhdI71UIcM)smCF z%r!4-Q0ho$hwSyp2Zl?MFy}`yW4t#fc5lwA2V2)2$!*QxbDy-vutp}z$7vp>woX=)k|vp-P?-R62PR^0}p23hi2dJ&c2C5kt^FbmHpf7L)%;<^W(;G)D3 zII_$30lS|@>RLOsS{}@Ya+iKPd{WDF-U+YPMB@7Q3YR#!xG)l2kYGzVvJ<-Cw%v@p&9s?7{WYb1*6PXx)i&Z*D9`VITC= zul@^aGIAa0)9JMe@e`UYwBp+wCr`DCX~SpvQVsW<^g@d70t0(3?55(Y#2%+ zNQCE73~MUI@awUf7)Z%PaJ8!mQFb$9Y>;xcTWZ(o1x~Jodc8Msp5w!YYH!e|*CH^A{r^Re|d8~(l8RaS(^%DS%W@A+iLs3tqvJ4W8Mdt8192Y4WfrGdgkh>r)WIIE^Y{j*3YZ^VZNyAWgqqIAOkL+4gG#-P9q`rr*N*@{vVfGR&vVJNiN`O*8NL)`7+75 zzFQl>>Sb$=cPXl%{u50UQZn3NY~|wn=j9eFRIa*fJpKVL^7X@RLpovNLriaF+rqrg zMrxl{4T(o}GvBRQ)i)nu>H%-ciZ=8jwttZ>Pmf!z29(}8H-d)1zOXFOXuiEjl%c0R z)3q05rov67TE%Bsy>+QVpu!E`=QmSoG6)O)ZP9x&186{(cdD$>-WApIv74;ZKPU$t zUL|e*L}QzVg9KN0uIZhA=04NQdnS%c4(?YAPr2RWdoy}rHb6XVQS*67fc)U&>kE%c z1H1-uA>Vs@P>gmx&wQS>cZ3)1;A{TeOALrL-h=%`RznU?W{aQIjfRMNwG&sC_Xk3O$M7v`mVFM3-w3UfKul+KsBJ-L7RNDBhe`s4huyHc49UWF!` zyr(zxgdOixKa6*Pa1H4+X|8ikr~<`D7)Uh;@3h23av_#}B9TMxTdh%Bf7ul$d;Ce^ zm@|D&XYd~(SNFSOY4fA)*a7JZrPe$DD7oA}ov^$tOhO8673E6!op-aOb92SG74`c= z?FVW=2Iy)?*dPe5l<+n5X?pLsk;EVa6LU}Nh~G=2Bp=#o)=xNjP`cbne$caps{(nM zk)_e8vV20la}0gGKE6-OCRWS0j;M2upF{fLZl~4k%;`1b#-g3{p}}gMYfm!U*53tc zAb42s_1|L3K}J$;M`0idQNKF(Hf~Az#Bs`FhLbfelb-AM+Fzw!xK-` zdM*aKpDfjm0KCrYdhNJ{czgfYv<68Vk+80sro*%MS{jmPBagcM5uBh99#&~k4T$vN zBvOV^kVX7sbT=Ej9LSifL7ToP_Kyezl%@i~r4dDwJ8JE!H7Zo2}VkGb{J&MZv_NCC7KtxNZKuN8Jd zt=k{#dwo|P)+lg~-K@+Z~K0$!;;}rW&Q$uae1D%U5{B!Ikhw&20Y~v;)$>1=DO_n5h*3C zz5{9+*yyFQ4h(ykt$x^UiVYqc->3RXktP3H`V}z6i#%JYIS|%xm6G(=UMwes+z3Co zRFI%1T4Ih)R5q=q%SA0M`V))zUyc;gXkg}u-%HK$!?`Xc0UHh0Fs)8DBy+QVRCI?^ zRn*X@G^LMFy4i%rc^WS%-?F@ESlD6fX#UIa*R}Dx2n-Q2Al=Tk@tPc`-Cs8Mn^IyM zJ>Yr})B$7lxaNkzx{i>;cOAsxyuygRR-iTGv)vo-6tgye=Jp-&@Gg4fcaM`!jrtF?9PSZy2_?gl~or*-v( z(y2dfQ^M|qDfJkHIf3H|C2rE(0aI3{=|@zP<;Sch^pr!Iw^i?v5ve?l)j;FIF@(~I ze)hKc(zgV!=674ifl~L)VadvEhrQO7NZEtrPr^f7ST&c;kX1fjfcCKfsv5c!wwg1B ziV$LeMIp0))Lg!GTcDxg)%6?RK-ojsrtXBDot;{cgds8c{U7WA&vPK6l81>gGagdmi-o|FDuy+TMXno0hvIk$xmg)$$_VbT< zhJ^zyrvqExY{w6rp__>Zg=x12cX@6Cj+Bdn{V9u!%CFD0Sk4w#Y6B+%OtzNaL1hu@ zNh{{zNpjjSWIlHIGSfGsA=CE;_`xQYbtQ#&tRJ4mCXmD;TfN0E7Vd^@fF@kM^!RZL zJZ+lRN8fTXOs6$vw2v0<)4Z~p*ZYK7E#%KPyZiONu|?z^{sj3w?;C^`u@}ROniC01 zggAepE1-xwDBIV6V}C4m@4qF|`P6Ae0VBLl7jbd*3-=y*lpwKkjO@E#z7&SBlHaei z*LUd1sZ2ID6Rq~!NdE%V|9N;%Z(RjKg)KJrO+us8NVI>X${gvl=T%-DoF)_!ZNVdVW~0JubUw*FOno*3sSQJjov z<-~G+oe50(Z1Nz*foNNbo_I#Ngag##4`X@=!n8*-N)eOYhlKl3W`K3KaHhYH$@Lme zO%FGYCy~S(A-{+=gZHSGuU}o=xocQ?eTX=JSO=@{;rkyhjg^Szipg??2`- zF>T8^mD`h3#j~biMifGrPbQ9TexKaUU>s3z+Yb?*C7Z(34bK?(y3-w!HA;1I6SLj{ zi!{!7v6WtJ*Zx|(u}twWeA}#rJpKzmzeGoCTWLe=_QfZBfWc?yNH91`Br z5x9&wi@Er~ea+v>U3u358)N_jq<<#Yweds!H_O&b-(kS5Li-L$qs zwfD1ZL^~73Po6m!XDr_tNb6nRzoGeo1Brs)vvh8oTHj5_2y%>AjN6Y#j%YHXMwpL5 zXvUl06a14Jtl_h*zQeF2d$jw`+-PVl12+{44c7l@H?3htF_A?|$#~R`ZD}*{2qxZp z-AKJ=*Yu&#-3?b<&Mv6i51^HI~62`LPDbakGk^lc7?8=R6e0c-1b33j1=5DIz|Xicq`;+Z67F zss%!!yxd~Nc7gq+P4<^ud0Lx&_-Q}JosXHTXeX8WroR#>HQX2cyqkN(g*4@L*|1)d z!HmlvZh#K;DBm7OrkiY%O zO^Ob9sKJK5McVhL(h?pCy|KHi?pD=(^BZjYar4!2?IXfUbm1dbxD>Ih;%=e53xJ7F z8Glz2AlM8i6f)&@PYvw~dhBB4V;q5d{pTNUbrIN_RTh)`F`(=RjS#CrAhk@c4yNTW zh90!Z^Jk;jBdMIa_PJ)A_BX`7-ayc@f!f?H_?dr9z1ckFtnC$j(1qh1uFHRUZmzg- zs;|{3MAk#~Ph!;l zmkz=WC1(@4+jMo_FnbxqlRxWkp`U_E(l65mwpXyq8$YD<|17}$d6dJ$OW7asLJPbi z*mF(%8GF;8O&nkZp3?Cvz~GAN-;rJ^i@j%-kGr0@Lj1|ewrT9(r-82wKJ={^FO@L1 zH!RF}H7J~GAGRtjTd-J~5vUG{ylz8~h*oME8-`Z00m|KnNrd>dA8F*FUh{fc3v(iF ztAz##h(0o@%mmMQvfr|zWNWAUbJ|9E1xg&6rXT!%wp#o!PM%gX8fOOMTgotJe52W7 zeZMc=k}+G(Dd);hkeNoN=k7x2g~-JTe!OVzo@cXlqRFYf8d43*V2o>9JiB8;yNrdO zr6q>RLTZZK6vL`m+vKCAO#iXDOKZ64+z1-^1NYg_AF6PHd_&&qEbS zJQ>~$P9}-J9C=I+Z$W{bOL;>CM$0e7p4pzH>h-Y?lNF1}$7VprDH^9*e(&bW4>B;; z9~@-xgglL-SNxgJ@TP8V(24q1dWj^e?pTjT`mRR=;K<0gBR9HrftGuM%-2Hb6WhA- z%ZrE!x@cH`PMHj9Fclzjq2~Euos~lYF2JghJ6pmZ>h~N{?<4$Hwa^5~n{F%MK7Zt~&R@ELLg&@i&!m%^Lr~uboD>vk zQPE!m(<6)zbQlj4ZtdZC1pm?>k1*okTbEZ}@-gZ%T_1=}F*Dl7HK(MoM2-Ps;|hLt>x;d*E{8m0_XghbdDPDkO0e*jq0Z*jgg)+aFBFc-aHsMWVI!~96=|uT zAGlkXDe-<{d)d|0s|B+SGd;8}kne1Y94g_d#eVtp8WaoXr|dS|MA@7J9(4)R30szN z8a?cLh0r1+d#b&hur|0@pmDPw7Q2f}%j4P%zMng}n{n3%XsJdm{f;opRN zCi`lTqtw2KoEmHiP@N(5DG*-pXQwR`G>_*KnCQa-6wee$Dp<9jE1nAXXwCdLghHa@ z*cpRe?S}HGG-28cBDM5P=9rPPfHU(Tdp&7=Eq-9zqBl?)=Q0F2lN?A z@`Jwyq(TGmQh+6vP6UDe#=3co`C^f2X-s*nWAIyD|FPokgmk%qofl?_WPG1-A_ZAC z%A{uEU&qy1k@keDfl94-urUs)#2Fuaudf z+6U$lSU|(26A#`Iy7zPB6IQXqz^UE%Ih|HzXkp4JIcTh2dhdsTar5nbImwDUj3Fm@ zZ6vp|&($ZqcB(upYajp4{XW;am9&i*{dM{S?AW+=agBlu8?Q8SA+whda7^n-I@cl~?mRL9(rm3JiD_&Vb3bUU zZ=cgMBxVcb6KH0N$XOM8iqHW2=Lq-tix*>`X~LA zhbbFy2wE#XlWqbPR9$G4$S&11EFx%C)*S7G zf9|zK+&|cUl3y!6d;v28HXd@~{BXvAm$CK5!<%O7w^JV|sEtdsv_0iHd{4tJWwN|O zxiix07_$2F1N{QnxxBD9#1nFrzUK_ka|XSMVk%iw-mjIdp7VE*-mEJYPjvZn>K?pA zz~S$M-Eka`-VsF~jovT5xK+#nq~Yk*|aSM$vwECOqIUv^&PI2K5(&FW2_ zBiI;BlLh*Wp8nQFyh+{+Msv?0|j24R9_&GNke_`rOrj?qWV z^_l8+n~yl1l8~vp!_pO{^!O?ID<=bMUA*#K%Gr%@Gw*+^g+@Yq<3xO$44) z8csd8Xx!TUg|wfhaXfDN(!#C5+@CRcx(X>$Qhseh&A{x6!l|%zxCn7jg{FW4z=KuT z&!?T~+!fi-m@Y51lmARfE&T<(GA_a2KY6I=BpzYcYJYon;6-&@H?P%oqk*fU#C_8_ z`jMIw!rv(yp(#%05ux=@MVu%<@1v)J_PI}DBt!ppnRps({2CYeFHjr}|4nwAtI-K$ z4Y$+9c00J+@n8*-3#0B;Ea+l=iKfg11O4k?r1-g2l&bWsE~@JF8um5xI?T%3&6l+$ z(^ezClP{2mc=oHDYhhZunwKf1i(|jTanidsC-%H)@AQ_W7O=F{8=bJHyF#9jpFT}< ziw+LdI>|jinz~7Jy-81(F#{}84KdB zmEV@0#p? zNv$}wxRJFxk>Hgak)jjx%-O$61$`OxsRz2bb6v%^a$wu3B19KE=19+kc=;7b?GFtp zl>EK9bdnFpP6b_WfDg)%503!f2fS}zI}0gscP}lv!NC%;1-w}eO9JbZW#QGoBfOW; zIDpWyjKyR)&G*$dB9CcX0b)%4MqHE@9p$2-B`};dN!m>>EM>efEGh6m3#e@smI=@HuVnnQX_Lf?t5OI zO?XRLi?fG{fs?Oir076DDs(z+lx#bDubO+ahkmWCXlW@p;T$rgCA3Eul{i^Mf5vpP zPuev8*L2<`xWG(clCU}stz zU1r$inPbZRk7htSAkv+lT-VFM3-*)Y-K%dxQZUI6j*#ahg*}G{OV<^2-OfkP?k+=` zapgXHj+Ravf3?~XWLPf-P5N87p)cIS(5MndN}0=E`o#W%j6eW(Bp3h-WJ@=|zQ*)v z(H83d9=iV~<60AS#ZOe7n3m?fo5`i>;dvBH%OlJkKrwp2knVmqJKfl3o)^WP)(Z+? z&wM}gNWQdN^MLz}(9syJ$Ty@G{@B8F`o7m)^2K6*S{I25&WlTLNAWieUbYi;d%2x- z1tZ6^P4*NkR?l-h$|;p_&R4V~lAYA5cAIfI@-jlnZ#&5$XlG()EqqcLy)2j9&E8A$ zSk_QnGbqj~hs4=QP1b8~))EIh()^Bs*8Q1zEiblrs_=-*p=%yHDqA^W*3Tn6)?<^N z8dg4K$EQrYNlY*(ML3uTL-bG{y4fe>v0U{EWJXEzdwGYq-&1rHemnDh%C@rDoNdDG zS%oEE2>#mTj6ED!e(DQu>SxJW+y8h06nL2@Y47!|{Ec3yG+cd=7Wn?L3oa-7(6MqQ zfqo}}nOa}h3raN~#_QGB-G~xvg9&X@8HN~R{|Muf#IP}md}O`_HOxMDVCBOgpWyd| zL2Uc=m{=#t#~zdASpFI;`KgKi4EKw~KFp!hdorcoo+Q-tr_3H%zFcwkHsgbbZWdb7 zV@PGhVpvmGcB#(9z+-@WW33@Qg?o$v(f*Q8C}27mo~mi-T`_L4ukG-k0lR?ubbU8> z{a@cy02-Swn#-UpX}FJbUg)&^4=(OX_Pc&MjHT%h!m-_56qc7xHYsp+BIf^av79}^ z<93?2-C^I-cv<|d&ok2(K&-#caj&)`^dIO$?_*gbhdQ@qpM21>5ER3n>aQ)yV^{~iY~VPp;_7yUs*SKkbiE%j|t!1lFa4aZR7P|GTS#gA9H|Ku863od{|iq01>)zbVST4n`i z?L?@SS3+3*V)Y>x(fd5iJJJSTd>t}ps6Ws`?h_*I=4i})saHPtYl=skhXqR{_mc9q zHVZ#knzQEAq9$vid7sYW_-}tyMlT(>TA8G_3hHSMychjh@8uY^9LIFw1w)>f#fMAW zLsr$3g#AZU|EQCn6hbHZg@583P@TJD-0A8#I$ZYY%r+A}5qm%J1=69*HxV zd*Yyw-esP-y^b52Bkgy~bvKu;efBE+i5v(?uG<>a`{?!|^$_v%_-Aq4_E3_BH>^Ws zyFzNXMx$C=%OH3X^S6{EC0}?OM6?EUdbJ`tKkS=a<%V{Vob;_O{1-eHkM@56WDNQ| zeT$P2)s#)L^L3(+hYkrRom~yFnYeOY$SEBBoeiKI!KK#N!`G!NvKQ^|rT|vc2PYD& zyW3(l=ts~^baNh;$~MFY4o$hKb^lndi8h~Rl!X9N{|-2k`Z@C0ma2APB; zwpi)bVh*Fl`9G(^ZWx}>|IME6&M8YDoquUrv$??5VA-o8Z^-Z8Xp|>$O>Q;AGC*@T zyO#{H)qU5sh%4{xrguw?)){IK9=d%qkTMEsrDgWq_qAr6$&f;eP1#&Z;qDNwn#h4s zWk0zGU)SA?d)3);M%;~wNJI>$l<~f!ZDMhUJW8(p<~<1_>$i|kC1q7an38x}FtM3~ zBmJZKk&0Y1`F9Yt?{)e_Tv{OP;zsvuZ8`Sz*fvJYE<9zC`K~@9d|~sHJi$9G<4?fZ zNo2=0mKk?WiR#zMD7M$#_Scr*YX`%xp^LZ8d`;Yr*SLZzZ}hq1$8Gl^(KX9>Cpy@V zrhTlKlK#(wnWe*zqoqBa5?+e6HmfW8^v&p@!T(3qd-zlRHvZ#As1$DrQK*!WRT-J* zC~=Hzk=?ekg=3tPBxRlKtb;h&WE^`Yd+(L)*z0i4;q2e3_kDlv@9$qY=kdC(=k=U^ z=bz-f6H{s#qSX;tmlpccnInTIK1)kVIF>HIu-eN055c(ba>|Bzu*!NrI1<3GXjSS+ zR==Wwvd4N@Q=A*P>`OBE6o5aAuYRA@RY{^~J)HS{j(=#Zd7`E|ZmcM$7C^!inKuh&YD9KgN*N;-Zay*75-u#6AMEAGGy(}! zUQFa-6yhJ1z$l4EG3HFZ7~---Ye^8_{aZjo_76N5qzH#;7>_0y!0mBnA z3h2Y3810!*t`L1s`&;+%*DyKMm6W;XO9;IDxaYRyoI)2NT#_9ivz{T7u@=x zreyGTh{i%5lkZ_DUkao0o>DEN2Ji zOGIX;2IGl+}M)z3;gsq1el9Z2qKg$@F zaaP`1Jcr1C_O)E%36A%mC~Sh>Y+vn}=qo_0+1V$WS!0S#3_j&b%H?bljD+t#kAc~{Nn1tSh9ZSKYA8C7eo=A~jZ!2$h*I@CGH0-Fo}ztp0S_Ca$}=9neVWkZ z#Z-w&QpLfg{rAK)i>0bQdKpS$SvkKiC_n~m9}$WOOVBzQ_pKtz0 zC&#g*$C<@IY0KWQ?^IEGVz=#V<9pXgjGZ`fx-YOhcr8HKY@`A%9TfR5XiQkipe@O7 zqKEa!%bZ_Jof?6BNkGN~3Fsf$3(rMv?rk-XNvKfVALn5e8ZD z6T0ERUN&Xd(5er%VB->iz zE>L7(C5}g1p{xe()IAYbExE{mgt_*@K+i1Qdhyt>B3{GB3hM`E1s5DzH()!^S|?nEIsWi zV)ru0VRUc&^UR0H_WwkUMvbX{TvyoYd)(`@5yX+qJC+*fJ0KH`Q^#Orn~?0g(HIxR6G(P&k~!Ek$O(pEMB{blf?6n^uL9{DS@>zusN7_IM?!9%yb7g*VcjUsG2I^!A}DTUFKb`zx26 zCY?vKvEFat6YPnj+Y~WtX9TM~(2RH)$zcDIE(@8uLD%`f*}mZcMXL1u@Lp(74rJDiM0_zFyu2g$Ds^#YI z&xrgZ_PUpI*q6{;nf*`~Dc?oo3kOf79Ea@e9e>L3CpW&7#z^E$KyF-zY-Cxh>R^Mu zgbE|Kh3?FW+(e8=XGVIXS6{Fzr}AX4V-ePBpj_;^6~NvozzSK1de=9G=6va1fC6IkMaDza&5WtuQG4X?W?x9KY46Iy=8;Fh3t~Yx@_&@!{|f+h zX?$`~Fo+fwGBf=ud)uxs5h{CqyVbenDibN2A?gAG)sE#Grq|lVh7%fi%ydB$Ve&46 zY)w3p*kPwXwBa|#nl`kGx}T3WbrUO|d2!5YJM1rc*rYRE{6F>(JL)ZB0Sf|+r{-o1 zJ^#qINI=02wzIgQ{hz3s-`WMA9Dzj#EsGEy+zwl>W#A!7#?nr^m$L)~R;synoi(29 z-1*xfPuqxZp-NJ!J4VsTkKi1P5NP!^Z7>vCoIL`OJ>| zKdd2F{{OLt9fBvebwBIw2^@tB-Zt>HIUaL6KS7yiw8cWX38s6@R3U8GJSA(rZ*Icg za_F)_HK5`Z&(`U&p0#4ZV#1`(;3<86{4kG7i(WCLficM|( zGTex>K_i8J-xkNrrTifV<&=NHI)6uZ4y+FEuGVG~&YF&}yfd3`OX>&_`ttW}A*F1H zU~mS{&_|wf`NehnmVtQU+~t%rOnPeNe0q{N;j}wDp{Hk&8yn&qGMX;OHncNtWMU&^ z^4)!P`|}q)(y=(0{Rf|1(~^b&3Hsgu-#C>AQ|iX9MEo$uEqukV8eI()ojH=|=SOi!=jAC!6z-72QE) zV_w=Q@C{EaL}xR}PpWwYn}X5Zt&5xUv2LKpzP+ss*7X-c+>7n>F$8_45Yw4hMns%v7!wK^Du zM0zt4GNUTlbo+g-X;1FM1zOq5`g-n+ZNm%`1^T#loI1EFNwEz-zD=c>qD{?_q(#U= zEOaZZ%=$9a5$}>EV~E@xzEz5?<{&F{PIi>#FNR25>=EEC9g@-7B(ahs=L(5pap$J=G|;IJFZZG*B_p=6~N~0e(~~C{b2@T0~e`*K|y-3B-<}*x7mrnj(jB40l~XF)`kZ}AN^5`OLl&L zUNnD?B;Qb_e;4hyS+U-8VD2A`D28!(T-Y#>+94rmHzGfwV9AQu%LhFL9}K#A1(t3w zPi{A9G?AZLaK>9;9%XvyZ+%*jw9oJaXWFMR+@PqfOwM4(K*Q@veuP=x*=f$~-O@yM z*vSEz9HgZ(D#QLHRq)3z7u6#W5-$nECrqpGC_jrQArjX+l4(JQU&%-!a%I+-*w$-8 z2!H`bFgE}xmPeyj@YC}IKrTi@WtaHLXg6*IJfK!g8}2aI(+8_{OFQDb(2J-4&%$LK zs-n0f6bc{i3cl5P_EJJFS1*9E;N4A!2Xo?v8)ZO)CnHA_&E`Y)y)6>u09nU?jYNO7 ztRDu0XP^c&QFsanKP4p0p6P1q!r~;R*FA40rW_UgB2 zP&CmY-e5R^F_1Z2(Y6{;(;>!-dgU)7h9?>~pkuo~@;3#g3 zv}qikX);z4@HyMCK2UeTsf_w4a?ESn&;MhF>d^CI^GzNd|IKGYNW#z`uCJH*d&+NT z>+Qy?v_%kj=!_1%CCy`R6bI`FhcL}2$_>XN1GOQNZE46@wE_fyN69q;$Iq+yAr)Yrxdt#1~0}8T%xBl zKwTM}uD?m=e11p=Qiup@@GEXyYtR!$hu!NGgwf|$ zH$54ABsm?D10B;p*sqx@OJmea`o6@m{_tDQHgEc?86BiN&3HJicBJp}dGwwx2{oLd z4rTg63cZ&AzsDd8#2i0tT5auC1l%gU3Arc{RGk=oGWJ}$h$1$U;WGQ3QtU39`HH?d{{jQcyo zTTob9D)5uaQRT0IK3}JG^*b3|(KU(NV#VE(7rkLyFT|t5BZQXU^tGK3X!Fey6U01{ z%j{8d?iF^_7e5{6LtD#US{YvLp`1uW!>TBirCT=&_PsIF zrvrU6`D2*OZ3$@c;3M{cq3`yHV*IwG-7dt%V)Pq*8J8vTNQc@p)m_5tJeRnWgJM6n zm&8SsB7177Vor=>W{UlS$G~%aWw7j5Lp`;1=vdGgyGRnrN;_9kk{T_wPFIsGd}gCA1%knHs|_<<9=Y&!8!W}<~*k6x>^?+K!*&I_t$6$?gt+sm`na}YlIcI*_F3N>kbIF)Z zyl&Sq^CzGE>KONVxXj^?%G{FqM`ea;3b1)_R_IQlZzwycFfBTSlv_U#xVr}bd9BT>3ifw640p{~- zaavuWkxFP4RFU=d30FiAh1S8JLTksTTKTy#7QB^BjLsWtsjXzhlyMfnz1gA_X6|Ga z5vT!Q<6+!~7uaMdH(g_w{TA{l62JrY?r>k#;g+Tid+9DB`AIu;VcFiO#Dqy^Lu<13 zF^I(>t;up`wkK&vCRTpdd4*2!D%RZ8wm!pH%b92398(LgqNQzJhV$V0^j0CHuigOq z#2@wtTHY&KL>^W)-YaKNJ;ZkRlbtNDb1JKfuql>G+tt`DKl@yO+-W9 zhAnRd{!Eg(F-Drys_mfa9%80~2-!PPXqus#SiL}_Gs*6~N9dDkZ&e;znVn<9Zok!5 z#x|_@@Wx5Y%!!=htn2h{9+^@C`A(V}Ff%c0We;N{@o(j0T$51*RK*~r3cyZ85OfhEdD*= zE84KYA)i9D@w@`KklKzC@3srgrRq-l8x}@V<{6ZD?w&Q~eN8N}oWmUy5-kQ;A_FNj zSy9?%@Z9roZ1VjUa;&|S7S z+4poRT{#I_7$p)9471PhL0br6LUQRIPP>8+L={&qCc&HyoJv9NhD91BJ6EC>CBdUz z_9iKU2C5Y$=YRs@ad!Mj|3iiC-w!y;<)YG7JZi6>*ft8*svU2=OV<+cbK>k_#zQj~ zVWR9`8yOR3e)TLaN6nnv4~UWzMB;K(#_mLg=Fk{xmG$bJ0NXhci`{W?f!M<(F2V0% zdwu?&Q$B!mw@4#TXOyWg6KH}cpxsZMs)|@x z@#cCY>B(kmATLV7uoD;+$XeB@n5X?!%|>ou`LLGoekEs!Sh3;`NK(jR;xtw%-pY+a z|FjF#QA{H4B@j|%vXMauZ~t9cj-V%qg=Cb-UlLLhY3D7VN(`;Iw#<6<4<_Ar`Y$G} zpLu`UDeTX&yeh=ClhKpy7e2mR{g~84QQ(u{2p|&|sE~ddpWhafCwnzB@E6@flaCWb z9kw^>_NUyV4VBvdIppYv`CPSS8;J14UfZ%KbXz&RWGU!pF>mThkj_@>N?eaR?Jg0o zY;r0?@h6^B@-H$?<;Zk|v;XMf+^o0}kag%Fko{|vqTSo(rh2Syb6UUq~m=X@ERilbhcNP#I8h*7uHq82&7HIYO97{Uw3Q}F zPxHXt76)z@(>{tNE79lxS=xi^CCIw>4+SY1zXkdOOSAJ(8Z%w^Hd={U{Gs9g1zM5h zysk4IJt8x(Zn3&}Ss~*LPGg^zA|P3kLO;^RHF`Uzi*LB00`6}+%SLxTP_pcZ`Rd8+ z^nl)h6>E0tOe}*Zcw{v=5ovfj<|>}Bm1Cx8xxwju%mUCt`=*xPrT?N`NafcvWgMytCJp}eta zYcR-6a?ed_fnM`!Mz+{)X*SVKcmBXG|GX9b?pe+>zLXC#9>`ZqetL89dr5B|2_0*- zrF34%h>njoZI%z$xm%KcV?O+}@^*ils9;Wz?QWf=qRNq_|9q6slfVWWM zmYodf4gl+c&2m$?u3DxH(cQbdz?=SgSv&u{aZ0z-Wu0f^qLcLghbOgs03{&H%+fU! zPqy&>+%?}XT)scP$><1z-urw_B$@@A`TnbE2g4e-rbC^m?m2=PpIpf~0ng2@M`zNx zHNGixlXL(kUc^liBZqM|v(sbZov7hoZQQl;(GplyB*Ssjy}7^sQgtM0L)eL>VlF%1K# z)O669v?%`;0^+QL$c$|)L<{?*oJU3Es?GkZjKb?8Hek>u(S(w=(mTDL`TWhQ?jvnQ zpoa;tN3uq2^9f8P8?VO>2b2o42v9q^#q3fH#M=|TmSWR~52sgxbOWW(GyKbP1PTRC zTP-b|)F481gy!$1C*!sv5?BI0M*R~j0DJhiDWjHVEhGP9{8Svu9IsR%qxxdi>-EnW zdfmxVO_PX5RpAnygQ3qB@z%2GvlM>Vq22quW=VK_+*fIx?K!;}n^~t<2Jc z&X4^o^0?oGSIIQ7ZGmD<<~lrCqjs*=lDP3JwIp%h#fdX65pzYF`AtJIvCa)ulv+Yz zquM7YJAJ!i>Dv;45oCYEJ^YxP<0^tK`^KPHZamhW`xVC1K_<;e2Rvm|j6x7=alh5tqns!!Le3o#0EqkP<7WM688kVMV8KujbOQz>H{y>IkA`R zUf!w-Tc)>{rs2?5FZ9M^(GJ(&XR`8@wM87E+{Iq=Z{--QgZ4Hajph{9599h#s(dq~ z9ir>R(Pfu+XY>asNWVIGIXy*y(cs4mc+c@?`{WXR1f{}zkfT9{z1sfyjFHnM-@*!X z2{vK=0S06&ZJdbExx0BU+O5@z9Am2KRuELevY$RFYG`wbOd58;|5P)${9HNdgd_e> zs{3v53dbI#upzV)<-Ejs)P7-*HaUvO&e%_)mN}?qM zdgRV-AV&vYiOiVKvK3D7yQL6nuj@H5T&3yW0ehJt>HU9gkx~`0>G5W7gf&<+w$`*q z>8y>&`<6`>gp%`>j<$b(!z8$uPxw+}Z2iQ3(L`y{`%Oyl=g}5s+FjgT)0g#LuBkx(11z9kIP6B#gd0 zGwvl^v@B>=pd1Am=z={oPs`a0RiZeq27D2;?}y*~55|1CVV=Eg;3n_}Sq&|G2C}=a z74AP~YhG2FLs9X0&7JjosBOj-`3Gilk+yq+Oyb$1BiV~q9P`Q7V=e<7mBpJIFM)CS z+?93+={<#}SBxfldvYbTX&}uJc!eQNX0kxDw>t$jSKBGx%Ge(NPJc`$??ao{c=M)~ zv`qQ_p}WpZH27zhjvPRaBm?qNZ8EPs6mVr(pHPqn;+jiG?z68vlb|CbR4FQb6lIU#1 ziqaTb6!^n9FcKrOSymrD+G-2s)vJz652p3*)@vNr!16X zQJ}0UdrJ!UO%*GD$h0V&=Ai@rSL@zKAW4|5BwG3%M)Tqy>EEo+JmiM!N)yOurR=(a zODao#owxRxic8I|z-)5smm`Tr;F0Hl_DGpBi@i_fF)#=wtj~LAgGag!z4%j;RQbcb+!go>(Z%55gW*aZ1fjK6 z-+8WzGCuNURh#CaOpH#{q}4tQBE!D#ntxM(T}+F|NHrEox#=B}4B##H@??fW{Exmndqov6-=&6COT=-6I79RM#n!TBKh%|o3LZvN7 z(?C)?Ccu4K#xTI=`4azEj`&-T9-L=y0M+r$)II18Iqssqv9v-z%Q|!3UFax1R7RK% zL2_I*jFu>BiWw>F4#3B*NT3@BbH8lq20+gz5*C$8TxLJcytv`SUvxA5apw4(!Q*dus8sv?+1QuU zddLLi1TV~P7eBcCYo4Qdmm>{SUvEMUiICR|raN=v)#D%zep!w0Qm36Gp7nm`URs`r#Sl;0K9gfxdqxPrLraUL7oeTH!f>sthE?24Jn=Y^e zp1vXpC}LrlXx&Y)4776PsFFSGS@p;cu3J25=GyjAvHR^DkB`1#Wv>M-{WVY2r{&u2 z$L)X2p2EavjAInBZcJ%X)iTi?f#aziLtXqY5)X5Ngl5bFRzo9`Zi4S)zuN;~{7}|F zj;%Mnr9NoX$$Vl*w_-SI#4oU;ra38TZ5EVIn6Py%*c{CNv8qMRFiP&q@;W{`OgQLAQNRzh)VF7=hh==?TUdSk+^-m3 zF~3#2K6bfj!tN)EL>rLXS~rAD1;d7as5rlnsz2|n5L z;$BlVsjTT{(2)B^fU0ao@YqhZhJBF(uZu$?=Zx~|Y~4twzessPwNK`Cz|)6sb7}}{ zo)y@XRW~x_e z+7#~A8UXY;RkT_7Gc10XVh$rsOa&tv_fgqIV!WVUMw0aVD5oQ)ll0TJ;R`cV-UJXP z%ynbHMCU%tF!$LNJoatkY`;A9Qn!~su5Z71!!3GfoczkALHWYK25J6um#PHZJ=8M%05 zeYChCEQ^(32XE5ELI6s6Gmz!wMmFN8;J4sf76%*Ro~r@*Qa5e!JS{=s^K$)VnbCw~ zs1r2it>gC~2Y@HC_~9nkqd)l$gCM7X(w?EOuVI|&3QIB=}=A*0Hgq;LN)uDkUTkv;? zCH4XUJ`hfobfYb_SS$cH2i_j~eQfS*;cWVvduXQWXOa%*_s!637srF5b5VT>^&$;A zGr+<$0>Jr_`Ev1~rjfQVxdScq>6|!cEL+1EpX({0lb}p1f@twiS+9T2ISsreQey17z zs+e?tNu~1az6(uZi8IxV&f{#Yg(L5o4|tcJ13X!5F1ANq;sL0|Rr|S@Osw0Z;auet zT-C8~N#*5A{x`$Q0N>6&?~$4t5V@!>kt(2Av-$g*yK|JR#1duw#$ENZ8ouW|(a*_` zN9b)`)>GyLH=>1UF$s!8QB$K50v}x|Gt->ZQy6LJOn96@jG%MS(n@Z!I*RP^tbBbsB9$mOvfxl{GNfO9j;?91nqc0t5j!47{I$3!S|5H_#eDq8R3fVX1mRN&F`%+_ID`Xcysa`wQOd=NehBd+Ed&;)6BH9PumoQtv4`o`C@MNoIjnZ zc31pMB5s1{y*RdfP380P2bPNq9vgbF(EYc!ZSf^zNQP|Es;ca_toRY080w=Gn`l*Z z-^QZ5X`TAR6%X?9p>(6omMK6)nHTvok9`)!9O&PF%HC#Ich(i z4(N8^xl0tXB#NOMFNq0CN_?(1-i$B_@#aUUyiBu8v z%fSyOUP&6Bp5n-DER~GxexlGeeV@I5i|2SmOSPXwr-c*CU138~?mNGtOhSPJ`m#ty@U(|iEh93MEiY-{l)d5G#U`e+ zQab_=v%u15UP6n9X&av8eKpC#&3|{^1gHXz{^S-G=ykDlFM;18a$#S0KFt*?wv8({R>>=h7T1dhFk)a_@tUo4y=QtR|Dr1>Fzy z=EC%yECi-@Rw>9pwo2s`QP1pia4JZs^GOUP;BCNz{Kt^9zO>37>8Dw+8`Z`$who69 zw$5(mB=Cq~fn>9VY3#G1g&KR!7>?J%KlZ}gUZamB3utwwS7?WwaZ~mZInRUZ;a(9g zK6!HDwNVQE@xfTuqb7t%K9sIfE_ye{c@Q)@jIjXg$ko1TKwwK7NvGjBwy}AB3zV&) zjBg8<=?@mmGk5r0Z}dMw%gK4u;+{*EH@YeGK`DZ98oGwQ~EmTaeu{J0@8E zx=J+5x)`DnYo#kFpcrNZnh(!dX*H9?s;0iCn>8(@M_w0CS6$q?-f2Gy_nstzIvD3( z<)Zd=K4~eN+tu<q8}%A(89}F*ikn-UmEFo$C+@nc8q6y0 zfg7Gz-&R(2Ae40I?z$8Li>Ae0ON%JzernP>CW6-Tv_HTTLN>Hn6e^OeQ;uFC`ql9F z?Drp$&O%$#rAA^+s9FZ7FK|Ir0O`1Or(g=9TGqo_#=1`w^8B3?H}8f|;{bD)P80VO z!vFltdKurSM%KFmrsrI(G2e-OZH8)rLMpaTdqUYgHQlYa#LwR{3FJ{K=lOQAxR4wf%cXAEi6xgd`)Cu<_&N z$0+Y;afYqQ%fR8;=ANQzYt^PL7Sng2(ux*D=SuVIT)A?aCsE@7z}X)^_EX*ZIvu>R zEM@!zbAg#U*2kmGX0}G8kj5LZ1^bnT4DMK_0XJ}mG%X{r?lu26&Yh3eczo4O{e{Pe z3(|aQlBgP_uYwMv%nAQOZ~@xdkFVLOh0CHTG5X^#F9Pj<%YxH#O0Eyv-iChst(?`U z+Z0S|oFvr{`@Hp2pl99<02BbBiR(9?#aCA8RZ9z@Mm9$atNqBIXkT=-R_Jk zCJdcOyxYgo&5Gd2PfA)>O3g;NG6^VB`#GvRQ_(G<^OkkjXDJ+i@ZcQN4l9W4BhLi% zNl-SFYc$5+5(uf{DSYa9y!D=4Oo*=0X*WrDy@T-tTxuNvNR^I)_|SrC3!fJem_@Y4 zOA3PW=sU>=c>VGq5E7J*@~P+tY93am)vnjB*BqCNCAmu5%-Ic0!0ZG0wRK%)!;pcl z+P)cw~4;@BDT@wG=V-Z6OI6KFR!J zSY=##oCmvZ6v(lY8#XvGN!vKAJoGzS!9DWLqx!+8m(T|utW2HVj-+hQ8b%r(RdHnW z92gwHESTq<3@kq$n-T25Jp(s|N!iQ+qke+2Ay_v+R?>2@anQ<}EI>Qo475>b_rHhLEpMx{08H4`&A z$W(4k;CsMdi0CN7Ez-%DG_Jg8w)Z0-(_XUL4v58UPf91`j>S@jyzL^nWm^VL^BhSk zi*=RxIXWsl(Tl!Z9K)3-DWJR!Kdv}2gNPOpvIwF4-^{2oqkI2{6p^cKsR>WD z1FE5Ui0x<-H{zEE!~7xE4Nss6-tWw3YbW>QhWKv}W36VBD=nil3Rl~5+XYxFM(KKy zp8iACXtqlGHAON{))iOIJ=yln@~%4>h{6@fem;`YgvSOJW9tXS0R~&tHa3lA{mR4r z1jcdwi$gw{d&>*H*_A<0G@ePFx;ia=&#Zq@OtzMZ<5|)B$510v-LPtszx!?S9_@HI zj!}izDzp=pdm3N8m}!VI*C(d`W-wtl7|l=xuIm}x_wU4a47xWxh;ugOvvX{a2c&lE zF1X1yB@HyO%F!Ov1DG0E@sDEAw7~~x{ObQuBfpDUNw`@Xk~7EK_9l7yw%kw%=hn@Z z;h}?)cgffwkw(8g#qK{RLpk5f^H{I7u&qz@iMdV3^Mw^A>FDKW$2yFS_=Z?W z`RG=exj2H=@GkdvBzKb|S|exuK5&(>`f;-=ifPd&)3WVLU~j_K$B%yxNsAv-_GS$y znCS6W6&L2u{DPn9jFjCUD&2DA@v(BZe%~3TN*)vt5PkrfsUF6$yT|cg|F0L|N48s3 z$9aBM;;jQ55b3VD?dWkbS-21qer?nDb^R{CfiCJEBa0&b>92V+hK$?p8j(}IqER!K z>Sf z`JwAe9!(1KoB~7h${b<|KQpGE?r13zV|KWNRpgX&F@`=@Fe6>0!6)e0IS%xia)D5r zk!W`63l?xhGr8u)>UL*=x8A@ewd1#iI`%&50&G_){P@>Ciq)~=^-+8k#f|*26*bKF zk=sXJbLVDT#>>#zvx1rE#!nJ2_!56v<@iIS}Yf2wyyCkoO8!gU)M0o^#UCnUC_LlFY?SkP6GQdB3E2Dyz?(C-mAzP zliKlTagt|xNPC?rIl;1VPCs)hn=-^;PdC7(hB|w0y19QvOwz1^GF9d({=*y*~DLvg8YbdT#f9)0{{H z@kGdbgUCr#-c-8+kzz8IN7FZE{3+B3Sg`}S5Zd0tgoj9&3)q- z`uWXs;ObUO5UXWiW1#R{mhsN}N2+S=gXN!3_x-4Ls)@ULtBL!e{FB~>; z+i^>vkN?8U>3EvDxPug(xvO^g z0h?g$l16EyUAAY>C{SO2c6A3R83lwF?nq(1D7Wi?HTWR;Q7WEvBW;6povOX z{&m{T0_u{QDjEn{NFE z7Z+02vzx8J+id-!XVEMBJczbVD@PxQ*{DC&&uJJ03&I=-c{Be%WaK%e)xv9qUUICJ z*Hwione#4PR@i?CmTx@!dg6H8e|lG-=TVT_R5mJgJowFd>(>*bpXkq-M;rNW6qU#b zwFvGjRx-;Ce)vrIXaru~k{T$?XaIN@m@?vVbe&h~LO%YZK3`h9+3J zS?#vT%!P!PeVbeBnWh2D<2#zOaR~}M^Vwg|D1cZ0K+Bh-8&NOJvu|_%*sv2X6{WP= z%DdQ!E0y?)xI=WEr9Bj>`1OoIFw3oDXhc%vfaQG^PiKJ~dhVtd2Uj`Yd7~b-G8^hD zx;pQvArJi!51yG_#44qA9Y$<)>8RYhod{3bxV9tO^__!|jJzQHP3RfQ97SANQEv!& zl0A&I7!5-)etbM%;ET8uEP`KnVD^gk)p?4;ws#-FI~ID?b1xje+Jbe}a2*xzKK`!;*+iWHB5d;?zaOzNR`7|fd`{~>5r=Ld4fujmiN ze6rFSWIF({!u7Svs99>8(^YqB4DFwz{^08*ixNVnJ*o@d6b+5=>gGzs=*7TSlT?doVpaY0@e{o4-t8v zG!in(x^m1x9$Z#kA->m_idj>>FRgl%U3^dj7D(CS21W=(HARP(>`1?`OXi^bVI3ur zE`p6H6rws)+1NMppc%yWmPg$m9%tpp*Xbm%^C;G2ah=^eh#_gx=9y%L1>VAw<(8Dz z%ok6RYo=p`v2%?rggap*Afe}8IEPidw`^gb(CxN?tsJ7B*&6Z9@33^V0xV}x$gWl0f1o2hf$qf5>HlNv-Q$^l{QqwfD&Z}pFsmfxSSW{K9k3!q zSO}e+a!gJ$(?N2`LWmeS=UmP?%puKr&T<@P&Wtf*cDP=>KcDaK_q}e{^?$cpw|J{*G=uSt`k|o*Xu-(lw2Kj^mPucuLk5Qu%ty~`%6?gbksXY8AD=Piv%5=|0a2=UD}_X+b+%W zto69navOAy@S&W`De)`FJi0jD*dW?h(yX`{K z&I)#>YW-p>qpe5X9XGui4=r=P#;@mc5b0Qi9jm_v; zPc}5{jt89HQOlQ@D899$T^2OO&=7olcWp0>QeOcCt&0(og6WVuj`S7zc4x!`B#Ni? zitMPgJ6q%LiI{fSRi^*%du}3DOnoy&&=f_Ysrc!!P)7`O`wj=m=m%2tY4FN}uWN$trQHxlB)P}(*^q?@s>_D0I-l|nZ`Zohpy z?SFBl^VQ<0)!)hzm~98062*Jsi@x0MR?EtK|Zc$rLW~+Vuj;WZ-Q-ui^#h@?W@2vh`r0gC0*5Xh8 zCc+x}uA*$scg^6TTbe;Ldw19WC(He87c-*Dd!t|I~3tH)xpY*|N94a zPpOSH=x=*)*3$nZ`Bw`1PO);2_c-Y&f>Y%RqFokHuz)FX3D5e?`ee zOA86C?)Y!npflzc>~opy$1|7rx6<7M@y>iY!F>C*P(YH@BwuX>A;TZ(hx9qUE8)Kn zT=~-x=s$+}b;JJ{`LJoNbe}wJ{c1c0%>L{u)nNV4_$z~X6i@2V*V6ef4TLyw z$kQ+l5-{!_cOyvM2MYT0>h}wIp3NiBpDlkniGf0`upBfcwA}hru9|Ih8gc54$uYZj zybj^@b_$iLusvpEtj&@uo1AIR-85v}49-X&O{-Fj^X1U5h`-~J1u@MemJO3L7pZAP zL{sRS@f#ECcQi@g0E)az$XxlCBAJQ(y;bpeL)S>~VcOQ#K;4Zs%NeR*86*512zUFl z;aYRCUwV&wnznZNX;@>iPlWZ3O<&4jS%@pm-3hnh^d~f`wzmu8QNjCoXOR0l=IjGu5Lr;55j&&+G8GBxO47g ztCj*Z+3$6wl6$Poq8P|^PL&%iQF36jj%KcP6EDj6AzaBwP*;ejTJ-rZ?;DRG3O+!N zWUK~QH6YeAe+5K;dJ!bgPM{%L!1@;SGySz4j)}F%zSP`$5v@m`%%qFlFRz(b%E402qe%-LxnUmh>SAfXN`_&|=#?efRZ0WbagKtZ z?2WS$xjXu#L3`L-$n6@nzUym_7SY69YE?Fy%Xsl&e!H`O3PKpD<9zg)kc2eCyi!(0TQB*LE2xU5rgy6|nm>7nA_keSOj|T?*Kx%SPwifbFu@ zJW%_Qjtj@HaA>DjHjuM5q{7$BGmB$IHTt73E2F9Cy`;eC-p&h!=QHudBVFw?CvQT7 zbrE5chOF_^6R5bkv3;q+b-g=L^SK{+CNog3NPLc2I=|P_y{5CLRZ`)<`n*zdWNzch zwV47zA|s8%3pT-$LX!xXi_p2iyW`!&oh_5WTd=nEkb*J4v(fwR&~~Y>N=462?GN8W znX{=$=ezxMTs2p=B)Mrkf+sxI50#K@`ULEouoyATVY3zG*Q-ags+z|H*Z9XH3urQ# zoOHqvEcy{i8@FD5Tf=Gs(%`xYM6z9WW0rWFh*3^^Wt9GEV^^?Ai$K@;jy=BJ$-tqo zuxAUh5eW$9c#}s_mTL4yDvOg^f?}>XG)Q}CEfx@$Q`Z)^$$5fBuP%HjVrBO=7Usa4 z3$>s|%4NFlYm)93NzY#XlPxe1L6=+lK;#oI?8+mI z6|rYel{vN+Z>$Ky=vL1K?|*rDRB6yZN2@|k8NR!F>MP7q)cAxEb;gisr-(FTelcUk zB*YdCkFQlyf8iGo5>~zJefI}i*g%$?)kKa+uAW}+NXK#5pG;s`uGB{bx;kSqR4%zN z1#nY*87{5p1SFNq?=4;rIg4hT*nT2q%Jvb{dU0bw!Dju+`602Qx{O_3#9_&T7ygxi z453(5|LcEw(~=&CJI$|YQ!u+BQ@yEgjsY5~Ld7TIuph77ou%l3^HT_eDt&gXrf0Hy z*9+mi{;k@+L(H!Gw}gjI_wJtD+nKujygpJRU)PmheaY{w`Bp zaI?=>{)-1mY-9yUc9IcQ)!Vk);hf(7?+{qg2KC@I_)T(W+^Z#!;{F7ir z6fh5uu8dbF+WBQ}-UDpuevi!QAcY2H+d!Da1DB1X9|0Azm(iraz9>9Ms2aO&(jZa3 zt-l{;|5t|u9hj&(K`rSU98+2aQ0!DKb93WPLjf~Sw5V0?Wnb8*+xN!mx3oLS1T6OD zW4DqSbq`d-5Q`S_PL`Uhrc=H=B&0D#r2I9P!6Tg3+b_9UP73K+6c>$$#hL`ejwA$@ zI?)mAD43dLvcX?AE0Ic&kVB7EqNgay`W|9Gxhykx_qjfC(j>3SDa-NRBtWgUsH~ao z2Vnz<%PjK7l>~VbNor;)pzvsq@gtz=4u$d?=XS_*|h1`u56E zNP+OWW>SntwX%}*WZAYEKdq;HE&5Xgc5n69;+lcBT)Q$b^ZB-&N0OW|CACb%ooqE-ll=Yzu)LC>K!}U zTTQ8=BqIa-ZD3X#LOb&_U+))ERU`1F%0|JaJ`=g>4Wgv2d0c?jX6xPpn4AVsqG^dp zhF``bXiYzg+>d#{T3K1)Mav$nS-6lYgVVL7lyJBrhFS)o#=69kj}6y+F^J}fjM&@s z4@A|Fft}kQeVm6X!T?4Tt-f9}67H8atATKO=+gn5rvHHN05r(%k}My|@mNkox#(MHA!cKDZwF4uE|*U}5VGiGTYAQ(_Qq&LvmhplRONb1Cd7pAJLeeZ zyszWq{I$C91S9+aU@if;I1i6xFKbg(ciCJ;_tq<_`X~YVQX@Bv*%p{4qL1}l=%2U* z8ZnsH<7DYVJc&BLzvqAr)+yIKAxJ{Fc4qqD4D&g2zyuoXQ2`}{E-=DLNJGbtO`I1; zk6^IiKizkoC_?39q8#(zl&rG9y#4}N%08C0@GJpe_Vp>MY(dA$JYO(8jo*mjc@TS$ z%t#3Z|I3U~sLpBUpcFykoOy88-kaM}^IKl~4`vV-MtmP@2GQQT@Tp)?{#*Q!Mu_pB zqB~8Sstl+9o9=-;3F5Zl$BABqFd<8P{g2#B#1dS1!KD zs^i$5YR^MhV!oLawHZwY@xE@=Rm@wwnGatg8#GO@m*yNfR)fM=3ByWG!gliXu+dEp zZ3du^zhM}x4@kNZ&d{q)l=+$431QHA1v6_Xml?fsV2dPv{cW&}UO7w(RcXY2lx3F^ z-{AQ3P0XWT_ESFm*xC2-KXULMWFc|1hXfJ-;pK!&F}6MX^Xf9G+= zyW45#X&nLYv$}^&XI&pR8j$+@6r$I!QkQSoI$zqq@lNek2Y=KzyP&P+XpE(rk@Bd# zn~JzaiH?`>V$tCpqV7`EieOtd2uOQG<`(4X0D_ct`)kghE%%5^`_&k|O&bQ_ilhw7 z?4m5~-cb^+ZoOeI_#f_nF-$A#byXgP+$kHARtgd?`Gua7J(A1}SaTTT6tTpnuN(Pc z3~;i2|24L~Cj9vNcD_1Zy}1@}aF~uU6Rg>NmR`kj+~ZX&Qk*(4!Gj)7gTDc^M~bY} z1Bl%a{s$0qntG*Gi*(uSg)J~#%5@AXQHpo=gLtCK(LU=@e6<&e@)1#+r%5$FUOSV^ zyibO}@(vtzAyCTr@ zrn3aKT*2WnvyL@S#oM-SU+)JP;9ZTpIanawlzs**n%9qbK`r3g)60BD>sJ+jeFicYwg&@JYwjR^b_#Ej3xZ64+gj_zY>`(N3`>`Y7m4&l@lmLIaV~o)crvNXX z-3<@xpto+|x#$wBV5iB+RN5zaByM_6P4{PSm=QP|()n%DlWe>8rvDJOoUe5!#@MS) zhuY!_PAZSbkFV|2-&It_s84>G%^b;SSs05 zQr&w`so20x<9h#psE}%eJk5Pb?ZtwGm;>;F?TNS`fOW(rKjpoj(C{tztkkI`Vfa-W zlZlW-T|sQ$ZXv}kAi{nxPj&0jOW1*yIGW4u3FiyYzd$I&ft-H#r`h(>_!anrooT)P zCCito-=LMcBNXDl-gf6N!OH{|shX*w;yWz#12Ubz1LC|N?DNPscV}+s6u(p2hlY+m z3y*S6Vo&Nc)lPn_3B+{O=VKTD5xB`T(9Q`ses9dJh>?{V1MeMJN5aRjQ{AxWW*w8! zjfm9e$vV;0DDp)?zqHEbJHxx4IUaqCG&t)vJcoY}`0LZF!gkT{ZLo+vigGky_#y;-e4lh&JAmh& z5LFp*Ed>TvrQ-$#T#5)Z1;^hno))%s=8Lk-oR*?6V`eF9la};a8+ry^J(D4=_%z3+ z=J1c9a#~)cC9!UG2iJ@W3JL;hM1Hs-W&8EYP2={c-z7q)l7N5AT6(7v&v^21o5BjCE zX|$K=`)bW!R=n?GxBaVuIFps=`KX{b4UUugt25uA8B4g}#7^kd4rqMK{;oj%v5QUc z&%SRWp%G!bPPL+~VA6S#|8p@xpZlT>TNdM-_-yc-0a;#ug8{ZvfXmW|{}QrC`5wh% zNN@-?%n=ePD>21DzcmgB#FqR=HthJaSzO!quW;zp*vM}pvl&uGNYHulA1+LPnz6Jm zH%;&1kXNq9{)Y?CBYUh?0Nw-t?vVfXAJOnCz}mB4?TOWX8<|3Re9){XP5tZEzejj~ z3dfIyR6#x5*p~zF+SlbVR`nTIJYojCKZXo!0;iS8AVYiRS{u0eK*v$3;JDcl`q|N) zk>s#tXJM5A?#C;u{x*Rz3^Te$I;%^4=uH*y=qf~?L!uiAzU7o=s$^$28EnSzl7tpR zLj1o6oin{8hYqnY_>_xvdvj_)~qTfV%xq66{mxP$V)RCroCRcd_m-Ip23 zXYm0~Y#e*#CaxX3CB5~5oSPEA?u4wOl*IGOR=ovrDzSiYWoI+meDNkHN^5=h>)N+a zL#=H3=Hib9ysiD%{yiv$rn9kFZHdlugi(r@j&ZH>umrppKK&AV;#UyxUpl0uQY2bi zqVIOK4Dp-qz1SI?{e}!6;IxaR<#)-is=R+)&eoyC)z}L3-{`zskY*1!FahY40+0BK z3dk|tq7pMY>e*hj_KxVFeXusFlGLX?$*D)y<8F9blBB&LXtlY390@y%6MhfLN~A;Y zc^WcLk4Slv(H%=^=W=JBGWs;mN{vY1_l3M*W-A^iJ^|BXc-5-|*}seeo$7(OPeI7a zkU;uNq@{SE)5GUsM8`YgY4M3Z-;)V3{W}fR6qr;K(EHc9|MH_Ka3H~%9c0m?LukIV z3-bI8$}<8?PhX#kXt}nDz2B{3C$Ta?V*ldfx2oa);-geIm!i|v31O+XUUKrn+1?r6 zHnMfvEWmTxv<*mMcY!Wb$Q*;WTdO*SL-$m^Eay zKT^O29~$Q))vINY%cN1CDw)nZqp?J&G1ex4O8Gns03^xX^!`*j^y(UVtN7MCQEMFX~fgae563f1vO26{2Qy<1EpP# zdfARLSzdD8f=DG*Z1vP9K2ZGg&}>v$XWh+n*>sLVC68o`H}w0t{`sPt8<_DHLMXM-n!@Kq;I zH^}J*^Zun%W%tF=)XvTF%-5unBPGfK+;&eF`W}~9x!7I2e7)wC$+I9E=CH4ZVcpyO zLh)R0xBlS>$NalxTR#HhvE0Y^f2GS8I{*&?&KK<5xHNv}rsX4Js!dgoQW8Oe>8&l{ zJVgsjysv-;*%-?jz^8Y0m!gNjC_@f&w@kVZ=0q#WMBvUWrEK_&76jY`przUKv!d^rE2TiAEHe&1nFkL z=csL<4>qs`>uXB)=8(g|+er*#gWB+MBW?8Ahdy<~ z&J%OLp1|FMM!P?$OO`>HBs&{L)i*rIJyv72hIxs8&;j}-+ z<&j;sd`6Lv9un{`VS&c97e6t&yaZXj{NrBudws0+;oad8%` zwl^}8cp>2&rL@H`7M`P1XvhxlDD!_mB8Q)M7LVx1LOaZQ@)yvqU}xytGO)2p zS`*fK*SZneFaKsh9j1tXLAu)M^0u;{E%)as?OSeFwN@=x_L4QXqE6ZF?J*Z#73ZP~ z;I@5)Tcj*gDnJcv)?gKU5O@>UIn;_Z0vm@#Z!rH(KZ&jJ0dOhHUud=FKmJe_WGuL0 z1{pgw`3XE!aF+Kb{;nnt+!3u6t^DLs$~(cO5J#DSNEZxRu{I4)ZAlkJ2!u zYMAz1>&X3g)fj7kd8Eu(#?-^|C5Jj^s>uSMJSFP5sQge*?8_W*TLz@k&to-=%h;#r-H2b26R+P!$sKIk zH_-I+e>!L`0nSb4_Ch4T@o5bQesg*T?w8a}<{Bn=D*6x@r`?{K_xTz6aS;*n^W!Uo zJs(tM68>r)T2D#cz46&&VSelK6ona5X<9_P&?<3Uo0YY&535mrFx7;rJC^G8mb@*P z_g8deQ$XRfrD^HgP*u?^E>6YwNW?pHg zR?x7(UElgSEvtKuiWn2U`}GZu%_~Os*TTxSoW6Uka45X^YP0iN1FaiK$Gv=TeeI5J zdb6rdTsqz&_t^r;`rOU$Zu&!E@>IWyZGEbT!5++}L$1Qg`%$ypa;k={PEV&hN`nRX zZ{u)l4E6w~iMGxYm@<20d#6NY#m#j8Wdr$|+v@?@TxB%YmQ;I+72|vQz}SfyQI7w? z%&oi}_UqqmNzAX;fx<V-fMJboX%WhEWQqhX~9TD&RTZ{zVjW}yY4MycNkxc*Zs8x2Lk)`uaHcb!w;j7eni0_Kc$_w# zk?BV2_8*ya(zZhSFZ*4d@)|p^jZz4SGuo&XFZtJ@b|ema@vgb=G8j+BhY#83JJen? zawtc=?_ZFs_&t$iQiwkuEUQ z51jRj$O63=;&vsQ2PhMa{ESR}>6f46_+=fqHS@uWWpy7I>RL7Erz@1J6Z}hms7%Jp zRQi}FA>68IOJvy^$5VXw8A5d;iJ>Fs3#UK%=)>#r#pKvv?VqcA1I>f88(CE{3#TeL zcdM*`h;fbg`^q)Bpwev0fak*Tx3ye&60YTi@I{M;yTMgFnbN#=LR4A9EKvcd|3dRPjuFAij-BD7xAO0f{c}&a20Uk@#!tD_Q zSwU%ABSPNOGwb&L7^RzY>pR`WX`j<(1kbYS2q+h%l&MDX&5~bt5pM{kbw{@o3$-he zCMN8~E!DYQbzoER{)o-qkGGZ*YmbyGSUimsj0Wr~E)srl=mv0T-fvLX>d{Ma-`dv? zvyRqiLCnKo{|QKTJG|=OA#7pJLi<1)!!*Y&A)o~AjKUxqLLYBPFYXHr{*H(n-XDb! ztX2z!m#jP2Mo=vy>lZ(a9Zt;l6EyW3^n$F095|~}Ip?2v;+JU}$_nsY4($OsA1VJ7 zw{xYs<*y zoDEu^jVtq%*)Klq=P=pJylB;EGS~&YVP7h!ueuXrDF8k_MPeH$TU9~q$L->$EMFFi zlXdGKAsQ>b-)Vrizu`{s{?Q}m@3m5W?3A#%O?jQ}VkRraZY>qv_j}+ZvT3w!y~=aT z7XnC&U5TMvyg1~mUU%R5O_+PY9cI0F$sWoXGyBB%iA#_&X}L;W5^tAR)7S?sj;qyEN%YVpgJ?Ebn z$ov{?fXY+ylW=)?wP;4u^qe1UfLx=ypdn* zEJ(vD;2i3PUzdNt&%0T%++1S<&;7xezHJH4B8I_kp4PpOO168nw~jN2)1kt4zV_P* zjzFcsdad&&??hbM`Fm?ahS?Ca%;hEH>J=q>j7R zCCi)4*G%^Jk2s1b>VjEJ`{jYn+MLw0H>(cHMM~kV$Nii+^(rxe=asPXEwK)6_V43} z8%?Z6nx{8IcVh$BjrPR9pd6CQ6=j!f3T?KW7&AxJQG|ZB1}V?{l?)A59F5#b5AEUd zrdm+Dw%y;4!9b~!$*I>~@(cePa8US`L(G?74SxGD%Qp~E%tce|^)EIkTB=%BSFJx8 zA8v&5(KfrHZC+Ad-d(Ql${bjhk)_Q^Se*=L_GtH;`}?`U+hmqwuAGQzFaxy0uNr+i zhTAnxX(RMIHzAUWn!|^OaVLXtafxZxVuc_Lo8pszai52rrc9q#R5o<>@1tY_;-2fe zwn7I-D3G9wDNm0loSYd5vM6sEG7tXKQ#$FrlQ=`Oy3=iP=0XuVmesQC@DWrIy`Az9 z=Rwz2%fDaYc@`x=O|=(#>?T;p$eVN=QNI1P7(r+ePM+HF{Dsx;s98`$8Iyooo}ddw z6zJ6~uIMqZDq_nWp3GFxoRgig?{v8|`gV;1(^$HOHPt1DJc5h7*Z0n+R?T)c7y6&~ z)qM4#`R=pw9)poqGlY$0r8%QuH%Wy4GGU=}5)a6dIlGiRh92ItMSnq zzkH}Yt&_fbvm2Sd<*842Q!k^eG)bHbNd24g$a=`~d54d0di{6#`yG*Kk1QIF6!e$> zydjjzQ&4Zcg17?emK{z_ta^l_AE;hVx9VT&`RRVTO!5!?ekWQw1A+EY7KRQ6?I->< zi7mX}D)jj2F((g0M*Zx36vh|zZmpOMs?#r)v0!(0r`bwu^^DRVZkuE45#_~s4%Be6 zZfpA5#ZP3>tup|$m31iZ|7Es`(EyJncryNgon`sQnF!{FDD#u$W^`}i54R5si`RMA z0*nBwI9Sw)1>YJ;s+&*C2)Mw)kcD^OFk^<^m4!SVA!lrBOEsbV#fCq zzE~N(>CZ?duL(3!+HC zlga}W%nj-Rax%e1T%}#1E~9+2YRJKTz0W>pADwgQh1sr~4E}vhrI-uLcKvuz@wIJd zUG9H5!#~W6c*l^HE|NnpxZ69t?5)_xwg9u6@Unh^SumZ?ce>?$qPS3jyT6xF(~-+G34$PMOljV zz&GRt|FOx_?g0ehhA`#ZwN2Tkfdu*7mxoyk%O`r&3lFXrm6_Yu2PvPDG-hX#zAZTr zvuT=;Pn0)b+cov};KkUc*0>MnC4K-ZV9?JS&J)+<`uZv<&+1m()ufczqQ)VUy*kE< zNPX#!hI}C3M$(DI-ZOT5y+hm}V>lTKjdE@ReQRH=u)c2+8WfoRe(M9c?x?alD36;UcG1yA(9u-~%`I1+h(|HD>5(mlYpwh~#$LtG=^ zXA#aGf!Ur0197Rr{7FmQc~|S{kJn75>i6*`m;F`+y7CfcE_9weL?0l}3rf1ZkWbCdcE0)u3P|>BY;4HQBhp?Nm#w z_BN5&l2QVcHkCZI5+g_m3SNKf4q@q_gxyD{8a}lF)C<-rc~&6w!q*Y(H1}E(tFsfH zgMG6lsKtYPfro9}!yn&!j$tL7#{E?8GRwGs&uow`!O@vH@F@F_uC4Q=kQ#DSTehqp z4g{Oh4g{|7F|ap4Ih)o2x5Nte;x9iXelI(`UxxgGn!+N zbW{??2IVHu6YcTspjDEjV^%|JE zu)5q9)bsGq!*7#1Q!aS;V@oIx4toYFiTCZ zvRLx@Kg9^sb5XlWn}4$}2%!!a=tj=t|6rLD{@qO&_5j=vWo09;*>Cauj=1o$Dm28I zpC$T{ByqNWu$z3WT@L@=GrNbWAfe{BdlXz(M-^fHSWz9mfO7!eNE=sf0B@wD0o#8H z;tjcq+vr0!yT^qbuHjNDmuY_5|LvN@zZ?ZV9mW$Tdfu>YKLN#eCy6Kp-!I_?Z*GhXWX0U$-{upKU!ys2w7ftr626^d_9l;`jjNi7t2gQ6!1Sh=|))wD+7E*3Yk=t_Q z8`U(`IhTeUj4+(0dx*U^6Ut{uj@vzVl}VJI&H9hc?Mg=cIwb1|Hdj7x-`dd;-D!ez z-7#qF|H8JRzu0!BVM`k;TjkyX3fN%N$TC)xO2j5^r7Cm>!)L1GBE8i}|4DLi%Y8OC-xQiJ7Q#(1=`uHq9 zh#ywJqizEYytc)|CR;%bKK8l05Iv3H62p5{!pXzQyl?^4JXK3?+UBrMf!`Jo#K6p1Xn&e@-`1CK%puONm*}wyCSV z>q@_j#P{AXc-nCrx>oMw6*wUBDttnl@`_R^-9 zZR&C+HAn{MS-C8`?te@7l%YKjwYM+^YIQd+7D>v^Yc zXI1kHos~`PIokDpMXuJgXvh90(^&`drGLBdb(O%=O(7AMiBH;vaosb4a(*}O?rXMP zj|7hYFs3Q9%XdYfuM>ZA#TOrP>KlTH`Hc#N4{!yM1+IitzA@=c3nNc{^?SUo9q+Hj z@jK|6z>Qg~{4Zuz-0ICNjd;-nICpNG_20If@QvAB7>VN`DU zbFnF~9{GtJAN=5866x3fO#>!ve4TH?N>-)$WteDP_vO{wr^i6{pN&)i<~j4LI@bd* z-Z#bL+_T|*Df;GS%y61Nk^hKx!K4s~+KF~}XPbinJw>Vn1V zizS~}Q{^_2#lCNy^>{CgeZ7CuTKDy`Oy=F>SA`Twe%dMU9qAUHD4&gpvS35%KU%jf zN83k?zV`&~7L3r2`Xk%q7d?HY6n9)fAy1VoB<%iyM*&5wI$ym<276sLFN3M*Jk@0r znF560^|wWOm3~0t#bqh-KMmziph3Acj+AS#VwWWSODgn4;LDCc%zD33{MOMie9 zu-?$>U4(MxltDQF#$r3g2P@G-4V~i+{$BBux$>O(p7HoQlEU)FH^t@)}7@gmu7LvA0i{JuNRW4LC*s( zYXRDZ6-$SIe>wG+Iqb%)>VQ0dotMtZuR{F8g$(qkOyC=jX_xQd1G|!KfoZ?X?(ZZ9 z-GxR4C?sx&&m2x`?w`qZJ2bDjx4EIlczw=1`WM8srfz4WKE$({a-uKy_W)%8Fo4I> z8hr*?=T&%k^v{oqwc7yWWQ%3@Jj-A_!pbU);s-L5tG5BTZrD6b%|@w!U~ zG6s%7wT>hY|7;KYbQ8!3PJwQ4c%Y^RSdR%84YLCj3NTjvQQW&2FC)I9K-5LP^WWGT z9kzA2sl2qU25|RX@a=)zY&fMQ7CSseXq}Q4>$h?Gh7O$FRR?AE(x%syF2@TCOED++ zk(mpKo2QY#;v`Qi4?%nIHYEJ7yElWQ5m&hW73iEV=ZQOI>O_*W&FR?-2E@o5bGnTE zjc0+Xa(4{&rZ3uR&&lSm8c^5@BA#*!vt$F+j?AaBL!LN28Cpd}KZsumDWol?E9vm= zshT3BSE|>yh6=@jokBr0SlY;(d_X?>IDWL_5IQD_U=>_V`Q(2HK6|12(~G^$qgS&I ziz({p03WM}VB<5hdws4K2xc9lGnP)B4H@T)s>CaR=rn$Gf3eSE)HlUi$>V3jqy0iz z4|t#Upx@7rpOtQAQAX7=f^Si==^$`2sGsoJ0#;}U@W8uUx_>vwV8v@AurlFYW-xFO%P=s)ssNBH2w|GK+is@(#! zp+Bys%I9E_ziD}m`&qI$dg~At1pKxIWufLbRDl21Tp`Ep>WOZx zJvU=86%)bCI-}tqnMH{P%MxLHdojLUqADR38{C>7&7^`MfdBq6O~Kv>9D0u2`bu}Y zCmLEiU?^d7#5A~YN9}rUap{W#iK6b+nz~0$?(~ErT(aO ztE|mW?aF_Ad2xN9Z?jcS(#gy0biV})|jrS4)PHkE;%2i^eXH?7!j2{;0BnVI=$rS>q> zU4%rK?no^p8|n~3H~fTt_AVR0{QJghaM=moViMiJ`!5Fm^&<)ozZ`oIfWfM$0Nb!U zt?+AB0nu*}=|i=B4-pU6H%>0g+PC%n9Pqr-3QR0I=8^k0=xBKFLtiPK9hdC>5V;@0 z&RiPW(;bBb!e*@CgcWn=6WoNK&l;tx;I@`|9<>JE_~F<-i?D z)$RT1gWuvy%ONL#fBJgy;%3?Z((QD)e)?#fcbo2)uF?o;6Xh)uJ+1@MbewqQ0*F9+ zmXPpzpVkf$V?4KJ^6~B->@%!=jaqsi&na=ina56$*PGFgonh)2r_#0}?V&LJ;2V%1 z-%_2lM^4#NBL`0Fq`eYC^zKTO@dfjCkWz_&a2IEnARgFJ<`Z<4dg?`EQ>ZC1em91p z*W^-SB{vx^Zdu~qZ0DD5yW6?L`(FTr|Imnfb3E9n7`R{)EY3U9;J*^5zuumCc`cu@ zljcn>VjpKuf0tdKx~r^At#@Kdkbp^Pki%*v{OBGyK5UE@a-V`tc36)$`nNP z9V764>jC3XCI^H5M89pM%ECy4Xz#l1hGFc7LUfFF#a2RHF3T3jdyJjKZViCG^BY7z zQ@Gu-WT%-%$V`+No8~0<$sbo?`cBFMOkWY|uPMg1m#;f|a4gH`9#!4P^WLIw6 z zp}ZTIbO9?;jspk?%%O=mf({i;)l_#z-%ODTDbIAXur~`Ut9aboLR;_6lvlFR25~&b zaQ)i4W44Y>eFDp%G7BER#AHaAYV|swRv{1Sp0zI^C$SC=U>KaIQtYQ96vnszw3S`! zUvh!=zFGBtvP^id{6ATMl^2T8==bY7{q<*2c@LSLe;dflS<+AZI21eMX4e(l z1e;OSA!J3S;g)qj!{*vrrAWg|*0nf?7@#F0K#09q9Ey0sIkkz&87bVH5oPuj_c9Un-xl+4N-qCr)q2rOl7wQ@P=Fo=4Ao#BG#sVt@8dZ4DaIaNcTVpGvg)=*!Rqdl~_=c>cYu8uH{bgN+u028Ej8Ci{ z`akWEA75K{#n3H4zabUOW#eMDZ#XY4TnAH6Aw{U%q_WTXrE>EQU{ z&Wc;f-aQfy7@*^!Z*KF0#z0j0FBq=g6cdESJQOEJLN9816MKe#bjOBU?zb?FOItt($YSnNH^>1LXQ* z0^xmLs#SB4RRwx_!v}=!kw0OM*H%oyT}K|bV@q;IKXy#1>PB!(HmO7`?id&R>%%4l z*D<8s&LIw$);rR0?qaoEqAP7@7IqDz-wt4^=($P3YR5F7gX|B3UrAWLeEo= z#vW75TgwgpI8RQ>qwO1rfZa-}r)){Q;%wr=22Ev&nWX*hH@*>YEWSTlZVX9lfB@DBfq@ou!sJaB{Ei~oO0%l}H-%-Q26WQ$~)oQ8h#A4d6OjI+bw<%&k5f}4Pfw(>}wb`na4$dt8%zHS&+k8Pn>40LlIkTc} zBpG!w39tE?8zgDEKv;xLiZa8y_TJfR9TB%5tUJo@Fq=7cVsXVeaP%d^PoFJ)t$^2F zlhh>E8!A0alV%7nl$?@OMGb?u6xDQW_PME4pjAeTjT0Tf^2wOf>$9_IqT3@S4kQOQ z9lnQRli4~z-=U9x)Y9`M43)j@m|YPo{oxmnjPrG5)?@KLFS~Fd2eYJhO2OQhGxC$t zGcHe6jYj4Lz~X_%gfMVdu%Ja2p-;$tC6J(A7br(IdnTnGDP)Za{#DE}=`!t0`QEYrIC_X|bL~s06s`@Jh@htB-tBz{(6_-W zjAg96c<%dqpPfH4f3|Qq*xuhJ9`I&t7_q5!*7;OsI~-V0C@$UT9ra6h8H7Bj?}&&7 zS>;D9XP(08$r^ex=d>Ou?hmfkB;r7v$>>Ln2RvkN2^bDus%o$di zS-CQ~B+^XHyOL~&UPVS_#+&(yFmgGDCYW1Atc_aQD$**8^$u9rwJ#rAj9>i59!>4( z;`~QU_XB(uMiJ&NsJHp%Bb%SyFdhDS3S!~WG3jgOy}M>>FGWhx{g!fF&bE4`f5kzY z;e-sSWB^wbP=gxT^nuPQib#?2?)kUuz-4SJw0CzC78k`Z+s~q&jzAldhx~70%^1rJ zFYO)MnH`^rV1_O*Nu#%ar(G#zapSvSBgGyykdaCHTUfuw^z;opWBP0fqBFIH(}ctg>a)WomhMt}(Ln_h*|g2KkvV9yn>!a;yGep<)Nt8ge`Tr_b9y&!^`AKZ z=U-0NU9LZ-98X%RZn;I|dyy4&F7dfh9KzYl#{xE+bW0QAOj@`qe5>}ZB6M3x-KfU< zQ-d-fzzOR8xX?(c8v;MQ6JbgHc2?kt4t1p2l@x{|*O))ek}BHZ1)N`zeNObGHR%sj zs_VLmD`J{d;d?DmHzU|c^n5&;vC=^GNh8iS`{US*1M+-U{T^29je0L$6 zUYFdzeQztMp*J-lDd~vdjIHl4Dy0Y%q$pLA5zkYdz4_`*L7TY1HYdL9HO0F01|~4Y zRS$PYg=~(@shdX`FvCa4_qOy`#t(Vuo9^|#T9<&HUh?6EPV4(xmr?hoScbs+L3iE) zl<_YY%GF4VE{o)UsNHdLY}fQ~oUxC*7h) zd>ymHE3ra=$}dMe1)FrxR2ISP&`w_4`lb{2Dj~RLPsy!;pK^_yA2(Z;z_w&umv1o3 zBjz36Qj;crS(S&JMd}l+nL<2KPyNIl?s~Gq%SNo~H-ez&1yH{?JμAC5R?dcl}b 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

N=Bt?X!{ZJ~OR=OM1x_cLP{40!7H<@Ii&_wE9 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

qwssSCAEY%e&`v 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

1;RD_I{D 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

6aEY?FZ1On3>Sym^9x?cRvT%I9E$2 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 P1748J)~59)9*_SItid`f literal 0 HcmV?d00001 diff --git a/docs/images/keycloak-mcp-inspector-connect.png b/docs/images/keycloak-mcp-inspector-connect.png new file mode 100644 index 0000000000000000000000000000000000000000..4a582d1c8cacf0e44afa473d83af1ec495de3528 GIT binary patch literal 172961 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≥HE?JtK`$+ZP-M?5-lbB_h`-#Z-%+pzne{Vtv=Qw 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 L-_E=F;Q4<5_lK2C literal 0 HcmV?d00001 diff --git a/docs/images/keycloak-mcp-inspector-results.png b/docs/images/keycloak-mcp-inspector-results.png new file mode 100644 index 0000000000000000000000000000000000000000..7498e4d1652cb1e72484db40e149e6854b843fb5 GIT binary patch literal 343570 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

Sau=r;wIx?db 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

hkHAheWv+>Tl2cHCgW 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 literal 0 HcmV?d00001 From ba18f139fdd6bf14b453543e18d5b211d76e6780 Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Mon, 27 Oct 2025 11:27:20 -0400 Subject: [PATCH 29/57] feat: provider config parsers are able to resolve filepaths relative to the config file path (#412) Signed-off-by: Calum Murray --- pkg/config/config.go | 44 ++++++++++++++++++++++++++---- pkg/config/provider_config.go | 23 +++++++++++++++- pkg/config/provider_config_test.go | 35 ++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 5bd00ff3..81bec2b7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -2,8 +2,10 @@ package config import ( "bytes" + "context" "fmt" "os" + "path/filepath" "github.com/BurntSushi/toml" ) @@ -68,6 +70,9 @@ type StaticConfig struct { // Internal: parsed provider configs (not exposed to TOML package) parsedClusterProviderConfigs map[string]ProviderConfig + + // Internal: the config.toml directory, to help resolve relative file paths + configDirPath string } type GroupVersionKind struct { @@ -76,23 +81,48 @@ type GroupVersionKind struct { Kind string `toml:"kind,omitempty"` } -// Read reads the toml file and returns the StaticConfig. -func Read(configPath string) (*StaticConfig, error) { +type ReadConfigOpt func(cfg *StaticConfig) + +func withDirPath(path string) ReadConfigOpt { + return func(cfg *StaticConfig) { + cfg.configDirPath = path + } +} + +// Read reads the toml file and returns the StaticConfig, with any opts applied. +func Read(configPath string, opts ...ReadConfigOpt) (*StaticConfig, error) { configData, err := os.ReadFile(configPath) if err != nil { return nil, err } - return ReadToml(configData) + + // get and save the absolute dir path to the config file, so that other config parsers can use it + absPath, err := filepath.Abs(configPath) + if err != nil { + return nil, fmt.Errorf("failed to resolve absolute path to config file: %w", err) + } + dirPath := filepath.Dir(absPath) + + cfg, err := ReadToml(configData, append(opts, withDirPath(dirPath))...) + if err != nil { + return nil, err + } + + return cfg, nil } -// ReadToml reads the toml data and returns the StaticConfig. -func ReadToml(configData []byte) (*StaticConfig, error) { +// ReadToml reads the toml data and returns the StaticConfig, with any opts applied +func ReadToml(configData []byte, opts ...ReadConfigOpt) (*StaticConfig, error) { config := Default() md, err := toml.NewDecoder(bytes.NewReader(configData)).Decode(config) if err != nil { return nil, err } + for _, opt := range opts { + opt(config) + } + if err := config.parseClusterProviderConfigs(md); err != nil { return nil, err } @@ -111,13 +141,15 @@ func (c *StaticConfig) parseClusterProviderConfigs(md toml.MetaData) error { c.parsedClusterProviderConfigs = make(map[string]ProviderConfig, len(c.ClusterProviderConfigs)) } + ctx := withConfigDirPath(context.Background(), c.configDirPath) + for strategy, primitive := range c.ClusterProviderConfigs { parser, ok := getProviderConfigParser(strategy) if !ok { continue } - providerConfig, err := parser(primitive, md) + providerConfig, err := parser(ctx, primitive, md) if err != nil { return fmt.Errorf("failed to parse config for ClusterProvider '%s': %w", strategy, err) } diff --git a/pkg/config/provider_config.go b/pkg/config/provider_config.go index 23c5fffe..45dd2f8d 100644 --- a/pkg/config/provider_config.go +++ b/pkg/config/provider_config.go @@ -1,6 +1,7 @@ package config import ( + "context" "fmt" "github.com/BurntSushi/toml" @@ -12,7 +13,27 @@ type ProviderConfig interface { Validate() error } -type ProviderConfigParser func(primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) +type ProviderConfigParser func(ctx context.Context, primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) + +type configDirPathKey struct{} + +func withConfigDirPath(ctx context.Context, dirPath string) context.Context { + return context.WithValue(ctx, configDirPathKey{}, dirPath) +} + +func ConfigDirPathFromContext(ctx context.Context) string { + val := ctx.Value(configDirPathKey{}) + + if val == nil { + return "" + } + + if strVal, ok := val.(string); ok { + return strVal + } + + return "" +} var ( providerConfigParsers = make(map[string]ProviderConfigParser) diff --git a/pkg/config/provider_config_test.go b/pkg/config/provider_config_test.go index d933d894..84902da4 100644 --- a/pkg/config/provider_config_test.go +++ b/pkg/config/provider_config_test.go @@ -1,7 +1,9 @@ package config import ( + "context" "errors" + "path/filepath" "testing" "github.com/BurntSushi/toml" @@ -42,7 +44,7 @@ func (p *ProviderConfigForTest) Validate() error { return nil } -func providerConfigForTestParser(primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { +func providerConfigForTestParser(ctx context.Context, primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { var providerConfigForTest ProviderConfigForTest if err := md.PrimitiveDecode(primitive, &providerConfigForTest); err != nil { return nil, err @@ -131,7 +133,7 @@ func (s *ProviderConfigSuite) TestReadConfigUnregisteredProviderConfig() { } func (s *ProviderConfigSuite) TestReadConfigParserError() { - RegisterProviderConfig("test", func(primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { + RegisterProviderConfig("test", func(ctx context.Context, primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { return nil, errors.New("parser error forced by test") }) invalidConfigPath := s.writeConfig(` @@ -152,6 +154,35 @@ func (s *ProviderConfigSuite) TestReadConfigParserError() { }) } +func (s *ProviderConfigSuite) TestConfigDirPathInContext() { + var capturedDirPath string + RegisterProviderConfig("test", func(ctx context.Context, primitive toml.Primitive, md toml.MetaData) (ProviderConfig, error) { + capturedDirPath = ConfigDirPathFromContext(ctx) + var providerConfigForTest ProviderConfigForTest + if err := md.PrimitiveDecode(primitive, &providerConfigForTest); err != nil { + return nil, err + } + return &providerConfigForTest, nil + }) + configPath := s.writeConfig(` + cluster_provider_strategy = "test" + [cluster_provider_configs.test] + bool_prop = true + str_prop = "a string" + int_prop = 42 + `) + + absConfigPath, err := filepath.Abs(configPath) + s.Require().NoError(err, "test error: getting the absConfigPath should not fail") + + _, err = Read(configPath) + s.Run("provides config directory path in context to parser", func() { + s.Require().NoError(err, "Expected no error reading config") + s.NotEmpty(capturedDirPath, "Expected non-empty directory path in context") + s.Equal(filepath.Dir(absConfigPath), capturedDirPath, "Expected directory path to match config file directory") + }) +} + func TestProviderConfig(t *testing.T) { suite.Run(t, new(ProviderConfigSuite)) } From c0019979797230dc698c049323bc07f13d9c9837 Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Tue, 28 Oct 2025 10:53:04 +0100 Subject: [PATCH 30/57] chore(docs): adding a preview note at the top of the setup guide (#415) Signed-off-by: Matthias Wessendorf --- docs/KEYCLOAK_OIDC_SETUP.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/KEYCLOAK_OIDC_SETUP.md b/docs/KEYCLOAK_OIDC_SETUP.md index 2d6d53c6..149324fb 100644 --- a/docs/KEYCLOAK_OIDC_SETUP.md +++ b/docs/KEYCLOAK_OIDC_SETUP.md @@ -1,5 +1,9 @@ # Keycloak OIDC Setup for Kubernetes MCP Server +> **⚠️ Preview Feature** +> +> OIDC/OAuth authentication setup is currently in **preview**. Configuration flags or fields may change. Use for **development and testing only**. + This guide shows you how to set up a local development environment with Keycloak for OIDC authentication testing. ## Overview From 44053f10332d1bf772f530216a9272e1ece04ac5 Mon Sep 17 00:00:00 2001 From: Neeraj Krishna Gopalakrishna Date: Tue, 28 Oct 2025 15:31:25 +0530 Subject: [PATCH 31/57] fix(nodes): nodes_log query and tailLines arguments (#409) * Fix the node log query Signed-off-by: Neeraj Krishna Gopalakrishna * Fix the node log query Signed-off-by: Neeraj Krishna Gopalakrishna * fix(nodes): nodes_log query and tailLines arguments Signed-off-by: Marc Nuri --------- Signed-off-by: Neeraj Krishna Gopalakrishna Signed-off-by: Marc Nuri Co-authored-by: Marc Nuri --- README.md | 5 ++ pkg/kubernetes/accesscontrol_clientset.go | 4 +- pkg/kubernetes/nodes.go | 10 ++- pkg/mcp/nodes_test.go | 86 ++++++++++++------- pkg/mcp/testdata/toolsets-core-tools.json | 14 +-- ...toolsets-full-tools-multicluster-enum.json | 14 +-- .../toolsets-full-tools-multicluster.json | 14 +-- .../toolsets-full-tools-openshift.json | 14 +-- pkg/mcp/testdata/toolsets-full-tools.json | 14 +-- pkg/toolsets/core/nodes.go | 25 +++--- 10 files changed, 114 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index eab469c6..a81daea1 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,11 @@ In case multi-cluster support is enabled (default) and you have access to multip - **projects_list** - List all the OpenShift projects in the current cluster +- **nodes_log** - Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet + - `name` (`string`) **(required)** - Name of the node to get logs from + - `query` (`string`) **(required)** - query specifies services(s) or files from which to return logs (required). Example: "kubelet" to fetch kubelet logs, "/" to fetch a specific log file from the node (e.g., "/var/log/kubelet.log" or "/var/log/kube-proxy.log") + - `tailLines` (`integer`) - Number of lines to retrieve from the end of the logs (Optional, 0 means all logs) + - **pods_list** - List all the Kubernetes pods in the current cluster from all namespaces - `labelSelector` (`string`) - Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label diff --git a/pkg/kubernetes/accesscontrol_clientset.go b/pkg/kubernetes/accesscontrol_clientset.go index 0ce64c49..b4ab315c 100644 --- a/pkg/kubernetes/accesscontrol_clientset.go +++ b/pkg/kubernetes/accesscontrol_clientset.go @@ -39,7 +39,7 @@ func (a *AccessControlClientset) DiscoveryClient() discovery.DiscoveryInterface return a.discoveryClient } -func (a *AccessControlClientset) NodesLogs(ctx context.Context, name, logPath string) (*rest.Request, error) { +func (a *AccessControlClientset) NodesLogs(ctx context.Context, name string) (*rest.Request, error) { gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"} if !isAllowed(a.staticConfig, gvk) { return nil, isNotAllowedError(gvk) @@ -49,7 +49,7 @@ func (a *AccessControlClientset) NodesLogs(ctx context.Context, name, logPath st return nil, fmt.Errorf("failed to get node %s: %w", name, err) } - url := []string{"api", "v1", "nodes", name, "proxy", "logs", logPath} + url := []string{"api", "v1", "nodes", name, "proxy", "logs"} return a.delegate.CoreV1().RESTClient(). Get(). AbsPath(url...), nil diff --git a/pkg/kubernetes/nodes.go b/pkg/kubernetes/nodes.go index 76d9cc92..abdf16f2 100644 --- a/pkg/kubernetes/nodes.go +++ b/pkg/kubernetes/nodes.go @@ -5,21 +5,23 @@ import ( "fmt" ) -func (k *Kubernetes) NodesLog(ctx context.Context, name string, logPath string, tail int64) (string, error) { +func (k *Kubernetes) NodesLog(ctx context.Context, name string, query string, tailLines int64) (string, error) { // Use the node proxy API to access logs from the kubelet + // https://kubernetes.io/docs/concepts/cluster-administration/system-logs/#log-query // Common log paths: // - /var/log/kubelet.log - kubelet logs // - /var/log/kube-proxy.log - kube-proxy logs // - /var/log/containers/ - container logs - req, err := k.AccessControlClientset().NodesLogs(ctx, name, logPath) + req, err := k.AccessControlClientset().NodesLogs(ctx, name) if err != nil { return "", err } + req.Param("query", query) // Query parameters for tail - if tail > 0 { - req.Param("tailLines", fmt.Sprintf("%d", tail)) + if tailLines > 0 { + req.Param("tailLines", fmt.Sprintf("%d", tailLines)) } result := req.Do(ctx) diff --git a/pkg/mcp/nodes_test.go b/pkg/mcp/nodes_test.go index ce2cbc7e..c0135de6 100644 --- a/pkg/mcp/nodes_test.go +++ b/pkg/mcp/nodes_test.go @@ -2,6 +2,7 @@ package mcp import ( "net/http" + "strconv" "testing" "github.com/BurntSushi/toml" @@ -43,21 +44,25 @@ func (s *NodesSuite) TestNodesLog() { }`)) return } - // Get Empty Log response - if req.URL.Path == "/api/v1/nodes/existing-node/proxy/logs/empty.log" { + // Get Proxy Logs + if req.URL.Path == "/api/v1/nodes/existing-node/proxy/logs" { w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(``)) - return - } - // Get Kubelet Log response - if req.URL.Path == "/api/v1/nodes/existing-node/proxy/logs/kubelet.log" { - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - logContent := "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n" - if req.URL.Query().Get("tailLines") != "" { + query := req.URL.Query().Get("query") + var logContent string + switch query { + case "/empty.log": + logContent = "" + case "/kubelet.log": + logContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n" + default: + w.WriteHeader(http.StatusNotFound) + return + } + _, err := strconv.Atoi(req.URL.Query().Get("tailLines")) + if err == nil { logContent = "Line 4\nLine 5\n" } + w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(logContent)) return } @@ -77,9 +82,25 @@ func (s *NodesSuite) TestNodesLog() { "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) - s.Run("nodes_log(name=inexistent-node)", func() { + s.Run("nodes_log(name=existing-node, query=nil)", func() { + toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ + "name": "existing-node", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes missing name", func() { + expectedMessage := "failed to get node log, missing argument query" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_log(name=inexistent-node, query=/kubelet.log)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "inexistent-node", + "name": "inexistent-node", + "query": "/kubelet.log", }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("has error", func() { @@ -92,10 +113,10 @@ func (s *NodesSuite) TestNodesLog() { "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) - s.Run("nodes_log(name=existing-node, log_path=missing.log)", func() { + s.Run("nodes_log(name=existing-node, query=/missing.log)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "existing-node", - "log_path": "missing.log", + "name": "existing-node", + "query": "/missing.log", }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("has error", func() { @@ -108,10 +129,10 @@ func (s *NodesSuite) TestNodesLog() { "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) - s.Run("nodes_log(name=existing-node, log_path=empty.log)", func() { + s.Run("nodes_log(name=existing-node, query=/empty.log)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "existing-node", - "log_path": "empty.log", + "name": "existing-node", + "query": "/empty.log", }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("no error", func() { @@ -124,10 +145,10 @@ func (s *NodesSuite) TestNodesLog() { "expected descriptive message '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) - s.Run("nodes_log(name=existing-node, log_path=kubelet.log)", func() { + s.Run("nodes_log(name=existing-node, query=/kubelet.log)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "existing-node", - "log_path": "kubelet.log", + "name": "existing-node", + "query": "/kubelet.log", }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("no error", func() { @@ -141,11 +162,11 @@ func (s *NodesSuite) TestNodesLog() { }) }) for _, tailCase := range []interface{}{2, int64(2), float64(2)} { - s.Run("nodes_log(name=existing-node, log_path=kubelet.log, tail=2)", func() { + s.Run("nodes_log(name=existing-node, query=/kubelet.log, tailLines=2)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "existing-node", - "log_path": "kubelet.log", - "tail": tailCase, + "name": "existing-node", + "query": "/kubelet.log", + "tailLines": tailCase, }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("no error", func() { @@ -158,11 +179,11 @@ func (s *NodesSuite) TestNodesLog() { "expected log content '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) }) }) - s.Run("nodes_log(name=existing-node, log_path=kubelet.log, tail=-1)", func() { + s.Run("nodes_log(name=existing-node, query=/kubelet.log, tailLines=-1)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "existing-node", - "log_path": "kubelet.log", - "tail": -1, + "name": "existing-node", + "query": "/kubelet.log", + "tail": -1, }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("no error", func() { @@ -185,7 +206,8 @@ func (s *NodesSuite) TestNodesLogDenied() { s.InitMcpClient() s.Run("nodes_log (denied)", func() { toolResult, err := s.CallTool("nodes_log", map[string]interface{}{ - "name": "does-not-matter", + "name": "does-not-matter", + "query": "/does-not-matter-either.log", }) s.Require().NotNil(toolResult, "toolResult should not be nil") s.Run("has error", func() { diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index 37345100..fc5e86b7 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -45,16 +45,15 @@ "inputSchema": { "type": "object", "properties": { - "log_path": { - "default": "kubelet.log", - "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", - "type": "string" - }, "name": { "description": "Name of the node to get logs from", "type": "string" }, - "tail": { + "query": { + "description": "query specifies services(s) or files from which to return logs (required). Example: \"kubelet\" to fetch kubelet logs, \"/\u003clog-file-name\u003e\" to fetch a specific log file from the node (e.g., \"/var/log/kubelet.log\" or \"/var/log/kube-proxy.log\")", + "type": "string" + }, + "tailLines": { "default": 100, "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", "minimum": 0, @@ -62,7 +61,8 @@ } }, "required": [ - "name" + "name", + "query" ] }, "name": "nodes_log" diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index 7041bd3f..83b32139 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -215,16 +215,15 @@ ], "type": "string" }, - "log_path": { - "default": "kubelet.log", - "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", - "type": "string" - }, "name": { "description": "Name of the node to get logs from", "type": "string" }, - "tail": { + "query": { + "description": "query specifies services(s) or files from which to return logs (required). Example: \"kubelet\" to fetch kubelet logs, \"/\u003clog-file-name\u003e\" to fetch a specific log file from the node (e.g., \"/var/log/kubelet.log\" or \"/var/log/kube-proxy.log\")", + "type": "string" + }, + "tailLines": { "default": 100, "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", "minimum": 0, @@ -232,7 +231,8 @@ } }, "required": [ - "name" + "name", + "query" ] }, "name": "nodes_log" diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index a454f1ef..a7cdeb47 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -191,16 +191,15 @@ "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", "type": "string" }, - "log_path": { - "default": "kubelet.log", - "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", - "type": "string" - }, "name": { "description": "Name of the node to get logs from", "type": "string" }, - "tail": { + "query": { + "description": "query specifies services(s) or files from which to return logs (required). Example: \"kubelet\" to fetch kubelet logs, \"/\u003clog-file-name\u003e\" to fetch a specific log file from the node (e.g., \"/var/log/kubelet.log\" or \"/var/log/kube-proxy.log\")", + "type": "string" + }, + "tailLines": { "default": 100, "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", "minimum": 0, @@ -208,7 +207,8 @@ } }, "required": [ - "name" + "name", + "query" ] }, "name": "nodes_log" diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index 5e5fa4ea..3bba52d2 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -151,16 +151,15 @@ "inputSchema": { "type": "object", "properties": { - "log_path": { - "default": "kubelet.log", - "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", - "type": "string" - }, "name": { "description": "Name of the node to get logs from", "type": "string" }, - "tail": { + "query": { + "description": "query specifies services(s) or files from which to return logs (required). Example: \"kubelet\" to fetch kubelet logs, \"/\u003clog-file-name\u003e\" to fetch a specific log file from the node (e.g., \"/var/log/kubelet.log\" or \"/var/log/kube-proxy.log\")", + "type": "string" + }, + "tailLines": { "default": 100, "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", "minimum": 0, @@ -168,7 +167,8 @@ } }, "required": [ - "name" + "name", + "query" ] }, "name": "nodes_log" diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index 56a160ed..d9d6b91e 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -151,16 +151,15 @@ "inputSchema": { "type": "object", "properties": { - "log_path": { - "default": "kubelet.log", - "description": "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", - "type": "string" - }, "name": { "description": "Name of the node to get logs from", "type": "string" }, - "tail": { + "query": { + "description": "query specifies services(s) or files from which to return logs (required). Example: \"kubelet\" to fetch kubelet logs, \"/\u003clog-file-name\u003e\" to fetch a specific log file from the node (e.g., \"/var/log/kubelet.log\" or \"/var/log/kube-proxy.log\")", + "type": "string" + }, + "tailLines": { "default": 100, "description": "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", "minimum": 0, @@ -168,7 +167,8 @@ } }, "required": [ - "name" + "name", + "query" ] }, "name": "nodes_log" diff --git a/pkg/toolsets/core/nodes.go b/pkg/toolsets/core/nodes.go index 6c669398..c9e84032 100644 --- a/pkg/toolsets/core/nodes.go +++ b/pkg/toolsets/core/nodes.go @@ -22,19 +22,18 @@ func initNodes() []api.ServerTool { Type: "string", Description: "Name of the node to get logs from", }, - "log_path": { + "query": { Type: "string", - Description: "Path to the log file on the node (e.g. 'kubelet.log', 'kube-proxy.log'). Default is 'kubelet.log'", - Default: api.ToRawMessage("kubelet.log"), + Description: `query specifies services(s) or files from which to return logs (required). Example: "kubelet" to fetch kubelet logs, "/" to fetch a specific log file from the node (e.g., "/var/log/kubelet.log" or "/var/log/kube-proxy.log")`, }, - "tail": { + "tailLines": { Type: "integer", Description: "Number of lines to retrieve from the end of the logs (Optional, 0 means all logs)", Default: api.ToRawMessage(100), Minimum: ptr.To(float64(0)), }, }, - Required: []string{"name"}, + Required: []string{"name", "query"}, }, Annotations: api.ToolAnnotations{ Title: "Node: Log", @@ -52,25 +51,25 @@ func nodesLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) { if !ok || name == "" { return api.NewToolCallResult("", errors.New("failed to get node log, missing argument name")), nil } - logPath, ok := params.GetArguments()["log_path"].(string) - if !ok || logPath == "" { - logPath = "kubelet.log" + query, ok := params.GetArguments()["query"].(string) + if !ok || query == "" { + return api.NewToolCallResult("", errors.New("failed to get node log, missing argument query")), nil } - tail := params.GetArguments()["tail"] + tailLines := params.GetArguments()["tailLines"] var tailInt int64 - if tail != nil { + if tailLines != nil { // Convert to int64 - safely handle both float64 (JSON number) and int types - switch v := tail.(type) { + switch v := tailLines.(type) { case float64: tailInt = int64(v) case int: case int64: tailInt = v default: - return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tail)), nil + return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tailLines)), nil } } - ret, err := params.NodesLog(params, name, logPath, tailInt) + ret, err := params.NodesLog(params, name, query, tailInt) if err != nil { return api.NewToolCallResult("", fmt.Errorf("failed to get node log for %s: %v", name, err)), nil } else if ret == "" { From 5da1a92fe609b3f0d8ebbaf40b7aeb550506d6d0 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 29 Oct 2025 08:13:26 +0100 Subject: [PATCH 32/57] test(mcp): update MCP headers tests to use testify and improve readability (#417) Signed-off-by: Marc Nuri --- internal/test/mcp.go | 5 ++- pkg/mcp/common_test.go | 4 +- pkg/mcp/mcp_test.go | 92 +++++++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/internal/test/mcp.go b/internal/test/mcp.go index b82e3194..5fa0d0a4 100644 --- a/internal/test/mcp.go +++ b/internal/test/mcp.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/mark3labs/mcp-go/client" + "github.com/mark3labs/mcp-go/client/transport" "github.com/mark3labs/mcp-go/mcp" "github.com/stretchr/testify/require" "golang.org/x/net/context" @@ -17,12 +18,12 @@ type McpClient struct { *client.Client } -func NewMcpClient(t *testing.T, mcpHttpServer http.Handler) *McpClient { +func NewMcpClient(t *testing.T, mcpHttpServer http.Handler, options ...transport.StreamableHTTPCOption) *McpClient { require.NotNil(t, mcpHttpServer, "McpHttpServer must be provided") var err error ret := &McpClient{ctx: t.Context()} ret.testServer = httptest.NewServer(mcpHttpServer) - ret.Client, err = client.NewStreamableHttpClient(ret.testServer.URL + "/mcp") + ret.Client, err = client.NewStreamableHttpClient(ret.testServer.URL+"/mcp", options...) require.NoError(t, err, "Expected no error creating MCP client") err = ret.Start(t.Context()) require.NoError(t, err, "Expected no error starting MCP client") diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go index e9c49758..86f2e8d6 100644 --- a/pkg/mcp/common_test.go +++ b/pkg/mcp/common_test.go @@ -443,9 +443,9 @@ func (s *BaseMcpSuite) TearDownTest() { } } -func (s *BaseMcpSuite) InitMcpClient() { +func (s *BaseMcpSuite) InitMcpClient(options ...transport.StreamableHTTPCOption) { var err error s.mcpServer, err = NewServer(Configuration{StaticConfig: s.Cfg}) s.Require().NoError(err, "Expected no error creating MCP server") - s.McpClient = test.NewMcpClient(s.T(), s.mcpServer.ServeHTTP(nil)) + s.McpClient = test.NewMcpClient(s.T(), s.mcpServer.ServeHTTP(nil), options...) } diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index 7be9a423..484d8b59 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -10,8 +10,9 @@ import ( "time" "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/mark3labs/mcp-go/client" + "github.com/mark3labs/mcp-go/client/transport" "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" ) func TestWatchKubeConfig(t *testing.T) { @@ -48,16 +49,19 @@ func TestWatchKubeConfig(t *testing.T) { }) } -func TestSseHeaders(t *testing.T) { - mockServer := test.NewMockServer() - defer mockServer.Close() - before := func(c *mcpContext) { - c.withKubeConfig(mockServer.Config()) - c.clientOptions = append(c.clientOptions, client.WithHeaders(map[string]string{"kubernetes-authorization": "Bearer a-token-from-mcp-client"})) - } - pathHeaders := make(map[string]http.Header, 0) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - pathHeaders[req.URL.Path] = req.Header.Clone() +type McpHeadersSuite struct { + BaseMcpSuite + mockServer *test.MockServer + pathHeaders map[string]http.Header +} + +func (s *McpHeadersSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) + s.pathHeaders = make(map[string]http.Header) + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + s.pathHeaders[req.URL.Path] = req.Header.Clone() // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) if req.URL.Path == "/api" { w.Header().Set("Content-Type", "application/json") @@ -90,38 +94,42 @@ func TestSseHeaders(t *testing.T) { } w.WriteHeader(404) })) - testCaseWithContext(t, &mcpContext{before: before}, func(c *mcpContext) { - _, _ = c.callTool("pods_list", map[string]interface{}{}) - t.Run("DiscoveryClient propagates headers to Kube API", func(t *testing.T) { - if len(pathHeaders) == 0 { - t.Fatalf("No requests were made to Kube API") - } - if pathHeaders["/api"] == nil || pathHeaders["/api"].Get("Authorization") != "Bearer a-token-from-mcp-client" { - t.Fatalf("Overridden header Authorization not found in request to /api") - } - if pathHeaders["/apis"] == nil || pathHeaders["/apis"].Get("Authorization") != "Bearer a-token-from-mcp-client" { - t.Fatalf("Overridden header Authorization not found in request to /apis") - } - if pathHeaders["/api/v1"] == nil || pathHeaders["/api/v1"].Get("Authorization") != "Bearer a-token-from-mcp-client" { - t.Fatalf("Overridden header Authorization not found in request to /api/v1") - } +} + +func (s *McpHeadersSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *McpHeadersSuite) TestAuthorizationHeaderPropagation() { + cases := []string{"kubernetes-authorization", "Authorization"} + for _, header := range cases { + s.InitMcpClient(transport.WithHTTPHeaders(map[string]string{header: "Bearer a-token-from-mcp-client"})) + _, _ = s.CallTool("pods_list", map[string]interface{}{}) + s.Require().Greater(len(s.pathHeaders), 0, "No requests were made to Kube API") + s.Run("DiscoveryClient propagates "+header+" header to Kube API", func() { + s.Require().NotNil(s.pathHeaders["/api"], "No requests were made to /api") + s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api"].Get("Authorization"), "Overridden header Authorization not found in request to /api") + s.Require().NotNil(s.pathHeaders["/apis"], "No requests were made to /apis") + s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/apis"].Get("Authorization"), "Overridden header Authorization not found in request to /apis") + s.Require().NotNil(s.pathHeaders["/api/v1"], "No requests were made to /api/v1") + s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api/v1"].Get("Authorization"), "Overridden header Authorization not found in request to /api/v1") }) - t.Run("DynamicClient propagates headers to Kube API", func(t *testing.T) { - if len(pathHeaders) == 0 { - t.Fatalf("No requests were made to Kube API") - } - if pathHeaders["/api/v1/namespaces/default/pods"] == nil || pathHeaders["/api/v1/namespaces/default/pods"].Get("Authorization") != "Bearer a-token-from-mcp-client" { - t.Fatalf("Overridden header Authorization not found in request to /api/v1/namespaces/default/pods") - } + s.Run("DynamicClient propagates "+header+" header to Kube API", func() { + s.Require().NotNil(s.pathHeaders["/api/v1/namespaces/default/pods"], "No requests were made to /api/v1/namespaces/default/pods") + s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api/v1/namespaces/default/pods"].Get("Authorization"), "Overridden header Authorization not found in request to /api/v1/namespaces/default/pods") }) - _, _ = c.callTool("pods_delete", map[string]interface{}{"name": "a-pod-to-delete"}) - t.Run("kubernetes.Interface propagates headers to Kube API", func(t *testing.T) { - if len(pathHeaders) == 0 { - t.Fatalf("No requests were made to Kube API") - } - if pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"] == nil || pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"].Get("Authorization") != "Bearer a-token-from-mcp-client" { - t.Fatalf("Overridden header Authorization not found in request to /api/v1/namespaces/default/pods/a-pod-to-delete") - } + _, _ = s.CallTool("pods_delete", map[string]interface{}{"name": "a-pod-to-delete"}) + s.Run("kubernetes.Interface propagates "+header+" header to Kube API", func() { + s.Require().NotNil(s.pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"], "No requests were made to /api/v1/namespaces/default/pods/a-pod-to-delete") + s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"].Get("Authorization"), "Overridden header Authorization not found in request to /api/v1/namespaces/default/pods/a-pod-to-delete") }) - }) + + } +} + +func TestMcpHeaders(t *testing.T) { + suite.Run(t, new(McpHeadersSuite)) } From e526a20acd378072b427d7c44b27d373ea004fa7 Mon Sep 17 00:00:00 2001 From: Neeraj Krishna Gopalakrishna Date: Wed, 29 Oct 2025 14:13:56 +0530 Subject: [PATCH 33/57] Enable Kubernetes MCP Server to get PSI metrics (#410) Signed-off-by: Neeraj Krishna Gopalakrishna --- pkg/kubernetes/accesscontrol_clientset.go | 16 +++ pkg/kubernetes/nodes.go | 22 ++++ pkg/mcp/nodes_test.go | 109 ++++++++++++++++++ pkg/mcp/testdata/toolsets-core-tools.json | 23 ++++ ...toolsets-full-tools-multicluster-enum.json | 31 +++++ .../toolsets-full-tools-multicluster.json | 27 +++++ .../toolsets-full-tools-openshift.json | 23 ++++ pkg/mcp/testdata/toolsets-full-tools.json | 23 ++++ pkg/toolsets/core/nodes.go | 33 ++++++ 9 files changed, 307 insertions(+) diff --git a/pkg/kubernetes/accesscontrol_clientset.go b/pkg/kubernetes/accesscontrol_clientset.go index b4ab315c..c36a9b7f 100644 --- a/pkg/kubernetes/accesscontrol_clientset.go +++ b/pkg/kubernetes/accesscontrol_clientset.go @@ -55,6 +55,22 @@ func (a *AccessControlClientset) NodesLogs(ctx context.Context, name string) (*r AbsPath(url...), nil } +func (a *AccessControlClientset) NodesStatsSummary(ctx context.Context, name string) (*rest.Request, error) { + gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"} + if !isAllowed(a.staticConfig, gvk) { + return nil, isNotAllowedError(gvk) + } + + if _, err := a.delegate.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{}); err != nil { + return nil, fmt.Errorf("failed to get node %s: %w", name, err) + } + + url := []string{"api", "v1", "nodes", name, "proxy", "stats", "summary"} + return a.delegate.CoreV1().RESTClient(). + Get(). + AbsPath(url...), nil +} + func (a *AccessControlClientset) Pods(namespace string) (corev1.PodInterface, error) { gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"} if !isAllowed(a.staticConfig, gvk) { diff --git a/pkg/kubernetes/nodes.go b/pkg/kubernetes/nodes.go index abdf16f2..c53ef5b6 100644 --- a/pkg/kubernetes/nodes.go +++ b/pkg/kubernetes/nodes.go @@ -36,3 +36,25 @@ func (k *Kubernetes) NodesLog(ctx context.Context, name string, query string, ta return string(rawData), nil } + +func (k *Kubernetes) NodesStatsSummary(ctx context.Context, name string) (string, error) { + // Use the node proxy API to access stats summary from the kubelet + // This endpoint provides CPU, memory, filesystem, and network statistics + + req, err := k.AccessControlClientset().NodesStatsSummary(ctx, name) + if err != nil { + return "", err + } + + result := req.Do(ctx) + if result.Error() != nil { + return "", fmt.Errorf("failed to get node stats summary: %w", result.Error()) + } + + rawData, err := result.Raw() + if err != nil { + return "", fmt.Errorf("failed to read node stats summary response: %w", err) + } + + return string(rawData), nil +} diff --git a/pkg/mcp/nodes_test.go b/pkg/mcp/nodes_test.go index c0135de6..62ac55e9 100644 --- a/pkg/mcp/nodes_test.go +++ b/pkg/mcp/nodes_test.go @@ -222,6 +222,115 @@ func (s *NodesSuite) TestNodesLogDenied() { }) } +func (s *NodesSuite) TestNodesStatsSummary() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Get Node response + if req.URL.Path == "/api/v1/nodes/existing-node" { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "existing-node" + } + }`)) + return + } + // Get Stats Summary response + if req.URL.Path == "/api/v1/nodes/existing-node/proxy/stats/summary" { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{ + "node": { + "nodeName": "existing-node", + "cpu": { + "time": "2025-10-27T00:00:00Z", + "usageNanoCores": 1000000000, + "usageCoreNanoSeconds": 5000000000 + }, + "memory": { + "time": "2025-10-27T00:00:00Z", + "availableBytes": 8000000000, + "usageBytes": 4000000000, + "workingSetBytes": 3500000000 + } + }, + "pods": [] + }`)) + return + } + w.WriteHeader(http.StatusNotFound) + })) + s.InitMcpClient() + s.Run("nodes_stats_summary(name=nil)", func() { + toolResult, err := s.CallTool("nodes_stats_summary", map[string]interface{}{}) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes missing name", func() { + expectedMessage := "failed to get node stats summary, missing argument name" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_stats_summary(name=inexistent-node)", func() { + toolResult, err := s.CallTool("nodes_stats_summary", map[string]interface{}{ + "name": "inexistent-node", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes missing node", func() { + expectedMessage := "failed to get node stats summary for inexistent-node: failed to get node inexistent-node: the server could not find the requested resource (get nodes inexistent-node)" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) + s.Run("nodes_stats_summary(name=existing-node)", func() { + toolResult, err := s.CallTool("nodes_stats_summary", map[string]interface{}{ + "name": "existing-node", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns stats summary", func() { + content := toolResult.Content[0].(mcp.TextContent).Text + s.Containsf(content, "existing-node", "expected stats to contain node name, got %v", content) + s.Containsf(content, "usageNanoCores", "expected stats to contain CPU metrics, got %v", content) + s.Containsf(content, "usageBytes", "expected stats to contain memory metrics, got %v", content) + }) + }) +} + +func (s *NodesSuite) TestNodesStatsSummaryDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` + denied_resources = [ { version = "v1", kind = "Node" } ] + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("nodes_stats_summary (denied)", func() { + toolResult, err := s.CallTool("nodes_stats_summary", map[string]interface{}{ + "name": "does-not-matter", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { + expectedMessage := "failed to get node stats summary for does-not-matter: resource not allowed: /v1, Kind=Node" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) +} + func TestNodes(t *testing.T) { suite.Run(t, new(NodesSuite)) } diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index fc5e86b7..56b998da 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -67,6 +67,29 @@ }, "name": "nodes_log" }, + { + "annotations": { + "title": "Node: Stats Summary", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "description": "Name of the node to get stats from", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "name": "nodes_stats_summary" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index 83b32139..1551b4c2 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -237,6 +237,37 @@ }, "name": "nodes_log" }, + { + "annotations": { + "title": "Node: Stats Summary", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", + "inputSchema": { + "type": "object", + "properties": { + "context": { + "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", + "enum": [ + "extra-cluster", + "fake-context" + ], + "type": "string" + }, + "name": { + "description": "Name of the node to get stats from", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "name": "nodes_stats_summary" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index a7cdeb47..6e85e401 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -213,6 +213,33 @@ }, "name": "nodes_log" }, + { + "annotations": { + "title": "Node: Stats Summary", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", + "inputSchema": { + "type": "object", + "properties": { + "context": { + "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", + "type": "string" + }, + "name": { + "description": "Name of the node to get stats from", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "name": "nodes_stats_summary" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index 3bba52d2..fb24138e 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -173,6 +173,29 @@ }, "name": "nodes_log" }, + { + "annotations": { + "title": "Node: Stats Summary", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "description": "Name of the node to get stats from", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "name": "nodes_stats_summary" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index d9d6b91e..5a4b5112 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -173,6 +173,29 @@ }, "name": "nodes_log" }, + { + "annotations": { + "title": "Node: Stats Summary", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "description": "Name of the node to get stats from", + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "name": "nodes_stats_summary" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/toolsets/core/nodes.go b/pkg/toolsets/core/nodes.go index c9e84032..fc06a2d9 100644 --- a/pkg/toolsets/core/nodes.go +++ b/pkg/toolsets/core/nodes.go @@ -43,6 +43,27 @@ func initNodes() []api.ServerTool { OpenWorldHint: ptr.To(true), }, }, Handler: nodesLog}, + {Tool: api.Tool{ + Name: "nodes_stats_summary", + Description: "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "name": { + Type: "string", + Description: "Name of the node to get stats from", + }, + }, + Required: []string{"name"}, + }, + Annotations: api.ToolAnnotations{ + Title: "Node: Stats Summary", + ReadOnlyHint: ptr.To(true), + DestructiveHint: ptr.To(false), + IdempotentHint: ptr.To(false), + OpenWorldHint: ptr.To(true), + }, + }, Handler: nodesStatsSummary}, } } @@ -77,3 +98,15 @@ func nodesLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) { } return api.NewToolCallResult(ret, nil), nil } + +func nodesStatsSummary(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + name, ok := params.GetArguments()["name"].(string) + if !ok || name == "" { + return api.NewToolCallResult("", errors.New("failed to get node stats summary, missing argument name")), nil + } + ret, err := params.NodesStatsSummary(params, name) + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to get node stats summary for %s: %v", name, err)), nil + } + return api.NewToolCallResult(ret, nil), nil +} From ebdeb6a5b64698f8b62b26af68af2e92de338b71 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 30 Oct 2025 08:12:34 +0100 Subject: [PATCH 34/57] test(resources): update generic resources tests to use testify and improve readability (#422) Signed-off-by: Marc Nuri --- pkg/mcp/common_test.go | 18 + pkg/mcp/resources_test.go | 928 +++++++++++++++----------------------- 2 files changed, 383 insertions(+), 563 deletions(-) diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go index 86f2e8d6..d15dca39 100644 --- a/pkg/mcp/common_test.go +++ b/pkg/mcp/common_test.go @@ -449,3 +449,21 @@ func (s *BaseMcpSuite) InitMcpClient(options ...transport.StreamableHTTPCOption) s.Require().NoError(err, "Expected no error creating MCP server") s.McpClient = test.NewMcpClient(s.T(), s.mcpServer.ServeHTTP(nil), options...) } + +// CrdWaitUntilReady waits for a CRD to be established +func (s *BaseMcpSuite) CrdWaitUntilReady(name string) { + apiExtensionClient := apiextensionsv1.NewForConfigOrDie(envTestRestConfig) + watcher, err := apiExtensionClient.CustomResourceDefinitions().Watch(s.T().Context(), metav1.ListOptions{ + FieldSelector: "metadata.name=" + name, + }) + s.Require().NoError(err, "failed to watch CRD") + _, err = toolswatch.UntilWithoutRetry(s.T().Context(), watcher, func(event watch.Event) (bool, error) { + for _, c := range event.Object.(*apiextensionsv1spec.CustomResourceDefinition).Status.Conditions { + if c.Type == apiextensionsv1spec.Established && c.Status == apiextensionsv1spec.ConditionTrue { + return true, nil + } + } + return false, nil + }) + s.Require().NoError(err, "failed to wait for CRD") +} diff --git a/pkg/mcp/resources_test.go b/pkg/mcp/resources_test.go index 3aa7b875..83401377 100644 --- a/pkg/mcp/resources_test.go +++ b/pkg/mcp/resources_test.go @@ -5,193 +5,140 @@ import ( "strings" "testing" + "github.com/BurntSushi/toml" "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/yaml" - "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/containers/kubernetes-mcp-server/pkg/config" "github.com/containers/kubernetes-mcp-server/pkg/output" ) -func TestResourcesList(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("resources_list with missing apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_list", map[string]interface{}{}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, missing argument apiVersion" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - } - }) - t.Run("resources_list with missing kind returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, missing argument kind" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - } - }) - t.Run("resources_list with invalid apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, invalid argument apiVersion" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - } - }) - t.Run("resources_list with nonexistent apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - } - if toolResult.Content[0].(mcp.TextContent).Text != `failed to list resources: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"` { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - } - }) - namespaces, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) - t.Run("resources_list returns namespaces", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if namespaces.IsError { - t.Fatalf("call tool failed") - return - } +type ResourcesSuite struct { + BaseMcpSuite +} + +func (s *ResourcesSuite) TestResourcesList() { + s.InitMcpClient() + s.Run("resources_list with missing apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_list", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to list resources, missing argument apiVersion", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_list with missing kind returns error", func() { + toolResult, _ := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "v1"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to list resources, missing argument kind", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_list with invalid apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to list resources, invalid argument apiVersion", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_list with nonexistent apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf(`failed to list resources: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"`, + toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_list(apiVersion=v1, kind=Namespace) returns namespaces", func() { + namespaces, err := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(namespaces.IsError, "call tool failed") }) var decodedNamespaces []unstructured.Unstructured err = yaml.Unmarshal([]byte(namespaces.Content[0].(mcp.TextContent).Text), &decodedNamespaces) - t.Run("resources_list has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("resources_list returns more than 2 items", func(t *testing.T) { - if len(decodedNamespaces) < 3 { - t.Fatalf("invalid namespace count, expected >2, got %v", len(decodedNamespaces)) - } + s.Run("returns more than 2 items", func() { + s.Truef(len(decodedNamespaces) >= 3, "invalid namespace count, expected >2, got %v", len(decodedNamespaces)) }) - - // Test label selector functionality - t.Run("resources_list with label selector returns filtered pods", func(t *testing.T) { - - // List pods with label selector - result, err := c.callTool("resources_list", map[string]interface{}{ + }) + s.Run("resources_list with label selector returns filtered pods", func() { + s.Run("list pods with app=nginx label", func() { + result, err := s.CallTool("resources_list", map[string]interface{}{ "apiVersion": "v1", "kind": "Pod", "namespace": "default", "labelSelector": "app=nginx", }) - - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if result.IsError { - t.Fatalf("call tool failed") - return - } + s.Nilf(err, "call tool failed %v", err) + s.Falsef(result.IsError, "call tool failed") var decodedPods []unstructured.Unstructured err = yaml.Unmarshal([]byte(result.Content[0].(mcp.TextContent).Text), &decodedPods) - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } - - // Verify only the pod with matching label is returned - if len(decodedPods) != 1 { - t.Fatalf("expected 1 pod, got %d", len(decodedPods)) - return - } + s.Nilf(err, "invalid tool result content %v", err) - if decodedPods[0].GetName() != "a-pod-in-default" { - t.Fatalf("expected pod-with-label, got %s", decodedPods[0].GetName()) - return - } - - // Test that multiple label selectors work - result, err = c.callTool("resources_list", map[string]interface{}{ + s.Lenf(decodedPods, 1, "expected 1 pod, got %d", len(decodedPods)) + s.Equalf("a-pod-in-default", decodedPods[0].GetName(), "expected a-pod-in-default, got %s", decodedPods[0].GetName()) + }) + s.Run("list pods with multiple label selectors", func() { + result, err := s.CallTool("resources_list", map[string]interface{}{ "apiVersion": "v1", "kind": "Pod", "namespace": "default", "labelSelector": "test-label=test-value,another=value", }) + s.Nilf(err, "call tool failed %v", err) + s.Falsef(result.IsError, "call tool failed") - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if result.IsError { - t.Fatalf("call tool failed") - return - } - + var decodedPods []unstructured.Unstructured err = yaml.Unmarshal([]byte(result.Content[0].(mcp.TextContent).Text), &decodedPods) - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } + s.Nilf(err, "invalid tool result content %v", err) - // Verify no pods match multiple label selector - if len(decodedPods) != 0 { - t.Fatalf("expected 0 pods, got %d", len(decodedPods)) - return - } + s.Lenf(decodedPods, 0, "expected 0 pods, got %d", len(decodedPods)) }) }) } -func TestResourcesListDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *ResourcesSuite) TestResourcesListDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Secret" }, { group = "rbac.authorization.k8s.io", version = "v1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - deniedByKind, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Secret"}) - t.Run("resources_list (denied by kind) has error", func(t *testing.T) { - if !deniedByKind.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("resources_list (denied by kind) describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("resources_list (denied by kind)", func() { + deniedByKind, err := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Secret"}) + s.Run("has error", func() { + s.Truef(deniedByKind.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to list resources: resource not allowed: /v1, Kind=Secret" - if deniedByKind.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) }) - deniedByGroup, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role"}) - t.Run("resources_list (denied by group) has error", func(t *testing.T) { - if !deniedByGroup.IsError { - t.Fatalf("call tool should fail") - } + }) + s.Run("resources_list (denied by group)", func() { + deniedByGroup, err := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role"}) + s.Run("has error", func() { + s.Truef(deniedByGroup.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("resources_list (denied by group) describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to list resources: resource not allowed: rbac.authorization.k8s.io/v1, Kind=Role" - if deniedByGroup.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } - }) - allowedResource, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) - t.Run("resources_list (not denied) returns list", func(t *testing.T) { - if allowedResource.IsError { - t.Fatalf("call tool should not fail") - } + s.Equalf(expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text) }) }) + s.Run("resources_list (not denied) returns list", func() { + allowedResource, _ := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) + s.Falsef(allowedResource.IsError, "call tool should not fail") + }) } func TestResourcesListAsTable(t *testing.T) { @@ -271,229 +218,156 @@ func TestResourcesListAsTable(t *testing.T) { }) } -func TestResourcesGet(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("resources_get with missing apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_get", map[string]interface{}{}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get resource, missing argument apiVersion" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_get with missing kind returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get resource, missing argument kind" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_get with invalid apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod", "name": "a-pod"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get resource, invalid argument apiVersion" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_get with nonexistent apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom", "name": "a-custom"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != `failed to get resource: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"` { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_get with missing name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get resource, missing argument name" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - namespace, err := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "default"}) - t.Run("resources_get returns namespace", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if namespace.IsError { - t.Fatalf("call tool failed") - return - } +func (s *ResourcesSuite) TestResourcesGet() { + s.InitMcpClient() + s.Run("resources_get with missing apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_get", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get resource, missing argument apiVersion", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_get with missing kind returns error", func() { + toolResult, _ := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "v1"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get resource, missing argument kind", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_get with invalid apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod", "name": "a-pod"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get resource, invalid argument apiVersion", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_get with nonexistent apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom", "name": "a-custom"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf(`failed to get resource: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"`, + toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_get with missing name returns error", func() { + toolResult, _ := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get resource, missing argument name", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_get returns namespace", func() { + namespace, err := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "default"}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(namespace.IsError, "call tool failed") }) var decodedNamespace unstructured.Unstructured err = yaml.Unmarshal([]byte(namespace.Content[0].(mcp.TextContent).Text), &decodedNamespace) - t.Run("resources_get has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("resources_get returns default namespace", func(t *testing.T) { - if decodedNamespace.GetName() != "default" { - t.Fatalf("invalid namespace name, expected default, got %v", decodedNamespace.GetName()) - return - } + s.Run("returns default namespace", func() { + s.Equalf("default", decodedNamespace.GetName(), "invalid namespace name, expected default, got %v", decodedNamespace.GetName()) }) }) } -func TestResourcesGetDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *ResourcesSuite) TestResourcesGetDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Secret" }, { group = "rbac.authorization.k8s.io", version = "v1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - kc := c.newKubernetesClient() - _, _ = kc.CoreV1().Secrets("default").Create(c.ctx, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "denied-secret"}, - }, metav1.CreateOptions{}) - _, _ = kc.RbacV1().Roles("default").Create(c.ctx, &v1.Role{ - ObjectMeta: metav1.ObjectMeta{Name: "denied-role"}, - }, metav1.CreateOptions{}) - deniedByKind, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Secret", "namespace": "default", "name": "denied-secret"}) - t.Run("resources_get (denied by kind) has error", func(t *testing.T) { - if !deniedByKind.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("resources_get (denied by kind) describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + _, _ = kc.CoreV1().Secrets("default").Create(s.T().Context(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "denied-secret"}, + }, metav1.CreateOptions{}) + _, _ = kc.RbacV1().Roles("default").Create(s.T().Context(), &v1.Role{ + ObjectMeta: metav1.ObjectMeta{Name: "denied-role"}, + }, metav1.CreateOptions{}) + s.Run("resources_get (denied by kind)", func() { + deniedByKind, err := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Secret", "namespace": "default", "name": "denied-secret"}) + s.Run("has error", func() { + s.Truef(deniedByKind.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to get resource: resource not allowed: /v1, Kind=Secret" - if deniedByKind.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) }) - deniedByGroup, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role", "namespace": "default", "name": "denied-role"}) - t.Run("resources_get (denied by group) has error", func(t *testing.T) { - if !deniedByGroup.IsError { - t.Fatalf("call tool should fail") - } + }) + s.Run("resources_get (denied by group)", func() { + deniedByGroup, err := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role", "namespace": "default", "name": "denied-role"}) + s.Run("has error", func() { + s.Truef(deniedByGroup.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("resources_get (denied by group) describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to get resource: resource not allowed: rbac.authorization.k8s.io/v1, Kind=Role" - if deniedByGroup.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } - }) - allowedResource, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "default"}) - t.Run("resources_get (not denied) returns resource", func(t *testing.T) { - if allowedResource.IsError { - t.Fatalf("call tool should not fail") - } + s.Equalf(expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text) }) }) + s.Run("resources_get (not denied) returns resource", func() { + allowedResource, err := s.CallTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "default"}) + s.Falsef(allowedResource.IsError, "call tool should not fail") + s.Nilf(err, "call tool should not return error object") + }) } -func TestResourcesCreateOrUpdate(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("resources_create_or_update with nil resource returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_create_or_update", map[string]interface{}{}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to create or update resources, missing argument resource" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_create_or_update with empty resource returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_create_or_update", map[string]interface{}{"resource": ""}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to create or update resources, missing argument resource" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - client := c.newKubernetesClient() +func (s *ResourcesSuite) TestResourcesCreateOrUpdate() { + s.InitMcpClient() + client := kubernetes.NewForConfigOrDie(envTestRestConfig) + + s.Run("resources_create_or_update with nil resource returns error", func() { + toolResult, _ := s.CallTool("resources_create_or_update", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to create or update resources, missing argument resource", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_create_or_update with empty resource returns error", func() { + toolResult, _ := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": ""}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to create or update resources, missing argument resource", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + + s.Run("resources_create_or_update with valid namespaced yaml resource", func() { configMapYaml := "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: a-cm-created-or-updated\n namespace: default\n" - resourcesCreateOrUpdateCm1, err := c.callTool("resources_create_or_update", map[string]interface{}{"resource": configMapYaml}) - t.Run("resources_create_or_update with valid namespaced yaml resource returns success", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesCreateOrUpdateCm1.IsError { - t.Errorf("call tool failed") - return - } + resourcesCreateOrUpdateCm1, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": configMapYaml}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesCreateOrUpdateCm1.IsError, "call tool failed") }) var decodedCreateOrUpdateCm1 []unstructured.Unstructured err = yaml.Unmarshal([]byte(resourcesCreateOrUpdateCm1.Content[0].(mcp.TextContent).Text), &decodedCreateOrUpdateCm1) - t.Run("resources_create_or_update with valid namespaced yaml resource returns yaml content", func(t *testing.T) { - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } - if !strings.HasPrefix(resourcesCreateOrUpdateCm1.Content[0].(mcp.TextContent).Text, "# The following resources (YAML) have been created or updated successfully") { - t.Errorf("Excpected success message, got %v", resourcesCreateOrUpdateCm1.Content[0].(mcp.TextContent).Text) - return - } - if len(decodedCreateOrUpdateCm1) != 1 { - t.Errorf("invalid resource count, expected 1, got %v", len(decodedCreateOrUpdateCm1)) - return - } - if decodedCreateOrUpdateCm1[0].GetName() != "a-cm-created-or-updated" { - t.Errorf("invalid resource name, expected a-cm-created-or-updated, got %v", decodedCreateOrUpdateCm1[0].GetName()) - return - } - if decodedCreateOrUpdateCm1[0].GetUID() == "" { - t.Errorf("invalid uid, got %v", decodedCreateOrUpdateCm1[0].GetUID()) - return - } - }) - t.Run("resources_create_or_update with valid namespaced yaml resource creates ConfigMap", func(t *testing.T) { - cm, _ := client.CoreV1().ConfigMaps("default").Get(c.ctx, "a-cm-created-or-updated", metav1.GetOptions{}) - if cm == nil { - t.Fatalf("ConfigMap not found") - return - } + s.Run("returns yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) + s.Truef(strings.HasPrefix(resourcesCreateOrUpdateCm1.Content[0].(mcp.TextContent).Text, "# The following resources (YAML) have been created or updated successfully"), + "Expected success message, got %v", resourcesCreateOrUpdateCm1.Content[0].(mcp.TextContent).Text) + s.Lenf(decodedCreateOrUpdateCm1, 1, "invalid resource count, expected 1, got %v", len(decodedCreateOrUpdateCm1)) + s.Equalf("a-cm-created-or-updated", decodedCreateOrUpdateCm1[0].GetName(), + "invalid resource name, expected a-cm-created-or-updated, got %v", decodedCreateOrUpdateCm1[0].GetName()) + s.NotEmptyf(decodedCreateOrUpdateCm1[0].GetUID(), "invalid uid, got %v", decodedCreateOrUpdateCm1[0].GetUID()) + }) + s.Run("creates ConfigMap", func() { + cm, _ := client.CoreV1().ConfigMaps("default").Get(s.T().Context(), "a-cm-created-or-updated", metav1.GetOptions{}) + s.NotNil(cm, "ConfigMap not found") }) + }) + + s.Run("resources_create_or_update with valid namespaced json resource", func() { configMapJson := "{\"apiVersion\": \"v1\", \"kind\": \"ConfigMap\", \"metadata\": {\"name\": \"a-cm-created-or-updated-2\", \"namespace\": \"default\"}}" - resourcesCreateOrUpdateCm2, err := c.callTool("resources_create_or_update", map[string]interface{}{"resource": configMapJson}) - t.Run("resources_create_or_update with valid namespaced json resource returns success", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesCreateOrUpdateCm2.IsError { - t.Fatalf("call tool failed") - return - } + resourcesCreateOrUpdateCm2, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": configMapJson}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesCreateOrUpdateCm2.IsError, "call tool failed") }) - t.Run("resources_create_or_update with valid namespaced json resource creates config map", func(t *testing.T) { - cm, _ := client.CoreV1().ConfigMaps("default").Get(c.ctx, "a-cm-created-or-updated-2", metav1.GetOptions{}) - if cm == nil { - t.Fatalf("ConfigMap not found") - return - } + s.Run("creates config map", func() { + cm, _ := client.CoreV1().ConfigMaps("default").Get(s.T().Context(), "a-cm-created-or-updated-2", metav1.GetOptions{}) + s.NotNil(cm, "ConfigMap not found") }) + }) + + s.Run("resources_create_or_update with valid cluster-scoped json resource", func() { customResourceDefinitionJson := ` { "apiVersion": "apiextensions.k8s.io/v1", @@ -509,284 +383,212 @@ func TestResourcesCreateOrUpdate(t *testing.T) { "names": {"plural": "customs","singular": "custom","kind": "Custom"} } }` - resourcesCreateOrUpdateCrd, err := c.callTool("resources_create_or_update", map[string]interface{}{"resource": customResourceDefinitionJson}) - t.Run("resources_create_or_update with valid cluster-scoped json resource returns success", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesCreateOrUpdateCrd.IsError { - t.Fatalf("call tool failed") - return - } + resourcesCreateOrUpdateCrd, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": customResourceDefinitionJson}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesCreateOrUpdateCrd.IsError, "call tool failed") }) - t.Run("resources_create_or_update with valid cluster-scoped json resource creates custom resource definition", func(t *testing.T) { - apiExtensionsV1Client := c.newApiExtensionsClient() - _, err = apiExtensionsV1Client.CustomResourceDefinitions().Get(c.ctx, "customs.example.com", metav1.GetOptions{}) - if err != nil { - t.Fatalf("custom resource definition not found") - return - } + s.Run("creates custom resource definition", func() { + apiExtensionsV1Client := apiextensionsv1.NewForConfigOrDie(envTestRestConfig) + _, err = apiExtensionsV1Client.CustomResourceDefinitions().Get(s.T().Context(), "customs.example.com", metav1.GetOptions{}) + s.Nilf(err, "custom resource definition not found") }) - c.crdWaitUntilReady("customs.example.com") + s.CrdWaitUntilReady("customs.example.com") + }) + + s.Run("resources_create_or_update creates custom resource", func() { customJson := "{\"apiVersion\": \"example.com/v1\", \"kind\": \"Custom\", \"metadata\": {\"name\": \"a-custom-resource\"}}" - resourcesCreateOrUpdateCustom, err := c.callTool("resources_create_or_update", map[string]interface{}{"resource": customJson}) - t.Run("resources_create_or_update with valid namespaced json resource returns success", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesCreateOrUpdateCustom.IsError { - t.Fatalf("call tool failed, got: %v", resourcesCreateOrUpdateCustom.Content) - return - } + resourcesCreateOrUpdateCustom, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": customJson}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesCreateOrUpdateCustom.IsError, "call tool failed, got: %v", resourcesCreateOrUpdateCustom.Content) }) - t.Run("resources_create_or_update with valid namespaced json resource creates custom resource", func(t *testing.T) { + s.Run("creates custom resource", func() { dynamicClient := dynamic.NewForConfigOrDie(envTestRestConfig) _, err = dynamicClient. Resource(schema.GroupVersionResource{Group: "example.com", Version: "v1", Resource: "customs"}). Namespace("default"). - Get(c.ctx, "a-custom-resource", metav1.GetOptions{}) - if err != nil { - t.Fatalf("custom resource not found") - return - } + Get(s.T().Context(), "a-custom-resource", metav1.GetOptions{}) + s.Nilf(err, "custom resource not found") }) + }) + + s.Run("resources_create_or_update with valid namespaced json resource", func() { customJsonUpdated := "{\"apiVersion\": \"example.com/v1\", \"kind\": \"Custom\", \"metadata\": {\"name\": \"a-custom-resource\",\"annotations\": {\"updated\": \"true\"}}}" - resourcesCreateOrUpdateCustomUpdated, err := c.callTool("resources_create_or_update", map[string]interface{}{"resource": customJsonUpdated}) - t.Run("resources_create_or_update with valid namespaced json resource updates custom resource", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesCreateOrUpdateCustomUpdated.IsError { - t.Fatalf("call tool failed") - return - } + resourcesCreateOrUpdateCustomUpdated, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": customJsonUpdated}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesCreateOrUpdateCustomUpdated.IsError, "call tool failed") }) - t.Run("resources_create_or_update with valid namespaced json resource updates custom resource", func(t *testing.T) { + s.Run("updates custom resource", func() { dynamicClient := dynamic.NewForConfigOrDie(envTestRestConfig) customResource, _ := dynamicClient. Resource(schema.GroupVersionResource{Group: "example.com", Version: "v1", Resource: "customs"}). Namespace("default"). - Get(c.ctx, "a-custom-resource", metav1.GetOptions{}) - if customResource == nil { - t.Fatalf("custom resource not found") - return - } + Get(s.T().Context(), "a-custom-resource", metav1.GetOptions{}) + s.NotNil(customResource, "custom resource not found") annotations := customResource.GetAnnotations() - if annotations == nil || annotations["updated"] != "true" { - t.Fatalf("custom resource not updated") - return - } + s.Require().NotNil(annotations, "annotations should not be nil") + s.Equalf("true", annotations["updated"], "custom resource not updated") }) }) } -func TestResourcesCreateOrUpdateDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *ResourcesSuite) TestResourcesCreateOrUpdateDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Secret" }, { group = "rbac.authorization.k8s.io", version = "v1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("resources_create_or_update (denied by kind)", func() { secretYaml := "apiVersion: v1\nkind: Secret\nmetadata:\n name: a-denied-secret\n namespace: default\n" - deniedByKind, _ := c.callTool("resources_create_or_update", map[string]interface{}{"resource": secretYaml}) - t.Run("resources_create_or_update (denied by kind) has error", func(t *testing.T) { - if !deniedByKind.IsError { - t.Fatalf("call tool should fail") - } + deniedByKind, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": secretYaml}) + s.Run("has error", func() { + s.Truef(deniedByKind.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("resources_create_or_update (denied by kind) describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to create or update resources: resource not allowed: /v1, Kind=Secret" - if deniedByKind.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) }) + }) + s.Run("resources_create_or_update (denied by group)", func() { roleYaml := "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n name: a-denied-role\n namespace: default\n" - deniedByGroup, _ := c.callTool("resources_create_or_update", map[string]interface{}{"resource": roleYaml}) - t.Run("resources_create_or_update (denied by group) has error", func(t *testing.T) { - if !deniedByGroup.IsError { - t.Fatalf("call tool should fail") - } + deniedByGroup, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": roleYaml}) + s.Run("has error", func() { + s.Truef(deniedByGroup.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("resources_create_or_update (denied by group) describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to create or update resources: resource not allowed: rbac.authorization.k8s.io/v1, Kind=Role" - if deniedByGroup.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text) }) + }) + s.Run("resources_create_or_update (not denied) creates or updates resource", func() { configMapYaml := "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: a-cm-created-or-updated\n namespace: default\n" - allowedResource, _ := c.callTool("resources_create_or_update", map[string]interface{}{"resource": configMapYaml}) - t.Run("resources_create_or_update (not denied) creates or updates resource", func(t *testing.T) { - if allowedResource.IsError { - t.Fatalf("call tool should not fail") - } - }) + allowedResource, err := s.CallTool("resources_create_or_update", map[string]interface{}{"resource": configMapYaml}) + s.Falsef(allowedResource.IsError, "call tool should not fail") + s.Nilf(err, "call tool should not return error object") }) } -func TestResourcesDelete(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("resources_delete with missing apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_delete", map[string]interface{}{}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to delete resource, missing argument apiVersion" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_delete with missing kind returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to delete resource, missing argument kind" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_delete with invalid apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod", "name": "a-pod"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to delete resource, invalid argument apiVersion" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_delete with nonexistent apiVersion returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom", "name": "a-custom"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != `failed to delete resource: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"` { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_delete with missing name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to delete resource, missing argument name" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("resources_delete with nonexistent resource returns error", func(t *testing.T) { - toolResult, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap", "name": "nonexistent-configmap"}) - if !toolResult.IsError { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != `failed to delete resource: configmaps "nonexistent-configmap" not found` { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - resourcesDeleteCm, err := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap", "name": "a-configmap-to-delete"}) - t.Run("resources_delete with valid namespaced resource returns success", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesDeleteCm.IsError { - t.Fatalf("call tool failed") - return - } - if resourcesDeleteCm.Content[0].(mcp.TextContent).Text != "Resource deleted successfully" { - t.Fatalf("invalid tool result content got: %v", resourcesDeleteCm.Content[0].(mcp.TextContent).Text) - return - } - }) - client := c.newKubernetesClient() - t.Run("resources_delete with valid namespaced resource deletes ConfigMap", func(t *testing.T) { - _, err := client.CoreV1().ConfigMaps("default").Get(c.ctx, "a-configmap-to-delete", metav1.GetOptions{}) - if err == nil { - t.Fatalf("ConfigMap not deleted") - return - } - }) - resourcesDeleteNamespace, err := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "ns-to-delete"}) - t.Run("resources_delete with valid namespaced resource returns success", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if resourcesDeleteNamespace.IsError { - t.Fatalf("call tool failed") - return - } - if resourcesDeleteNamespace.Content[0].(mcp.TextContent).Text != "Resource deleted successfully" { - t.Fatalf("invalid tool result content got: %v", resourcesDeleteNamespace.Content[0].(mcp.TextContent).Text) - return - } +func (s *ResourcesSuite) TestResourcesDelete() { + s.InitMcpClient() + client := kubernetes.NewForConfigOrDie(envTestRestConfig) + + s.Run("resources_delete with missing apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_delete", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to delete resource, missing argument apiVersion", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_delete with missing kind returns error", func() { + toolResult, _ := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to delete resource, missing argument kind", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_delete with invalid apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod", "name": "a-pod"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to delete resource, invalid argument apiVersion", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_delete with nonexistent apiVersion returns error", func() { + toolResult, _ := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom", "name": "a-custom"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf(`failed to delete resource: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"`, + toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_delete with missing name returns error", func() { + toolResult, _ := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to delete resource, missing argument name", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("resources_delete with nonexistent resource returns error", func() { + toolResult, _ := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap", "name": "nonexistent-configmap"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf(`failed to delete resource: configmaps "nonexistent-configmap" not found`, + toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + + s.Run("resources_delete with valid namespaced resource", func() { + resourcesDeleteCm, err := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap", "name": "a-configmap-to-delete"}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesDeleteCm.IsError, "call tool failed") + s.Equalf("Resource deleted successfully", resourcesDeleteCm.Content[0].(mcp.TextContent).Text, + "invalid tool result content got: %v", resourcesDeleteCm.Content[0].(mcp.TextContent).Text) + }) + s.Run("deletes ConfigMap", func() { + _, err := client.CoreV1().ConfigMaps("default").Get(s.T().Context(), "a-configmap-to-delete", metav1.GetOptions{}) + s.Error(err, "ConfigMap not deleted") }) - t.Run("resources_delete with valid namespaced resource deletes Namespace", func(t *testing.T) { - ns, err := client.CoreV1().Namespaces().Get(c.ctx, "ns-to-delete", metav1.GetOptions{}) - if err == nil && ns != nil && ns.DeletionTimestamp == nil { - t.Fatalf("Namespace not deleted") - return - } + }) + + s.Run("resources_delete with valid cluster scoped resource", func() { + resourcesDeleteNamespace, err := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "ns-to-delete"}) + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(resourcesDeleteNamespace.IsError, "call tool failed") + s.Equalf("Resource deleted successfully", resourcesDeleteNamespace.Content[0].(mcp.TextContent).Text, + "invalid tool result content got: %v", resourcesDeleteNamespace.Content[0].(mcp.TextContent).Text) + }) + s.Run(" deletes Namespace", func() { + ns, err := client.CoreV1().Namespaces().Get(s.T().Context(), "ns-to-delete", metav1.GetOptions{}) + s.Truef(err != nil || (ns != nil && ns.DeletionTimestamp != nil), "Namespace not deleted") }) }) } -func TestResourcesDeleteDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *ResourcesSuite) TestResourcesDeleteDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Secret" }, { group = "rbac.authorization.k8s.io", version = "v1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - kc := c.newKubernetesClient() - _, _ = kc.CoreV1().ConfigMaps("default").Create(c.ctx, &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "allowed-configmap-to-delete"}, - }, metav1.CreateOptions{}) - deniedByKind, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "Secret", "namespace": "default", "name": "denied-secret"}) - t.Run("resources_delete (denied by kind) has error", func(t *testing.T) { - if !deniedByKind.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("resources_delete (denied by kind) describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + _, _ = kc.CoreV1().ConfigMaps("default").Create(s.T().Context(), &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "allowed-configmap-to-delete"}, + }, metav1.CreateOptions{}) + s.Run("resources_delete (denied by kind)", func() { + deniedByKind, err := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "Secret", "namespace": "default", "name": "denied-secret"}) + s.Run("has error", func() { + s.Truef(deniedByKind.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to delete resource: resource not allowed: /v1, Kind=Secret" - if deniedByKind.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) }) - deniedByGroup, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role", "namespace": "default", "name": "denied-role"}) - t.Run("resources_delete (denied by group) has error", func(t *testing.T) { - if !deniedByGroup.IsError { - t.Fatalf("call tool should fail") - } + }) + s.Run("resources_delete (denied by group)", func() { + deniedByGroup, err := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role", "namespace": "default", "name": "denied-role"}) + s.Run("has error", func() { + s.Truef(deniedByGroup.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("resources_delete (denied by group) describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to delete resource: resource not allowed: rbac.authorization.k8s.io/v1, Kind=Role" - if deniedByGroup.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, deniedByKind.Content[0].(mcp.TextContent).Text) - } - }) - allowedResource, _ := c.callTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap", "name": "allowed-configmap-to-delete"}) - t.Run("resources_delete (not denied) deletes resource", func(t *testing.T) { - if allowedResource.IsError { - t.Fatalf("call tool should not fail") - } + s.Equalf(expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, deniedByGroup.Content[0].(mcp.TextContent).Text) }) }) + s.Run("resources_delete (not denied) deletes resource", func() { + allowedResource, err := s.CallTool("resources_delete", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap", "name": "allowed-configmap-to-delete"}) + s.Falsef(allowedResource.IsError, "call tool should not fail") + s.Nilf(err, "call tool should not return error object") + }) +} + +func TestResources(t *testing.T) { + suite.Run(t, new(ResourcesSuite)) } From 09c8b235bc36eb89d2feeddf2dd19f87acb4ab33 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 30 Oct 2025 16:31:55 +0100 Subject: [PATCH 35/57] chore(docs): missing documentation for nodes_stats_summary (#424) Signed-off-by: Marc Nuri --- README.md | 3 +++ pkg/kubernetes/nodes.go | 1 + 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index a81daea1..4c916af9 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,9 @@ In case multi-cluster support is enabled (default) and you have access to multip - `query` (`string`) **(required)** - query specifies services(s) or files from which to return logs (required). Example: "kubelet" to fetch kubelet logs, "/" to fetch a specific log file from the node (e.g., "/var/log/kubelet.log" or "/var/log/kube-proxy.log") - `tailLines` (`integer`) - Number of lines to retrieve from the end of the logs (Optional, 0 means all logs) +- **nodes_stats_summary** - Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics + - `name` (`string`) **(required)** - Name of the node to get stats from + - **pods_list** - List all the Kubernetes pods in the current cluster from all namespaces - `labelSelector` (`string`) - Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label diff --git a/pkg/kubernetes/nodes.go b/pkg/kubernetes/nodes.go index c53ef5b6..e242eac8 100644 --- a/pkg/kubernetes/nodes.go +++ b/pkg/kubernetes/nodes.go @@ -39,6 +39,7 @@ func (k *Kubernetes) NodesLog(ctx context.Context, name string, query string, ta func (k *Kubernetes) NodesStatsSummary(ctx context.Context, name string) (string, error) { // Use the node proxy API to access stats summary from the kubelet + // https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ // This endpoint provides CPU, memory, filesystem, and network statistics req, err := k.AccessControlClientset().NodesStatsSummary(ctx, name) From ce6ec78e3c2396c50bf4db1f486ab4bb930c6408 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 3 Nov 2025 07:59:29 +0100 Subject: [PATCH 36/57] test(pods): update pods tests to use testify and improve readability (#426) Signed-off-by: Marc Nuri --- pkg/mcp/pods_run_test.go | 218 +++++++ pkg/mcp/pods_test.go | 1238 +++++++++++++------------------------- 2 files changed, 623 insertions(+), 833 deletions(-) create mode 100644 pkg/mcp/pods_run_test.go diff --git a/pkg/mcp/pods_run_test.go b/pkg/mcp/pods_run_test.go new file mode 100644 index 00000000..082ac310 --- /dev/null +++ b/pkg/mcp/pods_run_test.go @@ -0,0 +1,218 @@ +package mcp + +import ( + "strings" + "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/mark3labs/mcp-go/mcp" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/yaml" +) + +func TestPodsRun(t *testing.T) { + testCase(t, func(c *mcpContext) { + c.withEnvTest() + t.Run("pods_run with nil image returns error", func(t *testing.T) { + toolResult, _ := c.callTool("pods_run", map[string]interface{}{}) + if toolResult.IsError != true { + t.Errorf("call tool should fail") + return + } + if toolResult.Content[0].(mcp.TextContent).Text != "failed to run pod, missing argument image" { + t.Errorf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + return + } + }) + podsRunNilNamespace, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx"}) + t.Run("pods_run with image and nil namespace runs pod", func(t *testing.T) { + if err != nil { + t.Errorf("call tool failed %v", err) + return + } + if podsRunNilNamespace.IsError { + t.Errorf("call tool failed") + return + } + }) + var decodedNilNamespace []unstructured.Unstructured + err = yaml.Unmarshal([]byte(podsRunNilNamespace.Content[0].(mcp.TextContent).Text), &decodedNilNamespace) + t.Run("pods_run with image and nil namespace has yaml content", func(t *testing.T) { + if err != nil { + t.Errorf("invalid tool result content %v", err) + return + } + }) + t.Run("pods_run with image and nil namespace returns 1 item (Pod)", func(t *testing.T) { + if len(decodedNilNamespace) != 1 { + t.Errorf("invalid pods count, expected 1, got %v", len(decodedNilNamespace)) + return + } + if decodedNilNamespace[0].GetKind() != "Pod" { + t.Errorf("invalid pod kind, expected Pod, got %v", decodedNilNamespace[0].GetKind()) + return + } + }) + t.Run("pods_run with image and nil namespace returns pod in default", func(t *testing.T) { + if decodedNilNamespace[0].GetNamespace() != "default" { + t.Errorf("invalid pod namespace, expected default, got %v", decodedNilNamespace[0].GetNamespace()) + return + } + }) + t.Run("pods_run with image and nil namespace returns pod with random name", func(t *testing.T) { + if !strings.HasPrefix(decodedNilNamespace[0].GetName(), "kubernetes-mcp-server-run-") { + t.Errorf("invalid pod name, expected random, got %v", decodedNilNamespace[0].GetName()) + return + } + }) + t.Run("pods_run with image and nil namespace returns pod with labels", func(t *testing.T) { + labels := decodedNilNamespace[0].Object["metadata"].(map[string]interface{})["labels"].(map[string]interface{}) + if labels["app.kubernetes.io/name"] == "" { + t.Errorf("invalid labels, expected app.kubernetes.io/name, got %v", labels) + return + } + if labels["app.kubernetes.io/component"] == "" { + t.Errorf("invalid labels, expected app.kubernetes.io/component, got %v", labels) + return + } + if labels["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" { + t.Errorf("invalid labels, expected app.kubernetes.io/managed-by, got %v", labels) + return + } + if labels["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" { + t.Errorf("invalid labels, expected app.kubernetes.io/part-of, got %v", labels) + return + } + }) + t.Run("pods_run with image and nil namespace returns pod with nginx container", func(t *testing.T) { + containers := decodedNilNamespace[0].Object["spec"].(map[string]interface{})["containers"].([]interface{}) + if containers[0].(map[string]interface{})["image"] != "nginx" { + t.Errorf("invalid container name, expected nginx, got %v", containers[0].(map[string]interface{})["image"]) + return + } + }) + + podsRunNamespaceAndPort, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) + t.Run("pods_run with image, namespace, and port runs pod", func(t *testing.T) { + if err != nil { + t.Errorf("call tool failed %v", err) + return + } + if podsRunNamespaceAndPort.IsError { + t.Errorf("call tool failed") + return + } + }) + var decodedNamespaceAndPort []unstructured.Unstructured + err = yaml.Unmarshal([]byte(podsRunNamespaceAndPort.Content[0].(mcp.TextContent).Text), &decodedNamespaceAndPort) + t.Run("pods_run with image, namespace, and port has yaml content", func(t *testing.T) { + if err != nil { + t.Errorf("invalid tool result content %v", err) + return + } + }) + t.Run("pods_run with image, namespace, and port returns 2 items (Pod + Service)", func(t *testing.T) { + if len(decodedNamespaceAndPort) != 2 { + t.Errorf("invalid pods count, expected 2, got %v", len(decodedNamespaceAndPort)) + return + } + if decodedNamespaceAndPort[0].GetKind() != "Pod" { + t.Errorf("invalid pod kind, expected Pod, got %v", decodedNamespaceAndPort[0].GetKind()) + return + } + if decodedNamespaceAndPort[1].GetKind() != "Service" { + t.Errorf("invalid service kind, expected Service, got %v", decodedNamespaceAndPort[1].GetKind()) + return + } + }) + t.Run("pods_run with image, namespace, and port returns pod with port", func(t *testing.T) { + containers := decodedNamespaceAndPort[0].Object["spec"].(map[string]interface{})["containers"].([]interface{}) + ports := containers[0].(map[string]interface{})["ports"].([]interface{}) + if ports[0].(map[string]interface{})["containerPort"] != int64(80) { + t.Errorf("invalid container port, expected 80, got %v", ports[0].(map[string]interface{})["containerPort"]) + return + } + }) + t.Run("pods_run with image, namespace, and port returns service with port and selector", func(t *testing.T) { + ports := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["ports"].([]interface{}) + if ports[0].(map[string]interface{})["port"] != int64(80) { + t.Errorf("invalid service port, expected 80, got %v", ports[0].(map[string]interface{})["port"]) + return + } + if ports[0].(map[string]interface{})["targetPort"] != int64(80) { + t.Errorf("invalid service target port, expected 80, got %v", ports[0].(map[string]interface{})["targetPort"]) + return + } + selector := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["selector"].(map[string]interface{}) + if selector["app.kubernetes.io/name"] == "" { + t.Errorf("invalid service selector, expected app.kubernetes.io/name, got %v", selector) + return + } + if selector["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" { + t.Errorf("invalid service selector, expected app.kubernetes.io/managed-by, got %v", selector) + return + } + if selector["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" { + t.Errorf("invalid service selector, expected app.kubernetes.io/part-of, got %v", selector) + return + } + }) + }) +} + +func TestPodsRunDenied(t *testing.T) { + deniedResourcesServer := test.Must(config.ReadToml([]byte(` + denied_resources = [ { version = "v1", kind = "Pod" } ] + `))) + testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { + c.withEnvTest() + podsRun, _ := c.callTool("pods_run", map[string]interface{}{"image": "nginx"}) + t.Run("pods_run has error", func(t *testing.T) { + if !podsRun.IsError { + t.Fatalf("call tool should fail") + } + }) + t.Run("pods_run describes denial", func(t *testing.T) { + expectedMessage := "failed to run pod in namespace : resource not allowed: /v1, Kind=Pod" + if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage { + t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text) + } + }) + }) +} + +func TestPodsRunInOpenShift(t *testing.T) { + testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { + t.Run("pods_run with image, namespace, and port returns route with port", func(t *testing.T) { + podsRunInOpenShift, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) + if err != nil { + t.Errorf("call tool failed %v", err) + return + } + if podsRunInOpenShift.IsError { + t.Errorf("call tool failed") + return + } + var decodedPodServiceRoute []unstructured.Unstructured + err = yaml.Unmarshal([]byte(podsRunInOpenShift.Content[0].(mcp.TextContent).Text), &decodedPodServiceRoute) + if err != nil { + t.Errorf("invalid tool result content %v", err) + return + } + if len(decodedPodServiceRoute) != 3 { + t.Errorf("invalid pods count, expected 3, got %v", len(decodedPodServiceRoute)) + return + } + if decodedPodServiceRoute[2].GetKind() != "Route" { + t.Errorf("invalid route kind, expected Route, got %v", decodedPodServiceRoute[2].GetKind()) + return + } + targetPort := decodedPodServiceRoute[2].Object["spec"].(map[string]interface{})["port"].(map[string]interface{})["targetPort"].(int64) + if targetPort != 80 { + t.Errorf("invalid route target port, expected 80, got %v", targetPort) + return + } + }) + }) +} diff --git a/pkg/mcp/pods_test.go b/pkg/mcp/pods_test.go index cfa20dcb..8cff3e41 100644 --- a/pkg/mcp/pods_test.go +++ b/pkg/mcp/pods_test.go @@ -5,9 +5,8 @@ import ( "strings" "testing" - "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/containers/kubernetes-mcp-server/pkg/config" - "github.com/containers/kubernetes-mcp-server/pkg/output" + "github.com/BurntSushi/toml" + "github.com/stretchr/testify/suite" "github.com/mark3labs/mcp-go/mcp" corev1 "k8s.io/api/core/v1" @@ -16,228 +15,194 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/yaml" ) -func TestPodsListInAllNamespaces(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - toolResult, err := c.callTool("pods_list", map[string]interface{}{}) - t.Run("pods_list returns pods list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if toolResult.IsError { - t.Fatalf("call tool failed") - } +type PodsSuite struct { + BaseMcpSuite +} + +func (s *PodsSuite) TestPodsListInAllNamespaces() { + s.InitMcpClient() + s.Run("pods_list returns pods list in all namespaces", func() { + toolResult, err := s.CallTool("pods_list", map[string]interface{}{}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed") }) var decoded []unstructured.Unstructured err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - t.Run("pods_list has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_list returns 3 items", func(t *testing.T) { - if len(decoded) != 3 { - t.Fatalf("invalid pods count, expected 3, got %v", len(decoded)) - } + s.Run("returns at least 3 items", func() { + s.GreaterOrEqualf(len(decoded), 3, "invalid pods count, expected at least 3, got %v", len(decoded)) }) - t.Run("pods_list returns pod in ns-1", func(t *testing.T) { - if decoded[1].GetName() != "a-pod-in-ns-1" { - t.Fatalf("invalid pod name, expected a-pod-in-ns-1, got %v", decoded[1].GetName()) - } - if decoded[1].GetNamespace() != "ns-1" { - t.Fatalf("invalid pod namespace, expected ns-1, got %v", decoded[1].GetNamespace()) + var aPodInNs1, aPodInNs2 *unstructured.Unstructured + for _, pod := range decoded { + switch pod.GetName() { + case "a-pod-in-ns-1": + aPodInNs1 = &pod + case "a-pod-in-ns-2": + aPodInNs2 = &pod } + } + s.Run("returns pod in ns-1", func() { + s.Require().NotNil(aPodInNs1, "aPodInNs1 is nil") + s.Equalf("a-pod-in-ns-1", aPodInNs1.GetName(), "invalid pod name, expected a-pod-in-ns-1, got %v", aPodInNs1.GetName()) + s.Equalf("ns-1", aPodInNs1.GetNamespace(), "invalid pod namespace, expected ns-1, got %v", aPodInNs1.GetNamespace()) }) - t.Run("pods_list returns pod in ns-2", func(t *testing.T) { - if decoded[2].GetName() != "a-pod-in-ns-2" { - t.Fatalf("invalid pod name, expected a-pod-in-ns-2, got %v", decoded[2].GetName()) - } - if decoded[2].GetNamespace() != "ns-2" { - t.Fatalf("invalid pod namespace, expected ns-2, got %v", decoded[2].GetNamespace()) - } + s.Run("returns pod in ns-2", func() { + s.Require().NotNil(aPodInNs2, "aPodInNs2 is nil") + s.Equalf("a-pod-in-ns-2", aPodInNs2.GetName(), "invalid pod name, expected a-pod-in-ns-2, got %v", aPodInNs2.GetName()) + s.Equalf("ns-2", aPodInNs2.GetNamespace(), "invalid pod namespace, expected ns-2, got %v", aPodInNs2.GetNamespace()) }) - t.Run("pods_list omits managed fields", func(t *testing.T) { - if decoded[1].GetManagedFields() != nil { - t.Fatalf("managed fields should be omitted, got %v", decoded[0].GetManagedFields()) - } + s.Run("omits managed fields", func() { + s.Nilf(decoded[1].GetManagedFields(), "managed fields should be omitted, got %v", decoded[1].GetManagedFields()) }) }) } -func TestPodsListInAllNamespacesUnauthorized(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - defer restoreAuth(c.ctx) - client := c.newKubernetesClient() - // Authorize user only for default/configured namespace - r, _ := client.RbacV1().Roles("default").Create(c.ctx, &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{Name: "allow-pods-list"}, - Rules: []rbacv1.PolicyRule{{ - Verbs: []string{"get", "list"}, - APIGroups: []string{""}, - Resources: []string{"pods"}, - }}, - }, metav1.CreateOptions{}) - _, _ = client.RbacV1().RoleBindings("default").Create(c.ctx, &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: "allow-pods-list"}, - Subjects: []rbacv1.Subject{{Kind: "User", Name: envTestUser.Name}}, - RoleRef: rbacv1.RoleRef{Kind: "Role", Name: r.Name}, - }, metav1.CreateOptions{}) - // Deny cluster by removing cluster rule - _ = client.RbacV1().ClusterRoles().Delete(c.ctx, "allow-all", metav1.DeleteOptions{}) - toolResult, err := c.callTool("pods_list", map[string]interface{}{}) - t.Run("pods_list returns pods list for default namespace only", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if toolResult.IsError { - t.Fatalf("call tool failed %v", toolResult.Content) - return - } +func (s *PodsSuite) TestPodsListInAllNamespacesUnauthorized() { + s.InitMcpClient() + defer restoreAuth(s.T().Context()) + client := kubernetes.NewForConfigOrDie(envTestRestConfig) + // Authorize user only for default/configured namespace + r, _ := client.RbacV1().Roles("default").Create(s.T().Context(), &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{Name: "allow-pods-list"}, + Rules: []rbacv1.PolicyRule{{ + Verbs: []string{"get", "list"}, + APIGroups: []string{""}, + Resources: []string{"pods"}, + }}, + }, metav1.CreateOptions{}) + _, _ = client.RbacV1().RoleBindings("default").Create(s.T().Context(), &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: "allow-pods-list"}, + Subjects: []rbacv1.Subject{{Kind: "User", Name: envTestUser.Name}}, + RoleRef: rbacv1.RoleRef{Kind: "Role", Name: r.Name}, + }, metav1.CreateOptions{}) + // Deny cluster by removing cluster rule + _ = client.RbacV1().ClusterRoles().Delete(s.T().Context(), "allow-all", metav1.DeleteOptions{}) + s.Run("pods_list returns pods list for default namespace only", func() { + toolResult, err := s.CallTool("pods_list", map[string]interface{}{}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed %v", toolResult.Content) }) var decoded []unstructured.Unstructured err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - t.Run("pods_list has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_list returns 1 items", func(t *testing.T) { - if len(decoded) != 1 { - t.Fatalf("invalid pods count, expected 1, got %v", len(decoded)) - return - } + s.Run("returns at least 1 item", func() { + s.GreaterOrEqualf(len(decoded), 1, "invalid pods count, expected at least 1, got %v", len(decoded)) }) - t.Run("pods_list returns pod in default", func(t *testing.T) { - if decoded[0].GetName() != "a-pod-in-default" { - t.Fatalf("invalid pod name, expected a-pod-in-default, got %v", decoded[0].GetName()) - return + s.Run("all pods are in default namespace", func() { + for _, pod := range decoded { + s.Equalf("default", pod.GetNamespace(), "all pods should be in default namespace, got pod %s in namespace %s", pod.GetName(), pod.GetNamespace()) } - if decoded[0].GetNamespace() != "default" { - t.Fatalf("invalid pod namespace, expected default, got %v", decoded[0].GetNamespace()) - return + }) + s.Run("includes a-pod-in-default", func() { + found := false + for _, pod := range decoded { + if pod.GetName() == "a-pod-in-default" { + found = true + break + } } + s.Truef(found, "expected to find pod a-pod-in-default") }) }) } -func TestPodsListInNamespace(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("pods_list_in_namespace with nil namespace returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_list_in_namespace", map[string]interface{}{}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to list pods in namespace, missing argument namespace" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - toolResult, err := c.callTool("pods_list_in_namespace", map[string]interface{}{ +func (s *PodsSuite) TestPodsListInNamespace() { + s.InitMcpClient() + s.Run("pods_list_in_namespace with nil namespace returns error", func() { + toolResult, _ := s.CallTool("pods_list_in_namespace", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to list pods in namespace, missing argument namespace", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_list_in_namespace(namespace=ns-1) returns pods list", func() { + toolResult, err := s.CallTool("pods_list_in_namespace", map[string]interface{}{ "namespace": "ns-1", }) - t.Run("pods_list_in_namespace returns pods list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if toolResult.IsError { - t.Fatalf("call tool failed") - } + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed") }) var decoded []unstructured.Unstructured err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - t.Run("pods_list_in_namespace has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_list_in_namespace returns 1 items", func(t *testing.T) { - if len(decoded) != 1 { - t.Fatalf("invalid pods count, expected 1, got %v", len(decoded)) - } + s.Run("returns 1 item", func() { + s.Lenf(decoded, 1, "invalid pods count, expected 1, got %v", len(decoded)) }) - t.Run("pods_list_in_namespace returns pod in ns-1", func(t *testing.T) { - if decoded[0].GetName() != "a-pod-in-ns-1" { - t.Errorf("invalid pod name, expected a-pod-in-ns-1, got %v", decoded[0].GetName()) - } - if decoded[0].GetNamespace() != "ns-1" { - t.Errorf("invalid pod namespace, expected ns-1, got %v", decoded[0].GetNamespace()) - } + s.Run("returns pod in ns-1", func() { + s.Equalf("a-pod-in-ns-1", decoded[0].GetName(), "invalid pod name, expected a-pod-in-ns-1, got %v", decoded[0].GetName()) + s.Equalf("ns-1", decoded[0].GetNamespace(), "invalid pod namespace, expected ns-1, got %v", decoded[0].GetNamespace()) }) - t.Run("pods_list_in_namespace omits managed fields", func(t *testing.T) { - if decoded[0].GetManagedFields() != nil { - t.Fatalf("managed fields should be omitted, got %v", decoded[0].GetManagedFields()) - } + s.Run("omits managed fields", func() { + s.Nilf(decoded[0].GetManagedFields(), "managed fields should be omitted, got %v", decoded[0].GetManagedFields()) }) }) } -func TestPodsListDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsSuite) TestPodsListDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsList, _ := c.callTool("pods_list", map[string]interface{}{}) - t.Run("pods_list has error", func(t *testing.T) { - if !podsList.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("pods_list describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("pods_list (denied)", func() { + podsList, err := s.CallTool("pods_list", map[string]interface{}{}) + s.Run("has error", func() { + s.Truef(podsList.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to list pods in all namespaces: resource not allowed: /v1, Kind=Pod" - if podsList.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsList.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, podsList.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, podsList.Content[0].(mcp.TextContent).Text) }) - podsListInNamespace, _ := c.callTool("pods_list_in_namespace", map[string]interface{}{"namespace": "ns-1"}) - t.Run("pods_list_in_namespace has error", func(t *testing.T) { - if !podsListInNamespace.IsError { - t.Fatalf("call tool should fail") - } + }) + s.Run("pods_list_in_namespace (denied)", func() { + podsListInNamespace, err := s.CallTool("pods_list_in_namespace", map[string]interface{}{"namespace": "ns-1"}) + s.Run("has error", func() { + s.Truef(podsListInNamespace.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("pods_list_in_namespace describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to list pods in namespace ns-1: resource not allowed: /v1, Kind=Pod" - if podsListInNamespace.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsListInNamespace.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, podsListInNamespace.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, podsListInNamespace.Content[0].(mcp.TextContent).Text) }) }) } -func TestPodsListAsTable(t *testing.T) { - testCaseWithContext(t, &mcpContext{listOutput: output.Table}, func(c *mcpContext) { - c.withEnvTest() - podsList, err := c.callTool("pods_list", map[string]interface{}{}) - t.Run("pods_list returns pods list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if podsList.IsError { - t.Fatalf("call tool failed") - } - }) +func (s *PodsSuite) TestPodsListAsTable() { + s.Cfg.ListOutput = "table" + s.InitMcpClient() + s.Run("pods_list (list_output=table)", func() { + podsList, err := s.CallTool("pods_list", map[string]interface{}{}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsList.IsError, "call tool failed") + }) + s.Require().NotNil(podsList, "Expected tool result from call") outPodsList := podsList.Content[0].(mcp.TextContent).Text - t.Run("pods_list returns table with 1 header and 3 rows", func(t *testing.T) { + s.Run("returns table with header and rows", func() { lines := strings.Count(outPodsList, "\n") - if lines != 4 { - t.Fatalf("invalid line count, expected 4 (1 header, 3 row), got %v", lines) - } + s.GreaterOrEqualf(lines, 3, "invalid line count, expected at least 3 (1 header, 2+ rows), got %v", lines) }) - t.Run("pods_list_in_namespace returns column headers", func(t *testing.T) { + s.Run("returns column headers", func() { expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+READY\\s+STATUS\\s+RESTARTS\\s+AGE\\s+IP\\s+NODE\\s+NOMINATED NODE\\s+READINESS GATES\\s+LABELS" - if m, e := regexp.MatchString(expectedHeaders, outPodsList); !m || e != nil { - t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsList) - } + m, e := regexp.MatchString(expectedHeaders, outPodsList) + s.Truef(m, "Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsList) + s.NoErrorf(e, "Error matching headers regex: %v", e) }) - t.Run("pods_list_in_namespace returns formatted row for a-pod-in-ns-1", func(t *testing.T) { + s.Run("returns formatted row for a-pod-in-ns-1", func() { expectedRow := "(?ns-1)\\s+" + "(?v1)\\s+" + "(?Pod)\\s+" + @@ -251,11 +216,11 @@ func TestPodsListAsTable(t *testing.T) { "(?)\\s+" + "(?)\\s+" + "(?)" - if m, e := regexp.MatchString(expectedRow, outPodsList); !m || e != nil { - t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList) - } + m, e := regexp.MatchString(expectedRow, outPodsList) + s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, outPodsList) + s.NoErrorf(e, "Error matching a-pod-in-ns-1 regex: %v", e) }) - t.Run("pods_list_in_namespace returns formatted row for a-pod-in-default", func(t *testing.T) { + s.Run("returns formatted row for a-pod-in-default", func() { expectedRow := "(?default)\\s+" + "(?v1)\\s+" + "(?Pod)\\s+" + @@ -269,36 +234,32 @@ func TestPodsListAsTable(t *testing.T) { "(?)\\s+" + "(?)\\s+" + "(?app=nginx)" - if m, e := regexp.MatchString(expectedRow, outPodsList); !m || e != nil { - t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList) - } + m, e := regexp.MatchString(expectedRow, outPodsList) + s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, outPodsList) + s.NoErrorf(e, "Error matching a-pod-in-default regex: %v", e) }) - podsListInNamespace, err := c.callTool("pods_list_in_namespace", map[string]interface{}{ + }) + s.Run("pods_list_in_namespace (list_output=table)", func() { + podsListInNamespace, err := s.CallTool("pods_list_in_namespace", map[string]interface{}{ "namespace": "ns-1", }) - t.Run("pods_list_in_namespace returns pods list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsListInNamespace.IsError { - t.Fatalf("call tool failed") - } + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsListInNamespace.IsError, "call tool failed") }) + s.Require().NotNil(podsListInNamespace, "Expected tool result from call") outPodsListInNamespace := podsListInNamespace.Content[0].(mcp.TextContent).Text - t.Run("pods_list_in_namespace returns table with 1 header and 1 row", func(t *testing.T) { + s.Run("returns table with header and row", func() { lines := strings.Count(outPodsListInNamespace, "\n") - if lines != 2 { - t.Fatalf("invalid line count, expected 2 (1 header, 1 row), got %v", lines) - } + s.GreaterOrEqualf(lines, 1, "invalid line count, expected at least 1 (1 header, 1+ rows), got %v", lines) }) - t.Run("pods_list_in_namespace returns column headers", func(t *testing.T) { + s.Run("returns column headers", func() { expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+READY\\s+STATUS\\s+RESTARTS\\s+AGE\\s+IP\\s+NODE\\s+NOMINATED NODE\\s+READINESS GATES\\s+LABELS" - if m, e := regexp.MatchString(expectedHeaders, outPodsListInNamespace); !m || e != nil { - t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsListInNamespace) - } + m, e := regexp.MatchString(expectedHeaders, outPodsListInNamespace) + s.Truef(m, "Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsListInNamespace) + s.NoErrorf(e, "Error matching headers regex: %v", e) }) - t.Run("pods_list_in_namespace returns formatted row", func(t *testing.T) { + s.Run("returns formatted row", func() { expectedRow := "(?ns-1)\\s+" + "(?v1)\\s+" + "(?Pod)\\s+" + @@ -312,279 +273,183 @@ func TestPodsListAsTable(t *testing.T) { "(?)\\s+" + "(?)\\s+" + "(?)" - if m, e := regexp.MatchString(expectedRow, outPodsListInNamespace); !m || e != nil { - t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsListInNamespace) - } + m, e := regexp.MatchString(expectedRow, outPodsListInNamespace) + s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, outPodsListInNamespace) + s.NoErrorf(e, "Error matching formatted row regex: %v", e) }) }) } -func TestPodsGet(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("pods_get with nil name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_get", map[string]interface{}{}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get pod, missing argument name" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("pods_get with not found name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_get", map[string]interface{}{"name": "not-found"}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get pod not-found in namespace : pods \"not-found\" not found" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - podsGetNilNamespace, err := c.callTool("pods_get", map[string]interface{}{ +func (s *PodsSuite) TestPodsGet() { + s.InitMcpClient() + s.Run("pods_get with nil name returns error", func() { + toolResult, _ := s.CallTool("pods_get", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get pod, missing argument name", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_get(name=not-found) with not found name returns error", func() { + toolResult, _ := s.CallTool("pods_get", map[string]interface{}{"name": "not-found"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get pod not-found in namespace : pods \"not-found\" not found", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_get(name=a-pod-in-default, namespace=nil), uses configured namespace", func() { + podsGetNilNamespace, err := s.CallTool("pods_get", map[string]interface{}{ "name": "a-pod-in-default", }) - t.Run("pods_get with name and nil namespace returns pod", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsGetNilNamespace.IsError { - t.Fatalf("call tool failed") - return - } + s.Run("returns pod", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsGetNilNamespace.IsError, "call tool failed") }) var decodedNilNamespace unstructured.Unstructured err = yaml.Unmarshal([]byte(podsGetNilNamespace.Content[0].(mcp.TextContent).Text), &decodedNilNamespace) - t.Run("pods_get with name and nil namespace has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_get with name and nil namespace returns pod in default", func(t *testing.T) { - if decodedNilNamespace.GetName() != "a-pod-in-default" { - t.Fatalf("invalid pod name, expected a-pod-in-default, got %v", decodedNilNamespace.GetName()) - return - } - if decodedNilNamespace.GetNamespace() != "default" { - t.Fatalf("invalid pod namespace, expected default, got %v", decodedNilNamespace.GetNamespace()) - return - } + s.Run("returns pod in default", func() { + s.Equalf("a-pod-in-default", decodedNilNamespace.GetName(), "invalid pod name, expected a-pod-in-default, got %v", decodedNilNamespace.GetName()) + s.Equalf("default", decodedNilNamespace.GetNamespace(), "invalid pod namespace, expected default, got %v", decodedNilNamespace.GetNamespace()) }) - t.Run("pods_get with name and nil namespace omits managed fields", func(t *testing.T) { - if decodedNilNamespace.GetManagedFields() != nil { - t.Fatalf("managed fields should be omitted, got %v", decodedNilNamespace.GetManagedFields()) - return - } + s.Run("omits managed fields", func() { + s.Nilf(decodedNilNamespace.GetManagedFields(), "managed fields should be omitted, got %v", decodedNilNamespace.GetManagedFields()) }) - podsGetInNamespace, err := c.callTool("pods_get", map[string]interface{}{ + }) + s.Run("pods_get(name=a-pod-in-default, namespace=ns-1)", func() { + podsGetInNamespace, err := s.CallTool("pods_get", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", }) - t.Run("pods_get with name and namespace returns pod", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsGetInNamespace.IsError { - t.Fatalf("call tool failed") - return - } + s.Run("returns pod", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsGetInNamespace.IsError, "call tool failed") }) var decodedInNamespace unstructured.Unstructured err = yaml.Unmarshal([]byte(podsGetInNamespace.Content[0].(mcp.TextContent).Text), &decodedInNamespace) - t.Run("pods_get with name and namespace has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_get with name and namespace returns pod in ns-1", func(t *testing.T) { - if decodedInNamespace.GetName() != "a-pod-in-ns-1" { - t.Fatalf("invalid pod name, expected a-pod-in-ns-1, got %v", decodedInNamespace.GetName()) - return - } - if decodedInNamespace.GetNamespace() != "ns-1" { - t.Fatalf("invalid pod namespace, ns-1 ns-1, got %v", decodedInNamespace.GetNamespace()) - return - } + s.Run("returns pod in ns-1", func() { + s.Equalf("a-pod-in-ns-1", decodedInNamespace.GetName(), "invalid pod name, expected a-pod-in-ns-1, got %v", decodedInNamespace.GetName()) + s.Equalf("ns-1", decodedInNamespace.GetNamespace(), "invalid pod namespace, expected ns-1, got %v", decodedInNamespace.GetNamespace()) }) }) } -func TestPodsGetDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsSuite) TestPodsGetDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsGet, _ := c.callTool("pods_get", map[string]interface{}{"name": "a-pod-in-default"}) - t.Run("pods_get has error", func(t *testing.T) { - if !podsGet.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("pods_get describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("pods_get (denied)", func() { + podsGet, err := s.CallTool("pods_get", map[string]interface{}{"name": "a-pod-in-default"}) + s.Run("has error", func() { + s.Truef(podsGet.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to get pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod" - if podsGet.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsGet.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, podsGet.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, podsGet.Content[0].(mcp.TextContent).Text) }) }) } -func TestPodsDelete(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - // Errors - t.Run("pods_delete with nil name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_delete", map[string]interface{}{}) - if toolResult.IsError != true { - t.Errorf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to delete pod, missing argument name" { - t.Errorf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("pods_delete with not found name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_delete", map[string]interface{}{"name": "not-found"}) - if toolResult.IsError != true { - t.Errorf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to delete pod not-found in namespace : pods \"not-found\" not found" { - t.Errorf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - // Default/nil Namespace - kc := c.newKubernetesClient() - _, _ = kc.CoreV1().Pods("default").Create(c.ctx, &corev1.Pod{ +func (s *PodsSuite) TestPodsDelete() { + s.InitMcpClient() + s.Run("pods_delete with nil name returns error", func() { + toolResult, _ := s.CallTool("pods_delete", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to delete pod, missing argument name", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_delete(name=not-found) with not found name returns error", func() { + toolResult, _ := s.CallTool("pods_delete", map[string]interface{}{"name": "not-found"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to delete pod not-found in namespace : pods \"not-found\" not found", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_delete(name=a-pod-to-delete, namespace=nil), uses configured namespace", func() { + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + _, _ = kc.CoreV1().Pods("default").Create(s.T().Context(), &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "a-pod-to-delete"}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, metav1.CreateOptions{}) - podsDeleteNilNamespace, err := c.callTool("pods_delete", map[string]interface{}{ + podsDeleteNilNamespace, err := s.CallTool("pods_delete", map[string]interface{}{ "name": "a-pod-to-delete", }) - t.Run("pods_delete with name and nil namespace returns success", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsDeleteNilNamespace.IsError { - t.Errorf("call tool failed") - return - } - if podsDeleteNilNamespace.Content[0].(mcp.TextContent).Text != "Pod deleted successfully" { - t.Errorf("invalid tool result content, got %v", podsDeleteNilNamespace.Content[0].(mcp.TextContent).Text) - return - } + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsDeleteNilNamespace.IsError, "call tool failed") + s.Equalf("Pod deleted successfully", podsDeleteNilNamespace.Content[0].(mcp.TextContent).Text, "invalid tool result content, got %v", podsDeleteNilNamespace.Content[0].(mcp.TextContent).Text) }) - t.Run("pods_delete with name and nil namespace deletes Pod", func(t *testing.T) { - p, pErr := kc.CoreV1().Pods("default").Get(c.ctx, "a-pod-to-delete", metav1.GetOptions{}) - if pErr == nil && p != nil && p.DeletionTimestamp == nil { - t.Errorf("Pod not deleted") - return - } + s.Run("deletes Pod", func() { + p, pErr := kc.CoreV1().Pods("default").Get(s.T().Context(), "a-pod-to-delete", metav1.GetOptions{}) + s.Truef(pErr != nil || p == nil || p.DeletionTimestamp != nil, "Pod not deleted") }) - // Provided Namespace - _, _ = kc.CoreV1().Pods("ns-1").Create(c.ctx, &corev1.Pod{ + }) + s.Run("pods_delete(name=a-pod-to-delete-in-ns-1, namespace=ns-1)", func() { + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + _, _ = kc.CoreV1().Pods("ns-1").Create(s.T().Context(), &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "a-pod-to-delete-in-ns-1"}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, metav1.CreateOptions{}) - podsDeleteInNamespace, err := c.callTool("pods_delete", map[string]interface{}{ + podsDeleteInNamespace, err := s.CallTool("pods_delete", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-to-delete-in-ns-1", }) - t.Run("pods_delete with name and namespace returns success", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsDeleteInNamespace.IsError { - t.Errorf("call tool failed") - return - } - if podsDeleteInNamespace.Content[0].(mcp.TextContent).Text != "Pod deleted successfully" { - t.Errorf("invalid tool result content, got %v", podsDeleteInNamespace.Content[0].(mcp.TextContent).Text) - return - } + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsDeleteInNamespace.IsError, "call tool failed") + s.Equalf("Pod deleted successfully", podsDeleteInNamespace.Content[0].(mcp.TextContent).Text, "invalid tool result content, got %v", podsDeleteInNamespace.Content[0].(mcp.TextContent).Text) }) - t.Run("pods_delete with name and namespace deletes Pod", func(t *testing.T) { - p, pErr := kc.CoreV1().Pods("ns-1").Get(c.ctx, "a-pod-to-delete-in-ns-1", metav1.GetOptions{}) - if pErr == nil && p != nil && p.DeletionTimestamp == nil { - t.Errorf("Pod not deleted") - return - } + s.Run("deletes Pod", func() { + p, pErr := kc.CoreV1().Pods("ns-1").Get(s.T().Context(), "a-pod-to-delete-in-ns-1", metav1.GetOptions{}) + s.Truef(pErr != nil || p == nil || p.DeletionTimestamp != nil, "Pod not deleted") }) - // Managed Pod + }) + s.Run("pods_delete(name=a-managed-pod-to-delete, namespace=ns-1) with managed pod", func() { + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) managedLabels := map[string]string{ "app.kubernetes.io/managed-by": "kubernetes-mcp-server", "app.kubernetes.io/name": "a-manged-pod-to-delete", } - _, _ = kc.CoreV1().Pods("default").Create(c.ctx, &corev1.Pod{ + _, _ = kc.CoreV1().Pods("default").Create(s.T().Context(), &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "a-managed-pod-to-delete", Labels: managedLabels}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, metav1.CreateOptions{}) - _, _ = kc.CoreV1().Services("default").Create(c.ctx, &corev1.Service{ + _, _ = kc.CoreV1().Services("default").Create(s.T().Context(), &corev1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "a-managed-service-to-delete", Labels: managedLabels}, Spec: corev1.ServiceSpec{Selector: managedLabels, Ports: []corev1.ServicePort{{Port: 80}}}, }, metav1.CreateOptions{}) - podsDeleteManaged, err := c.callTool("pods_delete", map[string]interface{}{ + podsDeleteManaged, err := s.CallTool("pods_delete", map[string]interface{}{ "name": "a-managed-pod-to-delete", }) - t.Run("pods_delete with managed pod returns success", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsDeleteManaged.IsError { - t.Errorf("call tool failed") - return - } - if podsDeleteManaged.Content[0].(mcp.TextContent).Text != "Pod deleted successfully" { - t.Errorf("invalid tool result content, got %v", podsDeleteManaged.Content[0].(mcp.TextContent).Text) - return - } + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsDeleteManaged.IsError, "call tool failed") + s.Equalf("Pod deleted successfully", podsDeleteManaged.Content[0].(mcp.TextContent).Text, "invalid tool result content, got %v", podsDeleteManaged.Content[0].(mcp.TextContent).Text) }) - t.Run("pods_delete with managed pod deletes Pod and Service", func(t *testing.T) { - p, pErr := kc.CoreV1().Pods("default").Get(c.ctx, "a-managed-pod-to-delete", metav1.GetOptions{}) - if pErr == nil && p != nil && p.DeletionTimestamp == nil { - t.Errorf("Pod not deleted") - return - } - s, sErr := kc.CoreV1().Services("default").Get(c.ctx, "a-managed-service-to-delete", metav1.GetOptions{}) - if sErr == nil && s != nil && s.DeletionTimestamp == nil { - t.Errorf("Service not deleted") - return - } + s.Run("deletes Pod and Service", func() { + p, pErr := kc.CoreV1().Pods("default").Get(s.T().Context(), "a-managed-pod-to-delete", metav1.GetOptions{}) + s.Truef(pErr != nil || p == nil || p.DeletionTimestamp != nil, "Pod not deleted") + svc, sErr := kc.CoreV1().Services("default").Get(s.T().Context(), "a-managed-service-to-delete", metav1.GetOptions{}) + s.Truef(sErr != nil || svc == nil || svc.DeletionTimestamp != nil, "Service not deleted") }) }) } -func TestPodsDeleteDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsSuite) TestPodsDeleteDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsDelete, _ := c.callTool("pods_delete", map[string]interface{}{"name": "a-pod-in-default"}) - t.Run("pods_delete has error", func(t *testing.T) { - if !podsDelete.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("pods_delete describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("pods_delete (denied)", func() { + podsDelete, err := s.CallTool("pods_delete", map[string]interface{}{"name": "a-pod-in-default"}) + s.Run("has error", func() { + s.Truef(podsDelete.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to delete pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod" - if podsDelete.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsDelete.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, podsDelete.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, podsDelete.Content[0].(mcp.TextContent).Text) }) }) } @@ -644,485 +509,192 @@ func TestPodsDeleteInOpenShift(t *testing.T) { }) } -func TestPodsLog(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("pods_log with nil name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_log", map[string]interface{}{}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get pod log, missing argument name" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - t.Run("pods_log with not found name returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_log", map[string]interface{}{"name": "not-found"}) - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get pod not-found log in namespace : pods \"not-found\" not found" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - podsLogNilNamespace, err := c.callTool("pods_log", map[string]interface{}{ +func (s *PodsSuite) TestPodsLog() { + s.InitMcpClient() + s.Run("pods_log with nil name returns error", func() { + toolResult, _ := s.CallTool("pods_log", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get pod log, missing argument name", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_log with not found name returns error", func() { + toolResult, _ := s.CallTool("pods_log", map[string]interface{}{"name": "not-found"}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get pod not-found log in namespace : pods \"not-found\" not found", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_log(name=a-pod-in-default, namespace=nil), uses configured namespace", func() { + podsLogNilNamespace, err := s.CallTool("pods_log", map[string]interface{}{ "name": "a-pod-in-default", }) - t.Run("pods_log with name and nil namespace returns pod log", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsLogNilNamespace.IsError { - t.Fatalf("call tool failed") - return - } - }) - podsLogInNamespace, err := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsLogNilNamespace.IsError, "call tool failed") + }) + s.Run("pods_log(name=a-pod-in-ns-1, namespace=ns-1)", func() { + podsLogInNamespace, err := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", }) - t.Run("pods_log with name and namespace returns pod log", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsLogInNamespace.IsError { - t.Fatalf("call tool failed") - return - } - }) - podsContainerLogInNamespace, err := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsLogInNamespace.IsError, "call tool failed") + }) + s.Run("pods_log(name=a-pod-in-ns-1, namespace=ns-1, container=nginx)", func() { + podsContainerLogInNamespace, err := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", "container": "nginx", }) - t.Run("pods_log with name, container and namespace returns pod log", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsContainerLogInNamespace.IsError { - t.Fatalf("call tool failed") - return - } - }) - toolResult, err := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsContainerLogInNamespace.IsError, "call tool failed") + }) + s.Run("with non existing container returns error", func() { + toolResult, err := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", "container": "a-not-existing-container", }) - t.Run("pods_log with non existing container returns error", func(t *testing.T) { - if toolResult.IsError != true { - t.Fatalf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to get pod a-pod-in-ns-1 log in namespace ns-1: container a-not-existing-container is not valid for pod a-pod-in-ns-1" { - t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - podsPreviousLogInNamespace, err := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool should not return error object") + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to get pod a-pod-in-ns-1 log in namespace ns-1: container a-not-existing-container is not valid for pod a-pod-in-ns-1", toolResult.Content[0].(mcp.TextContent).Text, "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_log(previous=true) returns previous pod log", func() { + podsPreviousLogInNamespace, err := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", "previous": true, }) - t.Run("pods_log with previous=true returns previous pod log", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsPreviousLogInNamespace.IsError { - t.Fatalf("call tool failed") - return - } - }) - podsPreviousLogFalse, err := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsPreviousLogInNamespace.IsError, "call tool failed") + }) + s.Run("pods_log(previous=false) returns current pod log", func() { + podsPreviousLogFalse, err := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", "previous": false, }) - t.Run("pods_log with previous=false returns current pod log", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsPreviousLogFalse.IsError { - t.Fatalf("call tool failed") - return - } - }) - - // Test with tail parameter - podsTailLines, err := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsPreviousLogFalse.IsError, "call tool failed") + }) + s.Run("pods_log(tail=50) returns pod log", func() { + podsTailLines, err := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", "tail": 50, }) - t.Run("pods_log with tail=50 returns pod log", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if podsTailLines.IsError { - t.Fatalf("call tool failed") - return - } - }) - - // Test with invalid tail parameter - podsInvalidTailLines, _ := c.callTool("pods_log", map[string]interface{}{ + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsTailLines.IsError, "call tool failed") + }) + s.Run("with invalid tail returns error", func() { + podsInvalidTailLines, _ := s.CallTool("pods_log", map[string]interface{}{ "namespace": "ns-1", "name": "a-pod-in-ns-1", "tail": "invalid", }) - t.Run("pods_log with invalid tail returns error", func(t *testing.T) { - if !podsInvalidTailLines.IsError { - t.Fatalf("call tool should fail") - return - } - expectedErrorMsg := "failed to parse tail parameter: expected integer" - if errMsg := podsInvalidTailLines.Content[0].(mcp.TextContent).Text; !strings.Contains(errMsg, expectedErrorMsg) { - t.Fatalf("unexpected error message, expected to contain '%s', got '%s'", expectedErrorMsg, errMsg) - return - } - }) + s.Truef(podsInvalidTailLines.IsError, "call tool should fail") + expectedErrorMsg := "failed to parse tail parameter: expected integer" + errMsg := podsInvalidTailLines.Content[0].(mcp.TextContent).Text + s.Containsf(errMsg, expectedErrorMsg, "unexpected error message, expected to contain '%s', got '%s'", expectedErrorMsg, errMsg) }) } -func TestPodsLogDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsSuite) TestPodsLogDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsLog, _ := c.callTool("pods_log", map[string]interface{}{"name": "a-pod-in-default"}) - t.Run("pods_log has error", func(t *testing.T) { - if !podsLog.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("pods_log describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("pods_log (denied)", func() { + podsLog, err := s.CallTool("pods_log", map[string]interface{}{"name": "a-pod-in-default"}) + s.Run("has error", func() { + s.Truef(podsLog.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to get pod a-pod-in-default log in namespace : resource not allowed: /v1, Kind=Pod" - if podsLog.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsLog.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, podsLog.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, podsLog.Content[0].(mcp.TextContent).Text) }) }) } -func TestPodsRun(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("pods_run with nil image returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_run", map[string]interface{}{}) - if toolResult.IsError != true { - t.Errorf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to run pod, missing argument image" { - t.Errorf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - podsRunNilNamespace, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx"}) - t.Run("pods_run with image and nil namespace runs pod", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsRunNilNamespace.IsError { - t.Errorf("call tool failed") - return - } - }) - var decodedNilNamespace []unstructured.Unstructured - err = yaml.Unmarshal([]byte(podsRunNilNamespace.Content[0].(mcp.TextContent).Text), &decodedNilNamespace) - t.Run("pods_run with image and nil namespace has yaml content", func(t *testing.T) { - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } - }) - t.Run("pods_run with image and nil namespace returns 1 item (Pod)", func(t *testing.T) { - if len(decodedNilNamespace) != 1 { - t.Errorf("invalid pods count, expected 1, got %v", len(decodedNilNamespace)) - return - } - if decodedNilNamespace[0].GetKind() != "Pod" { - t.Errorf("invalid pod kind, expected Pod, got %v", decodedNilNamespace[0].GetKind()) - return - } - }) - t.Run("pods_run with image and nil namespace returns pod in default", func(t *testing.T) { - if decodedNilNamespace[0].GetNamespace() != "default" { - t.Errorf("invalid pod namespace, expected default, got %v", decodedNilNamespace[0].GetNamespace()) - return - } +func (s *PodsSuite) TestPodsListWithLabelSelector() { + s.InitMcpClient() + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + // Create pods with labels + _, _ = kc.CoreV1().Pods("default").Create(s.T().Context(), &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-with-labels", + Labels: map[string]string{"app": "test", "env": "dev"}, + }, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, metav1.CreateOptions{}) + _, _ = kc.CoreV1().Pods("ns-1").Create(s.T().Context(), &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "another-pod-with-labels", + Labels: map[string]string{"app": "test", "env": "prod"}, + }, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, + }, metav1.CreateOptions{}) + + s.Run("pods_list(labelSelector=app=test) returns filtered pods from configured namespace", func() { + toolResult, err := s.CallTool("pods_list", map[string]interface{}{ + "labelSelector": "app=test", }) - t.Run("pods_run with image and nil namespace returns pod with random name", func(t *testing.T) { - if !strings.HasPrefix(decodedNilNamespace[0].GetName(), "kubernetes-mcp-server-run-") { - t.Errorf("invalid pod name, expected random, got %v", decodedNilNamespace[0].GetName()) - return - } + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed") }) - t.Run("pods_run with image and nil namespace returns pod with labels", func(t *testing.T) { - labels := decodedNilNamespace[0].Object["metadata"].(map[string]interface{})["labels"].(map[string]interface{}) - if labels["app.kubernetes.io/name"] == "" { - t.Errorf("invalid labels, expected app.kubernetes.io/name, got %v", labels) - return - } - if labels["app.kubernetes.io/component"] == "" { - t.Errorf("invalid labels, expected app.kubernetes.io/component, got %v", labels) - return - } - if labels["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" { - t.Errorf("invalid labels, expected app.kubernetes.io/managed-by, got %v", labels) - return - } - if labels["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" { - t.Errorf("invalid labels, expected app.kubernetes.io/part-of, got %v", labels) - return - } + var decoded []unstructured.Unstructured + err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_run with image and nil namespace returns pod with nginx container", func(t *testing.T) { - containers := decodedNilNamespace[0].Object["spec"].(map[string]interface{})["containers"].([]interface{}) - if containers[0].(map[string]interface{})["image"] != "nginx" { - t.Errorf("invalid container name, expected nginx, got %v", containers[0].(map[string]interface{})["image"]) - return - } + s.Run("returns 2 pods", func() { + s.Lenf(decoded, 2, "invalid pods count, expected 2, got %v", len(decoded)) }) + }) - podsRunNamespaceAndPort, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) - t.Run("pods_run with image, namespace, and port runs pod", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsRunNamespaceAndPort.IsError { - t.Errorf("call tool failed") - return - } + s.Run("pods_list_in_namespace(labelSelector=env=prod, namespace=ns-1) returns filtered pods", func() { + toolResult, err := s.CallTool("pods_list_in_namespace", map[string]interface{}{ + "namespace": "ns-1", + "labelSelector": "env=prod", }) - var decodedNamespaceAndPort []unstructured.Unstructured - err = yaml.Unmarshal([]byte(podsRunNamespaceAndPort.Content[0].(mcp.TextContent).Text), &decodedNamespaceAndPort) - t.Run("pods_run with image, namespace, and port has yaml content", func(t *testing.T) { - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed") }) - t.Run("pods_run with image, namespace, and port returns 2 items (Pod + Service)", func(t *testing.T) { - if len(decodedNamespaceAndPort) != 2 { - t.Errorf("invalid pods count, expected 2, got %v", len(decodedNamespaceAndPort)) - return - } - if decodedNamespaceAndPort[0].GetKind() != "Pod" { - t.Errorf("invalid pod kind, expected Pod, got %v", decodedNamespaceAndPort[0].GetKind()) - return - } - if decodedNamespaceAndPort[1].GetKind() != "Service" { - t.Errorf("invalid service kind, expected Service, got %v", decodedNamespaceAndPort[1].GetKind()) - return - } + var decoded []unstructured.Unstructured + err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_run with image, namespace, and port returns pod with port", func(t *testing.T) { - containers := decodedNamespaceAndPort[0].Object["spec"].(map[string]interface{})["containers"].([]interface{}) - ports := containers[0].(map[string]interface{})["ports"].([]interface{}) - if ports[0].(map[string]interface{})["containerPort"] != int64(80) { - t.Errorf("invalid container port, expected 80, got %v", ports[0].(map[string]interface{})["containerPort"]) - return - } + s.Run("returns 1 pod", func() { + s.Lenf(decoded, 1, "invalid pods count, expected 1, got %v", len(decoded)) }) - t.Run("pods_run with image, namespace, and port returns service with port and selector", func(t *testing.T) { - ports := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["ports"].([]interface{}) - if ports[0].(map[string]interface{})["port"] != int64(80) { - t.Errorf("invalid service port, expected 80, got %v", ports[0].(map[string]interface{})["port"]) - return - } - if ports[0].(map[string]interface{})["targetPort"] != int64(80) { - t.Errorf("invalid service target port, expected 80, got %v", ports[0].(map[string]interface{})["targetPort"]) - return - } - selector := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["selector"].(map[string]interface{}) - if selector["app.kubernetes.io/name"] == "" { - t.Errorf("invalid service selector, expected app.kubernetes.io/name, got %v", selector) - return - } - if selector["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" { - t.Errorf("invalid service selector, expected app.kubernetes.io/managed-by, got %v", selector) - return - } - if selector["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" { - t.Errorf("invalid service selector, expected app.kubernetes.io/part-of, got %v", selector) - return - } + s.Run("returns another-pod-with-labels", func() { + s.Equalf("another-pod-with-labels", decoded[0].GetName(), "invalid pod name, expected another-pod-with-labels, got %v", decoded[0].GetName()) }) }) -} -func TestPodsRunDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` - denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsRun, _ := c.callTool("pods_run", map[string]interface{}{"image": "nginx"}) - t.Run("pods_run has error", func(t *testing.T) { - if !podsRun.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("pods_run describes denial", func(t *testing.T) { - expectedMessage := "failed to run pod in namespace : resource not allowed: /v1, Kind=Pod" - if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text) - } + s.Run("pods_list(labelSelector=app=test,env=prod) with multiple label selectors returns filtered pods", func() { + toolResult, err := s.CallTool("pods_list", map[string]interface{}{ + "labelSelector": "app=test,env=prod", }) - }) -} - -func TestPodsRunInOpenShift(t *testing.T) { - testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { - t.Run("pods_run with image, namespace, and port returns route with port", func(t *testing.T) { - podsRunInOpenShift, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsRunInOpenShift.IsError { - t.Errorf("call tool failed") - return - } - var decodedPodServiceRoute []unstructured.Unstructured - err = yaml.Unmarshal([]byte(podsRunInOpenShift.Content[0].(mcp.TextContent).Text), &decodedPodServiceRoute) - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } - if len(decodedPodServiceRoute) != 3 { - t.Errorf("invalid pods count, expected 3, got %v", len(decodedPodServiceRoute)) - return - } - if decodedPodServiceRoute[2].GetKind() != "Route" { - t.Errorf("invalid route kind, expected Route, got %v", decodedPodServiceRoute[2].GetKind()) - return - } - targetPort := decodedPodServiceRoute[2].Object["spec"].(map[string]interface{})["port"].(map[string]interface{})["targetPort"].(int64) - if targetPort != 80 { - t.Errorf("invalid route target port, expected 80, got %v", targetPort) - return - } + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed") }) - }) -} - -func TestPodsListWithLabelSelector(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - kc := c.newKubernetesClient() - // Create pods with labels - _, _ = kc.CoreV1().Pods("default").Create(c.ctx, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-with-labels", - Labels: map[string]string{"app": "test", "env": "dev"}, - }, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, metav1.CreateOptions{}) - _, _ = kc.CoreV1().Pods("ns-1").Create(c.ctx, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "another-pod-with-labels", - Labels: map[string]string{"app": "test", "env": "prod"}, - }, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, - }, metav1.CreateOptions{}) - - // Test pods_list with label selector - t.Run("pods_list with label selector returns filtered pods", func(t *testing.T) { - toolResult, err := c.callTool("pods_list", map[string]interface{}{ - "labelSelector": "app=test", - }) - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if toolResult.IsError { - t.Fatalf("call tool failed") - return - } - var decoded []unstructured.Unstructured - err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } - if len(decoded) != 2 { - t.Fatalf("invalid pods count, expected 2, got %v", len(decoded)) - return - } + var decoded []unstructured.Unstructured + err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - - // Test pods_list_in_namespace with label selector - t.Run("pods_list_in_namespace with label selector returns filtered pods", func(t *testing.T) { - toolResult, err := c.callTool("pods_list_in_namespace", map[string]interface{}{ - "namespace": "ns-1", - "labelSelector": "env=prod", - }) - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if toolResult.IsError { - t.Fatalf("call tool failed") - return - } - var decoded []unstructured.Unstructured - err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } - if len(decoded) != 1 { - t.Fatalf("invalid pods count, expected 1, got %v", len(decoded)) - return - } - if decoded[0].GetName() != "another-pod-with-labels" { - t.Fatalf("invalid pod name, expected another-pod-with-labels, got %v", decoded[0].GetName()) - return - } + s.Run("returns 1 pod", func() { + s.Lenf(decoded, 1, "invalid pods count, expected 1, got %v", len(decoded)) }) - - // Test multiple label selectors - t.Run("pods_list with multiple label selectors returns filtered pods", func(t *testing.T) { - toolResult, err := c.callTool("pods_list", map[string]interface{}{ - "labelSelector": "app=test,env=prod", - }) - if err != nil { - t.Fatalf("call tool failed %v", err) - return - } - if toolResult.IsError { - t.Fatalf("call tool failed") - return - } - var decoded []unstructured.Unstructured - err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - if err != nil { - t.Fatalf("invalid tool result content %v", err) - return - } - if len(decoded) != 1 { - t.Fatalf("invalid pods count, expected 1, got %v", len(decoded)) - return - } - if decoded[0].GetName() != "another-pod-with-labels" { - t.Fatalf("invalid pod name, expected another-pod-with-labels, got %v", decoded[0].GetName()) - return - } + s.Run("returns another-pod-with-labels", func() { + s.Equalf("another-pod-with-labels", decoded[0].GetName(), "invalid pod name, expected another-pod-with-labels, got %v", decoded[0].GetName()) }) }) } + +func TestPods(t *testing.T) { + suite.Run(t, new(PodsSuite)) +} From c58f6207c0da34fd0400b6a92ab8ec3b76e6e0ed Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Mon, 3 Nov 2025 09:16:48 +0100 Subject: [PATCH 37/57] chore(makefile): script clean ups (#431) * Add grouping for keycloak Signed-off-by: Matthias Wessendorf * Do not expose the kind tools target, since it is part of larger tools Signed-off-by: Matthias Wessendorf --------- Signed-off-by: Matthias Wessendorf --- build/keycloak.mk | 2 ++ build/tools.mk | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/keycloak.mk b/build/keycloak.mk index d541c8b4..86b63907 100644 --- a/build/keycloak.mk +++ b/build/keycloak.mk @@ -50,6 +50,8 @@ keycloak-install: keycloak-uninstall: @kubectl delete -f dev/config/keycloak/deployment.yaml 2>/dev/null || true +##@ Keycloak + .PHONY: keycloak-status keycloak-status: ## Show Keycloak status and connection info @if kubectl get svc -n $(KEYCLOAK_NAMESPACE) keycloak >/dev/null 2>&1; then \ diff --git a/build/tools.mk b/build/tools.mk index 9c9945a8..20482bc9 100644 --- a/build/tools.mk +++ b/build/tools.mk @@ -17,4 +17,4 @@ $(KIND): GOBIN=$(PWD)/_output/bin go install sigs.k8s.io/kind@$(KIND_VERSION) .PHONY: kind -kind: $(KIND) ## Download kind locally if necessary +kind: $(KIND) From ce404f7ed3e99bd3a8819b503f55873f3fdaee24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:28:57 +0100 Subject: [PATCH 38/57] build(deps): bump github.com/mark3labs/mcp-go from 0.42.0 to 0.43.0 (#429) Bumps [github.com/mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) from 0.42.0 to 0.43.0. - [Release notes](https://github.com/mark3labs/mcp-go/releases) - [Commits](https://github.com/mark3labs/mcp-go/compare/v0.42.0...v0.43.0) --- updated-dependencies: - dependency-name: github.com/mark3labs/mcp-go dependency-version: 0.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index de49820d..e15c6bb6 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/go-jose/go-jose/v4 v4.1.3 github.com/google/jsonschema-go v0.3.0 - github.com/mark3labs/mcp-go v0.42.0 + github.com/mark3labs/mcp-go v0.43.0 github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.1 diff --git a/go.sum b/go.sum index 52a72c8b..4b9d1e29 100644 --- a/go.sum +++ b/go.sum @@ -187,8 +187,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mark3labs/mcp-go v0.42.0 h1:gk/8nYJh8t3yroCAOBhNbYsM9TCKvkM13I5t5Hfu6Ls= -github.com/mark3labs/mcp-go v0.42.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= +github.com/mark3labs/mcp-go v0.43.0 h1:lgiKcWMddh4sngbU+hoWOZ9iAe/qp/m851RQpj3Y7jA= +github.com/mark3labs/mcp-go v0.43.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= From 9d82959e8e1165ebfb3d7c45eaf808a8259aaa4b Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 3 Nov 2025 16:29:42 +0100 Subject: [PATCH 39/57] test(pods): update PodsRun tests to use testify and improve readability (#433) Signed-off-by: Marc Nuri --- pkg/mcp/pods_run_test.go | 215 +++++++++++++-------------------------- 1 file changed, 73 insertions(+), 142 deletions(-) diff --git a/pkg/mcp/pods_run_test.go b/pkg/mcp/pods_run_test.go index 082ac310..1dc751a9 100644 --- a/pkg/mcp/pods_run_test.go +++ b/pkg/mcp/pods_run_test.go @@ -4,180 +4,107 @@ import ( "strings" "testing" - "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/BurntSushi/toml" "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" ) -func TestPodsRun(t *testing.T) { - testCase(t, func(c *mcpContext) { - c.withEnvTest() - t.Run("pods_run with nil image returns error", func(t *testing.T) { - toolResult, _ := c.callTool("pods_run", map[string]interface{}{}) - if toolResult.IsError != true { - t.Errorf("call tool should fail") - return - } - if toolResult.Content[0].(mcp.TextContent).Text != "failed to run pod, missing argument image" { - t.Errorf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) - return - } - }) - podsRunNilNamespace, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx"}) - t.Run("pods_run with image and nil namespace runs pod", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsRunNilNamespace.IsError { - t.Errorf("call tool failed") - return - } +type PodsRunSuite struct { + BaseMcpSuite +} + +func (s *PodsRunSuite) TestPodsRun() { + s.InitMcpClient() + s.Run("pods_run with nil image returns error", func() { + toolResult, _ := s.CallTool("pods_run", map[string]interface{}{}) + s.Truef(toolResult.IsError, "call tool should fail") + s.Equalf("failed to run pod, missing argument image", toolResult.Content[0].(mcp.TextContent).Text, + "invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text) + }) + s.Run("pods_run(image=nginx, namespace=nil), uses configured namespace", func() { + podsRunNilNamespace, err := s.CallTool("pods_run", map[string]interface{}{"image": "nginx"}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsRunNilNamespace.IsError, "call tool failed") }) var decodedNilNamespace []unstructured.Unstructured err = yaml.Unmarshal([]byte(podsRunNilNamespace.Content[0].(mcp.TextContent).Text), &decodedNilNamespace) - t.Run("pods_run with image and nil namespace has yaml content", func(t *testing.T) { - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_run with image and nil namespace returns 1 item (Pod)", func(t *testing.T) { - if len(decodedNilNamespace) != 1 { - t.Errorf("invalid pods count, expected 1, got %v", len(decodedNilNamespace)) - return - } - if decodedNilNamespace[0].GetKind() != "Pod" { - t.Errorf("invalid pod kind, expected Pod, got %v", decodedNilNamespace[0].GetKind()) - return - } + s.Run("returns 1 item (Pod)", func() { + s.Lenf(decodedNilNamespace, 1, "invalid pods count, expected 1, got %v", len(decodedNilNamespace)) + s.Equalf("Pod", decodedNilNamespace[0].GetKind(), "invalid pod kind, expected Pod, got %v", decodedNilNamespace[0].GetKind()) }) - t.Run("pods_run with image and nil namespace returns pod in default", func(t *testing.T) { - if decodedNilNamespace[0].GetNamespace() != "default" { - t.Errorf("invalid pod namespace, expected default, got %v", decodedNilNamespace[0].GetNamespace()) - return - } + s.Run("returns pod in default", func() { + s.Equalf("default", decodedNilNamespace[0].GetNamespace(), "invalid pod namespace, expected default, got %v", decodedNilNamespace[0].GetNamespace()) }) - t.Run("pods_run with image and nil namespace returns pod with random name", func(t *testing.T) { - if !strings.HasPrefix(decodedNilNamespace[0].GetName(), "kubernetes-mcp-server-run-") { - t.Errorf("invalid pod name, expected random, got %v", decodedNilNamespace[0].GetName()) - return - } + s.Run("returns pod with random name", func() { + s.Truef(strings.HasPrefix(decodedNilNamespace[0].GetName(), "kubernetes-mcp-server-run-"), + "invalid pod name, expected random, got %v", decodedNilNamespace[0].GetName()) }) - t.Run("pods_run with image and nil namespace returns pod with labels", func(t *testing.T) { + s.Run("returns pod with labels", func() { labels := decodedNilNamespace[0].Object["metadata"].(map[string]interface{})["labels"].(map[string]interface{}) - if labels["app.kubernetes.io/name"] == "" { - t.Errorf("invalid labels, expected app.kubernetes.io/name, got %v", labels) - return - } - if labels["app.kubernetes.io/component"] == "" { - t.Errorf("invalid labels, expected app.kubernetes.io/component, got %v", labels) - return - } - if labels["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" { - t.Errorf("invalid labels, expected app.kubernetes.io/managed-by, got %v", labels) - return - } - if labels["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" { - t.Errorf("invalid labels, expected app.kubernetes.io/part-of, got %v", labels) - return - } + s.NotEqualf("", labels["app.kubernetes.io/name"], "invalid labels, expected app.kubernetes.io/name, got %v", labels) + s.NotEqualf("", labels["app.kubernetes.io/component"], "invalid labels, expected app.kubernetes.io/component, got %v", labels) + s.Equalf("kubernetes-mcp-server", labels["app.kubernetes.io/managed-by"], "invalid labels, expected app.kubernetes.io/managed-by, got %v", labels) + s.Equalf("kubernetes-mcp-server-run-sandbox", labels["app.kubernetes.io/part-of"], "invalid labels, expected app.kubernetes.io/part-of, got %v", labels) }) - t.Run("pods_run with image and nil namespace returns pod with nginx container", func(t *testing.T) { + s.Run("returns pod with nginx container", func() { containers := decodedNilNamespace[0].Object["spec"].(map[string]interface{})["containers"].([]interface{}) - if containers[0].(map[string]interface{})["image"] != "nginx" { - t.Errorf("invalid container name, expected nginx, got %v", containers[0].(map[string]interface{})["image"]) - return - } + s.Equalf("nginx", containers[0].(map[string]interface{})["image"], "invalid container name, expected nginx, got %v", containers[0].(map[string]interface{})["image"]) }) - - podsRunNamespaceAndPort, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) - t.Run("pods_run with image, namespace, and port runs pod", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsRunNamespaceAndPort.IsError { - t.Errorf("call tool failed") - return - } + }) + s.Run("pods_run(image=nginx, namespace=nil, port=80)", func() { + podsRunNamespaceAndPort, err := s.CallTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsRunNamespaceAndPort.IsError, "call tool failed") }) var decodedNamespaceAndPort []unstructured.Unstructured err = yaml.Unmarshal([]byte(podsRunNamespaceAndPort.Content[0].(mcp.TextContent).Text), &decodedNamespaceAndPort) - t.Run("pods_run with image, namespace, and port has yaml content", func(t *testing.T) { - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("pods_run with image, namespace, and port returns 2 items (Pod + Service)", func(t *testing.T) { - if len(decodedNamespaceAndPort) != 2 { - t.Errorf("invalid pods count, expected 2, got %v", len(decodedNamespaceAndPort)) - return - } - if decodedNamespaceAndPort[0].GetKind() != "Pod" { - t.Errorf("invalid pod kind, expected Pod, got %v", decodedNamespaceAndPort[0].GetKind()) - return - } - if decodedNamespaceAndPort[1].GetKind() != "Service" { - t.Errorf("invalid service kind, expected Service, got %v", decodedNamespaceAndPort[1].GetKind()) - return - } + s.Run("returns 2 items (Pod + Service)", func() { + s.Lenf(decodedNamespaceAndPort, 2, "invalid pods count, expected 2, got %v", len(decodedNamespaceAndPort)) + s.Equalf("Pod", decodedNamespaceAndPort[0].GetKind(), "invalid pod kind, expected Pod, got %v", decodedNamespaceAndPort[0].GetKind()) + s.Equalf("Service", decodedNamespaceAndPort[1].GetKind(), "invalid service kind, expected Service, got %v", decodedNamespaceAndPort[1].GetKind()) }) - t.Run("pods_run with image, namespace, and port returns pod with port", func(t *testing.T) { + s.Run("returns pod with port", func() { containers := decodedNamespaceAndPort[0].Object["spec"].(map[string]interface{})["containers"].([]interface{}) ports := containers[0].(map[string]interface{})["ports"].([]interface{}) - if ports[0].(map[string]interface{})["containerPort"] != int64(80) { - t.Errorf("invalid container port, expected 80, got %v", ports[0].(map[string]interface{})["containerPort"]) - return - } + s.Equalf(int64(80), ports[0].(map[string]interface{})["containerPort"], "invalid container port, expected 80, got %v", ports[0].(map[string]interface{})["containerPort"]) }) - t.Run("pods_run with image, namespace, and port returns service with port and selector", func(t *testing.T) { + s.Run("returns service with port and selector", func() { ports := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["ports"].([]interface{}) - if ports[0].(map[string]interface{})["port"] != int64(80) { - t.Errorf("invalid service port, expected 80, got %v", ports[0].(map[string]interface{})["port"]) - return - } - if ports[0].(map[string]interface{})["targetPort"] != int64(80) { - t.Errorf("invalid service target port, expected 80, got %v", ports[0].(map[string]interface{})["targetPort"]) - return - } + s.Equalf(int64(80), ports[0].(map[string]interface{})["port"], "invalid service port, expected 80, got %v", ports[0].(map[string]interface{})["port"]) + s.Equalf(int64(80), ports[0].(map[string]interface{})["targetPort"], "invalid service target port, expected 80, got %v", ports[0].(map[string]interface{})["targetPort"]) selector := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["selector"].(map[string]interface{}) - if selector["app.kubernetes.io/name"] == "" { - t.Errorf("invalid service selector, expected app.kubernetes.io/name, got %v", selector) - return - } - if selector["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" { - t.Errorf("invalid service selector, expected app.kubernetes.io/managed-by, got %v", selector) - return - } - if selector["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" { - t.Errorf("invalid service selector, expected app.kubernetes.io/part-of, got %v", selector) - return - } + s.NotEqualf("", selector["app.kubernetes.io/name"], "invalid service selector, expected app.kubernetes.io/name, got %v", selector) + s.Equalf("kubernetes-mcp-server", selector["app.kubernetes.io/managed-by"], "invalid service selector, expected app.kubernetes.io/managed-by, got %v", selector) + s.Equalf("kubernetes-mcp-server-run-sandbox", selector["app.kubernetes.io/part-of"], "invalid service selector, expected app.kubernetes.io/part-of, got %v", selector) }) }) } -func TestPodsRunDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsRunSuite) TestPodsRunDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { version = "v1", kind = "Pod" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - c.withEnvTest() - podsRun, _ := c.callTool("pods_run", map[string]interface{}{"image": "nginx"}) - t.Run("pods_run has error", func(t *testing.T) { - if !podsRun.IsError { - t.Fatalf("call tool should fail") - } - }) - t.Run("pods_run describes denial", func(t *testing.T) { + `), s.Cfg), "Expected to parse denied resources config") + s.InitMcpClient() + s.Run("pods_run (denied)", func() { + podsRun, err := s.CallTool("pods_run", map[string]interface{}{"image": "nginx"}) + s.Run("has error", func() { + s.Truef(podsRun.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { expectedMessage := "failed to run pod in namespace : resource not allowed: /v1, Kind=Pod" - if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, podsRun.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text) }) }) } @@ -216,3 +143,7 @@ func TestPodsRunInOpenShift(t *testing.T) { }) }) } + +func TestPodsRun(t *testing.T) { + suite.Run(t, new(PodsRunSuite)) +} From d6f99e9a5202497a56effb722efb415aaa5ea203 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 07:13:22 +0100 Subject: [PATCH 40/57] build(deps): bump sigs.k8s.io/controller-runtime from 0.22.3 to 0.22.4 (#436) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.3 to 0.22.4. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.3...v0.22.4) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.22.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e15c6bb6..3a74120d 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( k8s.io/kubectl v0.34.1 k8s.io/metrics v0.34.1 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.22.3 + sigs.k8s.io/controller-runtime v0.22.4 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 4b9d1e29..7a5186b8 100644 --- a/go.sum +++ b/go.sum @@ -453,8 +453,8 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= -sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= +sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664 h1:xC7x7FsPURJYhZnWHsWFd7nkdD/WRtQVWPC28FWt85Y= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664/go.mod h1:Cq9jUhwSYol5tNB0O/1vLYxNV9KqnhpvEa6HvJ1w0wY= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= From 6f3a2e01697ffd552dd771bbb08559a9279355ae Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 4 Nov 2025 10:26:16 +0100 Subject: [PATCH 41/57] test(mcp): refactor WatchKubeConfig tests to use testify and improve structure (#432) Signed-off-by: Marc Nuri --- internal/test/mcp.go | 1 + pkg/mcp/mcp_test.go | 68 +++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/internal/test/mcp.go b/internal/test/mcp.go index 5fa0d0a4..0b411b1d 100644 --- a/internal/test/mcp.go +++ b/internal/test/mcp.go @@ -23,6 +23,7 @@ func NewMcpClient(t *testing.T, mcpHttpServer http.Handler, options ...transport var err error ret := &McpClient{ctx: t.Context()} ret.testServer = httptest.NewServer(mcpHttpServer) + options = append(options, transport.WithContinuousListening()) ret.Client, err = client.NewStreamableHttpClient(ret.testServer.URL+"/mcp", options...) require.NoError(t, err, "Expected no error creating MCP client") err = ret.Start(t.Context()) diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index 484d8b59..9dca88e4 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -4,7 +4,6 @@ import ( "context" "net/http" "os" - "path/filepath" "runtime" "testing" "time" @@ -15,38 +14,47 @@ import ( "github.com/stretchr/testify/suite" ) -func TestWatchKubeConfig(t *testing.T) { +type WatchKubeConfigSuite struct { + BaseMcpSuite +} + +func (s *WatchKubeConfigSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + kubeconfig := test.KubeConfigFake() + s.Cfg.KubeConfig = test.KubeconfigFile(s.T(), kubeconfig) +} + +func (s *WatchKubeConfigSuite) TestNotifiesToolsChange() { if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { - t.Skip("Skipping test on non-Unix-like platforms") + s.T().Skip("Skipping test on non-Unix-like platforms") } - testCase(t, func(c *mcpContext) { - // Given - withTimeout, cancel := context.WithTimeout(c.ctx, 5*time.Second) - defer cancel() - var notification *mcp.JSONRPCNotification - c.mcpClient.OnNotification(func(n mcp.JSONRPCNotification) { - notification = &n - }) - // When - f, _ := os.OpenFile(filepath.Join(c.tempDir, "config"), os.O_APPEND|os.O_WRONLY, 0644) - _, _ = f.WriteString("\n") - for notification == nil { - select { - case <-withTimeout.Done(): - default: - time.Sleep(100 * time.Millisecond) - } - } - // Then - t.Run("WatchKubeConfig notifies tools change", func(t *testing.T) { - if notification == nil { - t.Fatalf("WatchKubeConfig did not notify") - } - if notification.Method != "notifications/tools/list_changed" { - t.Fatalf("WatchKubeConfig did not notify tools change, got %s", notification.Method) - } - }) + // Given + s.InitMcpClient() + withTimeout, cancel := context.WithTimeout(s.T().Context(), 5*time.Second) + defer cancel() + var notification *mcp.JSONRPCNotification + s.OnNotification(func(n mcp.JSONRPCNotification) { + notification = &n }) + // When + f, _ := os.OpenFile(s.Cfg.KubeConfig, os.O_APPEND|os.O_WRONLY, 0644) + _, _ = f.WriteString("\n") + _ = f.Close() + for notification == nil { + select { + case <-withTimeout.Done(): + s.FailNow("timeout waiting for WatchKubeConfig notification") + default: + time.Sleep(100 * time.Millisecond) + } + } + // Then + s.NotNil(notification, "WatchKubeConfig did not notify") + s.Equal("notifications/tools/list_changed", notification.Method, "WatchKubeConfig did not notify tools change") +} + +func TestWatchKubeConfig(t *testing.T) { + suite.Run(t, new(WatchKubeConfigSuite)) } type McpHeadersSuite struct { From 1783144d8f81245395aa396acb9a00d16c3b3f35 Mon Sep 17 00:00:00 2001 From: Neeraj Krishna Gopalakrishna Date: Tue, 4 Nov 2025 14:59:03 +0530 Subject: [PATCH 42/57] feat(nodes): nodes_top retrieves Node resource consumption (metrics API) (#420) * Support for nodes_top similar to pods_top Signed-off-by: Neeraj Krishna Gopalakrishna * review(nodes): nodes_top retrieves Node resource consumption (metrics API) Signed-off-by: Marc Nuri --------- Signed-off-by: Neeraj Krishna Gopalakrishna Signed-off-by: Marc Nuri Co-authored-by: Marc Nuri --- README.md | 4 + pkg/kubernetes/accesscontrol_clientset.go | 31 +++ pkg/kubernetes/nodes.go | 18 ++ pkg/mcp/nodes_top_test.go | 248 ++++++++++++++++++ pkg/mcp/testdata/toolsets-core-tools.json | 25 ++ ...toolsets-full-tools-multicluster-enum.json | 33 +++ .../toolsets-full-tools-multicluster.json | 29 ++ .../toolsets-full-tools-openshift.json | 25 ++ pkg/mcp/testdata/toolsets-full-tools.json | 25 ++ pkg/toolsets/core/nodes.go | 81 ++++++ 10 files changed, 519 insertions(+) create mode 100644 pkg/mcp/nodes_top_test.go diff --git a/README.md b/README.md index 4c916af9..ee592bd5 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,10 @@ In case multi-cluster support is enabled (default) and you have access to multip - **nodes_stats_summary** - Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics - `name` (`string`) **(required)** - Name of the node to get stats from +- **nodes_top** - List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster + - `label_selector` (`string`) - Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided) + - `name` (`string`) - Name of the Node to get the resource consumption from (Optional, all Nodes if not provided) + - **pods_list** - List all the Kubernetes pods in the current cluster from all namespaces - `labelSelector` (`string`) - Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label diff --git a/pkg/kubernetes/accesscontrol_clientset.go b/pkg/kubernetes/accesscontrol_clientset.go index c36a9b7f..a6c3fccd 100644 --- a/pkg/kubernetes/accesscontrol_clientset.go +++ b/pkg/kubernetes/accesscontrol_clientset.go @@ -39,6 +39,14 @@ func (a *AccessControlClientset) DiscoveryClient() discovery.DiscoveryInterface return a.discoveryClient } +func (a *AccessControlClientset) Nodes() (corev1.NodeInterface, error) { + gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"} + if !isAllowed(a.staticConfig, gvk) { + return nil, isNotAllowedError(gvk) + } + return a.delegate.CoreV1().Nodes(), nil +} + func (a *AccessControlClientset) NodesLogs(ctx context.Context, name string) (*rest.Request, error) { gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"} if !isAllowed(a.staticConfig, gvk) { @@ -55,6 +63,29 @@ func (a *AccessControlClientset) NodesLogs(ctx context.Context, name string) (*r AbsPath(url...), nil } +func (a *AccessControlClientset) NodesMetricses(ctx context.Context, name string, listOptions metav1.ListOptions) (*metrics.NodeMetricsList, error) { + gvk := &schema.GroupVersionKind{Group: metrics.GroupName, Version: metricsv1beta1api.SchemeGroupVersion.Version, Kind: "NodeMetrics"} + if !isAllowed(a.staticConfig, gvk) { + return nil, isNotAllowedError(gvk) + } + versionedMetrics := &metricsv1beta1api.NodeMetricsList{} + var err error + if name != "" { + m, err := a.metricsV1beta1.NodeMetricses().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get metrics for node %s: %w", name, err) + } + versionedMetrics.Items = []metricsv1beta1api.NodeMetrics{*m} + } else { + versionedMetrics, err = a.metricsV1beta1.NodeMetricses().List(ctx, listOptions) + if err != nil { + return nil, fmt.Errorf("failed to list node metrics: %w", err) + } + } + convertedMetrics := &metrics.NodeMetricsList{} + return convertedMetrics, metricsv1beta1api.Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(versionedMetrics, convertedMetrics, nil) +} + func (a *AccessControlClientset) NodesStatsSummary(ctx context.Context, name string) (*rest.Request, error) { gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"} if !isAllowed(a.staticConfig, gvk) { diff --git a/pkg/kubernetes/nodes.go b/pkg/kubernetes/nodes.go index e242eac8..a4321a9f 100644 --- a/pkg/kubernetes/nodes.go +++ b/pkg/kubernetes/nodes.go @@ -2,7 +2,12 @@ package kubernetes import ( "context" + "errors" "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/metrics/pkg/apis/metrics" + metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) func (k *Kubernetes) NodesLog(ctx context.Context, name string, query string, tailLines int64) (string, error) { @@ -59,3 +64,16 @@ func (k *Kubernetes) NodesStatsSummary(ctx context.Context, name string) (string return string(rawData), nil } + +type NodesTopOptions struct { + metav1.ListOptions + Name string +} + +func (k *Kubernetes) NodesTop(ctx context.Context, options NodesTopOptions) (*metrics.NodeMetricsList, error) { + // TODO, maybe move to mcp Tools setup and omit in case metrics aren't available in the target cluster + if !k.supportsGroupVersion(metrics.GroupName + "/" + metricsv1beta1api.SchemeGroupVersion.Version) { + return nil, errors.New("metrics API is not available") + } + return k.manager.accessControlClientSet.NodesMetricses(ctx, options.Name, options.ListOptions) +} diff --git a/pkg/mcp/nodes_top_test.go b/pkg/mcp/nodes_top_test.go new file mode 100644 index 00000000..23ae9945 --- /dev/null +++ b/pkg/mcp/nodes_top_test.go @@ -0,0 +1,248 @@ +package mcp + +import ( + "net/http" + "testing" + + "github.com/BurntSushi/toml" + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" +) + +type NodesTopSuite struct { + BaseMcpSuite + mockServer *test.MockServer +} + +func (s *NodesTopSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":[],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + })) +} + +func (s *NodesTopSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *NodesTopSuite) WithMetricsServer() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[{"name":"metrics.k8s.io","versions":[{"groupVersion":"metrics.k8s.io/v1beta1","version":"v1beta1"}],"preferredVersion":{"groupVersion":"metrics.k8s.io/v1beta1","version":"v1beta1"}}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Resources) + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { + _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"nodes","singularName":"","namespaced":false,"kind":"NodeMetrics","verbs":["get","list"]}]}`)) + return + } + })) +} + +func (s *NodesTopSuite) TestNodesTop() { + s.WithMetricsServer() + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // List Nodes + if req.URL.Path == "/api/v1/nodes" { + _, _ = w.Write([]byte(`{ + "apiVersion": "v1", + "kind": "NodeList", + "items": [ + { + "metadata": { + "name": "node-1", + "labels": { + "node-role.kubernetes.io/worker": "" + } + }, + "status": { + "allocatable": { + "cpu": "4", + "memory": "16Gi" + }, + "nodeInfo": { + "swap": { + "capacity": 0 + } + } + } + }, + { + "metadata": { + "name": "node-2", + "labels": { + "node-role.kubernetes.io/worker": "" + } + }, + "status": { + "allocatable": { + "cpu": "4", + "memory": "16Gi" + }, + "nodeInfo": { + "swap": { + "capacity": 0 + } + } + } + } + ] + }`)) + return + } + // Get NodeMetrics + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/nodes" { + _, _ = w.Write([]byte(`{ + "apiVersion": "metrics.k8s.io/v1beta1", + "kind": "NodeMetricsList", + "items": [ + { + "metadata": { + "name": "node-1" + }, + "timestamp": "2025-10-29T09:00:00Z", + "window": "30s", + "usage": { + "cpu": "500m", + "memory": "2Gi" + } + }, + { + "metadata": { + "name": "node-2" + }, + "timestamp": "2025-10-29T09:00:00Z", + "window": "30s", + "usage": { + "cpu": "1000m", + "memory": "4Gi" + } + } + ] + }`)) + return + } + // Get specific NodeMetrics + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/nodes/node-1" { + _, _ = w.Write([]byte(`{ + "apiVersion": "metrics.k8s.io/v1beta1", + "kind": "NodeMetrics", + "metadata": { + "name": "node-1" + }, + "timestamp": "2025-10-29T09:00:00Z", + "window": "30s", + "usage": { + "cpu": "500m", + "memory": "2Gi" + } + }`)) + return + } + w.WriteHeader(http.StatusNotFound) + })) + s.InitMcpClient() + + s.Run("nodes_top() - all nodes", func() { + toolResult, err := s.CallTool("nodes_top", map[string]interface{}{}) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns metrics for all nodes", func() { + content := toolResult.Content[0].(mcp.TextContent).Text + s.Contains(content, "node-1", "expected metrics to contain node-1") + s.Contains(content, "node-2", "expected metrics to contain node-2") + s.Contains(content, "CPU(cores)", "expected header with CPU column") + s.Contains(content, "MEMORY(bytes)", "expected header with MEMORY column") + }) + }) + + s.Run("nodes_top(name=node-1) - specific node", func() { + toolResult, err := s.CallTool("nodes_top", map[string]interface{}{ + "name": "node-1", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns metrics for specific node", func() { + content := toolResult.Content[0].(mcp.TextContent).Text + s.Contains(content, "node-1", "expected metrics to contain node-1") + s.Contains(content, "500m", "expected CPU usage of 500m") + s.Contains(content, "2048Mi", "expected memory usage of 2048Mi") + }) + }) + + s.Run("nodes_top(label_selector=node-role.kubernetes.io/worker=)", func() { + toolResult, err := s.CallTool("nodes_top", map[string]interface{}{ + "label_selector": "node-role.kubernetes.io/worker=", + }) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("no error", func() { + s.Falsef(toolResult.IsError, "call tool should succeed") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("returns metrics for filtered nodes", func() { + content := toolResult.Content[0].(mcp.TextContent).Text + s.Contains(content, "node-1", "expected metrics to contain node-1") + s.Contains(content, "node-2", "expected metrics to contain node-2") + }) + }) +} + +func (s *NodesTopSuite) TestNodesTopMetricsUnavailable() { + s.InitMcpClient() + + s.Run("nodes_top() - metrics unavailable", func() { + toolResult, err := s.CallTool("nodes_top", map[string]interface{}{}) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail when metrics unavailable") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes metrics unavailable", func() { + content := toolResult.Content[0].(mcp.TextContent).Text + s.Contains(content, "failed to get nodes top", "expected error message about failing to get nodes top") + }) + }) +} + +func (s *NodesTopSuite) TestNodesTopDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` + denied_resources = [ { group = "metrics.k8s.io", version = "v1beta1" } ] + `), s.Cfg), "Expected to parse denied resources config") + s.WithMetricsServer() + s.InitMcpClient() + s.Run("nodes_top (denied)", func() { + toolResult, err := s.CallTool("nodes_top", map[string]interface{}{}) + s.Require().NotNil(toolResult, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(toolResult.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") + }) + s.Run("describes denial", func() { + expectedMessage := "failed to get nodes top: resource not allowed: metrics.k8s.io/v1beta1, Kind=NodeMetrics" + s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text) + }) + }) +} + +func TestNodesTop(t *testing.T) { + suite.Run(t, new(NodesTopSuite)) +} diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index 56b998da..e8753758 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -90,6 +90,31 @@ }, "name": "nodes_stats_summary" }, + { + "annotations": { + "title": "Nodes: Top", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" + }, + "label_selector": { + "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided)", + "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]", + "type": "string" + } + } + }, + "name": "nodes_top" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index 1551b4c2..08181078 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -268,6 +268,39 @@ }, "name": "nodes_stats_summary" }, + { + "annotations": { + "title": "Nodes: Top", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster", + "inputSchema": { + "type": "object", + "properties": { + "context": { + "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", + "enum": [ + "extra-cluster", + "fake-context" + ], + "type": "string" + }, + "name": { + "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" + }, + "label_selector": { + "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided)", + "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]", + "type": "string" + } + } + }, + "name": "nodes_top" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index 6e85e401..74a48d56 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -240,6 +240,35 @@ }, "name": "nodes_stats_summary" }, + { + "annotations": { + "title": "Nodes: Top", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster", + "inputSchema": { + "type": "object", + "properties": { + "context": { + "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set", + "type": "string" + }, + "name": { + "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" + }, + "label_selector": { + "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided)", + "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]", + "type": "string" + } + } + }, + "name": "nodes_top" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index fb24138e..041c8671 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -196,6 +196,31 @@ }, "name": "nodes_stats_summary" }, + { + "annotations": { + "title": "Nodes: Top", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" + }, + "label_selector": { + "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided)", + "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]", + "type": "string" + } + } + }, + "name": "nodes_top" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index 5a4b5112..2f314aec 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -196,6 +196,31 @@ }, "name": "nodes_stats_summary" }, + { + "annotations": { + "title": "Nodes: Top", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" + }, + "label_selector": { + "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided)", + "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]", + "type": "string" + } + } + }, + "name": "nodes_top" + }, { "annotations": { "title": "Pods: Delete", diff --git a/pkg/toolsets/core/nodes.go b/pkg/toolsets/core/nodes.go index fc06a2d9..04798d0d 100644 --- a/pkg/toolsets/core/nodes.go +++ b/pkg/toolsets/core/nodes.go @@ -1,13 +1,19 @@ package core import ( + "bytes" "errors" "fmt" "github.com/google/jsonschema-go/jsonschema" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubectl/pkg/metricsutil" "k8s.io/utils/ptr" "github.com/containers/kubernetes-mcp-server/pkg/api" + "github.com/containers/kubernetes-mcp-server/pkg/kubernetes" ) func initNodes() []api.ServerTool { @@ -64,6 +70,31 @@ func initNodes() []api.ServerTool { OpenWorldHint: ptr.To(true), }, }, Handler: nodesStatsSummary}, + {Tool: api.Tool{ + Name: "nodes_top", + Description: "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Nodes or all nodes in the cluster", + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "name": { + Type: "string", + Description: "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", + }, + "label_selector": { + Type: "string", + Description: "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes by label (Optional, only applicable when name is not provided)", + Pattern: "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]", + }, + }, + }, + Annotations: api.ToolAnnotations{ + Title: "Nodes: Top", + ReadOnlyHint: ptr.To(true), + DestructiveHint: ptr.To(false), + IdempotentHint: ptr.To(true), + OpenWorldHint: ptr.To(true), + }, + }, Handler: nodesTop}, } } @@ -110,3 +141,53 @@ func nodesStatsSummary(params api.ToolHandlerParams) (*api.ToolCallResult, error } return api.NewToolCallResult(ret, nil), nil } + +func nodesTop(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + nodesTopOptions := kubernetes.NodesTopOptions{} + if v, ok := params.GetArguments()["name"].(string); ok { + nodesTopOptions.Name = v + } + if v, ok := params.GetArguments()["label_selector"].(string); ok { + nodesTopOptions.LabelSelector = v + } + + nodeMetrics, err := params.NodesTop(params, nodesTopOptions) + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to get nodes top: %v", err)), nil + } + + // Get the list of nodes to extract their allocatable resources + nodes, err := params.AccessControlClientset().Nodes() + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to get nodes client: %v", err)), nil + } + + nodeList, err := nodes.List(params, metav1.ListOptions{ + LabelSelector: nodesTopOptions.LabelSelector, + }) + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to list nodes: %v", err)), nil + } + + // Build availableResources map + availableResources := make(map[string]v1.ResourceList) + for _, n := range nodeList.Items { + availableResources[n.Name] = n.Status.Allocatable + + // Handle swap if available + if n.Status.NodeInfo.Swap != nil && n.Status.NodeInfo.Swap.Capacity != nil { + swapCapacity := *n.Status.NodeInfo.Swap.Capacity + availableResources[n.Name]["swap"] = *resource.NewQuantity(swapCapacity, resource.BinarySI) + } + } + + // Print the metrics + buf := new(bytes.Buffer) + printer := metricsutil.NewTopCmdPrinter(buf, true) + err = printer.PrintNodeMetrics(nodeMetrics.Items, availableResources, false, "") + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to print node metrics: %v", err)), nil + } + + return api.NewToolCallResult(buf.String(), nil), nil +} From 711239b0291f22c21b3060e8ca4854bc501ae809 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 4 Nov 2025 11:13:17 +0100 Subject: [PATCH 43/57] test(mcp): remove redundant testCase function and streamline test setup (#437) Signed-off-by: Marc Nuri --- pkg/mcp/common_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go index d15dca39..892226ab 100644 --- a/pkg/mcp/common_test.go +++ b/pkg/mcp/common_test.go @@ -184,10 +184,6 @@ func (c *mcpContext) afterEach() { c.klogState.Restore() } -func testCase(t *testing.T, test func(c *mcpContext)) { - testCaseWithContext(t, &mcpContext{}, test) -} - func testCaseWithContext(t *testing.T, mcpCtx *mcpContext, test func(c *mcpContext)) { mcpCtx.beforeEach(t) defer mcpCtx.afterEach() From 62bd5820614383ade3047853bd58eae1e8275608 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 4 Nov 2025 12:00:19 +0100 Subject: [PATCH 44/57] test(mcp): refactored OpenShift related tests to use testify (#438) Signed-off-by: Marc Nuri --- pkg/mcp/common_test.go | 203 +++++++++++++++---------------------- pkg/mcp/namespaces_test.go | 80 +++++++-------- pkg/mcp/pods_run_test.go | 56 +++++----- pkg/mcp/pods_test.go | 52 ++++------ pkg/mcp/resources_test.go | 84 +++++++-------- 5 files changed, 211 insertions(+), 264 deletions(-) diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go index 892226ab..8546c4aa 100644 --- a/pkg/mcp/common_test.go +++ b/pkg/mcp/common_test.go @@ -222,120 +222,6 @@ func (c *mcpContext) withKubeConfig(rc *rest.Config) *clientcmdapi.Config { return fakeConfig } -// withEnvTest sets up the environment for kubeconfig to be used with envTest -func (c *mcpContext) withEnvTest() { - c.withKubeConfig(envTestRestConfig) -} - -// inOpenShift sets up the kubernetes environment to seem to be running OpenShift -func inOpenShift(c *mcpContext) { - c.withEnvTest() - crdTemplate := ` - { - "apiVersion": "apiextensions.k8s.io/v1", - "kind": "CustomResourceDefinition", - "metadata": {"name": "%s"}, - "spec": { - "group": "%s", - "versions": [{ - "name": "v1","served": true,"storage": true, - "schema": {"openAPIV3Schema": {"type": "object","x-kubernetes-preserve-unknown-fields": true}} - }], - "scope": "%s", - "names": {"plural": "%s","singular": "%s","kind": "%s"} - } - }` - tasks, _ := errgroup.WithContext(c.ctx) - tasks.Go(func() error { - return c.crdApply(fmt.Sprintf(crdTemplate, "projects.project.openshift.io", "project.openshift.io", - "Cluster", "projects", "project", "Project")) - }) - tasks.Go(func() error { - return c.crdApply(fmt.Sprintf(crdTemplate, "routes.route.openshift.io", "route.openshift.io", - "Namespaced", "routes", "route", "Route")) - }) - if err := tasks.Wait(); err != nil { - panic(err) - } -} - -// inOpenShiftClear clears the kubernetes environment so it no longer seems to be running OpenShift -func inOpenShiftClear(c *mcpContext) { - tasks, _ := errgroup.WithContext(c.ctx) - tasks.Go(func() error { return c.crdDelete("projects.project.openshift.io") }) - tasks.Go(func() error { return c.crdDelete("routes.route.openshift.io") }) - if err := tasks.Wait(); err != nil { - panic(err) - } -} - -// newKubernetesClient creates a new Kubernetes client with the envTest kubeconfig -func (c *mcpContext) newKubernetesClient() *kubernetes.Clientset { - return kubernetes.NewForConfigOrDie(envTestRestConfig) -} - -// newApiExtensionsClient creates a new ApiExtensions client with the envTest kubeconfig -func (c *mcpContext) newApiExtensionsClient() *apiextensionsv1.ApiextensionsV1Client { - return apiextensionsv1.NewForConfigOrDie(envTestRestConfig) -} - -// crdApply creates a CRD from the provided resource string and waits for it to be established -func (c *mcpContext) crdApply(resource string) error { - apiExtensionsV1Client := c.newApiExtensionsClient() - var crd = &apiextensionsv1spec.CustomResourceDefinition{} - err := json.Unmarshal([]byte(resource), crd) - if err != nil { - return fmt.Errorf("failed to create CRD %v", err) - } - _, err = apiExtensionsV1Client.CustomResourceDefinitions().Create(c.ctx, crd, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("failed to create CRD %v", err) - } - c.crdWaitUntilReady(crd.Name) - return nil -} - -// crdDelete deletes a CRD by name and waits for it to be removed -func (c *mcpContext) crdDelete(name string) error { - apiExtensionsV1Client := c.newApiExtensionsClient() - err := apiExtensionsV1Client.CustomResourceDefinitions().Delete(c.ctx, name, metav1.DeleteOptions{ - GracePeriodSeconds: ptr.To(int64(0)), - }) - iteration := 0 - for iteration < 100 { - if _, derr := apiExtensionsV1Client.CustomResourceDefinitions().Get(c.ctx, name, metav1.GetOptions{}); derr != nil { - break - } - time.Sleep(5 * time.Millisecond) - iteration++ - } - if err != nil { - return errors.Wrap(err, "failed to delete CRD") - } - return nil -} - -// crdWaitUntilReady waits for a CRD to be established -func (c *mcpContext) crdWaitUntilReady(name string) { - watcher, err := c.newApiExtensionsClient().CustomResourceDefinitions().Watch(c.ctx, metav1.ListOptions{ - FieldSelector: "metadata.name=" + name, - }) - if err != nil { - panic(fmt.Errorf("failed to watch CRD %v", err)) - } - _, err = toolswatch.UntilWithoutRetry(c.ctx, watcher, func(event watch.Event) (bool, error) { - for _, c := range event.Object.(*apiextensionsv1spec.CustomResourceDefinition).Status.Conditions { - if c.Type == apiextensionsv1spec.Established && c.Status == apiextensionsv1spec.ConditionTrue { - return true, nil - } - } - return false, nil - }) - if err != nil { - panic(fmt.Errorf("failed to wait for CRD %v", err)) - } -} - // callTool helper function to call a tool by name with arguments func (c *mcpContext) callTool(name string, args map[string]interface{}) (*mcp.CallToolResult, error) { callToolRequest := mcp.CallToolRequest{} @@ -446,14 +332,53 @@ func (s *BaseMcpSuite) InitMcpClient(options ...transport.StreamableHTTPCOption) s.McpClient = test.NewMcpClient(s.T(), s.mcpServer.ServeHTTP(nil), options...) } -// CrdWaitUntilReady waits for a CRD to be established -func (s *BaseMcpSuite) CrdWaitUntilReady(name string) { +// EnvTestInOpenShift sets up the kubernetes environment to seem to be running OpenShift +func EnvTestInOpenShift(ctx context.Context) error { + crdTemplate := ` + { + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": {"name": "%s"}, + "spec": { + "group": "%s", + "versions": [{ + "name": "v1","served": true,"storage": true, + "schema": {"openAPIV3Schema": {"type": "object","x-kubernetes-preserve-unknown-fields": true}} + }], + "scope": "%s", + "names": {"plural": "%s","singular": "%s","kind": "%s"} + } + }` + tasks, _ := errgroup.WithContext(ctx) + tasks.Go(func() error { + return EnvTestCrdApply(ctx, fmt.Sprintf(crdTemplate, "projects.project.openshift.io", "project.openshift.io", + "Cluster", "projects", "project", "Project")) + }) + tasks.Go(func() error { + return EnvTestCrdApply(ctx, fmt.Sprintf(crdTemplate, "routes.route.openshift.io", "route.openshift.io", + "Namespaced", "routes", "route", "Route")) + }) + return tasks.Wait() +} + +// EnvTestInOpenShiftClear clears the kubernetes environment so it no longer seems to be running OpenShift +func EnvTestInOpenShiftClear(ctx context.Context) error { + tasks, _ := errgroup.WithContext(ctx) + tasks.Go(func() error { return EnvTestCrdDelete(ctx, "projects.project.openshift.io") }) + tasks.Go(func() error { return EnvTestCrdDelete(ctx, "routes.route.openshift.io") }) + return tasks.Wait() +} + +// EnvTestCrdWaitUntilReady waits for a CRD to be established +func EnvTestCrdWaitUntilReady(ctx context.Context, name string) error { apiExtensionClient := apiextensionsv1.NewForConfigOrDie(envTestRestConfig) - watcher, err := apiExtensionClient.CustomResourceDefinitions().Watch(s.T().Context(), metav1.ListOptions{ + watcher, err := apiExtensionClient.CustomResourceDefinitions().Watch(ctx, metav1.ListOptions{ FieldSelector: "metadata.name=" + name, }) - s.Require().NoError(err, "failed to watch CRD") - _, err = toolswatch.UntilWithoutRetry(s.T().Context(), watcher, func(event watch.Event) (bool, error) { + if err != nil { + return fmt.Errorf("unable to watch CRDs: %w", err) + } + _, err = toolswatch.UntilWithoutRetry(ctx, watcher, func(event watch.Event) (bool, error) { for _, c := range event.Object.(*apiextensionsv1spec.CustomResourceDefinition).Status.Conditions { if c.Type == apiextensionsv1spec.Established && c.Status == apiextensionsv1spec.ConditionTrue { return true, nil @@ -461,5 +386,43 @@ func (s *BaseMcpSuite) CrdWaitUntilReady(name string) { } return false, nil }) - s.Require().NoError(err, "failed to wait for CRD") + if err != nil { + return fmt.Errorf("failed to wait for CRD: %w", err) + } + return nil +} + +// EnvTestCrdApply creates a CRD from the provided resource string and waits for it to be established +func EnvTestCrdApply(ctx context.Context, resource string) error { + apiExtensionsV1Client := apiextensionsv1.NewForConfigOrDie(envTestRestConfig) + var crd = &apiextensionsv1spec.CustomResourceDefinition{} + err := json.Unmarshal([]byte(resource), crd) + if err != nil { + return fmt.Errorf("failed to create CRD %v", err) + } + _, err = apiExtensionsV1Client.CustomResourceDefinitions().Create(ctx, crd, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create CRD %v", err) + } + return EnvTestCrdWaitUntilReady(ctx, crd.Name) +} + +// crdDelete deletes a CRD by name and waits for it to be removed +func EnvTestCrdDelete(ctx context.Context, name string) error { + apiExtensionsV1Client := apiextensionsv1.NewForConfigOrDie(envTestRestConfig) + err := apiExtensionsV1Client.CustomResourceDefinitions().Delete(ctx, name, metav1.DeleteOptions{ + GracePeriodSeconds: ptr.To(int64(0)), + }) + iteration := 0 + for iteration < 100 { + if _, derr := apiExtensionsV1Client.CustomResourceDefinitions().Get(ctx, name, metav1.GetOptions{}); derr != nil { + break + } + time.Sleep(5 * time.Millisecond) + iteration++ + } + if err != nil { + return errors.Wrap(err, "failed to delete CRD") + } + return nil } diff --git a/pkg/mcp/namespaces_test.go b/pkg/mcp/namespaces_test.go index a0a6ff23..25565512 100644 --- a/pkg/mcp/namespaces_test.go +++ b/pkg/mcp/namespaces_test.go @@ -13,9 +13,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "sigs.k8s.io/yaml" - - "github.com/containers/kubernetes-mcp-server/internal/test" - "github.com/containers/kubernetes-mcp-server/pkg/config" ) type NamespacesSuite struct { @@ -108,68 +105,67 @@ func (s *NamespacesSuite) TestNamespacesListAsTable() { }) } -func TestNamespaces(t *testing.T) { - suite.Run(t, new(NamespacesSuite)) -} +func (s *NamespacesSuite) TestProjectsListInOpenShift() { + s.Require().NoError(EnvTestInOpenShift(s.T().Context()), "Expected to configure test for OpenShift") + s.T().Cleanup(func() { + s.Require().NoError(EnvTestInOpenShiftClear(s.T().Context()), "Expected to clear OpenShift test configuration") + }) + s.InitMcpClient() -func TestProjectsListInOpenShift(t *testing.T) { - testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { + s.Run("projects_list returns project list in OpenShift", func() { dynamicClient := dynamic.NewForConfigOrDie(envTestRestConfig) _, _ = dynamicClient.Resource(schema.GroupVersionResource{Group: "project.openshift.io", Version: "v1", Resource: "projects"}). - Create(c.ctx, &unstructured.Unstructured{Object: map[string]interface{}{ + Create(s.T().Context(), &unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": "project.openshift.io/v1", "kind": "Project", "metadata": map[string]interface{}{ "name": "an-openshift-project", }, }}, metav1.CreateOptions{}) - toolResult, err := c.callTool("projects_list", map[string]interface{}{}) - t.Run("projects_list returns project list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if toolResult.IsError { - t.Fatalf("call tool failed") - } + toolResult, err := s.CallTool("projects_list", map[string]interface{}{}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(toolResult.IsError, "call tool failed") }) var decoded []unstructured.Unstructured err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded) - t.Run("projects_list has yaml content", func(t *testing.T) { - if err != nil { - t.Fatalf("invalid tool result content %v", err) - } + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) }) - t.Run("projects_list returns at least 1 items", func(t *testing.T) { - if len(decoded) < 1 { - t.Errorf("invalid project count, expected at least 1, got %v", len(decoded)) - } + s.Run("returns at least 1 item", func() { + s.GreaterOrEqualf(len(decoded), 1, "invalid project count, expected at least 1, got %v", len(decoded)) idx := slices.IndexFunc(decoded, func(ns unstructured.Unstructured) bool { return ns.GetName() == "an-openshift-project" }) - if idx == -1 { - t.Errorf("namespace %s not found in the list", "an-openshift-project") - } + s.NotEqualf(-1, idx, "namespace %s not found in the list", "an-openshift-project") }) }) } -func TestProjectsListInOpenShiftDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *NamespacesSuite) TestProjectsListInOpenShiftDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { group = "project.openshift.io", version = "v1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer, before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { - c.withEnvTest() - projectsList, _ := c.callTool("projects_list", map[string]interface{}{}) - t.Run("projects_list has error", func(t *testing.T) { - if !projectsList.IsError { - t.Fatalf("call tool should fail") - } + `), s.Cfg), "Expected to parse denied resources config") + s.Require().NoError(EnvTestInOpenShift(s.T().Context()), "Expected to configure test for OpenShift") + s.T().Cleanup(func() { + s.Require().NoError(EnvTestInOpenShiftClear(s.T().Context()), "Expected to clear OpenShift test configuration") + }) + s.InitMcpClient() + + s.Run("projects_list (denied)", func() { + projectsList, err := s.CallTool("projects_list", map[string]interface{}{}) + s.Run("has error", func() { + s.Truef(projectsList.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("projects_list describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to list projects: resource not allowed: project.openshift.io/v1, Kind=Project" - if projectsList.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, projectsList.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, projectsList.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, projectsList.Content[0].(mcp.TextContent).Text) }) }) } + +func TestNamespaces(t *testing.T) { + suite.Run(t, new(NamespacesSuite)) +} diff --git a/pkg/mcp/pods_run_test.go b/pkg/mcp/pods_run_test.go index 1dc751a9..4c329f3e 100644 --- a/pkg/mcp/pods_run_test.go +++ b/pkg/mcp/pods_run_test.go @@ -109,37 +109,33 @@ func (s *PodsRunSuite) TestPodsRunDenied() { }) } -func TestPodsRunInOpenShift(t *testing.T) { - testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { - t.Run("pods_run with image, namespace, and port returns route with port", func(t *testing.T) { - podsRunInOpenShift, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsRunInOpenShift.IsError { - t.Errorf("call tool failed") - return - } - var decodedPodServiceRoute []unstructured.Unstructured - err = yaml.Unmarshal([]byte(podsRunInOpenShift.Content[0].(mcp.TextContent).Text), &decodedPodServiceRoute) - if err != nil { - t.Errorf("invalid tool result content %v", err) - return - } - if len(decodedPodServiceRoute) != 3 { - t.Errorf("invalid pods count, expected 3, got %v", len(decodedPodServiceRoute)) - return - } - if decodedPodServiceRoute[2].GetKind() != "Route" { - t.Errorf("invalid route kind, expected Route, got %v", decodedPodServiceRoute[2].GetKind()) - return - } +func (s *PodsRunSuite) TestPodsRunInOpenShift() { + s.Require().NoError(EnvTestInOpenShift(s.T().Context()), "Expected to configure test for OpenShift") + s.T().Cleanup(func() { + s.Require().NoError(EnvTestInOpenShiftClear(s.T().Context()), "Expected to clear OpenShift test configuration") + }) + s.InitMcpClient() + + s.Run("pods_run(image=nginx, namespace=nil, port=80) returns route with port", func() { + podsRunInOpenShift, err := s.CallTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsRunInOpenShift.IsError, "call tool failed") + }) + var decodedPodServiceRoute []unstructured.Unstructured + err = yaml.Unmarshal([]byte(podsRunInOpenShift.Content[0].(mcp.TextContent).Text), &decodedPodServiceRoute) + s.Run("has yaml content", func() { + s.Nilf(err, "invalid tool result content %v", err) + }) + s.Run("returns 3 items (Pod + Service + Route)", func() { + s.Lenf(decodedPodServiceRoute, 3, "invalid pods count, expected 3, got %v", len(decodedPodServiceRoute)) + s.Equalf("Pod", decodedPodServiceRoute[0].GetKind(), "invalid pod kind, expected Pod, got %v", decodedPodServiceRoute[0].GetKind()) + s.Equalf("Service", decodedPodServiceRoute[1].GetKind(), "invalid service kind, expected Service, got %v", decodedPodServiceRoute[1].GetKind()) + s.Equalf("Route", decodedPodServiceRoute[2].GetKind(), "invalid route kind, expected Route, got %v", decodedPodServiceRoute[2].GetKind()) + }) + s.Run("returns route with port", func() { targetPort := decodedPodServiceRoute[2].Object["spec"].(map[string]interface{})["port"].(map[string]interface{})["targetPort"].(int64) - if targetPort != 80 { - t.Errorf("invalid route target port, expected 80, got %v", targetPort) - return - } + s.Equalf(int64(80), targetPort, "invalid route target port, expected 80, got %v", targetPort) }) }) } diff --git a/pkg/mcp/pods_test.go b/pkg/mcp/pods_test.go index 8cff3e41..ddeec3ea 100644 --- a/pkg/mcp/pods_test.go +++ b/pkg/mcp/pods_test.go @@ -454,20 +454,26 @@ func (s *PodsSuite) TestPodsDeleteDenied() { }) } -func TestPodsDeleteInOpenShift(t *testing.T) { - testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { +func (s *PodsSuite) TestPodsDeleteInOpenShift() { + s.Require().NoError(EnvTestInOpenShift(s.T().Context()), "Expected to configure test for OpenShift") + s.T().Cleanup(func() { + s.Require().NoError(EnvTestInOpenShiftClear(s.T().Context()), "Expected to clear OpenShift test configuration") + }) + s.InitMcpClient() + + s.Run("pods_delete with managed pod in OpenShift", func() { managedLabels := map[string]string{ "app.kubernetes.io/managed-by": "kubernetes-mcp-server", "app.kubernetes.io/name": "a-manged-pod-to-delete", } - kc := c.newKubernetesClient() - _, _ = kc.CoreV1().Pods("default").Create(c.ctx, &corev1.Pod{ + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + _, _ = kc.CoreV1().Pods("default").Create(s.T().Context(), &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "a-managed-pod-to-delete-in-openshift", Labels: managedLabels}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}}, }, metav1.CreateOptions{}) dynamicClient := dynamic.NewForConfigOrDie(envTestRestConfig) _, _ = dynamicClient.Resource(schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"}). - Namespace("default").Create(c.ctx, &unstructured.Unstructured{Object: map[string]interface{}{ + Namespace("default").Create(s.T().Context(), &unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": "route.openshift.io/v1", "kind": "Route", "metadata": map[string]interface{}{ @@ -475,36 +481,22 @@ func TestPodsDeleteInOpenShift(t *testing.T) { "labels": managedLabels, }, }}, metav1.CreateOptions{}) - podsDeleteManagedOpenShift, err := c.callTool("pods_delete", map[string]interface{}{ + podsDeleteManagedOpenShift, err := s.CallTool("pods_delete", map[string]interface{}{ "name": "a-managed-pod-to-delete-in-openshift", }) - t.Run("pods_delete with managed pod in OpenShift returns success", func(t *testing.T) { - if err != nil { - t.Errorf("call tool failed %v", err) - return - } - if podsDeleteManagedOpenShift.IsError { - t.Errorf("call tool failed") - return - } - if podsDeleteManagedOpenShift.Content[0].(mcp.TextContent).Text != "Pod deleted successfully" { - t.Errorf("invalid tool result content, got %v", podsDeleteManagedOpenShift.Content[0].(mcp.TextContent).Text) - return - } + s.Run("returns success", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(podsDeleteManagedOpenShift.IsError, "call tool failed") + s.Equalf("Pod deleted successfully", podsDeleteManagedOpenShift.Content[0].(mcp.TextContent).Text, + "invalid tool result content, got %v", podsDeleteManagedOpenShift.Content[0].(mcp.TextContent).Text) }) - t.Run("pods_delete with managed pod in OpenShift deletes Pod and Route", func(t *testing.T) { - p, pErr := kc.CoreV1().Pods("default").Get(c.ctx, "a-managed-pod-to-delete-in-openshift", metav1.GetOptions{}) - if pErr == nil && p != nil && p.DeletionTimestamp == nil { - t.Errorf("Pod not deleted") - return - } + s.Run("deletes Pod and Route", func() { + p, pErr := kc.CoreV1().Pods("default").Get(s.T().Context(), "a-managed-pod-to-delete-in-openshift", metav1.GetOptions{}) + s.False(pErr == nil && p != nil && p.DeletionTimestamp == nil, "Pod not deleted") r, rErr := dynamicClient. Resource(schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"}). - Namespace("default").Get(c.ctx, "a-managed-route-to-delete", metav1.GetOptions{}) - if rErr == nil && r != nil && r.GetDeletionTimestamp() == nil { - t.Errorf("Route not deleted") - return - } + Namespace("default").Get(s.T().Context(), "a-managed-route-to-delete", metav1.GetOptions{}) + s.False(rErr == nil && r != nil && r.GetDeletionTimestamp() == nil, "Route not deleted") }) }) } diff --git a/pkg/mcp/resources_test.go b/pkg/mcp/resources_test.go index 83401377..21329d20 100644 --- a/pkg/mcp/resources_test.go +++ b/pkg/mcp/resources_test.go @@ -17,8 +17,6 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "sigs.k8s.io/yaml" - - "github.com/containers/kubernetes-mcp-server/pkg/output" ) type ResourcesSuite struct { @@ -141,31 +139,34 @@ func (s *ResourcesSuite) TestResourcesListDenied() { }) } -func TestResourcesListAsTable(t *testing.T) { - testCaseWithContext(t, &mcpContext{listOutput: output.Table, before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) { - c.withEnvTest() - kc := c.newKubernetesClient() - _, _ = kc.CoreV1().ConfigMaps("default").Create(t.Context(), &corev1.ConfigMap{ +func (s *ResourcesSuite) TestResourcesListAsTable() { + s.Cfg.ListOutput = "table" + s.Require().NoError(EnvTestInOpenShift(s.T().Context()), "Expected to configure test for OpenShift") + s.T().Cleanup(func() { + s.Require().NoError(EnvTestInOpenShiftClear(s.T().Context()), "Expected to clear OpenShift test configuration") + }) + s.InitMcpClient() + + s.Run("resources_list(apiVersion=v1, kind=ConfigMap) (list_output=table)", func() { + kc := kubernetes.NewForConfigOrDie(envTestRestConfig) + _, _ = kc.CoreV1().ConfigMaps("default").Create(s.T().Context(), &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{Name: "a-configmap-to-list-as-table", Labels: map[string]string{"resource": "config-map"}}, Data: map[string]string{"key": "value"}, }, metav1.CreateOptions{}) - configMapList, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap"}) - t.Run("resources_list returns ConfigMap list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if configMapList.IsError { - t.Fatalf("call tool failed") - } + configMapList, err := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap"}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(configMapList.IsError, "call tool failed") }) + s.Require().NotNil(configMapList, "Expected tool result from call") outConfigMapList := configMapList.Content[0].(mcp.TextContent).Text - t.Run("resources_list returns column headers for ConfigMap list", func(t *testing.T) { + s.Run("returns column headers for ConfigMap list", func() { expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+DATA\\s+AGE\\s+LABELS" - if m, e := regexp.MatchString(expectedHeaders, outConfigMapList); !m || e != nil { - t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outConfigMapList) - } + m, e := regexp.MatchString(expectedHeaders, outConfigMapList) + s.Truef(m, "Expected headers '%s' not found in output:\n%s", expectedHeaders, outConfigMapList) + s.NoErrorf(e, "Error matching headers regex: %v", e) }) - t.Run("resources_list returns formatted row for a-configmap-to-list-as-table", func(t *testing.T) { + s.Run("returns formatted row for a-configmap-to-list-as-table", func() { expectedRow := "(?default)\\s+" + "(?v1)\\s+" + "(?ConfigMap)\\s+" + @@ -173,47 +174,46 @@ func TestResourcesListAsTable(t *testing.T) { "(?1)\\s+" + "(?(\\d+m)?(\\d+s)?)\\s+" + "(?resource=config-map)" - if m, e := regexp.MatchString(expectedRow, outConfigMapList); !m || e != nil { - t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outConfigMapList) - } + m, e := regexp.MatchString(expectedRow, outConfigMapList) + s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, outConfigMapList) + s.NoErrorf(e, "Error matching row regex: %v", e) }) - // Custom Resource List + }) + + s.Run("resources_list(apiVersion=route.openshift.io/v1, kind=Route) (list_output=table)", func() { _, _ = dynamic.NewForConfigOrDie(envTestRestConfig). Resource(schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"}). Namespace("default"). - Create(c.ctx, &unstructured.Unstructured{Object: map[string]interface{}{ + Create(s.T().Context(), &unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": "route.openshift.io/v1", "kind": "Route", "metadata": map[string]interface{}{ "name": "an-openshift-route-to-list-as-table", }, }}, metav1.CreateOptions{}) - routeList, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "route.openshift.io/v1", "kind": "Route"}) - t.Run("resources_list returns Route list", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if routeList.IsError { - t.Fatalf("call tool failed") - } + routeList, err := s.CallTool("resources_list", map[string]interface{}{"apiVersion": "route.openshift.io/v1", "kind": "Route"}) + s.Run("no error", func() { + s.Nilf(err, "call tool failed %v", err) + s.Falsef(routeList.IsError, "call tool failed") }) + s.Require().NotNil(routeList, "Expected tool result from call") outRouteList := routeList.Content[0].(mcp.TextContent).Text - t.Run("resources_list returns column headers for Route list", func(t *testing.T) { + s.Run("returns column headers for Route list", func() { expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+AGE\\s+LABELS" - if m, e := regexp.MatchString(expectedHeaders, outRouteList); !m || e != nil { - t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outRouteList) - } + m, e := regexp.MatchString(expectedHeaders, outRouteList) + s.Truef(m, "Expected headers '%s' not found in output:\n%s", expectedHeaders, outRouteList) + s.NoErrorf(e, "Error matching headers regex: %v", e) }) - t.Run("resources_list returns formatted row for an-openshift-route-to-list-as-table", func(t *testing.T) { + s.Run("returns formatted row for an-openshift-route-to-list-as-table", func() { expectedRow := "(?default)\\s+" + "(?route.openshift.io/v1)\\s+" + "(?Route)\\s+" + "(?an-openshift-route-to-list-as-table)\\s+" + "(?(\\d+m)?(\\d+s)?)\\s+" + "(?)" - if m, e := regexp.MatchString(expectedRow, outRouteList); !m || e != nil { - t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outRouteList) - } + m, e := regexp.MatchString(expectedRow, outRouteList) + s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, outRouteList) + s.NoErrorf(e, "Error matching row regex: %v", e) }) }) } @@ -393,7 +393,7 @@ func (s *ResourcesSuite) TestResourcesCreateOrUpdate() { _, err = apiExtensionsV1Client.CustomResourceDefinitions().Get(s.T().Context(), "customs.example.com", metav1.GetOptions{}) s.Nilf(err, "custom resource definition not found") }) - s.CrdWaitUntilReady("customs.example.com") + s.Require().NoError(EnvTestCrdWaitUntilReady(s.T().Context(), "customs.example.com")) }) s.Run("resources_create_or_update creates custom resource", func() { From a301b0f62000afacb306c9f9ac35809389f4a349 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 4 Nov 2025 15:03:01 +0100 Subject: [PATCH 45/57] test(mcp): refactor logging tests to use testify (#439) All tests now use testify, removing unneeded testing infrastructure for legacy tests. Signed-off-by: Marc Nuri --- pkg/mcp/common_test.go | 139 --------------------------------- pkg/mcp/mcp_middleware_test.go | 127 +++++++++++++++++------------- 2 files changed, 73 insertions(+), 193 deletions(-) diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go index 8546c4aa..b91df691 100644 --- a/pkg/mcp/common_test.go +++ b/pkg/mcp/common_test.go @@ -1,23 +1,16 @@ package mcp import ( - "bytes" "context" "encoding/json" - "flag" "fmt" - "net/http/httptest" "os" "path/filepath" "runtime" - "strconv" "testing" "time" - "github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/client/transport" - "github.com/mark3labs/mcp-go/mcp" - "github.com/mark3labs/mcp-go/server" "github.com/pkg/errors" "github.com/spf13/afero" "github.com/stretchr/testify/suite" @@ -30,11 +23,7 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" toolswatch "k8s.io/client-go/tools/watch" - "k8s.io/klog/v2" - "k8s.io/klog/v2/textlogger" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/tools/setup-envtest/env" @@ -45,7 +34,6 @@ import ( "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/containers/kubernetes-mcp-server/pkg/config" - "github.com/containers/kubernetes-mcp-server/pkg/output" ) // envTest has an expensive setup, so we only want to do it once per entire test run. @@ -103,133 +91,6 @@ func TestMain(m *testing.M) { os.Exit(code) } -type mcpContext struct { - toolsets []string - listOutput output.Output - logLevel int - - staticConfig *config.StaticConfig - clientOptions []transport.ClientOption - before func(*mcpContext) - after func(*mcpContext) - ctx context.Context - tempDir string - cancel context.CancelFunc - mcpServer *Server - mcpHttpServer *httptest.Server - mcpClient *client.Client - klogState klog.State - logBuffer bytes.Buffer -} - -func (c *mcpContext) beforeEach(t *testing.T) { - var err error - c.ctx, c.cancel = context.WithCancel(t.Context()) - c.tempDir = t.TempDir() - c.withKubeConfig(nil) - if c.staticConfig == nil { - c.staticConfig = config.Default() - // Default to use YAML output for lists (previously the default) - c.staticConfig.ListOutput = "yaml" - } - if c.toolsets != nil { - c.staticConfig.Toolsets = c.toolsets - - } - if c.listOutput != nil { - c.staticConfig.ListOutput = c.listOutput.GetName() - } - if c.before != nil { - c.before(c) - } - // Set up logging - c.klogState = klog.CaptureState() - flags := flag.NewFlagSet("test", flag.ContinueOnError) - klog.InitFlags(flags) - _ = flags.Set("v", strconv.Itoa(c.logLevel)) - klog.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(c.logLevel), textlogger.Output(&c.logBuffer)))) - // MCP Server - if c.mcpServer, err = NewServer(Configuration{StaticConfig: c.staticConfig}); err != nil { - t.Fatal(err) - return - } - c.mcpHttpServer = server.NewTestServer(c.mcpServer.server, server.WithSSEContextFunc(contextFunc)) - if c.mcpClient, err = client.NewSSEMCPClient(c.mcpHttpServer.URL+"/sse", c.clientOptions...); err != nil { - t.Fatal(err) - return - } - // MCP Client - if err = c.mcpClient.Start(c.ctx); err != nil { - t.Fatal(err) - return - } - initRequest := mcp.InitializeRequest{} - initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION - initRequest.Params.ClientInfo = mcp.Implementation{Name: "test", Version: "1.33.7"} - _, err = c.mcpClient.Initialize(c.ctx, initRequest) - if err != nil { - t.Fatal(err) - return - } -} - -func (c *mcpContext) afterEach() { - if c.after != nil { - c.after(c) - } - c.cancel() - c.mcpServer.Close() - _ = c.mcpClient.Close() - c.mcpHttpServer.Close() - c.klogState.Restore() -} - -func testCaseWithContext(t *testing.T, mcpCtx *mcpContext, test func(c *mcpContext)) { - mcpCtx.beforeEach(t) - defer mcpCtx.afterEach() - test(mcpCtx) -} - -// withKubeConfig sets up a fake kubeconfig in the temp directory based on the provided rest.Config -func (c *mcpContext) withKubeConfig(rc *rest.Config) *clientcmdapi.Config { - fakeConfig := clientcmdapi.NewConfig() - fakeConfig.Clusters["fake"] = clientcmdapi.NewCluster() - fakeConfig.Clusters["fake"].Server = "https://127.0.0.1:6443" - fakeConfig.Clusters["additional-cluster"] = clientcmdapi.NewCluster() - fakeConfig.AuthInfos["fake"] = clientcmdapi.NewAuthInfo() - fakeConfig.AuthInfos["additional-auth"] = clientcmdapi.NewAuthInfo() - if rc != nil { - fakeConfig.Clusters["fake"].Server = rc.Host - fakeConfig.Clusters["fake"].CertificateAuthorityData = rc.CAData - fakeConfig.AuthInfos["fake"].ClientKeyData = rc.KeyData - fakeConfig.AuthInfos["fake"].ClientCertificateData = rc.CertData - } - fakeConfig.Contexts["fake-context"] = clientcmdapi.NewContext() - fakeConfig.Contexts["fake-context"].Cluster = "fake" - fakeConfig.Contexts["fake-context"].AuthInfo = "fake" - fakeConfig.Contexts["additional-context"] = clientcmdapi.NewContext() - fakeConfig.Contexts["additional-context"].Cluster = "additional-cluster" - fakeConfig.Contexts["additional-context"].AuthInfo = "additional-auth" - fakeConfig.CurrentContext = "fake-context" - kubeConfig := filepath.Join(c.tempDir, "config") - _ = clientcmd.WriteToFile(*fakeConfig, kubeConfig) - _ = os.Setenv("KUBECONFIG", kubeConfig) - if c.mcpServer != nil { - if err := c.mcpServer.reloadKubernetesClusterProvider(); err != nil { - panic(err) - } - } - return fakeConfig -} - -// callTool helper function to call a tool by name with arguments -func (c *mcpContext) callTool(name string, args map[string]interface{}) (*mcp.CallToolResult, error) { - callToolRequest := mcp.CallToolRequest{} - callToolRequest.Params.Name = name - callToolRequest.Params.Arguments = args - return c.mcpClient.CallTool(c.ctx, callToolRequest) -} - func restoreAuth(ctx context.Context) { kubernetesAdmin := kubernetes.NewForConfigOrDie(envTest.Config) // Authorization diff --git a/pkg/mcp/mcp_middleware_test.go b/pkg/mcp/mcp_middleware_test.go index 987bfe4f..ce88e7b4 100644 --- a/pkg/mcp/mcp_middleware_test.go +++ b/pkg/mcp/mcp_middleware_test.go @@ -1,68 +1,87 @@ package mcp import ( + "bytes" + "flag" "regexp" - "strings" + "strconv" "testing" "github.com/mark3labs/mcp-go/client/transport" + "github.com/stretchr/testify/suite" + "k8s.io/klog/v2" + "k8s.io/klog/v2/textlogger" ) -func TestToolCallLogging(t *testing.T) { - testCaseWithContext(t, &mcpContext{logLevel: 5}, func(c *mcpContext) { - _, _ = c.callTool("configuration_view", map[string]interface{}{ - "minified": false, - }) - t.Run("Logs tool name", func(t *testing.T) { - expectedLog := "mcp tool call: configuration_view(" - if !strings.Contains(c.logBuffer.String(), expectedLog) { - t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String()) - } - }) - t.Run("Logs tool call arguments", func(t *testing.T) { - expected := `"mcp tool call: configuration_view\((.+)\)"` - m := regexp.MustCompile(expected).FindStringSubmatch(c.logBuffer.String()) - if len(m) != 2 { - t.Fatalf("Expected log entry to contain arguments, got %s", c.logBuffer.String()) - } - if m[1] != "map[minified:false]" { - t.Errorf("Expected log arguments to be 'map[minified:false]', got %s", m[1]) - } - }) +type McpLoggingSuite struct { + BaseMcpSuite + klogState klog.State + logBuffer bytes.Buffer +} + +func (s *McpLoggingSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.klogState = klog.CaptureState() +} + +func (s *McpLoggingSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + s.klogState.Restore() +} + +func (s *McpLoggingSuite) SetLogLevel(level int) { + flags := flag.NewFlagSet("test", flag.ContinueOnError) + klog.InitFlags(flags) + _ = flags.Set("v", strconv.Itoa(level)) + klog.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(level), textlogger.Output(&s.logBuffer)))) +} + +func (s *McpLoggingSuite) TestLogsToolCall() { + s.SetLogLevel(5) + s.InitMcpClient() + _, err := s.CallTool("configuration_view", map[string]interface{}{"minified": false}) + s.Require().NoError(err, "call to tool configuration_view failed") + + s.Run("Logs tool name", func() { + s.Contains(s.logBuffer.String(), "mcp tool call: configuration_view(") }) - before := func(c *mcpContext) { - c.clientOptions = append(c.clientOptions, transport.WithHeaders(map[string]string{ - "Accept-Encoding": "gzip", - "Authorization": "Bearer should-not-be-logged", - "authorization": "Bearer should-not-be-logged", - "a-loggable-header": "should-be-logged", - })) + s.Run("Logs tool call arguments", func() { + expected := `"mcp tool call: configuration_view\((.+)\)"` + m := regexp.MustCompile(expected).FindStringSubmatch(s.logBuffer.String()) + s.Len(m, 2, "Expected log entry to contain arguments") + s.Equal("map[minified:false]", m[1], "Expected log arguments to be 'map[minified:false]'") + }) +} + +func (s *McpLoggingSuite) TestLogsToolCallHeaders() { + s.SetLogLevel(7) + s.InitMcpClient(transport.WithHTTPHeaders(map[string]string{ + "Accept-Encoding": "gzip", + "Authorization": "Bearer should-not-be-logged", + "authorization": "Bearer should-not-be-logged", + "a-loggable-header": "should-be-logged", + })) + _, err := s.CallTool("configuration_view", map[string]interface{}{"minified": false}) + s.Require().NoError(err, "call to tool configuration_view failed") + + s.Run("Logs tool call headers", func() { + expectedLog := "mcp tool call headers: A-Loggable-Header: should-be-logged" + s.Contains(s.logBuffer.String(), expectedLog, "Expected log to contain loggable header") + }) + sensitiveHeaders := []string{ + "Authorization:", + // TODO: Add more sensitive headers as needed } - testCaseWithContext(t, &mcpContext{logLevel: 7, before: before}, func(c *mcpContext) { - _, _ = c.callTool("configuration_view", map[string]interface{}{ - "minified": false, - }) - t.Run("Logs tool call headers", func(t *testing.T) { - expectedLog := "mcp tool call headers: A-Loggable-Header: should-be-logged" - if !strings.Contains(c.logBuffer.String(), expectedLog) { - t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String()) - } - }) - sensitiveHeaders := []string{ - "Authorization:", - // TODO: Add more sensitive headers as needed + s.Run("Does not log sensitive headers", func() { + for _, header := range sensitiveHeaders { + s.NotContains(s.logBuffer.String(), header, "Log should not contain sensitive header") } - t.Run("Does not log sensitive headers", func(t *testing.T) { - for _, header := range sensitiveHeaders { - if strings.Contains(c.logBuffer.String(), header) { - t.Errorf("Log should not contain sensitive header '%s', got: %s", header, c.logBuffer.String()) - } - } - }) - t.Run("Does not log sensitive header values", func(t *testing.T) { - if strings.Contains(c.logBuffer.String(), "should-not-be-logged") { - t.Errorf("Log should not contain sensitive header value 'should-not-be-logged', got: %s", c.logBuffer.String()) - } - }) }) + s.Run("Does not log sensitive header values", func() { + s.NotContains(s.logBuffer.String(), "should-not-be-logged", "Log should not contain sensitive header value") + }) +} + +func TestMcpLogging(t *testing.T) { + suite.Run(t, new(McpLoggingSuite)) } From db3f51ddd20c75c2f701c52b2069b95185e2d3aa Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 5 Nov 2025 15:20:16 +0100 Subject: [PATCH 46/57] fix(toolsets): remove "redundant" annotations that have defaults (#441) The removed annotations have default values and are not needed. I personally prefer to keep them because it makes it easier for reviewers to understand the tool behaviors. However, this is problematic with go-sdk since these values aren't marshalled/serialized (omitempty). Signed-off-by: Marc Nuri --- pkg/mcp/testdata/toolsets-config-tools.json | 1 - pkg/mcp/testdata/toolsets-core-tools.json | 17 -------------- ...toolsets-full-tools-multicluster-enum.json | 22 ------------------ .../toolsets-full-tools-multicluster.json | 22 ------------------ .../toolsets-full-tools-openshift.json | 23 ------------------- pkg/mcp/testdata/toolsets-full-tools.json | 22 ------------------ pkg/mcp/testdata/toolsets-helm-tools.json | 4 ---- pkg/toolsets/config/configuration.go | 1 - pkg/toolsets/core/events.go | 1 - pkg/toolsets/core/namespaces.go | 2 -- pkg/toolsets/core/nodes.go | 2 -- pkg/toolsets/core/pods.go | 9 -------- pkg/toolsets/core/resources.go | 4 ---- pkg/toolsets/helm/helm.go | 5 +--- 14 files changed, 1 insertion(+), 134 deletions(-) diff --git a/pkg/mcp/testdata/toolsets-config-tools.json b/pkg/mcp/testdata/toolsets-config-tools.json index c1767491..2c5b7ae8 100644 --- a/pkg/mcp/testdata/toolsets-config-tools.json +++ b/pkg/mcp/testdata/toolsets-config-tools.json @@ -4,7 +4,6 @@ "title": "Configuration: View", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the current Kubernetes configuration content as a kubeconfig YAML", diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index e8753758..b4c5667f 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -4,7 +4,6 @@ "title": "Events: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes events in the current cluster from all namespaces", @@ -24,7 +23,6 @@ "title": "Namespaces: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes namespaces in the current cluster", @@ -38,7 +36,6 @@ "title": "Node: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", @@ -72,7 +69,6 @@ "title": "Node: Stats Summary", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", @@ -118,7 +114,6 @@ { "annotations": { "title": "Pods: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -145,9 +140,7 @@ { "annotations": { "title": "Pods: Exec", - "readOnlyHint": false, "destructiveHint": true, - "idempotentHint": false, "openWorldHint": true }, "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command", @@ -186,7 +179,6 @@ "title": "Pods: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name", @@ -213,7 +205,6 @@ "title": "Pods: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the current cluster from all namespaces", @@ -234,7 +225,6 @@ "title": "Pods: List in Namespace", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the specified namespace in the current cluster", @@ -262,7 +252,6 @@ "title": "Pods: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name", @@ -301,9 +290,7 @@ { "annotations": { "title": "Pods: Run", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name", @@ -370,7 +357,6 @@ { "annotations": { "title": "Resources: Create or Update", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -393,7 +379,6 @@ { "annotations": { "title": "Resources: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -432,7 +417,6 @@ "title": "Resources: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", @@ -469,7 +453,6 @@ "title": "Resources: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index 08181078..7831c054 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -18,7 +18,6 @@ "title": "Configuration: View", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the current Kubernetes configuration content as a kubeconfig YAML", @@ -38,7 +37,6 @@ "title": "Events: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes events in the current cluster from all namespaces", @@ -64,9 +62,7 @@ { "annotations": { "title": "Helm: Install", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Install a Helm chart in the current or provided namespace", @@ -109,7 +105,6 @@ "title": "Helm: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)", @@ -139,7 +134,6 @@ { "annotations": { "title": "Helm: Uninstall", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -176,7 +170,6 @@ "title": "Namespaces: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes namespaces in the current cluster", @@ -200,7 +193,6 @@ "title": "Node: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", @@ -242,7 +234,6 @@ "title": "Node: Stats Summary", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", @@ -304,7 +295,6 @@ { "annotations": { "title": "Pods: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -339,9 +329,7 @@ { "annotations": { "title": "Pods: Exec", - "readOnlyHint": false, "destructiveHint": true, - "idempotentHint": false, "openWorldHint": true }, "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command", @@ -388,7 +376,6 @@ "title": "Pods: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name", @@ -423,7 +410,6 @@ "title": "Pods: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the current cluster from all namespaces", @@ -452,7 +438,6 @@ "title": "Pods: List in Namespace", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the specified namespace in the current cluster", @@ -488,7 +473,6 @@ "title": "Pods: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name", @@ -535,9 +519,7 @@ { "annotations": { "title": "Pods: Run", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name", @@ -620,7 +602,6 @@ { "annotations": { "title": "Resources: Create or Update", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -651,7 +632,6 @@ { "annotations": { "title": "Resources: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -698,7 +678,6 @@ "title": "Resources: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", @@ -743,7 +722,6 @@ "title": "Resources: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index 74a48d56..b95f179c 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -18,7 +18,6 @@ "title": "Configuration: View", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the current Kubernetes configuration content as a kubeconfig YAML", @@ -38,7 +37,6 @@ "title": "Events: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes events in the current cluster from all namespaces", @@ -60,9 +58,7 @@ { "annotations": { "title": "Helm: Install", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Install a Helm chart in the current or provided namespace", @@ -101,7 +97,6 @@ "title": "Helm: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)", @@ -127,7 +122,6 @@ { "annotations": { "title": "Helm: Uninstall", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -160,7 +154,6 @@ "title": "Namespaces: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes namespaces in the current cluster", @@ -180,7 +173,6 @@ "title": "Node: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", @@ -218,7 +210,6 @@ "title": "Node: Stats Summary", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", @@ -272,7 +263,6 @@ { "annotations": { "title": "Pods: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -303,9 +293,7 @@ { "annotations": { "title": "Pods: Exec", - "readOnlyHint": false, "destructiveHint": true, - "idempotentHint": false, "openWorldHint": true }, "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command", @@ -348,7 +336,6 @@ "title": "Pods: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name", @@ -379,7 +366,6 @@ "title": "Pods: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the current cluster from all namespaces", @@ -404,7 +390,6 @@ "title": "Pods: List in Namespace", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the specified namespace in the current cluster", @@ -436,7 +421,6 @@ "title": "Pods: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name", @@ -479,9 +463,7 @@ { "annotations": { "title": "Pods: Run", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name", @@ -556,7 +538,6 @@ { "annotations": { "title": "Resources: Create or Update", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -583,7 +564,6 @@ { "annotations": { "title": "Resources: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -626,7 +606,6 @@ "title": "Resources: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", @@ -667,7 +646,6 @@ "title": "Resources: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index 041c8671..e4488b0a 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -4,7 +4,6 @@ "title": "Configuration: View", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the current Kubernetes configuration content as a kubeconfig YAML", @@ -24,7 +23,6 @@ "title": "Events: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes events in the current cluster from all namespaces", @@ -42,9 +40,7 @@ { "annotations": { "title": "Helm: Install", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Install a Helm chart in the current or provided namespace", @@ -79,7 +75,6 @@ "title": "Helm: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)", @@ -101,7 +96,6 @@ { "annotations": { "title": "Helm: Uninstall", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -130,7 +124,6 @@ "title": "Namespaces: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes namespaces in the current cluster", @@ -144,7 +137,6 @@ "title": "Node: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", @@ -178,7 +170,6 @@ "title": "Node: Stats Summary", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", @@ -224,7 +215,6 @@ { "annotations": { "title": "Pods: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -251,9 +241,7 @@ { "annotations": { "title": "Pods: Exec", - "readOnlyHint": false, "destructiveHint": true, - "idempotentHint": false, "openWorldHint": true }, "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command", @@ -292,7 +280,6 @@ "title": "Pods: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name", @@ -319,7 +306,6 @@ "title": "Pods: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the current cluster from all namespaces", @@ -340,7 +326,6 @@ "title": "Pods: List in Namespace", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the specified namespace in the current cluster", @@ -368,7 +353,6 @@ "title": "Pods: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name", @@ -407,9 +391,7 @@ { "annotations": { "title": "Pods: Run", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name", @@ -478,7 +460,6 @@ "title": "Projects: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the OpenShift projects in the current cluster", @@ -490,7 +471,6 @@ { "annotations": { "title": "Resources: Create or Update", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -513,7 +493,6 @@ { "annotations": { "title": "Resources: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -552,7 +531,6 @@ "title": "Resources: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress, route.openshift.io/v1 Route)", @@ -589,7 +567,6 @@ "title": "Resources: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress, route.openshift.io/v1 Route)", diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index 2f314aec..ca270027 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -4,7 +4,6 @@ "title": "Configuration: View", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the current Kubernetes configuration content as a kubeconfig YAML", @@ -24,7 +23,6 @@ "title": "Events: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes events in the current cluster from all namespaces", @@ -42,9 +40,7 @@ { "annotations": { "title": "Helm: Install", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Install a Helm chart in the current or provided namespace", @@ -79,7 +75,6 @@ "title": "Helm: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)", @@ -101,7 +96,6 @@ { "annotations": { "title": "Helm: Uninstall", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -130,7 +124,6 @@ "title": "Namespaces: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes namespaces in the current cluster", @@ -144,7 +137,6 @@ "title": "Node: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get logs from a Kubernetes node (kubelet, kube-proxy, or other system logs). This accesses node logs through the Kubernetes API proxy to the kubelet", @@ -178,7 +170,6 @@ "title": "Node: Stats Summary", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get detailed resource usage statistics from a Kubernetes node via the kubelet's Summary API. Provides comprehensive metrics including CPU, memory, filesystem, and network usage at the node, pod, and container levels. On systems with cgroup v2 and kernel 4.20+, also includes PSI (Pressure Stall Information) metrics that show resource pressure for CPU, memory, and I/O. See https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/ for details on PSI metrics", @@ -224,7 +215,6 @@ { "annotations": { "title": "Pods: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -251,9 +241,7 @@ { "annotations": { "title": "Pods: Exec", - "readOnlyHint": false, "destructiveHint": true, - "idempotentHint": false, "openWorldHint": true }, "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command", @@ -292,7 +280,6 @@ "title": "Pods: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name", @@ -319,7 +306,6 @@ "title": "Pods: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the current cluster from all namespaces", @@ -340,7 +326,6 @@ "title": "Pods: List in Namespace", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Kubernetes pods in the specified namespace in the current cluster", @@ -368,7 +353,6 @@ "title": "Pods: Log", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name", @@ -407,9 +391,7 @@ { "annotations": { "title": "Pods: Run", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name", @@ -476,7 +458,6 @@ { "annotations": { "title": "Resources: Create or Update", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -499,7 +480,6 @@ { "annotations": { "title": "Resources: Delete", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true @@ -538,7 +518,6 @@ "title": "Resources: Get", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", @@ -575,7 +554,6 @@ "title": "Resources: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)", diff --git a/pkg/mcp/testdata/toolsets-helm-tools.json b/pkg/mcp/testdata/toolsets-helm-tools.json index c57dfc27..6afd3f33 100644 --- a/pkg/mcp/testdata/toolsets-helm-tools.json +++ b/pkg/mcp/testdata/toolsets-helm-tools.json @@ -2,9 +2,7 @@ { "annotations": { "title": "Helm: Install", - "readOnlyHint": false, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "Install a Helm chart in the current or provided namespace", @@ -39,7 +37,6 @@ "title": "Helm: List", "readOnlyHint": true, "destructiveHint": false, - "idempotentHint": false, "openWorldHint": true }, "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)", @@ -61,7 +58,6 @@ { "annotations": { "title": "Helm: Uninstall", - "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true diff --git a/pkg/toolsets/config/configuration.go b/pkg/toolsets/config/configuration.go index 6b6b45d3..ab973da1 100644 --- a/pkg/toolsets/config/configuration.go +++ b/pkg/toolsets/config/configuration.go @@ -51,7 +51,6 @@ func initConfiguration() []api.ServerTool { Title: "Configuration: View", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, diff --git a/pkg/toolsets/core/events.go b/pkg/toolsets/core/events.go index f10ff576..43ae1cc1 100644 --- a/pkg/toolsets/core/events.go +++ b/pkg/toolsets/core/events.go @@ -28,7 +28,6 @@ func initEvents() []api.ServerTool { Title: "Events: List", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: eventsList}, diff --git a/pkg/toolsets/core/namespaces.go b/pkg/toolsets/core/namespaces.go index 71995d8c..2f2ee8fc 100644 --- a/pkg/toolsets/core/namespaces.go +++ b/pkg/toolsets/core/namespaces.go @@ -24,7 +24,6 @@ func initNamespaces(o internalk8s.Openshift) []api.ServerTool { Title: "Namespaces: List", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: namespacesList, @@ -41,7 +40,6 @@ func initNamespaces(o internalk8s.Openshift) []api.ServerTool { Title: "Projects: List", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: projectsList, diff --git a/pkg/toolsets/core/nodes.go b/pkg/toolsets/core/nodes.go index 04798d0d..e42a8a98 100644 --- a/pkg/toolsets/core/nodes.go +++ b/pkg/toolsets/core/nodes.go @@ -45,7 +45,6 @@ func initNodes() []api.ServerTool { Title: "Node: Log", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: nodesLog}, @@ -66,7 +65,6 @@ func initNodes() []api.ServerTool { Title: "Node: Stats Summary", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: nodesStatsSummary}, diff --git a/pkg/toolsets/core/pods.go b/pkg/toolsets/core/pods.go index 8744a974..78781332 100644 --- a/pkg/toolsets/core/pods.go +++ b/pkg/toolsets/core/pods.go @@ -33,7 +33,6 @@ func initPods() []api.ServerTool { Title: "Pods: List", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: podsListInAllNamespaces}, @@ -59,7 +58,6 @@ func initPods() []api.ServerTool { Title: "Pods: List in Namespace", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: podsListInNamespace}, @@ -84,7 +82,6 @@ func initPods() []api.ServerTool { Title: "Pods: Get", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: podsGet}, @@ -107,7 +104,6 @@ func initPods() []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Pods: Delete", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(true), IdempotentHint: ptr.To(true), OpenWorldHint: ptr.To(true), @@ -177,9 +173,7 @@ func initPods() []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Pods: Exec", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(true), // Depending on the Pod's entrypoint, executing certain commands may kill the Pod - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: podsExec}, @@ -218,7 +212,6 @@ func initPods() []api.ServerTool { Title: "Pods: Log", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: podsLog}, @@ -249,9 +242,7 @@ func initPods() []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Pods: Run", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: podsRun}, diff --git a/pkg/toolsets/core/resources.go b/pkg/toolsets/core/resources.go index a3536f56..52a613b3 100644 --- a/pkg/toolsets/core/resources.go +++ b/pkg/toolsets/core/resources.go @@ -51,7 +51,6 @@ func initResources(o internalk8s.Openshift) []api.ServerTool { Title: "Resources: List", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: resourcesList}, @@ -84,7 +83,6 @@ func initResources(o internalk8s.Openshift) []api.ServerTool { Title: "Resources: Get", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: resourcesGet}, @@ -103,7 +101,6 @@ func initResources(o internalk8s.Openshift) []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Resources: Create or Update", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(true), IdempotentHint: ptr.To(true), OpenWorldHint: ptr.To(true), @@ -136,7 +133,6 @@ func initResources(o internalk8s.Openshift) []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Resources: Delete", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(true), IdempotentHint: ptr.To(true), OpenWorldHint: ptr.To(true), diff --git a/pkg/toolsets/helm/helm.go b/pkg/toolsets/helm/helm.go index 0352cf60..646941f1 100644 --- a/pkg/toolsets/helm/helm.go +++ b/pkg/toolsets/helm/helm.go @@ -39,9 +39,8 @@ func initHelm() []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Helm: Install", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), // TODO: consider replacing implementation with equivalent to: helm upgrade --install + IdempotentHint: nil, // TODO: consider replacing implementation with equivalent to: helm upgrade --install OpenWorldHint: ptr.To(true), }, }, Handler: helmInstall}, @@ -65,7 +64,6 @@ func initHelm() []api.ServerTool { Title: "Helm: List", ReadOnlyHint: ptr.To(true), DestructiveHint: ptr.To(false), - IdempotentHint: ptr.To(false), OpenWorldHint: ptr.To(true), }, }, Handler: helmList}, @@ -88,7 +86,6 @@ func initHelm() []api.ServerTool { }, Annotations: api.ToolAnnotations{ Title: "Helm: Uninstall", - ReadOnlyHint: ptr.To(false), DestructiveHint: ptr.To(true), IdempotentHint: ptr.To(true), OpenWorldHint: ptr.To(true), From 6342379505a0726821c5d1420102a8969e99aadb Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Wed, 5 Nov 2025 17:36:17 +0100 Subject: [PATCH 47/57] fix(mcp): do not enable Resource / Prompts yet, as there are none (#442) Signed-off-by: Matthias Wessendorf --- pkg/mcp/mcp.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index f64d4104..5f7511cc 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -73,8 +73,6 @@ type Server struct { func NewServer(configuration Configuration) (*Server, error) { var serverOptions []server.ServerOption serverOptions = append(serverOptions, - server.WithResourceCapabilities(true, true), - server.WithPromptCapabilities(true), server.WithToolCapabilities(true), server.WithLogging(), server.WithToolHandlerMiddleware(toolCallLoggingMiddleware), From 7e9e17612a2f0bb9354bd1f4dfbe9da7299f0fde Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Fri, 7 Nov 2025 11:48:32 +0100 Subject: [PATCH 48/57] test(authorization): improved tests to use real MCP clients (#443) Signed-off-by: Marc Nuri --- internal/test/mcp.go | 12 +- internal/test/mock_server.go | 30 ++ internal/test/test.go | 30 ++ pkg/http/http_authorization_test.go | 472 ++++++++++++++++++ pkg/http/http_mcp_test.go | 67 +++ pkg/http/http_test.go | 542 ++------------------- pkg/kubernetes/provider_kubeconfig_test.go | 22 +- pkg/kubernetes/provider_single_test.go | 23 +- 8 files changed, 660 insertions(+), 538 deletions(-) create mode 100644 pkg/http/http_authorization_test.go create mode 100644 pkg/http/http_mcp_test.go diff --git a/internal/test/mcp.go b/internal/test/mcp.go index 0b411b1d..4ddbe70b 100644 --- a/internal/test/mcp.go +++ b/internal/test/mcp.go @@ -12,6 +12,13 @@ import ( "golang.org/x/net/context" ) +func McpInitRequest() mcp.InitializeRequest { + initRequest := mcp.InitializeRequest{} + initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION + initRequest.Params.ClientInfo = mcp.Implementation{Name: "test", Version: "1.33.7"} + return initRequest +} + type McpClient struct { ctx context.Context testServer *httptest.Server @@ -28,10 +35,7 @@ func NewMcpClient(t *testing.T, mcpHttpServer http.Handler, options ...transport require.NoError(t, err, "Expected no error creating MCP client") err = ret.Start(t.Context()) require.NoError(t, err, "Expected no error starting MCP client") - initRequest := mcp.InitializeRequest{} - initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION - initRequest.Params.ClientInfo = mcp.Implementation{Name: "test", Version: "1.33.7"} - _, err = ret.Initialize(t.Context(), initRequest) + _, err = ret.Initialize(t.Context(), McpInitRequest()) require.NoError(t, err, "Expected no error initializing MCP client") return ret } diff --git a/internal/test/mock_server.go b/internal/test/mock_server.go index 58740ad6..36324a5e 100644 --- a/internal/test/mock_server.go +++ b/internal/test/mock_server.go @@ -216,3 +216,33 @@ func (h *InOpenShiftHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) return } } + +const tokenReviewSuccessful = ` + { + "kind": "TokenReview", + "apiVersion": "authentication.k8s.io/v1", + "spec": {"token": "valid-token"}, + "status": { + "authenticated": true, + "user": { + "username": "test-user", + "groups": ["system:authenticated"] + }, + "audiences": ["the-audience"] + } + }` + +type TokenReviewHandler struct { + TokenReviewed bool +} + +var _ http.Handler = (*TokenReviewHandler)(nil) + +func (h *TokenReviewHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(tokenReviewSuccessful)) + h.TokenReviewed = true + return + } +} diff --git a/internal/test/test.go b/internal/test/test.go index 03491422..c2ccec4e 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -1,9 +1,12 @@ package test import ( + "fmt" + "net" "os" "path/filepath" "runtime" + "time" ) func Must[T any](v T, err error) T { @@ -19,3 +22,30 @@ func ReadFile(path ...string) string { fileBytes := Must(os.ReadFile(filePath)) return string(fileBytes) } + +func RandomPortAddress() (*net.TCPAddr, error) { + ln, err := net.Listen("tcp", "0.0.0.0:0") + if err != nil { + return nil, fmt.Errorf("failed to find random port for HTTP server: %v", err) + } + defer func() { _ = ln.Close() }() + tcpAddr, ok := ln.Addr().(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("failed to cast listener address to TCPAddr") + } + return tcpAddr, nil +} + +func WaitForServer(tcpAddr *net.TCPAddr) error { + var conn *net.TCPConn + var err error + for i := 0; i < 10; i++ { + conn, err = net.DialTCP("tcp", nil, tcpAddr) + if err == nil { + _ = conn.Close() + break + } + time.Sleep(50 * time.Millisecond) + } + return err +} diff --git a/pkg/http/http_authorization_test.go b/pkg/http/http_authorization_test.go new file mode 100644 index 00000000..a8995c45 --- /dev/null +++ b/pkg/http/http_authorization_test.go @@ -0,0 +1,472 @@ +package http + +import ( + "bytes" + "flag" + "fmt" + "net/http" + "strconv" + "strings" + "testing" + "time" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/coreos/go-oidc/v3/oidc/oidctest" + "github.com/mark3labs/mcp-go/client" + "github.com/mark3labs/mcp-go/client/transport" + "github.com/stretchr/testify/suite" + "k8s.io/klog/v2" + "k8s.io/klog/v2/textlogger" +) + +type AuthorizationSuite struct { + BaseHttpSuite + mcpClient *client.Client + klogState klog.State + logBuffer bytes.Buffer +} + +func (s *AuthorizationSuite) SetupTest() { + s.BaseHttpSuite.SetupTest() + + // Capture logs + s.klogState = klog.CaptureState() + flags := flag.NewFlagSet("test", flag.ContinueOnError) + klog.InitFlags(flags) + _ = flags.Set("v", "5") + klog.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(5), textlogger.Output(&s.logBuffer)))) + + // Default Auth settings (overridden in tests as needed) + s.OidcProvider = nil + s.StaticConfig.RequireOAuth = true + s.StaticConfig.ValidateToken = true + s.StaticConfig.OAuthAudience = "" + s.StaticConfig.StsClientId = "" + s.StaticConfig.StsClientSecret = "" + s.StaticConfig.StsAudience = "" + s.StaticConfig.StsScopes = []string{} +} + +func (s *AuthorizationSuite) TearDownTest() { + s.BaseHttpSuite.TearDownTest() + s.klogState.Restore() + + if s.mcpClient != nil { + _ = s.mcpClient.Close() + } +} + +func (s *AuthorizationSuite) StartClient(options ...transport.StreamableHTTPCOption) { + var err error + s.mcpClient, err = client.NewStreamableHttpClient(fmt.Sprintf("http://127.0.0.1:%d/mcp", s.TcpAddr.Port), options...) + s.Require().NoError(err, "Expected no error creating Streamable HTTP MCP client") + err = s.mcpClient.Start(s.T().Context()) + s.Require().NoError(err, "Expected no error starting Streamable HTTP MCP client") +} + +func (s *AuthorizationSuite) HttpGet(authHeader string) *http.Response { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d/mcp", s.TcpAddr.Port), nil) + s.Require().NoError(err, "Failed to create request") + if authHeader != "" { + req.Header.Set("Authorization", authHeader) + } + resp, err := http.DefaultClient.Do(req) + s.Require().NoError(err, "Failed to get protected endpoint") + return resp +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedMissingHeader() { + // Missing Authorization header + s.StartServer() + s.StartClient() + + s.Run("Initialize returns error for MISSING Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Bearer token required") + }) + + s.Run("Protected resource with MISSING Authorization header", func() { + resp := s.HttpGet("") + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for MISSING Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", error="missing_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - missing or invalid bearer token", "Expected log entry for missing or invalid bearer token") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedHeaderIncompatible() { + // Authorization header without Bearer prefix + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Basic YWxhZGRpbjpvcGVuc2VzYW1l", + })) + + s.Run("Initialize returns error for INCOMPATIBLE Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Bearer token required") + }) + + s.Run("Protected resource with INCOMPATIBLE Authorization header", func() { + resp := s.HttpGet("Basic YWxhZGRpbjpvcGVuc2VzYW1l") + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for INCOMPATIBLE Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", error="missing_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - missing or invalid bearer token", "Expected log entry for missing or invalid bearer token") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedHeaderInvalid() { + // Invalid Authorization header + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + strings.ReplaceAll(tokenBasicNotExpired, ".", ".invalid"), + })) + + s.Run("Initialize returns error for INVALID Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Invalid token") + }) + + s.Run("Protected resource with INVALID Authorization header", func() { + resp := s.HttpGet("Bearer " + strings.ReplaceAll(tokenBasicNotExpired, ".", ".invalid")) + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for INVALID Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", error="invalid_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - JWT validation error", "Expected log entry for JWT validation error") + s.Contains(s.logBuffer.String(), "error: failed to parse JWT token: illegal base64 data", "Expected log entry for JWT validation error details") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedHeaderExpired() { + // Expired Authorization Bearer token + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + tokenBasicExpired, + })) + + s.Run("Initialize returns error for EXPIRED Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Invalid token") + }) + + s.Run("Protected resource with EXPIRED Authorization header", func() { + resp := s.HttpGet("Bearer " + tokenBasicExpired) + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for EXPIRED Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", error="invalid_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - JWT validation error", "Expected log entry for JWT validation error") + s.Contains(s.logBuffer.String(), "validation failed, token is expired (exp)", "Expected log entry for JWT validation error details") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedHeaderInvalidAudience() { + // Invalid audience claim Bearer token + s.StaticConfig.OAuthAudience = "expected-audience" + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + tokenBasicNotExpired, + })) + + s.Run("Initialize returns error for INVALID AUDIENCE Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Invalid token") + }) + + s.Run("Protected resource with INVALID AUDIENCE Authorization header", func() { + resp := s.HttpGet("Bearer " + tokenBasicNotExpired) + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for INVALID AUDIENCE Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", audience="expected-audience", error="invalid_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - JWT validation error", "Expected log entry for JWT validation error") + s.Contains(s.logBuffer.String(), "invalid audience claim (aud)", "Expected log entry for JWT validation error details") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedOidcValidation() { + // Failed OIDC validation + s.StaticConfig.OAuthAudience = "mcp-server" + oidcTestServer := NewOidcTestServer(s.T()) + s.T().Cleanup(oidcTestServer.Close) + s.OidcProvider = oidcTestServer.Provider + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + tokenBasicNotExpired, + })) + + s.Run("Initialize returns error for INVALID OIDC Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Invalid token") + }) + + s.Run("Protected resource with INVALID OIDC Authorization header", func() { + resp := s.HttpGet("Bearer " + tokenBasicNotExpired) + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for INVALID OIDC Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", audience="mcp-server", error="invalid_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - JWT validation error", "Expected log entry for JWT validation error") + s.Contains(s.logBuffer.String(), "OIDC token validation error: failed to verify signature", "Expected log entry for OIDC validation error details") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationUnauthorizedKubernetesValidation() { + // Failed Kubernetes TokenReview + s.StaticConfig.OAuthAudience = "mcp-server" + oidcTestServer := NewOidcTestServer(s.T()) + s.T().Cleanup(oidcTestServer.Close) + rawClaims := `{ + "iss": "` + oidcTestServer.URL + `", + "exp": ` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `, + "aud": "mcp-server" + }` + validOidcToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, rawClaims) + s.OidcProvider = oidcTestServer.Provider + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + validOidcToken, + })) + + s.Run("Initialize returns error for INVALID KUBERNETES Authorization header", func() { + _, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().Error(err, "Expected error creating initial request") + s.ErrorContains(err, "transport error: request failed with status 401: Unauthorized: Invalid token") + }) + + s.Run("Protected resource with INVALID KUBERNETES Authorization header", func() { + resp := s.HttpGet("Bearer " + validOidcToken) + s.T().Cleanup(func() { _ = resp.Body.Close }) + + s.Run("returns 401 - Unauthorized status", func() { + s.Equal(401, resp.StatusCode, "Expected HTTP 401 for INVALID KUBERNETES Authorization header") + }) + s.Run("returns WWW-Authenticate header", func() { + authHeader := resp.Header.Get("WWW-Authenticate") + expected := `Bearer realm="Kubernetes MCP Server", audience="mcp-server", error="invalid_token"` + s.Equal(expected, authHeader, "Expected WWW-Authenticate header to match") + }) + s.Run("logs error", func() { + s.Contains(s.logBuffer.String(), "Authentication failed - JWT validation error", "Expected log entry for JWT validation error") + s.Contains(s.logBuffer.String(), "kubernetes API token validation error: failed to create token review", "Expected log entry for Kubernetes TokenReview error details") + }) + }) +} + +func (s *AuthorizationSuite) TestAuthorizationRequireOAuthFalse() { + s.StaticConfig.RequireOAuth = false + s.StartServer() + s.StartClient() + + s.Run("Initialize returns OK for MISSING Authorization header", func() { + result, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().NoError(err, "Expected no error creating initial request") + s.Require().NotNil(result, "Expected initial request to not be nil") + }) +} + +func (s *AuthorizationSuite) TestAuthorizationRawToken() { + tokenReviewHandler := &test.TokenReviewHandler{} + s.MockServer.Handle(tokenReviewHandler) + + cases := []struct { + audience string + validateToken bool + }{ + {"", false}, // No audience, no validation + {"", true}, // No audience, validation enabled + {"mcp-server", false}, // Audience set, no validation + {"mcp-server", true}, // Audience set, validation enabled + } + for _, c := range cases { + s.StaticConfig.OAuthAudience = c.audience + s.StaticConfig.ValidateToken = c.validateToken + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + tokenBasicNotExpired, + })) + tokenReviewHandler.TokenReviewed = false + + s.Run(fmt.Sprintf("Protected resource with audience = '%s' and validate-token = '%t'", c.audience, c.validateToken), func() { + s.Run("Initialize returns OK for VALID Authorization header", func() { + result, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().NoError(err, "Expected no error creating initial request") + s.Require().NotNil(result, "Expected initial request to not be nil") + }) + + s.Run("Performs token validation accordingly", func() { + if tokenReviewHandler.TokenReviewed == true && !c.validateToken { + s.Fail("Expected token review to be skipped when validate-token is false, but it was performed") + } + if tokenReviewHandler.TokenReviewed == false && c.validateToken { + s.Fail("Expected token review to be performed when validate-token is true, but it was skipped") + } + }) + }) + _ = s.mcpClient.Close() + s.StopServer() + } +} + +func (s *AuthorizationSuite) TestAuthorizationOidcToken() { + tokenReviewHandler := &test.TokenReviewHandler{} + s.MockServer.Handle(tokenReviewHandler) + + oidcTestServer := NewOidcTestServer(s.T()) + s.T().Cleanup(oidcTestServer.Close) + rawClaims := `{ + "iss": "` + oidcTestServer.URL + `", + "exp": ` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `, + "aud": "mcp-server" + }` + validOidcToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, rawClaims) + + cases := []bool{false, true} + for _, validateToken := range cases { + s.OidcProvider = oidcTestServer.Provider + s.StaticConfig.OAuthAudience = "mcp-server" + s.StaticConfig.ValidateToken = validateToken + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + validOidcToken, + })) + tokenReviewHandler.TokenReviewed = false + + s.Run(fmt.Sprintf("Protected resource with validate-token = '%t'", validateToken), func() { + s.Run("Initialize returns OK for VALID OIDC Authorization header", func() { + result, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().NoError(err, "Expected no error creating initial request") + s.Require().NotNil(result, "Expected initial request to not be nil") + }) + + s.Run("Performs token validation accordingly for VALID OIDC Authorization header", func() { + if tokenReviewHandler.TokenReviewed == true && !validateToken { + s.Fail("Expected token review to be skipped when validate-token is false, but it was performed") + } + if tokenReviewHandler.TokenReviewed == false && validateToken { + s.Fail("Expected token review to be performed when validate-token is true, but it was skipped") + } + }) + }) + _ = s.mcpClient.Close() + s.StopServer() + } +} + +func (s *AuthorizationSuite) TestAuthorizationOidcTokenExchange() { + tokenReviewHandler := &test.TokenReviewHandler{} + s.MockServer.Handle(tokenReviewHandler) + + oidcTestServer := NewOidcTestServer(s.T()) + s.T().Cleanup(oidcTestServer.Close) + rawClaims := `{ + "iss": "` + oidcTestServer.URL + `", + "exp": ` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `, + "aud": "%s" + }` + validOidcClientToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, + fmt.Sprintf(rawClaims, "mcp-server")) + validOidcBackendToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, + fmt.Sprintf(rawClaims, "backend-audience")) + oidcTestServer.TokenEndpointHandler = func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = fmt.Fprintf(w, `{"access_token":"%s","token_type":"Bearer","expires_in":253402297199}`, validOidcBackendToken) + } + + cases := []bool{false, true} + for _, validateToken := range cases { + s.OidcProvider = oidcTestServer.Provider + s.StaticConfig.OAuthAudience = "mcp-server" + s.StaticConfig.ValidateToken = validateToken + s.StaticConfig.StsClientId = "test-sts-client-id" + s.StaticConfig.StsClientSecret = "test-sts-client-secret" + s.StaticConfig.StsAudience = "backend-audience" + s.StaticConfig.StsScopes = []string{"backend-scope"} + s.StartServer() + s.StartClient(transport.WithHTTPHeaders(map[string]string{ + "Authorization": "Bearer " + validOidcClientToken, + })) + tokenReviewHandler.TokenReviewed = false + + s.Run(fmt.Sprintf("Protected resource with validate-token='%t'", validateToken), func() { + s.Run("Initialize returns OK for VALID OIDC EXCHANGE Authorization header", func() { + result, err := s.mcpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().NoError(err, "Expected no error creating initial request") + s.Require().NotNil(result, "Expected initial request to not be nil") + }) + + s.Run("Performs token validation accordingly for VALID OIDC EXCHANGE Authorization header", func() { + if tokenReviewHandler.TokenReviewed == true && !validateToken { + s.Fail("Expected token review to be skipped when validate-token is false, but it was performed") + } + if tokenReviewHandler.TokenReviewed == false && validateToken { + s.Fail("Expected token review to be performed when validate-token is true, but it was skipped") + } + }) + }) + _ = s.mcpClient.Close() + s.StopServer() + } +} + +func TestAuthorization(t *testing.T) { + suite.Run(t, new(AuthorizationSuite)) +} diff --git a/pkg/http/http_mcp_test.go b/pkg/http/http_mcp_test.go new file mode 100644 index 00000000..2a79b4be --- /dev/null +++ b/pkg/http/http_mcp_test.go @@ -0,0 +1,67 @@ +package http + +import ( + "fmt" + "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/mark3labs/mcp-go/client" + "github.com/mark3labs/mcp-go/client/transport" + "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" +) + +type McpTransportSuite struct { + BaseHttpSuite +} + +func (s *McpTransportSuite) SetupTest() { + s.BaseHttpSuite.SetupTest() + s.StartServer() +} + +func (s *McpTransportSuite) TearDownTest() { + s.BaseHttpSuite.TearDownTest() +} + +func (s *McpTransportSuite) TestSseTransport() { + sseClient, sseClientErr := client.NewSSEMCPClient(fmt.Sprintf("http://127.0.0.1:%d/sse", s.TcpAddr.Port)) + s.Require().NoError(sseClientErr, "Expected no error creating SSE MCP client") + startErr := sseClient.Start(s.T().Context()) + s.Require().NoError(startErr, "Expected no error starting SSE MCP client") + s.Run("Can Initialize Session", func() { + _, err := sseClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().NoError(err, "Expected no error initializing SSE MCP client") + }) + s.Run("Can List Tools", func() { + tools, err := sseClient.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NoError(err, "Expected no error listing tools from SSE MCP client") + s.Greater(len(tools.Tools), 0, "Expected at least one tool from SSE MCP client") + }) + s.Run("Can close SSE client", func() { + s.Require().NoError(sseClient.Close(), "Expected no error closing SSE MCP client") + }) +} + +func (s *McpTransportSuite) TestStreamableHttpTransport() { + httpClient, httpClientErr := client.NewStreamableHttpClient(fmt.Sprintf("http://127.0.0.1:%d/mcp", s.TcpAddr.Port), transport.WithContinuousListening()) + s.Require().NoError(httpClientErr, "Expected no error creating Streamable HTTP MCP client") + startErr := httpClient.Start(s.T().Context()) + s.Require().NoError(startErr, "Expected no error starting Streamable HTTP MCP client") + s.Run("Can Initialize Session", func() { + _, err := httpClient.Initialize(s.T().Context(), test.McpInitRequest()) + s.Require().NoError(err, "Expected no error initializing Streamable HTTP MCP client") + }) + s.Run("Can List Tools", func() { + tools, err := httpClient.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NoError(err, "Expected no error listing tools from Streamable HTTP MCP client") + s.Greater(len(tools.Tools), 0, "Expected at least one tool from Streamable HTTP MCP client") + }) + s.Run("Can close Streamable HTTP client", func() { + s.Require().NoError(httpClient.Close(), "Expected no error closing Streamable HTTP MCP client") + }) +} + +func TestMcpTransport(t *testing.T) { + suite.Run(t, new(McpTransportSuite)) +} diff --git a/pkg/http/http_test.go b/pkg/http/http_test.go index ab531813..64c3355e 100644 --- a/pkg/http/http_test.go +++ b/pkg/http/http_test.go @@ -1,7 +1,6 @@ package http import ( - "bufio" "bytes" "context" "crypto/rand" @@ -22,6 +21,7 @@ import ( "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc/oidctest" + "github.com/stretchr/testify/suite" "golang.org/x/sync/errgroup" "k8s.io/klog/v2" "k8s.io/klog/v2/textlogger" @@ -30,6 +30,53 @@ import ( "github.com/containers/kubernetes-mcp-server/pkg/mcp" ) +type BaseHttpSuite struct { + suite.Suite + MockServer *test.MockServer + TcpAddr *net.TCPAddr + StaticConfig *config.StaticConfig + mcpServer *mcp.Server + OidcProvider *oidc.Provider + timeoutCancel context.CancelFunc + StopServer context.CancelFunc + WaitForShutdown func() error +} + +func (s *BaseHttpSuite) SetupTest() { + var err error + http.DefaultClient.Timeout = 10 * time.Second + s.MockServer = test.NewMockServer() + s.TcpAddr, err = test.RandomPortAddress() + s.Require().NoError(err, "Expected no error getting random port address") + s.StaticConfig = config.Default() + s.StaticConfig.KubeConfig = s.MockServer.KubeconfigFile(s.T()) + s.StaticConfig.Port = strconv.Itoa(s.TcpAddr.Port) +} + +func (s *BaseHttpSuite) StartServer() { + var err error + s.mcpServer, err = mcp.NewServer(mcp.Configuration{StaticConfig: s.StaticConfig}) + s.Require().NoError(err, "Expected no error creating MCP server") + s.Require().NotNil(s.mcpServer, "MCP server should not be nil") + var timeoutCtx, cancelCtx context.Context + timeoutCtx, s.timeoutCancel = context.WithTimeout(s.T().Context(), 10*time.Second) + group, gc := errgroup.WithContext(timeoutCtx) + cancelCtx, s.StopServer = context.WithCancel(gc) + group.Go(func() error { return Serve(cancelCtx, s.mcpServer, s.StaticConfig, s.OidcProvider, nil) }) + s.WaitForShutdown = group.Wait + s.Require().NoError(test.WaitForServer(s.TcpAddr), "HTTP server did not start in time") +} + +func (s *BaseHttpSuite) TearDownTest() { + s.MockServer.Close() + if s.mcpServer != nil { + s.mcpServer.Close() + } + s.StopServer() + s.Require().NoError(s.WaitForShutdown(), "HTTP server did not shut down gracefully") + s.timeoutCancel() +} + type httpContext struct { klogState klog.State mockServer *test.MockServer @@ -42,20 +89,6 @@ type httpContext struct { OidcProvider *oidc.Provider } -const tokenReviewSuccessful = ` - { - "kind": "TokenReview", - "apiVersion": "authentication.k8s.io/v1", - "spec": {"token": "valid-token"}, - "status": { - "authenticated": true, - "user": { - "username": "test-user", - "groups": ["system:authenticated"] - } - } - }` - func (c *httpContext) beforeEach(t *testing.T) { t.Helper() http.DefaultClient.Timeout = 10 * time.Second @@ -192,92 +225,6 @@ func TestGracefulShutdown(t *testing.T) { }) } -func TestSseTransport(t *testing.T) { - testCase(t, func(ctx *httpContext) { - sseResp, sseErr := http.Get(fmt.Sprintf("http://%s/sse", ctx.HttpAddress)) - t.Cleanup(func() { _ = sseResp.Body.Close() }) - t.Run("Exposes SSE endpoint at /sse", func(t *testing.T) { - if sseErr != nil { - t.Fatalf("Failed to get SSE endpoint: %v", sseErr) - } - if sseResp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", sseResp.StatusCode) - } - }) - t.Run("SSE endpoint returns text/event-stream content type", func(t *testing.T) { - if sseResp.Header.Get("Content-Type") != "text/event-stream" { - t.Errorf("Expected Content-Type text/event-stream, got %s", sseResp.Header.Get("Content-Type")) - } - }) - responseReader := bufio.NewReader(sseResp.Body) - event, eventErr := responseReader.ReadString('\n') - endpoint, endpointErr := responseReader.ReadString('\n') - t.Run("SSE endpoint returns stream with messages endpoint", func(t *testing.T) { - if eventErr != nil { - t.Fatalf("Failed to read SSE response body (event): %v", eventErr) - } - if event != "event: endpoint\n" { - t.Errorf("Expected SSE event 'endpoint', got %s", event) - } - if endpointErr != nil { - t.Fatalf("Failed to read SSE response body (endpoint): %v", endpointErr) - } - if !strings.HasPrefix(endpoint, "data: /message?sessionId=") { - t.Errorf("Expected SSE data: '/message', got %s", endpoint) - } - }) - messageResp, messageErr := http.Post( - fmt.Sprintf("http://%s/message?sessionId=%s", ctx.HttpAddress, strings.TrimSpace(endpoint[25:])), - "application/json", - bytes.NewBufferString("{}"), - ) - t.Cleanup(func() { _ = messageResp.Body.Close() }) - t.Run("Exposes message endpoint at /message", func(t *testing.T) { - if messageErr != nil { - t.Fatalf("Failed to get message endpoint: %v", messageErr) - } - if messageResp.StatusCode != http.StatusAccepted { - t.Errorf("Expected HTTP 202 OK, got %d", messageResp.StatusCode) - } - }) - }) -} - -func TestStreamableHttpTransport(t *testing.T) { - testCase(t, func(ctx *httpContext) { - mcpGetResp, mcpGetErr := http.Get(fmt.Sprintf("http://%s/mcp", ctx.HttpAddress)) - t.Cleanup(func() { _ = mcpGetResp.Body.Close() }) - t.Run("Exposes MCP GET endpoint at /mcp", func(t *testing.T) { - if mcpGetErr != nil { - t.Fatalf("Failed to get MCP endpoint: %v", mcpGetErr) - } - if mcpGetResp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", mcpGetResp.StatusCode) - } - }) - t.Run("MCP GET endpoint returns text/event-stream content type", func(t *testing.T) { - if mcpGetResp.Header.Get("Content-Type") != "text/event-stream" { - t.Errorf("Expected Content-Type text/event-stream (GET), got %s", mcpGetResp.Header.Get("Content-Type")) - } - }) - mcpPostResp, mcpPostErr := http.Post(fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), "application/json", bytes.NewBufferString("{}")) - t.Cleanup(func() { _ = mcpPostResp.Body.Close() }) - t.Run("Exposes MCP POST endpoint at /mcp", func(t *testing.T) { - if mcpPostErr != nil { - t.Fatalf("Failed to post to MCP endpoint: %v", mcpPostErr) - } - if mcpPostResp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", mcpPostResp.StatusCode) - } - }) - t.Run("MCP POST endpoint returns application/json content type", func(t *testing.T) { - if mcpPostResp.Header.Get("Content-Type") != "application/json" { - t.Errorf("Expected Content-Type application/json (POST), got %s", mcpPostResp.Header.Get("Content-Type")) - } - }) - }) -} - func TestHealthCheck(t *testing.T) { testCase(t, func(ctx *httpContext) { t.Run("Exposes health check endpoint at /healthz", func(t *testing.T) { @@ -616,396 +563,3 @@ func TestMiddlewareLogging(t *testing.T) { }) }) } - -func TestAuthorizationUnauthorized(t *testing.T) { - // Missing Authorization header - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - resp, err := http.Get(fmt.Sprintf("http://%s/mcp", ctx.HttpAddress)) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with MISSING Authorization header returns 401 - Unauthorized", func(t *testing.T) { - if resp.StatusCode != 401 { - t.Errorf("Expected HTTP 401, got %d", resp.StatusCode) - } - }) - t.Run("Protected resource with MISSING Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", error="missing_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with MISSING Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - missing or invalid bearer token") { - t.Errorf("Expected log entry for missing or invalid bearer token, got: %s", ctx.LogBuffer.String()) - } - }) - }) - // Authorization header without Bearer prefix - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l") - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with INCOMPATIBLE Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", error="missing_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with INCOMPATIBLE Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - missing or invalid bearer token") { - t.Errorf("Expected log entry for missing or invalid bearer token, got: %s", ctx.LogBuffer.String()) - } - }) - }) - // Invalid Authorization header - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+strings.ReplaceAll(tokenBasicNotExpired, ".", ".invalid")) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with INVALID Authorization header returns 401 - Unauthorized", func(t *testing.T) { - if resp.StatusCode != 401 { - t.Errorf("Expected HTTP 401, got %d", resp.StatusCode) - } - }) - t.Run("Protected resource with INVALID Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", error="invalid_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with INVALID Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - JWT validation error") || - !strings.Contains(ctx.LogBuffer.String(), "error: failed to parse JWT token: illegal base64 data") { - t.Errorf("Expected log entry for JWT validation error, got: %s", ctx.LogBuffer.String()) - } - }) - }) - // Expired Authorization Bearer token - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+tokenBasicExpired) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with EXPIRED Authorization header returns 401 - Unauthorized", func(t *testing.T) { - if resp.StatusCode != 401 { - t.Errorf("Expected HTTP 401, got %d", resp.StatusCode) - } - }) - t.Run("Protected resource with EXPIRED Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", error="invalid_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with EXPIRED Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - JWT validation error") || - !strings.Contains(ctx.LogBuffer.String(), "validation failed, token is expired (exp)") { - t.Errorf("Expected log entry for JWT validation error, got: %s", ctx.LogBuffer.String()) - } - }) - }) - // Invalid audience claim Bearer token - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "expected-audience", ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+tokenBasicExpired) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with INVALID AUDIENCE Authorization header returns 401 - Unauthorized", func(t *testing.T) { - if resp.StatusCode != 401 { - t.Errorf("Expected HTTP 401, got %d", resp.StatusCode) - } - }) - t.Run("Protected resource with INVALID AUDIENCE Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", audience="expected-audience", error="invalid_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with INVALID AUDIENCE Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - JWT validation error") || - !strings.Contains(ctx.LogBuffer.String(), "invalid audience claim (aud)") { - t.Errorf("Expected log entry for JWT validation error, got: %s", ctx.LogBuffer.String()) - } - }) - }) - // Failed OIDC validation - oidcTestServer := NewOidcTestServer(t) - t.Cleanup(oidcTestServer.Close) - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) { - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+tokenBasicNotExpired) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with INVALID OIDC Authorization header returns 401 - Unauthorized", func(t *testing.T) { - if resp.StatusCode != 401 { - t.Errorf("Expected HTTP 401, got %d", resp.StatusCode) - } - }) - t.Run("Protected resource with INVALID OIDC Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", audience="mcp-server", error="invalid_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with INVALID OIDC Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - JWT validation error") || - !strings.Contains(ctx.LogBuffer.String(), "OIDC token validation error: failed to verify signature") { - t.Errorf("Expected log entry for OIDC validation error, got: %s", ctx.LogBuffer.String()) - } - }) - }) - // Failed Kubernetes TokenReview - rawClaims := `{ - "iss": "` + oidcTestServer.URL + `", - "exp": ` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `, - "aud": "mcp-server" - }` - validOidcToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, rawClaims) - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) { - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+validOidcToken) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close }) - t.Run("Protected resource with INVALID KUBERNETES Authorization header returns 401 - Unauthorized", func(t *testing.T) { - if resp.StatusCode != 401 { - t.Errorf("Expected HTTP 401, got %d", resp.StatusCode) - } - }) - t.Run("Protected resource with INVALID KUBERNETES Authorization header returns WWW-Authenticate header", func(t *testing.T) { - authHeader := resp.Header.Get("WWW-Authenticate") - expected := `Bearer realm="Kubernetes MCP Server", audience="mcp-server", error="invalid_token"` - if authHeader != expected { - t.Errorf("Expected WWW-Authenticate header to be %q, got %q", expected, authHeader) - } - }) - t.Run("Protected resource with INVALID KUBERNETES Authorization header logs error", func(t *testing.T) { - if !strings.Contains(ctx.LogBuffer.String(), "Authentication failed - JWT validation error") || - !strings.Contains(ctx.LogBuffer.String(), "kubernetes API token validation error: failed to create token review") { - t.Errorf("Expected log entry for Kubernetes TokenReview error, got: %s", ctx.LogBuffer.String()) - } - }) - }) -} - -func TestAuthorizationRequireOAuthFalse(t *testing.T) { - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: false, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - resp, err := http.Get(fmt.Sprintf("http://%s/mcp", ctx.HttpAddress)) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close() }) - t.Run("Protected resource with MISSING Authorization header returns 200 - OK)", func(t *testing.T) { - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", resp.StatusCode) - } - }) - }) -} - -func TestAuthorizationRawToken(t *testing.T) { - cases := []struct { - audience string - validateToken bool - }{ - {"", false}, // No audience, no validation - {"", true}, // No audience, validation enabled - {"mcp-server", false}, // Audience set, no validation - {"mcp-server", true}, // Audience set, validation enabled - } - for _, c := range cases { - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: c.audience, ValidateToken: c.validateToken, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) { - tokenReviewed := false - ctx.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(tokenReviewSuccessful)) - tokenReviewed = true - return - } - })) - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+tokenBasicNotExpired) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close() }) - t.Run(fmt.Sprintf("Protected resource with audience = '%s' and validate-token = '%t', with VALID Authorization header returns 200 - OK", c.audience, c.validateToken), func(t *testing.T) { - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", resp.StatusCode) - } - }) - t.Run(fmt.Sprintf("Protected resource with audience = '%s' and validate-token = '%t', with VALID Authorization header performs token validation accordingly", c.audience, c.validateToken), func(t *testing.T) { - if tokenReviewed == true && !c.validateToken { - t.Errorf("Expected token review to be skipped when validate-token is false, but it was performed") - } - if tokenReviewed == false && c.validateToken { - t.Errorf("Expected token review to be performed when validate-token is true, but it was skipped") - } - }) - }) - } - -} - -func TestAuthorizationOidcToken(t *testing.T) { - oidcTestServer := NewOidcTestServer(t) - t.Cleanup(oidcTestServer.Close) - rawClaims := `{ - "iss": "` + oidcTestServer.URL + `", - "exp": ` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `, - "aud": "mcp-server" - }` - validOidcToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, rawClaims) - cases := []bool{false, true} - for _, validateToken := range cases { - testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: validateToken, ClusterProviderStrategy: config.ClusterProviderKubeConfig}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) { - tokenReviewed := false - ctx.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(tokenReviewSuccessful)) - tokenReviewed = true - return - } - })) - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+validOidcToken) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close() }) - t.Run(fmt.Sprintf("Protected resource with validate-token='%t' with VALID OIDC Authorization header returns 200 - OK", validateToken), func(t *testing.T) { - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", resp.StatusCode) - } - }) - t.Run(fmt.Sprintf("Protected resource with validate-token='%t' with VALID OIDC Authorization header performs token validation accordingly", validateToken), func(t *testing.T) { - if tokenReviewed == true && !validateToken { - t.Errorf("Expected token review to be skipped when validate-token is false, but it was performed") - } - if tokenReviewed == false && validateToken { - t.Errorf("Expected token review to be performed when validate-token is true, but it was skipped") - } - }) - }) - } -} - -func TestAuthorizationOidcTokenExchange(t *testing.T) { - oidcTestServer := NewOidcTestServer(t) - t.Cleanup(oidcTestServer.Close) - rawClaims := `{ - "iss": "` + oidcTestServer.URL + `", - "exp": ` + strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10) + `, - "aud": "%s" - }` - validOidcClientToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, - fmt.Sprintf(rawClaims, "mcp-server")) - validOidcBackendToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, - fmt.Sprintf(rawClaims, "backend-audience")) - oidcTestServer.TokenEndpointHandler = func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - _, _ = fmt.Fprintf(w, `{"access_token":"%s","token_type":"Bearer","expires_in":253402297199}`, validOidcBackendToken) - } - cases := []bool{false, true} - for _, validateToken := range cases { - staticConfig := &config.StaticConfig{ - RequireOAuth: true, - OAuthAudience: "mcp-server", - ValidateToken: validateToken, - StsClientId: "test-sts-client-id", - StsClientSecret: "test-sts-client-secret", - StsAudience: "backend-audience", - StsScopes: []string{"backend-scope"}, - ClusterProviderStrategy: config.ClusterProviderKubeConfig, - } - testCaseWithContext(t, &httpContext{StaticConfig: staticConfig, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) { - tokenReviewed := false - ctx.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(tokenReviewSuccessful)) - tokenReviewed = true - return - } - })) - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - req.Header.Set("Authorization", "Bearer "+validOidcClientToken) - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to get protected endpoint: %v", err) - } - t.Cleanup(func() { _ = resp.Body.Close() }) - t.Run(fmt.Sprintf("Protected resource with validate-token='%t' with VALID OIDC EXCHANGE Authorization header returns 200 - OK", validateToken), func(t *testing.T) { - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected HTTP 200 OK, got %d", resp.StatusCode) - } - }) - t.Run(fmt.Sprintf("Protected resource with validate-token='%t' with VALID OIDC EXCHANGE Authorization header performs token validation accordingly", validateToken), func(t *testing.T) { - if tokenReviewed == true && !validateToken { - t.Errorf("Expected token review to be skipped when validate-token is false, but it was performed") - } - if tokenReviewed == false && validateToken { - t.Errorf("Expected token review to be performed when validate-token is true, but it was skipped") - } - }) - }) - } -} diff --git a/pkg/kubernetes/provider_kubeconfig_test.go b/pkg/kubernetes/provider_kubeconfig_test.go index 17984990..33ba60d6 100644 --- a/pkg/kubernetes/provider_kubeconfig_test.go +++ b/pkg/kubernetes/provider_kubeconfig_test.go @@ -2,7 +2,6 @@ package kubernetes import ( "fmt" - "net/http" "testing" "github.com/containers/kubernetes-mcp-server/internal/test" @@ -57,25 +56,8 @@ func (s *ProviderKubeconfigTestSuite) TestWithOpenShiftCluster() { } func (s *ProviderKubeconfigTestSuite) TestVerifyToken() { - s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(` - { - "kind": "TokenReview", - "apiVersion": "authentication.k8s.io/v1", - "spec": {"token": "the-token"}, - "status": { - "authenticated": true, - "user": { - "username": "test-user", - "groups": ["system:authenticated"] - }, - "audiences": ["the-audience"] - } - }`)) - } - })) + s.mockServer.Handle(&test.TokenReviewHandler{}) + s.Run("VerifyToken returns UserInfo for non-empty context", func() { userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "fake-context", "some-token", "the-audience") s.Require().NoError(err, "Expected no error from VerifyToken with empty target") diff --git a/pkg/kubernetes/provider_single_test.go b/pkg/kubernetes/provider_single_test.go index ff03e26c..150926b4 100644 --- a/pkg/kubernetes/provider_single_test.go +++ b/pkg/kubernetes/provider_single_test.go @@ -1,7 +1,6 @@ package kubernetes import ( - "net/http" "testing" "github.com/containers/kubernetes-mcp-server/internal/test" @@ -50,6 +49,7 @@ func (s *ProviderSingleTestSuite) TestWithNonOpenShiftCluster() { func (s *ProviderSingleTestSuite) TestWithOpenShiftCluster() { s.mockServer.Handle(&test.InOpenShiftHandler{}) + s.Run("IsOpenShift returns true", func() { inOpenShift := s.provider.IsOpenShift(s.T().Context()) s.True(inOpenShift, "Expected InOpenShift to return true") @@ -57,25 +57,8 @@ func (s *ProviderSingleTestSuite) TestWithOpenShiftCluster() { } func (s *ProviderSingleTestSuite) TestVerifyToken() { - s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(` - { - "kind": "TokenReview", - "apiVersion": "authentication.k8s.io/v1", - "spec": {"token": "the-token"}, - "status": { - "authenticated": true, - "user": { - "username": "test-user", - "groups": ["system:authenticated"] - }, - "audiences": ["the-audience"] - } - }`)) - } - })) + s.mockServer.Handle(&test.TokenReviewHandler{}) + s.Run("VerifyToken returns UserInfo for empty target (default target)", func() { userInfo, audiences, err := s.provider.VerifyToken(s.T().Context(), "", "the-token", "the-audience") s.Require().NoError(err, "Expected no error from VerifyToken with empty target") From 9b1c814367da2530ab9fbd57565f4218551e7e0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 07:12:46 +0100 Subject: [PATCH 49/57] build(deps): bump golang.org/x/sync from 0.17.0 to 0.18.0 (#446) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.17.0 to 0.18.0. - [Commits](https://github.com/golang/sync/compare/v0.17.0...v0.18.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-version: 0.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3a74120d..398ec17a 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 golang.org/x/net v0.46.0 golang.org/x/oauth2 v0.32.0 - golang.org/x/sync v0.17.0 + golang.org/x/sync v0.18.0 helm.sh/helm/v3 v3.19.0 k8s.io/api v0.34.1 k8s.io/apiextensions-apiserver v0.34.1 diff --git a/go.sum b/go.sum index 7a5186b8..70aa2b51 100644 --- a/go.sum +++ b/go.sum @@ -374,8 +374,8 @@ golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 545b9462c3813eaf518f381b1d842d288ac311fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 07:37:01 +0100 Subject: [PATCH 50/57] build(deps): bump golang.org/x/oauth2 from 0.32.0 to 0.33.0 (#447) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.32.0 to 0.33.0. - [Commits](https://github.com/golang/oauth2/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 398ec17a..4bdaa5c5 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.46.0 - golang.org/x/oauth2 v0.32.0 + golang.org/x/oauth2 v0.33.0 golang.org/x/sync v0.18.0 helm.sh/helm/v3 v3.19.0 k8s.io/api v0.34.1 diff --git a/go.sum b/go.sum index 70aa2b51..21def988 100644 --- a/go.sum +++ b/go.sum @@ -369,8 +369,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 86ed1ba99982e91cc8f27c15110bed65ee10fc35 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 11 Nov 2025 15:04:36 +0100 Subject: [PATCH 51/57] chore(ci): restrict publish jobs to specific repository (#448) --- .github/workflows/release-image.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml index d11dbf0d..903951b8 100644 --- a/.github/workflows/release-image.yml +++ b/.github/workflows/release-image.yml @@ -14,6 +14,7 @@ env: jobs: publish-platform-images: name: 'Publish: linux-${{ matrix.platform.tag }}' + if: github.repository == 'containers/kubernetes-mcp-server' strategy: fail-fast: true matrix: @@ -47,6 +48,7 @@ jobs: publish-manifest: name: Publish Manifest + if: github.repository == 'containers/kubernetes-mcp-server' runs-on: ubuntu-latest needs: publish-platform-images steps: From 24fd9bc1bd2658f13e7f3e11dc43e8ecfbac9fb4 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 11 Nov 2025 15:37:12 +0100 Subject: [PATCH 52/57] chore(ci): update macOS build (Amd64/x86-64) target from 13 to 15 (#450) https://github.com/actions/runner-images/issues/13046 Signed-off-by: Marc Nuri --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 01271f9c..2d31d19c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -36,7 +36,7 @@ jobs: - ubuntu-latest #x64 - ubuntu-24.04-arm #arm64 - windows-latest #x64 - - macos-13 #x64 + - macos-15-intel #x64 - macos-latest #arm64 runs-on: ${{ matrix.os }} steps: From ae6eced946a1776c0488b945c1be33cf6a8de01e Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 11 Nov 2025 19:29:36 +0100 Subject: [PATCH 53/57] test(mcp): add tests for WatchKubeConfig tools reload (#449) Verifies that tools are added and **removed** when kubeconfig file changes. Signed-off-by: Marc Nuri --- internal/test/mock_server.go | 4 ++ pkg/mcp/mcp_test.go | 48 ---------------- pkg/mcp/mcp_watch_test.go | 103 +++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 pkg/mcp/mcp_watch_test.go diff --git a/internal/test/mock_server.go b/internal/test/mock_server.go index 36324a5e..e256f425 100644 --- a/internal/test/mock_server.go +++ b/internal/test/mock_server.go @@ -59,6 +59,10 @@ func (m *MockServer) Handle(handler http.Handler) { m.restHandlers = append(m.restHandlers, handler.ServeHTTP) } +func (m *MockServer) ResetHandlers() { + m.restHandlers = make([]http.HandlerFunc, 0) +} + func (m *MockServer) Config() *rest.Config { return m.config } diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index 9dca88e4..25e1c651 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -1,62 +1,14 @@ package mcp import ( - "context" "net/http" - "os" - "runtime" "testing" - "time" "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/mark3labs/mcp-go/client/transport" - "github.com/mark3labs/mcp-go/mcp" "github.com/stretchr/testify/suite" ) -type WatchKubeConfigSuite struct { - BaseMcpSuite -} - -func (s *WatchKubeConfigSuite) SetupTest() { - s.BaseMcpSuite.SetupTest() - kubeconfig := test.KubeConfigFake() - s.Cfg.KubeConfig = test.KubeconfigFile(s.T(), kubeconfig) -} - -func (s *WatchKubeConfigSuite) TestNotifiesToolsChange() { - if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { - s.T().Skip("Skipping test on non-Unix-like platforms") - } - // Given - s.InitMcpClient() - withTimeout, cancel := context.WithTimeout(s.T().Context(), 5*time.Second) - defer cancel() - var notification *mcp.JSONRPCNotification - s.OnNotification(func(n mcp.JSONRPCNotification) { - notification = &n - }) - // When - f, _ := os.OpenFile(s.Cfg.KubeConfig, os.O_APPEND|os.O_WRONLY, 0644) - _, _ = f.WriteString("\n") - _ = f.Close() - for notification == nil { - select { - case <-withTimeout.Done(): - s.FailNow("timeout waiting for WatchKubeConfig notification") - default: - time.Sleep(100 * time.Millisecond) - } - } - // Then - s.NotNil(notification, "WatchKubeConfig did not notify") - s.Equal("notifications/tools/list_changed", notification.Method, "WatchKubeConfig did not notify tools change") -} - -func TestWatchKubeConfig(t *testing.T) { - suite.Run(t, new(WatchKubeConfigSuite)) -} - type McpHeadersSuite struct { BaseMcpSuite mockServer *test.MockServer diff --git a/pkg/mcp/mcp_watch_test.go b/pkg/mcp/mcp_watch_test.go new file mode 100644 index 00000000..68287279 --- /dev/null +++ b/pkg/mcp/mcp_watch_test.go @@ -0,0 +1,103 @@ +package mcp + +import ( + "context" + "os" + "testing" + "time" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/suite" +) + +type WatchKubeConfigSuite struct { + BaseMcpSuite + mockServer *test.MockServer +} + +func (s *WatchKubeConfigSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) +} + +func (s *WatchKubeConfigSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *WatchKubeConfigSuite) WriteKubeconfig() { + f, _ := os.OpenFile(s.Cfg.KubeConfig, os.O_APPEND|os.O_WRONLY, 0644) + _, _ = f.WriteString("\n") + _ = f.Close() +} + +// WaitForNotification waits for an MCP server notification or fails the test after a timeout +func (s *WatchKubeConfigSuite) WaitForNotification() *mcp.JSONRPCNotification { + withTimeout, cancel := context.WithTimeout(s.T().Context(), 5*time.Second) + defer cancel() + var notification *mcp.JSONRPCNotification + s.OnNotification(func(n mcp.JSONRPCNotification) { + notification = &n + }) + for notification == nil { + select { + case <-withTimeout.Done(): + s.FailNow("timeout waiting for WatchKubeConfig notification") + default: + time.Sleep(100 * time.Millisecond) + } + } + return notification +} + +func (s *WatchKubeConfigSuite) TestNotifiesToolsChange() { + // Given + s.InitMcpClient() + // When + s.WriteKubeconfig() + notification := s.WaitForNotification() + // Then + s.NotNil(notification, "WatchKubeConfig did not notify") + s.Equal("notifications/tools/list_changed", notification.Method, "WatchKubeConfig did not notify tools change") +} + +func (s *WatchKubeConfigSuite) TestClearsNoLongerAvailableTools() { + s.mockServer.Handle(&test.InOpenShiftHandler{}) + s.InitMcpClient() + + s.Run("OpenShift tool is available", func() { + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NoError(err, "call ListTools failed") + s.Require().NotNil(tools, "list tools failed") + var found bool + for _, tool := range tools.Tools { + if tool.Name == "projects_list" { + found = true + break + } + } + s.Truef(found, "expected OpenShift tool to be available") + }) + + s.Run("OpenShift tool is removed after kubeconfig change", func() { + // Reload Config without OpenShift + s.mockServer.ResetHandlers() + s.WriteKubeconfig() + s.WaitForNotification() + + tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{}) + s.Require().NoError(err, "call ListTools failed") + s.Require().NotNil(tools, "list tools failed") + for _, tool := range tools.Tools { + s.Require().Falsef(tool.Name == "projects_list", "expected OpenShift tool to be removed") + } + }) +} + +func TestWatchKubeConfig(t *testing.T) { + suite.Run(t, new(WatchKubeConfigSuite)) +} From 87039f3ab3fc990f11fcc51dd5338abe1d2f619a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 06:32:04 +0100 Subject: [PATCH 54/57] build(deps): bump helm.sh/helm/v3 from 3.19.0 to 3.19.1 (#451) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.19.0 to 3.19.1. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.19.0...v3.19.1) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-version: 3.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 4bdaa5c5..a3c2fdde 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/net v0.46.0 golang.org/x/oauth2 v0.33.0 golang.org/x/sync v0.18.0 - helm.sh/helm/v3 v3.19.0 + helm.sh/helm/v3 v3.19.1 k8s.io/api v0.34.1 k8s.io/apiextensions-apiserver v0.34.1 k8s.io/apimachinery v0.34.1 @@ -47,11 +47,11 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect - github.com/containerd/containerd v1.7.28 // indirect + github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect diff --git a/go.sum b/go.sum index 21def988..38162eea 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= -github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -55,8 +55,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -425,8 +425,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= -helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= +helm.sh/helm/v3 v3.19.1 h1:QVMzHbanyurO8oynx0drDOfG02XxSvrHqaFrf9yrMf0= +helm.sh/helm/v3 v3.19.1/go.mod h1:gX10tB5ErM+8fr7bglUUS/UfTOO8UUTYWIBH1IYNnpE= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= From 3a56319f0e74ab83753d2c42321e0c5b478341ee Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 12 Nov 2025 06:49:35 +0100 Subject: [PATCH 55/57] fix: use proper context import (#454) Signed-off-by: Marc Nuri --- go.mod | 2 +- internal/test/mcp.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a3c2fdde..07c0b678 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.46.0 golang.org/x/oauth2 v0.33.0 golang.org/x/sync v0.18.0 helm.sh/helm/v3 v3.19.1 @@ -123,6 +122,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.46.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/term v0.36.0 // indirect golang.org/x/text v0.30.0 // indirect diff --git a/internal/test/mcp.go b/internal/test/mcp.go index 4ddbe70b..174fe4eb 100644 --- a/internal/test/mcp.go +++ b/internal/test/mcp.go @@ -1,6 +1,7 @@ package test import ( + "context" "net/http" "net/http/httptest" "testing" @@ -9,7 +10,6 @@ import ( "github.com/mark3labs/mcp-go/client/transport" "github.com/mark3labs/mcp-go/mcp" "github.com/stretchr/testify/require" - "golang.org/x/net/context" ) func McpInitRequest() mcp.InitializeRequest { From 70b24cc36e9ef7d654b47a5f81c205231e866497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 06:58:54 +0100 Subject: [PATCH 56/57] build(deps): bump golang.org/x/net from 0.46.0 to 0.47.0 (#452) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.46.0 to 0.47.0. - [Commits](https://github.com/golang/net/compare/v0.46.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 07c0b678..eb404df0 100644 --- a/go.mod +++ b/go.mod @@ -121,11 +121,11 @@ require ( github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/term v0.36.0 // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect google.golang.org/grpc v1.72.1 // indirect diff --git a/go.sum b/go.sum index 38162eea..83314406 100644 --- a/go.sum +++ b/go.sum @@ -357,18 +357,18 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -382,22 +382,22 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 220bca1206deca4de9b7d43024617b1555db5bb6 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 12 Nov 2025 10:17:05 +0100 Subject: [PATCH 57/57] chore(deps): update Go version from 1.24.1 to 1.24.10 (#455) https://issues.redhat.com/browse/OLS-2259 Signed-off-by: Marc Nuri --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index eb404df0..efb19dc0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/containers/kubernetes-mcp-server -go 1.24.1 +go 1.24.10 require ( github.com/BurntSushi/toml v1.5.0

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