Skip to content

Commit f2ba0c6

Browse files
authored
Merge pull request #92 from dhruvthak3r/feat/add_configmap
Feat/add configmap
2 parents 26e6459 + 7c5677e commit f2ba0c6

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

src/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
describeDeployment,
6868
describeDeploymentSchema,
6969
} from "./tools/describe_deployment.js";
70+
import {createConfigMap, CreateConfigMapSchema } from "./tools/create_configmap.js";
7071

7172
const k8sManager = new KubernetesManager();
7273

@@ -112,6 +113,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
112113
StopPortForwardSchema,
113114
scaleDeploymentSchema,
114115
DeleteCronJobSchema,
116+
CreateConfigMapSchema,
115117
],
116118
};
117119
});
@@ -449,6 +451,17 @@ server.setRequestHandler(
449451
);
450452
}
451453

454+
case "create_configmap" : {
455+
return await createConfigMap(
456+
k8sManager,
457+
input as {
458+
name : string,
459+
namespace : string,
460+
data : Record<string, string>
461+
}
462+
);
463+
}
464+
452465
default:
453466
throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
454467
}

src/models/response-schemas.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,12 @@ export const DeleteCronJobResponseSchema = z.object({
108108
})
109109
),
110110
});
111+
112+
export const CreateConfigMapResponseSchema = z.object({
113+
content: z.array(
114+
z.object({
115+
success: z.boolean(),
116+
message: z.string(),
117+
})
118+
),
119+
});

src/tools/create_configmap.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { KubernetesManager } from "../types.js";
2+
import * as k8s from "@kubernetes/client-node";
3+
export const CreateConfigMapSchema = {
4+
name : "create_configmap",
5+
description : "Create a new Kubernetes ConfigMap",
6+
inputSchema : {
7+
type : "object",
8+
properties : {
9+
name : { type : "string" },
10+
namespace : { type : "string" },
11+
data : {
12+
type : "object",
13+
ConfigData : { type : "string" },
14+
},
15+
},
16+
required : ["name", "namespace", "data"],
17+
},
18+
};
19+
20+
export async function createConfigMap(
21+
k8sManager : KubernetesManager,
22+
input : {
23+
name : string;
24+
namespace : string;
25+
data : Record<string, string>;
26+
}
27+
): Promise<{ content: { success: boolean; message: string}[] }> {
28+
try {
29+
const configmap : k8s.V1ConfigMap = {
30+
apiVersion : "v1",
31+
kind : "ConfigMap",
32+
binaryData : undefined,
33+
data : input.data,
34+
immutable : false,
35+
metadata : {
36+
name : input.name,
37+
namespace : input.namespace,
38+
labels : {
39+
"mcp-managed" : "true",
40+
app : input.name,
41+
},
42+
},
43+
}
44+
const response = await k8sManager.getCoreApi().createNamespacedConfigMap(input.namespace, configmap);
45+
if(response.response?.statusCode !== undefined && (response.response.statusCode == 200 || response.response.statusCode == 201 || response.response.statusCode == 202)) {
46+
return {
47+
content : [
48+
{
49+
success : true,
50+
message : `Created ConfigMap ${response.body.metadata?.name} in namespace ${response.body.metadata?.namespace}`,
51+
}
52+
]
53+
}
54+
}
55+
else {
56+
return {
57+
content : [
58+
{
59+
success : false,
60+
message : `Failed to create ConfigMap ${response.body.metadata?.name} in namespace ${response.body.metadata?.namespace}`,
61+
}
62+
]
63+
}
64+
}
65+
} catch (error : any) {
66+
return {
67+
content : [
68+
{
69+
success : false,
70+
message : `Failed to create ConfigMap ${input.name} in namespace ${input.namespace}. Error: ${error.message}`,
71+
}
72+
]
73+
};
74+
}
75+
}

tests/configmap.test.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { expect, test, describe, beforeEach, afterEach } from "vitest";
2+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4+
import { CreateNamespaceResponseSchema } from "../src/types";
5+
import {CreateConfigMapResponseSchema} from "../src/models/response-schemas.js";
6+
import { KubernetesManager } from "../src/types";
7+
async function sleep(ms: number): Promise<void> {
8+
return new Promise((resolve) => setTimeout(resolve, ms));
9+
}
10+
11+
function generateRandomId(): string {
12+
return Math.random().toString(36).substring(2, 10);
13+
}
14+
15+
function generateRandomSHA(): string {
16+
return Math.random().toString(36).substring(2, 15);
17+
}
18+
19+
20+
describe("test kubernetes configmap",()=> {
21+
let transport: StdioClientTransport;
22+
let client: Client;
23+
const NAMESPACE_PREFIX = "test-configmap";
24+
let testNamespace : string;
25+
26+
const testName = `test-configmap-${generateRandomSHA()}`
27+
28+
beforeEach(async () =>{
29+
try {
30+
transport = new StdioClientTransport({
31+
command: "bun",
32+
args: ["src/index.ts"],
33+
stderr: "pipe",
34+
});
35+
36+
client = new Client(
37+
{
38+
name: "test-client",
39+
version: "1.0.0",
40+
},
41+
{
42+
capabilities: {},
43+
}
44+
);
45+
46+
await client.connect(transport);
47+
// Wait for connection to be established
48+
await sleep(1000);
49+
50+
// Create a unique test namespace for test isolation
51+
testNamespace = `${NAMESPACE_PREFIX}-${generateRandomId()}`;
52+
console.log(`Creating test namespace: ${testNamespace}`);
53+
54+
await client.request(
55+
{
56+
method: "tools/call",
57+
params: {
58+
name: "create_namespace",
59+
arguments: {
60+
name: testNamespace,
61+
},
62+
},
63+
},
64+
CreateNamespaceResponseSchema
65+
);
66+
67+
// Wait for namespace to be fully created
68+
await sleep(2000);
69+
} catch (error : any) {
70+
console.error("Error in beforeEach:", error);
71+
throw error;
72+
}
73+
});
74+
75+
afterEach(async () => {
76+
try {
77+
// Clean up namespace using direct API call
78+
console.log(`Cleaning up test namespace: ${testNamespace}`);
79+
const k8sManager = new KubernetesManager();
80+
await k8sManager.getCoreApi().deleteNamespace(testNamespace);
81+
82+
// Close client connection
83+
await transport.close();
84+
await sleep(1000);
85+
} catch (e) {
86+
console.error("Error during cleanup:", e);
87+
}
88+
});
89+
90+
test("verify creation of configmap",async () =>{
91+
const testdata = {
92+
key1: "hello",
93+
key2: "world",
94+
};
95+
const configmap_response = client.request(
96+
{
97+
method : "tools/call",
98+
params : {
99+
name : "create_configmap",
100+
arguments : {
101+
name : testName,
102+
namespace : testNamespace,
103+
data : testdata,
104+
},
105+
},
106+
},
107+
CreateConfigMapResponseSchema,
108+
);
109+
110+
await sleep(2000);
111+
console.log((await configmap_response).content[0]);
112+
expect((await configmap_response).content[0].success).toBe(true);
113+
expect((await configmap_response).content[0].message).toContain(`Created ConfigMap ${testName} in namespace ${testNamespace}`);
114+
115+
116+
}
117+
);
118+
});

0 commit comments

Comments
 (0)