Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions internal/test/kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package test

import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

func KubeConfigFake() *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()
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"
return fakeConfig
}
52 changes: 52 additions & 0 deletions internal/test/mcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package test

import (
"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"
)

type McpClient struct {
ctx context.Context
testServer *httptest.Server
*client.Client
}

func NewMcpClient(t *testing.T, mcpHttpServer *server.StreamableHTTPServer) *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")
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)
require.NoError(t, err, "Expected no error initializing MCP client")
return ret
}

func (m *McpClient) Close() {
if m.Client != nil {
_ = m.Client.Close()
}
if m.testServer != nil {
m.testServer.Close()
}
}

// CallTool helper function to call a tool by name with arguments
func (m *McpClient) CallTool(name string, args map[string]interface{}) (*mcp.CallToolResult, error) {
callToolRequest := mcp.CallToolRequest{}
callToolRequest.Params.Name = name
callToolRequest.Params.Arguments = args
return m.Client.CallTool(m.ctx, callToolRequest)
}
60 changes: 51 additions & 9 deletions internal/test/mock_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import (
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)

Expand Down Expand Up @@ -46,7 +50,9 @@ func NewMockServer() *MockServer {
}

func (m *MockServer) Close() {
m.server.Close()
if m.server != nil {
m.server.Close()
}
}

func (m *MockServer) Handle(handler http.Handler) {
Expand All @@ -57,21 +63,22 @@ func (m *MockServer) Config() *rest.Config {
return m.config
}

func (m *MockServer) KubeConfig() *api.Config {
fakeConfig := api.NewConfig()
fakeConfig.Clusters["fake"] = api.NewCluster()
func (m *MockServer) Kubeconfig() *api.Config {
fakeConfig := KubeConfigFake()
fakeConfig.Clusters["fake"].Server = m.config.Host
fakeConfig.Clusters["fake"].CertificateAuthorityData = m.config.CAData
fakeConfig.AuthInfos["fake"] = api.NewAuthInfo()
fakeConfig.AuthInfos["fake"].ClientKeyData = m.config.KeyData
fakeConfig.AuthInfos["fake"].ClientCertificateData = m.config.CertData
fakeConfig.Contexts["fake-context"] = api.NewContext()
fakeConfig.Contexts["fake-context"].Cluster = "fake"
fakeConfig.Contexts["fake-context"].AuthInfo = "fake"
fakeConfig.CurrentContext = "fake-context"
return fakeConfig
}

func (m *MockServer) KubeconfigFile(t *testing.T) string {
kubeconfig := filepath.Join(t.TempDir(), "config")
err := clientcmd.WriteToFile(*m.Kubeconfig(), kubeconfig)
require.NoError(t, err, "Expected no error writing kubeconfig file")
return kubeconfig
}

func WriteObject(w http.ResponseWriter, obj runtime.Object) {
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
if err := json.NewEncoder(w).Encode(obj); err != nil {
Expand Down Expand Up @@ -170,3 +177,38 @@ WaitForStreams:

return ctx, nil
}

type InOpenShiftHandler struct {
}

var _ http.Handler = (*InOpenShiftHandler)(nil)

func (h *InOpenShiftHandler) ServeHTTP(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",
"groups":[{
"name":"project.openshift.io",
"versions":[{"groupVersion":"project.openshift.io/v1","version":"v1"}],
"preferredVersion":{"groupVersion":"project.openshift.io/v1","version":"v1"}
}]}`))
return
}
if req.URL.Path == "/apis/project.openshift.io/v1" {
_, _ = w.Write([]byte(`{
"kind":"APIResourceList",
"apiVersion":"v1",
"groupVersion":"project.openshift.io/v1",
"resources":[
{"name":"projects","singularName":"","namespaced":false,"kind":"Project","verbs":["create","delete","get","list","patch","update","watch"],"shortNames":["pr"]}
]}`))
return
}
}
7 changes: 1 addition & 6 deletions pkg/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand All @@ -24,7 +23,6 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"github.com/coreos/go-oidc/v3/oidc/oidctest"
"golang.org/x/sync/errgroup"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
"k8s.io/klog/v2/textlogger"

Expand Down Expand Up @@ -66,10 +64,7 @@ func (c *httpContext) beforeEach(t *testing.T) {
}
c.mockServer = test.NewMockServer()
// Fake Kubernetes configuration
mockKubeConfig := c.mockServer.KubeConfig()
kubeConfig := filepath.Join(t.TempDir(), "config")
_ = clientcmd.WriteToFile(*mockKubeConfig, kubeConfig)
c.StaticConfig.KubeConfig = kubeConfig
c.StaticConfig.KubeConfig = c.mockServer.KubeconfigFile(t)
// Capture logging
c.klogState = klog.CaptureState()
flags := flag.NewFlagSet("test", flag.ContinueOnError)
Expand Down
7 changes: 3 additions & 4 deletions pkg/mcp/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"sigs.k8s.io/controller-runtime/tools/setup-envtest/versions"
"sigs.k8s.io/controller-runtime/tools/setup-envtest/workflows"

"github.com/containers/kubernetes-mcp-server/internal/test"
"github.com/containers/kubernetes-mcp-server/pkg/config"
"github.com/containers/kubernetes-mcp-server/pkg/output"
)
Expand Down Expand Up @@ -82,11 +83,9 @@ func TestMain(m *testing.M) {
BinaryAssetsDirectory: filepath.Join(envTestDir, "k8s", versionDir),
}
adminSystemMasterBaseConfig, _ := envTest.Start()
au, err := envTest.AddUser(envTestUser, adminSystemMasterBaseConfig)
if err != nil {
panic(err)
}
au := test.Must(envTest.AddUser(envTestUser, adminSystemMasterBaseConfig))
envTestRestConfig = au.Config()
envTest.KubeConfig = test.Must(au.KubeConfig())

//Create test data as administrator
ctx := context.Background()
Expand Down
22 changes: 22 additions & 0 deletions pkg/mcp/testdata/toolsets-config-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"annotations": {
"title": "Configuration: View",
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true
},
"description": "Get the current Kubernetes configuration content as a kubeconfig YAML",
"inputSchema": {
"type": "object",
"properties": {
"minified": {
"description": "Return a minified version of the configuration. If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. If set to false, all contexts, clusters, auth-infos, and users are returned in the configuration. (Optional, default true)",
"type": "boolean"
}
}
},
"name": "configuration_view"
}
]
Loading
Loading