Skip to content

Commit df901ca

Browse files
committed
test: pods_exec tests executed from mcp client
1 parent 8dc7160 commit df901ca

File tree

7 files changed

+132
-80
lines changed

7 files changed

+132
-80
lines changed

pkg/kubernetes/kubernetes.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ type Kubernetes struct {
3636
CloseWatchKubeConfig CloseWatchKubeConfig
3737
scheme *runtime.Scheme
3838
parameterCodec runtime.ParameterCodec
39-
restClient rest.Interface
4039
clientSet kubernetes.Interface
4140
discoveryClient *discovery.DiscoveryClient
4241
deferredDiscoveryRESTMapper *restmapper.DeferredDiscoveryRESTMapper
@@ -48,11 +47,7 @@ func NewKubernetes() (*Kubernetes, error) {
4847
if err != nil {
4948
return nil, err
5049
}
51-
restClient, err := rest.HTTPClientFor(cfg)
52-
if err != nil {
53-
return nil, err
54-
}
55-
clientSet, err := kubernetes.NewForConfigAndClient(cfg, restClient)
50+
clientSet, err := kubernetes.NewForConfig(cfg)
5651
if err != nil {
5752
return nil, err
5853
}

pkg/kubernetes/pods.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ func (k *Kubernetes) PodsExec(ctx context.Context, namespace, name, container st
214214
func (k *Kubernetes) createExecutor(namespace, name string, podExecOptions *v1.PodExecOptions) (remotecommand.Executor, error) {
215215
// Compute URL
216216
// https://github.com/kubernetes/kubectl/blob/5366de04e168bcbc11f5e340d131a9ca8b7d0df4/pkg/cmd/exec/exec.go#L382-L397
217-
req := k.restClient.
217+
req := k.clientSet.CoreV1().RESTClient().
218218
Post().
219219
Resource("pods").
220220
Namespace(namespace).

pkg/kubernetes/pods_test.go

-53
This file was deleted.

pkg/mcp/mcp.go

+42
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,45 @@ func NewTextResult(content string, err error) *mcp.CallToolResult {
8686
},
8787
}
8888
}
89+
90+
//
91+
////go:embed resources/*
92+
//var resources embed.FS
93+
//
94+
//func (s *Server) addResources() error {
95+
// s.server.AddResource(mcp.NewResource("file://kubernetes-apiVersion-kind.csv",
96+
// "kubernetes-apiVersion-kind.csv",
97+
// mcp.WithResourceDescription("List of the most common Kubernetes apiVersion and kind in CSV format"),
98+
// mcp.WithMIMEType("text/csv"),
99+
// ), s.handleReadResource)
100+
// s.server.AddTool(mcp.NewTool("common_apiVersion_kind",
101+
// mcp.WithDescription("Lists the most common Kubernetes apiVersion and kind resources in CSV format"),
102+
// ), s.handleToolReadResource)
103+
// return nil
104+
//}
105+
//
106+
//func (s *Server) handleReadResource(_ context.Context, request mcp.ReadResourceRequest) ([]interface{}, error) {
107+
// f, err := resources.Open(path.Join("resources", strings.Replace(request.Params.URI, "file://", "", 1)))
108+
// if err != nil {
109+
// return nil, err
110+
// }
111+
// defer f.Close()
112+
// b, err := io.ReadAll(f)
113+
// return []interface{}{mcp.TextResourceContents{
114+
// ResourceContents: mcp.ResourceContents{
115+
// URI: request.Params.URI,
116+
// MIMEType: "text/csv",
117+
// },
118+
// Text: string(b),
119+
// }}, nil
120+
//}
121+
//
122+
//func (s *Server) handleToolReadResource(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
123+
// f, err := resources.Open(path.Join("resources", "kubernetes-apiVersion-kind.csv"))
124+
// if err != nil {
125+
// return nil, err
126+
// }
127+
// defer f.Close()
128+
// b, err := io.ReadAll(f)
129+
// return NewTextResult(string(b), err), nil
130+
//}

pkg/kubernetes/mock_server_test.go pkg/mcp/mock_server_test.go

+4-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kubernetes
1+
package mcp
22

33
import (
44
"encoding/json"
@@ -10,26 +10,21 @@ import (
1010
"k8s.io/apimachinery/pkg/runtime/serializer"
1111
"k8s.io/apimachinery/pkg/util/httpstream"
1212
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
13-
"k8s.io/client-go/kubernetes"
1413
"k8s.io/client-go/rest"
1514
"net/http"
1615
"net/http/httptest"
1716
)
1817

1918
type MockServer struct {
20-
server *httptest.Server
21-
config *rest.Config
22-
restClient *rest.RESTClient
23-
restHandlers []http.HandlerFunc
24-
clientSet kubernetes.Interface
25-
parameterCodec runtime.ParameterCodec
19+
server *httptest.Server
20+
config *rest.Config
21+
restHandlers []http.HandlerFunc
2622
}
2723

2824
func NewMockServer() *MockServer {
2925
ms := &MockServer{}
3026
scheme := runtime.NewScheme()
3127
codecs := serializer.NewCodecFactory(scheme)
32-
ms.parameterCodec = runtime.NewParameterCodec(scheme)
3328
ms.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
3429
for _, handler := range ms.restHandlers {
3530
handler(w, req)
@@ -44,9 +39,7 @@ func NewMockServer() *MockServer {
4439
GroupVersion: &v1.SchemeGroupVersion,
4540
},
4641
}
47-
ms.restClient, _ = rest.RESTClientFor(ms.config)
4842
ms.restHandlers = make([]http.HandlerFunc, 0)
49-
ms.clientSet = kubernetes.NewForConfigOrDie(ms.config)
5043
return ms
5144
}
5245

@@ -58,15 +51,6 @@ func (m *MockServer) Handle(handler http.Handler) {
5851
m.restHandlers = append(m.restHandlers, handler.ServeHTTP)
5952
}
6053

61-
func (m *MockServer) NewKubernetes() *Kubernetes {
62-
return &Kubernetes{
63-
cfg: m.config,
64-
restClient: m.restClient,
65-
clientSet: m.clientSet,
66-
parameterCodec: m.parameterCodec,
67-
}
68-
}
69-
7054
func writeObject(w http.ResponseWriter, obj runtime.Object) {
7155
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
7256
if err := json.NewEncoder(w).Encode(obj); err != nil {

pkg/mcp/pods_exec_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package mcp
2+
3+
import (
4+
"bytes"
5+
"github.com/mark3labs/mcp-go/mcp"
6+
"io"
7+
v1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"net/http"
10+
"strings"
11+
"testing"
12+
)
13+
14+
func TestPodsExec(t *testing.T) {
15+
testCase(t, func(c *mcpContext) {
16+
mockServer := NewMockServer()
17+
defer mockServer.Close()
18+
c.withKubeConfig(mockServer.config)
19+
mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
20+
if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec/exec" {
21+
return
22+
}
23+
var stdin, stdout bytes.Buffer
24+
ctx, err := createHTTPStreams(w, req, &StreamOptions{
25+
Stdin: &stdin,
26+
Stdout: &stdout,
27+
})
28+
if err != nil {
29+
w.WriteHeader(http.StatusInternalServerError)
30+
_, _ = w.Write([]byte(err.Error()))
31+
return
32+
}
33+
defer ctx.conn.Close()
34+
_, _ = io.WriteString(ctx.stdoutStream, strings.Join(req.URL.Query()["command"], " "))
35+
_, _ = io.WriteString(ctx.stdoutStream, "\ntotal 0\n")
36+
}))
37+
mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
38+
if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec" {
39+
return
40+
}
41+
writeObject(w, &v1.Pod{
42+
ObjectMeta: metav1.ObjectMeta{
43+
Namespace: "default",
44+
Name: "pod-to-exec",
45+
},
46+
Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container-to-exec"}}},
47+
})
48+
}))
49+
toolResult, err := c.callTool("pods_exec", map[string]interface{}{
50+
"namespace": "default",
51+
"name": "pod-to-exec",
52+
"command": []interface{}{"ls", "-l"},
53+
})
54+
t.Run("pods_exec returns command output", func(t *testing.T) {
55+
if err != nil {
56+
t.Fatalf("call tool failed %v", err)
57+
}
58+
if toolResult.IsError {
59+
t.Fatalf("call tool failed")
60+
}
61+
if toolResult.Content[0].(mcp.TextContent).Text != "ls -l\ntotal 0\n" {
62+
t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
63+
}
64+
})
65+
66+
})
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion,kind
2+
v1,ConfigMap
3+
v1,Endpoints
4+
v1,Event
5+
v1,LimitRange
6+
v1,Namespace
7+
v1,Node
8+
v1,PersistentVolume
9+
v1,PersistentVolumeClaim
10+
v1,Pod
11+
v1,ReplicationController
12+
v1,Secret
13+
v1,Service
14+
v1,ServiceAccount
15+
apps/v1,Deployment
16+
apps/v1,StatefulSet
17+
route.openshift.io/v1,Route

0 commit comments

Comments
 (0)