kubectl-mcp-server 1.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kubectl_mcp_server-1.12.0.dist-info/METADATA +711 -0
- kubectl_mcp_server-1.12.0.dist-info/RECORD +45 -0
- kubectl_mcp_server-1.12.0.dist-info/WHEEL +5 -0
- kubectl_mcp_server-1.12.0.dist-info/entry_points.txt +3 -0
- kubectl_mcp_server-1.12.0.dist-info/licenses/LICENSE +21 -0
- kubectl_mcp_server-1.12.0.dist-info/top_level.txt +2 -0
- kubectl_mcp_tool/__init__.py +21 -0
- kubectl_mcp_tool/__main__.py +46 -0
- kubectl_mcp_tool/auth/__init__.py +13 -0
- kubectl_mcp_tool/auth/config.py +71 -0
- kubectl_mcp_tool/auth/scopes.py +148 -0
- kubectl_mcp_tool/auth/verifier.py +82 -0
- kubectl_mcp_tool/cli/__init__.py +9 -0
- kubectl_mcp_tool/cli/__main__.py +10 -0
- kubectl_mcp_tool/cli/cli.py +111 -0
- kubectl_mcp_tool/diagnostics.py +355 -0
- kubectl_mcp_tool/k8s_config.py +289 -0
- kubectl_mcp_tool/mcp_server.py +530 -0
- kubectl_mcp_tool/prompts/__init__.py +5 -0
- kubectl_mcp_tool/prompts/prompts.py +823 -0
- kubectl_mcp_tool/resources/__init__.py +5 -0
- kubectl_mcp_tool/resources/resources.py +305 -0
- kubectl_mcp_tool/tools/__init__.py +28 -0
- kubectl_mcp_tool/tools/browser.py +371 -0
- kubectl_mcp_tool/tools/cluster.py +315 -0
- kubectl_mcp_tool/tools/core.py +421 -0
- kubectl_mcp_tool/tools/cost.py +680 -0
- kubectl_mcp_tool/tools/deployments.py +381 -0
- kubectl_mcp_tool/tools/diagnostics.py +174 -0
- kubectl_mcp_tool/tools/helm.py +1561 -0
- kubectl_mcp_tool/tools/networking.py +296 -0
- kubectl_mcp_tool/tools/operations.py +501 -0
- kubectl_mcp_tool/tools/pods.py +582 -0
- kubectl_mcp_tool/tools/security.py +333 -0
- kubectl_mcp_tool/tools/storage.py +133 -0
- kubectl_mcp_tool/utils/__init__.py +17 -0
- kubectl_mcp_tool/utils/helpers.py +80 -0
- tests/__init__.py +9 -0
- tests/conftest.py +379 -0
- tests/test_auth.py +256 -0
- tests/test_browser.py +349 -0
- tests/test_prompts.py +536 -0
- tests/test_resources.py +343 -0
- tests/test_server.py +384 -0
- tests/test_tools.py +659 -0
|
@@ -0,0 +1,1561 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from mcp.types import ToolAnnotations
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("mcp-server")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_helm_tools(
|
|
15
|
+
server,
|
|
16
|
+
non_destructive: bool,
|
|
17
|
+
check_helm_fn: Callable[[], bool]
|
|
18
|
+
):
|
|
19
|
+
"""Register all Helm-related tools with the MCP server.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
server: FastMCP server instance
|
|
23
|
+
non_destructive: If True, block destructive operations
|
|
24
|
+
check_helm_fn: Function to check if Helm is available
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@server.tool(
|
|
28
|
+
annotations=ToolAnnotations(
|
|
29
|
+
title="Install Helm Chart",
|
|
30
|
+
destructiveHint=True,
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
def install_helm_chart(
|
|
34
|
+
name: str,
|
|
35
|
+
chart: str,
|
|
36
|
+
namespace: str,
|
|
37
|
+
repo: Optional[str] = None,
|
|
38
|
+
values: Optional[dict] = None
|
|
39
|
+
) -> Dict[str, Any]:
|
|
40
|
+
"""Install a Helm chart."""
|
|
41
|
+
if non_destructive:
|
|
42
|
+
return {"success": False, "error": "Blocked: non-destructive mode"}
|
|
43
|
+
if not check_helm_fn():
|
|
44
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
if repo:
|
|
48
|
+
try:
|
|
49
|
+
repo_parts = repo.split('=')
|
|
50
|
+
if len(repo_parts) != 2:
|
|
51
|
+
return {"success": False, "error": "Repository format should be 'repo_name=repo_url'"}
|
|
52
|
+
|
|
53
|
+
repo_name, repo_url = repo_parts
|
|
54
|
+
repo_add_cmd = ["helm", "repo", "add", repo_name, repo_url]
|
|
55
|
+
logger.debug(f"Running command: {' '.join(repo_add_cmd)}")
|
|
56
|
+
subprocess.check_output(repo_add_cmd, stderr=subprocess.PIPE, text=True)
|
|
57
|
+
|
|
58
|
+
repo_update_cmd = ["helm", "repo", "update"]
|
|
59
|
+
logger.debug(f"Running command: {' '.join(repo_update_cmd)}")
|
|
60
|
+
subprocess.check_output(repo_update_cmd, stderr=subprocess.PIPE, text=True)
|
|
61
|
+
|
|
62
|
+
if '/' not in chart:
|
|
63
|
+
chart = f"{repo_name}/{chart}"
|
|
64
|
+
except subprocess.CalledProcessError as e:
|
|
65
|
+
logger.error(f"Error adding Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}")
|
|
66
|
+
return {"success": False, "error": f"Failed to add Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}"}
|
|
67
|
+
|
|
68
|
+
cmd = ["helm", "install", name, chart, "-n", namespace]
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
ns_cmd = ["kubectl", "get", "namespace", namespace]
|
|
72
|
+
subprocess.check_output(ns_cmd, stderr=subprocess.PIPE, text=True)
|
|
73
|
+
except subprocess.CalledProcessError:
|
|
74
|
+
logger.info(f"Namespace {namespace} not found, creating it")
|
|
75
|
+
create_ns_cmd = ["kubectl", "create", "namespace", namespace]
|
|
76
|
+
try:
|
|
77
|
+
subprocess.check_output(create_ns_cmd, stderr=subprocess.PIPE, text=True)
|
|
78
|
+
except subprocess.CalledProcessError as e:
|
|
79
|
+
logger.error(f"Error creating namespace: {e.stderr if hasattr(e, 'stderr') else str(e)}")
|
|
80
|
+
return {"success": False, "error": f"Failed to create namespace: {e.stderr if hasattr(e, 'stderr') else str(e)}"}
|
|
81
|
+
|
|
82
|
+
values_file = None
|
|
83
|
+
try:
|
|
84
|
+
if values:
|
|
85
|
+
with tempfile.NamedTemporaryFile("w", delete=False) as f:
|
|
86
|
+
yaml.dump(values, f)
|
|
87
|
+
values_file = f.name
|
|
88
|
+
cmd += ["-f", values_file]
|
|
89
|
+
|
|
90
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
91
|
+
result = subprocess.check_output(cmd, stderr=subprocess.PIPE, text=True)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"success": True,
|
|
95
|
+
"message": f"Helm chart {chart} installed as {name} in {namespace}",
|
|
96
|
+
"details": result
|
|
97
|
+
}
|
|
98
|
+
except subprocess.CalledProcessError as e:
|
|
99
|
+
error_msg = e.stderr if hasattr(e, 'stderr') else str(e)
|
|
100
|
+
logger.error(f"Error installing Helm chart: {error_msg}")
|
|
101
|
+
return {"success": False, "error": f"Failed to install Helm chart: {error_msg}"}
|
|
102
|
+
finally:
|
|
103
|
+
if values_file and os.path.exists(values_file):
|
|
104
|
+
os.unlink(values_file)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Unexpected error installing Helm chart: {str(e)}")
|
|
107
|
+
return {"success": False, "error": f"Unexpected error: {str(e)}"}
|
|
108
|
+
|
|
109
|
+
@server.tool(
|
|
110
|
+
annotations=ToolAnnotations(
|
|
111
|
+
title="Upgrade Helm Chart",
|
|
112
|
+
destructiveHint=True,
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
def upgrade_helm_chart(
|
|
116
|
+
name: str,
|
|
117
|
+
chart: str,
|
|
118
|
+
namespace: str,
|
|
119
|
+
repo: Optional[str] = None,
|
|
120
|
+
values: Optional[dict] = None
|
|
121
|
+
) -> Dict[str, Any]:
|
|
122
|
+
"""Upgrade a Helm release."""
|
|
123
|
+
if non_destructive:
|
|
124
|
+
return {"success": False, "error": "Blocked: non-destructive mode"}
|
|
125
|
+
if not check_helm_fn():
|
|
126
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
if repo:
|
|
130
|
+
try:
|
|
131
|
+
repo_parts = repo.split('=')
|
|
132
|
+
if len(repo_parts) != 2:
|
|
133
|
+
return {"success": False, "error": "Repository format should be 'repo_name=repo_url'"}
|
|
134
|
+
|
|
135
|
+
repo_name, repo_url = repo_parts
|
|
136
|
+
repo_add_cmd = ["helm", "repo", "add", repo_name, repo_url]
|
|
137
|
+
logger.debug(f"Running command: {' '.join(repo_add_cmd)}")
|
|
138
|
+
subprocess.check_output(repo_add_cmd, stderr=subprocess.PIPE, text=True)
|
|
139
|
+
|
|
140
|
+
repo_update_cmd = ["helm", "repo", "update"]
|
|
141
|
+
logger.debug(f"Running command: {' '.join(repo_update_cmd)}")
|
|
142
|
+
subprocess.check_output(repo_update_cmd, stderr=subprocess.PIPE, text=True)
|
|
143
|
+
|
|
144
|
+
if '/' not in chart:
|
|
145
|
+
chart = f"{repo_name}/{chart}"
|
|
146
|
+
except subprocess.CalledProcessError as e:
|
|
147
|
+
logger.error(f"Error adding Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}")
|
|
148
|
+
return {"success": False, "error": f"Failed to add Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}"}
|
|
149
|
+
|
|
150
|
+
cmd = ["helm", "upgrade", name, chart, "-n", namespace]
|
|
151
|
+
|
|
152
|
+
values_file = None
|
|
153
|
+
try:
|
|
154
|
+
if values:
|
|
155
|
+
with tempfile.NamedTemporaryFile("w", delete=False) as f:
|
|
156
|
+
yaml.dump(values, f)
|
|
157
|
+
values_file = f.name
|
|
158
|
+
cmd += ["-f", values_file]
|
|
159
|
+
|
|
160
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
161
|
+
result = subprocess.check_output(cmd, stderr=subprocess.PIPE, text=True)
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"success": True,
|
|
165
|
+
"message": f"Helm release {name} upgraded with chart {chart} in {namespace}",
|
|
166
|
+
"details": result
|
|
167
|
+
}
|
|
168
|
+
except subprocess.CalledProcessError as e:
|
|
169
|
+
error_msg = e.stderr if hasattr(e, 'stderr') else str(e)
|
|
170
|
+
logger.error(f"Error upgrading Helm chart: {error_msg}")
|
|
171
|
+
return {"success": False, "error": f"Failed to upgrade Helm chart: {error_msg}"}
|
|
172
|
+
finally:
|
|
173
|
+
if values_file and os.path.exists(values_file):
|
|
174
|
+
os.unlink(values_file)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Unexpected error upgrading Helm chart: {str(e)}")
|
|
177
|
+
return {"success": False, "error": f"Unexpected error: {str(e)}"}
|
|
178
|
+
|
|
179
|
+
@server.tool(
|
|
180
|
+
annotations=ToolAnnotations(
|
|
181
|
+
title="Uninstall Helm Chart",
|
|
182
|
+
destructiveHint=True,
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
def uninstall_helm_chart(name: str, namespace: str) -> Dict[str, Any]:
|
|
186
|
+
"""Uninstall a Helm release."""
|
|
187
|
+
if non_destructive:
|
|
188
|
+
return {"success": False, "error": "Blocked: non-destructive mode"}
|
|
189
|
+
if not check_helm_fn():
|
|
190
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
cmd = ["helm", "uninstall", name, "-n", namespace]
|
|
194
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
result = subprocess.check_output(cmd, stderr=subprocess.PIPE, text=True)
|
|
198
|
+
return {
|
|
199
|
+
"success": True,
|
|
200
|
+
"message": f"Helm release {name} uninstalled from {namespace}",
|
|
201
|
+
"details": result
|
|
202
|
+
}
|
|
203
|
+
except subprocess.CalledProcessError as e:
|
|
204
|
+
error_msg = e.stderr if hasattr(e, 'stderr') else str(e)
|
|
205
|
+
logger.error(f"Error uninstalling Helm chart: {error_msg}")
|
|
206
|
+
return {"success": False, "error": f"Failed to uninstall Helm chart: {error_msg}"}
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Unexpected error uninstalling Helm chart: {str(e)}")
|
|
209
|
+
return {"success": False, "error": f"Unexpected error: {str(e)}"}
|
|
210
|
+
|
|
211
|
+
@server.tool(
|
|
212
|
+
annotations=ToolAnnotations(
|
|
213
|
+
title="List Helm Releases",
|
|
214
|
+
readOnlyHint=True,
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
def helm_list(
|
|
218
|
+
namespace: Optional[str] = None,
|
|
219
|
+
all_namespaces: bool = False,
|
|
220
|
+
filter: Optional[str] = None,
|
|
221
|
+
deployed: bool = False,
|
|
222
|
+
failed: bool = False,
|
|
223
|
+
pending: bool = False,
|
|
224
|
+
uninstalled: bool = False,
|
|
225
|
+
superseded: bool = False
|
|
226
|
+
) -> Dict[str, Any]:
|
|
227
|
+
"""List Helm releases with optional filtering.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
namespace: Target namespace (default: current namespace)
|
|
231
|
+
all_namespaces: List releases across all namespaces
|
|
232
|
+
filter: Filter releases by name using regex
|
|
233
|
+
deployed: Show deployed releases only
|
|
234
|
+
failed: Show failed releases only
|
|
235
|
+
pending: Show pending releases only
|
|
236
|
+
uninstalled: Show uninstalled releases (if kept with --keep-history)
|
|
237
|
+
superseded: Show superseded releases only
|
|
238
|
+
"""
|
|
239
|
+
if not check_helm_fn():
|
|
240
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
cmd = ["helm", "list", "--output", "json"]
|
|
244
|
+
|
|
245
|
+
if all_namespaces:
|
|
246
|
+
cmd.append("--all-namespaces")
|
|
247
|
+
elif namespace:
|
|
248
|
+
cmd.extend(["-n", namespace])
|
|
249
|
+
|
|
250
|
+
if filter:
|
|
251
|
+
cmd.extend(["--filter", filter])
|
|
252
|
+
if deployed:
|
|
253
|
+
cmd.append("--deployed")
|
|
254
|
+
if failed:
|
|
255
|
+
cmd.append("--failed")
|
|
256
|
+
if pending:
|
|
257
|
+
cmd.append("--pending")
|
|
258
|
+
if uninstalled:
|
|
259
|
+
cmd.append("--uninstalled")
|
|
260
|
+
if superseded:
|
|
261
|
+
cmd.append("--superseded")
|
|
262
|
+
|
|
263
|
+
if not any([deployed, failed, pending, uninstalled, superseded]):
|
|
264
|
+
cmd.append("--all")
|
|
265
|
+
|
|
266
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
267
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
268
|
+
|
|
269
|
+
if result.returncode == 0:
|
|
270
|
+
releases = json.loads(result.stdout) if result.stdout.strip() else []
|
|
271
|
+
return {
|
|
272
|
+
"success": True,
|
|
273
|
+
"releases": releases,
|
|
274
|
+
"count": len(releases)
|
|
275
|
+
}
|
|
276
|
+
else:
|
|
277
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.error(f"Error listing Helm releases: {e}")
|
|
280
|
+
return {"success": False, "error": str(e)}
|
|
281
|
+
|
|
282
|
+
@server.tool(
|
|
283
|
+
annotations=ToolAnnotations(
|
|
284
|
+
title="Helm Release Status",
|
|
285
|
+
readOnlyHint=True,
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
def helm_status(
|
|
289
|
+
release_name: str,
|
|
290
|
+
namespace: str = "default",
|
|
291
|
+
revision: Optional[int] = None,
|
|
292
|
+
show_desc: bool = False,
|
|
293
|
+
show_resources: bool = False
|
|
294
|
+
) -> Dict[str, Any]:
|
|
295
|
+
"""Get the status of a Helm release.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
release_name: Name of the release
|
|
299
|
+
namespace: Kubernetes namespace
|
|
300
|
+
revision: Show status for a specific revision (default: latest)
|
|
301
|
+
show_desc: Show description of the release
|
|
302
|
+
show_resources: Show resources created by the release
|
|
303
|
+
"""
|
|
304
|
+
if not check_helm_fn():
|
|
305
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
cmd = ["helm", "status", release_name, "-n", namespace, "--output", "json"]
|
|
309
|
+
|
|
310
|
+
if revision:
|
|
311
|
+
cmd.extend(["--revision", str(revision)])
|
|
312
|
+
if show_desc:
|
|
313
|
+
cmd.append("--show-desc")
|
|
314
|
+
if show_resources:
|
|
315
|
+
cmd.append("--show-resources")
|
|
316
|
+
|
|
317
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
318
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
319
|
+
|
|
320
|
+
if result.returncode == 0:
|
|
321
|
+
status = json.loads(result.stdout) if result.stdout.strip() else {}
|
|
322
|
+
return {"success": True, "status": status}
|
|
323
|
+
else:
|
|
324
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.error(f"Error getting Helm status: {e}")
|
|
327
|
+
return {"success": False, "error": str(e)}
|
|
328
|
+
|
|
329
|
+
@server.tool(
|
|
330
|
+
annotations=ToolAnnotations(
|
|
331
|
+
title="Helm Release History",
|
|
332
|
+
readOnlyHint=True,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
def helm_history(
|
|
336
|
+
release_name: str,
|
|
337
|
+
namespace: str = "default",
|
|
338
|
+
max_revisions: int = 256
|
|
339
|
+
) -> Dict[str, Any]:
|
|
340
|
+
"""Get the revision history of a Helm release.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
release_name: Name of the release
|
|
344
|
+
namespace: Kubernetes namespace
|
|
345
|
+
max_revisions: Maximum number of revisions to return
|
|
346
|
+
"""
|
|
347
|
+
if not check_helm_fn():
|
|
348
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
cmd = ["helm", "history", release_name, "-n", namespace, "--output", "json", "--max", str(max_revisions)]
|
|
352
|
+
|
|
353
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
354
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
355
|
+
|
|
356
|
+
if result.returncode == 0:
|
|
357
|
+
history = json.loads(result.stdout) if result.stdout.strip() else []
|
|
358
|
+
return {
|
|
359
|
+
"success": True,
|
|
360
|
+
"history": history,
|
|
361
|
+
"revisions": len(history)
|
|
362
|
+
}
|
|
363
|
+
else:
|
|
364
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.error(f"Error getting Helm history: {e}")
|
|
367
|
+
return {"success": False, "error": str(e)}
|
|
368
|
+
|
|
369
|
+
@server.tool(
|
|
370
|
+
annotations=ToolAnnotations(
|
|
371
|
+
title="Helm Get Values",
|
|
372
|
+
readOnlyHint=True,
|
|
373
|
+
),
|
|
374
|
+
)
|
|
375
|
+
def helm_get_values(
|
|
376
|
+
release_name: str,
|
|
377
|
+
namespace: str = "default",
|
|
378
|
+
all_values: bool = False,
|
|
379
|
+
revision: Optional[int] = None
|
|
380
|
+
) -> Dict[str, Any]:
|
|
381
|
+
"""Get the values used for a Helm release.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
release_name: Name of the release
|
|
385
|
+
namespace: Kubernetes namespace
|
|
386
|
+
all_values: Include computed (default + user) values
|
|
387
|
+
revision: Get values for a specific revision
|
|
388
|
+
"""
|
|
389
|
+
if not check_helm_fn():
|
|
390
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
cmd = ["helm", "get", "values", release_name, "-n", namespace, "--output", "yaml"]
|
|
394
|
+
|
|
395
|
+
if all_values:
|
|
396
|
+
cmd.append("--all")
|
|
397
|
+
if revision:
|
|
398
|
+
cmd.extend(["--revision", str(revision)])
|
|
399
|
+
|
|
400
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
401
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
402
|
+
|
|
403
|
+
if result.returncode == 0:
|
|
404
|
+
values = yaml.safe_load(result.stdout) if result.stdout.strip() else {}
|
|
405
|
+
return {"success": True, "values": values, "raw": result.stdout}
|
|
406
|
+
else:
|
|
407
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
408
|
+
except Exception as e:
|
|
409
|
+
logger.error(f"Error getting Helm values: {e}")
|
|
410
|
+
return {"success": False, "error": str(e)}
|
|
411
|
+
|
|
412
|
+
@server.tool(
|
|
413
|
+
annotations=ToolAnnotations(
|
|
414
|
+
title="Helm Get Manifest",
|
|
415
|
+
readOnlyHint=True,
|
|
416
|
+
),
|
|
417
|
+
)
|
|
418
|
+
def helm_get_manifest(
|
|
419
|
+
release_name: str,
|
|
420
|
+
namespace: str = "default",
|
|
421
|
+
revision: Optional[int] = None
|
|
422
|
+
) -> Dict[str, Any]:
|
|
423
|
+
"""Get the manifest (rendered templates) of a Helm release.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
release_name: Name of the release
|
|
427
|
+
namespace: Kubernetes namespace
|
|
428
|
+
revision: Get manifest for a specific revision
|
|
429
|
+
"""
|
|
430
|
+
if not check_helm_fn():
|
|
431
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
432
|
+
|
|
433
|
+
try:
|
|
434
|
+
cmd = ["helm", "get", "manifest", release_name, "-n", namespace]
|
|
435
|
+
|
|
436
|
+
if revision:
|
|
437
|
+
cmd.extend(["--revision", str(revision)])
|
|
438
|
+
|
|
439
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
440
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
441
|
+
|
|
442
|
+
if result.returncode == 0:
|
|
443
|
+
return {"success": True, "manifest": result.stdout}
|
|
444
|
+
else:
|
|
445
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.error(f"Error getting Helm manifest: {e}")
|
|
448
|
+
return {"success": False, "error": str(e)}
|
|
449
|
+
|
|
450
|
+
@server.tool(
|
|
451
|
+
annotations=ToolAnnotations(
|
|
452
|
+
title="Helm Get Notes",
|
|
453
|
+
readOnlyHint=True,
|
|
454
|
+
),
|
|
455
|
+
)
|
|
456
|
+
def helm_get_notes(
|
|
457
|
+
release_name: str,
|
|
458
|
+
namespace: str = "default",
|
|
459
|
+
revision: Optional[int] = None
|
|
460
|
+
) -> Dict[str, Any]:
|
|
461
|
+
"""Get the notes (post-install message) of a Helm release.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
release_name: Name of the release
|
|
465
|
+
namespace: Kubernetes namespace
|
|
466
|
+
revision: Get notes for a specific revision
|
|
467
|
+
"""
|
|
468
|
+
if not check_helm_fn():
|
|
469
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
cmd = ["helm", "get", "notes", release_name, "-n", namespace]
|
|
473
|
+
|
|
474
|
+
if revision:
|
|
475
|
+
cmd.extend(["--revision", str(revision)])
|
|
476
|
+
|
|
477
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
478
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
479
|
+
|
|
480
|
+
if result.returncode == 0:
|
|
481
|
+
return {"success": True, "notes": result.stdout}
|
|
482
|
+
else:
|
|
483
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
484
|
+
except Exception as e:
|
|
485
|
+
logger.error(f"Error getting Helm notes: {e}")
|
|
486
|
+
return {"success": False, "error": str(e)}
|
|
487
|
+
|
|
488
|
+
@server.tool(
|
|
489
|
+
annotations=ToolAnnotations(
|
|
490
|
+
title="Helm Get Hooks",
|
|
491
|
+
readOnlyHint=True,
|
|
492
|
+
),
|
|
493
|
+
)
|
|
494
|
+
def helm_get_hooks(
|
|
495
|
+
release_name: str,
|
|
496
|
+
namespace: str = "default",
|
|
497
|
+
revision: Optional[int] = None
|
|
498
|
+
) -> Dict[str, Any]:
|
|
499
|
+
"""Get the hooks of a Helm release.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
release_name: Name of the release
|
|
503
|
+
namespace: Kubernetes namespace
|
|
504
|
+
revision: Get hooks for a specific revision
|
|
505
|
+
"""
|
|
506
|
+
if not check_helm_fn():
|
|
507
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
508
|
+
|
|
509
|
+
try:
|
|
510
|
+
cmd = ["helm", "get", "hooks", release_name, "-n", namespace]
|
|
511
|
+
|
|
512
|
+
if revision:
|
|
513
|
+
cmd.extend(["--revision", str(revision)])
|
|
514
|
+
|
|
515
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
516
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
517
|
+
|
|
518
|
+
if result.returncode == 0:
|
|
519
|
+
return {"success": True, "hooks": result.stdout}
|
|
520
|
+
else:
|
|
521
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
522
|
+
except Exception as e:
|
|
523
|
+
logger.error(f"Error getting Helm hooks: {e}")
|
|
524
|
+
return {"success": False, "error": str(e)}
|
|
525
|
+
|
|
526
|
+
@server.tool(
|
|
527
|
+
annotations=ToolAnnotations(
|
|
528
|
+
title="Helm Get All",
|
|
529
|
+
readOnlyHint=True,
|
|
530
|
+
),
|
|
531
|
+
)
|
|
532
|
+
def helm_get_all(
|
|
533
|
+
release_name: str,
|
|
534
|
+
namespace: str = "default",
|
|
535
|
+
revision: Optional[int] = None
|
|
536
|
+
) -> Dict[str, Any]:
|
|
537
|
+
"""Get all information about a Helm release (values, manifest, hooks, notes).
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
release_name: Name of the release
|
|
541
|
+
namespace: Kubernetes namespace
|
|
542
|
+
revision: Get info for a specific revision
|
|
543
|
+
"""
|
|
544
|
+
if not check_helm_fn():
|
|
545
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
cmd = ["helm", "get", "all", release_name, "-n", namespace]
|
|
549
|
+
|
|
550
|
+
if revision:
|
|
551
|
+
cmd.extend(["--revision", str(revision)])
|
|
552
|
+
|
|
553
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
554
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
555
|
+
|
|
556
|
+
if result.returncode == 0:
|
|
557
|
+
return {"success": True, "release_info": result.stdout}
|
|
558
|
+
else:
|
|
559
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
560
|
+
except Exception as e:
|
|
561
|
+
logger.error(f"Error getting all Helm info: {e}")
|
|
562
|
+
return {"success": False, "error": str(e)}
|
|
563
|
+
|
|
564
|
+
@server.tool(
|
|
565
|
+
annotations=ToolAnnotations(
|
|
566
|
+
title="Helm Show Chart",
|
|
567
|
+
readOnlyHint=True,
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
def helm_show_chart(
|
|
571
|
+
chart: str,
|
|
572
|
+
repo: Optional[str] = None,
|
|
573
|
+
version: Optional[str] = None
|
|
574
|
+
) -> Dict[str, Any]:
|
|
575
|
+
"""Show the chart definition (Chart.yaml).
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
chart: Chart reference (e.g., 'nginx', 'bitnami/nginx', or local path)
|
|
579
|
+
repo: Repository URL (for OCI or HTTP repos)
|
|
580
|
+
version: Specific chart version
|
|
581
|
+
"""
|
|
582
|
+
if not check_helm_fn():
|
|
583
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
584
|
+
|
|
585
|
+
try:
|
|
586
|
+
cmd = ["helm", "show", "chart", chart]
|
|
587
|
+
|
|
588
|
+
if repo:
|
|
589
|
+
cmd.extend(["--repo", repo])
|
|
590
|
+
if version:
|
|
591
|
+
cmd.extend(["--version", version])
|
|
592
|
+
|
|
593
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
594
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
595
|
+
|
|
596
|
+
if result.returncode == 0:
|
|
597
|
+
chart_info = yaml.safe_load(result.stdout) if result.stdout.strip() else {}
|
|
598
|
+
return {"success": True, "chart": chart_info, "raw": result.stdout}
|
|
599
|
+
else:
|
|
600
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
601
|
+
except Exception as e:
|
|
602
|
+
logger.error(f"Error showing chart: {e}")
|
|
603
|
+
return {"success": False, "error": str(e)}
|
|
604
|
+
|
|
605
|
+
@server.tool(
|
|
606
|
+
annotations=ToolAnnotations(
|
|
607
|
+
title="Helm Show Values",
|
|
608
|
+
readOnlyHint=True,
|
|
609
|
+
),
|
|
610
|
+
)
|
|
611
|
+
def helm_show_values(
|
|
612
|
+
chart: str,
|
|
613
|
+
repo: Optional[str] = None,
|
|
614
|
+
version: Optional[str] = None,
|
|
615
|
+
jsonpath: Optional[str] = None
|
|
616
|
+
) -> Dict[str, Any]:
|
|
617
|
+
"""Show the chart's default values.yaml.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
chart: Chart reference (e.g., 'nginx', 'bitnami/nginx', or local path)
|
|
621
|
+
repo: Repository URL
|
|
622
|
+
version: Specific chart version
|
|
623
|
+
jsonpath: JSONPath expression to filter values
|
|
624
|
+
"""
|
|
625
|
+
if not check_helm_fn():
|
|
626
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
627
|
+
|
|
628
|
+
try:
|
|
629
|
+
cmd = ["helm", "show", "values", chart]
|
|
630
|
+
|
|
631
|
+
if repo:
|
|
632
|
+
cmd.extend(["--repo", repo])
|
|
633
|
+
if version:
|
|
634
|
+
cmd.extend(["--version", version])
|
|
635
|
+
if jsonpath:
|
|
636
|
+
cmd.extend(["--jsonpath", jsonpath])
|
|
637
|
+
|
|
638
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
639
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
640
|
+
|
|
641
|
+
if result.returncode == 0:
|
|
642
|
+
values = yaml.safe_load(result.stdout) if result.stdout.strip() and not jsonpath else result.stdout
|
|
643
|
+
return {"success": True, "values": values, "raw": result.stdout}
|
|
644
|
+
else:
|
|
645
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
646
|
+
except Exception as e:
|
|
647
|
+
logger.error(f"Error showing chart values: {e}")
|
|
648
|
+
return {"success": False, "error": str(e)}
|
|
649
|
+
|
|
650
|
+
@server.tool(
|
|
651
|
+
annotations=ToolAnnotations(
|
|
652
|
+
title="Helm Show Readme",
|
|
653
|
+
readOnlyHint=True,
|
|
654
|
+
),
|
|
655
|
+
)
|
|
656
|
+
def helm_show_readme(
|
|
657
|
+
chart: str,
|
|
658
|
+
repo: Optional[str] = None,
|
|
659
|
+
version: Optional[str] = None
|
|
660
|
+
) -> Dict[str, Any]:
|
|
661
|
+
"""Show the chart's README file.
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
chart: Chart reference (e.g., 'nginx', 'bitnami/nginx', or local path)
|
|
665
|
+
repo: Repository URL
|
|
666
|
+
version: Specific chart version
|
|
667
|
+
"""
|
|
668
|
+
if not check_helm_fn():
|
|
669
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
cmd = ["helm", "show", "readme", chart]
|
|
673
|
+
|
|
674
|
+
if repo:
|
|
675
|
+
cmd.extend(["--repo", repo])
|
|
676
|
+
if version:
|
|
677
|
+
cmd.extend(["--version", version])
|
|
678
|
+
|
|
679
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
680
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
681
|
+
|
|
682
|
+
if result.returncode == 0:
|
|
683
|
+
return {"success": True, "readme": result.stdout}
|
|
684
|
+
else:
|
|
685
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
686
|
+
except Exception as e:
|
|
687
|
+
logger.error(f"Error showing chart readme: {e}")
|
|
688
|
+
return {"success": False, "error": str(e)}
|
|
689
|
+
|
|
690
|
+
@server.tool(
|
|
691
|
+
annotations=ToolAnnotations(
|
|
692
|
+
title="Helm Show CRDs",
|
|
693
|
+
readOnlyHint=True,
|
|
694
|
+
),
|
|
695
|
+
)
|
|
696
|
+
def helm_show_crds(
|
|
697
|
+
chart: str,
|
|
698
|
+
repo: Optional[str] = None,
|
|
699
|
+
version: Optional[str] = None
|
|
700
|
+
) -> Dict[str, Any]:
|
|
701
|
+
"""Show the chart's Custom Resource Definitions (CRDs).
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
chart: Chart reference
|
|
705
|
+
repo: Repository URL
|
|
706
|
+
version: Specific chart version
|
|
707
|
+
"""
|
|
708
|
+
if not check_helm_fn():
|
|
709
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
cmd = ["helm", "show", "crds", chart]
|
|
713
|
+
|
|
714
|
+
if repo:
|
|
715
|
+
cmd.extend(["--repo", repo])
|
|
716
|
+
if version:
|
|
717
|
+
cmd.extend(["--version", version])
|
|
718
|
+
|
|
719
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
720
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
721
|
+
|
|
722
|
+
if result.returncode == 0:
|
|
723
|
+
return {"success": True, "crds": result.stdout}
|
|
724
|
+
else:
|
|
725
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
726
|
+
except Exception as e:
|
|
727
|
+
logger.error(f"Error showing chart CRDs: {e}")
|
|
728
|
+
return {"success": False, "error": str(e)}
|
|
729
|
+
|
|
730
|
+
@server.tool(
|
|
731
|
+
annotations=ToolAnnotations(
|
|
732
|
+
title="Helm Show All",
|
|
733
|
+
readOnlyHint=True,
|
|
734
|
+
),
|
|
735
|
+
)
|
|
736
|
+
def helm_show_all(
|
|
737
|
+
chart: str,
|
|
738
|
+
repo: Optional[str] = None,
|
|
739
|
+
version: Optional[str] = None
|
|
740
|
+
) -> Dict[str, Any]:
|
|
741
|
+
"""Show all chart information (chart.yaml, values, readme, crds).
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
chart: Chart reference
|
|
745
|
+
repo: Repository URL
|
|
746
|
+
version: Specific chart version
|
|
747
|
+
"""
|
|
748
|
+
if not check_helm_fn():
|
|
749
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
750
|
+
|
|
751
|
+
try:
|
|
752
|
+
cmd = ["helm", "show", "all", chart]
|
|
753
|
+
|
|
754
|
+
if repo:
|
|
755
|
+
cmd.extend(["--repo", repo])
|
|
756
|
+
if version:
|
|
757
|
+
cmd.extend(["--version", version])
|
|
758
|
+
|
|
759
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
760
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
761
|
+
|
|
762
|
+
if result.returncode == 0:
|
|
763
|
+
return {"success": True, "chart_info": result.stdout}
|
|
764
|
+
else:
|
|
765
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
766
|
+
except Exception as e:
|
|
767
|
+
logger.error(f"Error showing all chart info: {e}")
|
|
768
|
+
return {"success": False, "error": str(e)}
|
|
769
|
+
|
|
770
|
+
@server.tool(
|
|
771
|
+
annotations=ToolAnnotations(
|
|
772
|
+
title="Helm Search Repo",
|
|
773
|
+
readOnlyHint=True,
|
|
774
|
+
),
|
|
775
|
+
)
|
|
776
|
+
def helm_search_repo(
|
|
777
|
+
keyword: str,
|
|
778
|
+
regexp: bool = False,
|
|
779
|
+
versions: bool = False,
|
|
780
|
+
version: Optional[str] = None,
|
|
781
|
+
max_results: int = 50
|
|
782
|
+
) -> Dict[str, Any]:
|
|
783
|
+
"""Search for charts in configured Helm repositories.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
keyword: Search keyword
|
|
787
|
+
regexp: Use regular expression for searching
|
|
788
|
+
versions: Show all chart versions (not just latest)
|
|
789
|
+
version: Version constraint (e.g., '>1.0.0')
|
|
790
|
+
max_results: Maximum number of results
|
|
791
|
+
"""
|
|
792
|
+
if not check_helm_fn():
|
|
793
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
794
|
+
|
|
795
|
+
try:
|
|
796
|
+
cmd = ["helm", "search", "repo", keyword, "--output", "json", "--max-col-width", "0"]
|
|
797
|
+
|
|
798
|
+
if regexp:
|
|
799
|
+
cmd.append("--regexp")
|
|
800
|
+
if versions:
|
|
801
|
+
cmd.append("--versions")
|
|
802
|
+
if version:
|
|
803
|
+
cmd.extend(["--version", version])
|
|
804
|
+
|
|
805
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
806
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
807
|
+
|
|
808
|
+
if result.returncode == 0:
|
|
809
|
+
charts = json.loads(result.stdout) if result.stdout.strip() else []
|
|
810
|
+
return {
|
|
811
|
+
"success": True,
|
|
812
|
+
"charts": charts[:max_results],
|
|
813
|
+
"count": len(charts[:max_results])
|
|
814
|
+
}
|
|
815
|
+
else:
|
|
816
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
817
|
+
except Exception as e:
|
|
818
|
+
logger.error(f"Error searching repos: {e}")
|
|
819
|
+
return {"success": False, "error": str(e)}
|
|
820
|
+
|
|
821
|
+
@server.tool(
|
|
822
|
+
annotations=ToolAnnotations(
|
|
823
|
+
title="Helm Search Hub",
|
|
824
|
+
readOnlyHint=True,
|
|
825
|
+
),
|
|
826
|
+
)
|
|
827
|
+
def helm_search_hub(
|
|
828
|
+
keyword: str,
|
|
829
|
+
max_results: int = 50,
|
|
830
|
+
list_repo_url: bool = True
|
|
831
|
+
) -> Dict[str, Any]:
|
|
832
|
+
"""Search for charts in Artifact Hub (https://artifacthub.io).
|
|
833
|
+
|
|
834
|
+
Args:
|
|
835
|
+
keyword: Search keyword
|
|
836
|
+
max_results: Maximum number of results
|
|
837
|
+
list_repo_url: Show repository URL for each chart
|
|
838
|
+
"""
|
|
839
|
+
if not check_helm_fn():
|
|
840
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
841
|
+
|
|
842
|
+
try:
|
|
843
|
+
cmd = ["helm", "search", "hub", keyword, "--output", "json", "--max-col-width", "0"]
|
|
844
|
+
|
|
845
|
+
if list_repo_url:
|
|
846
|
+
cmd.append("--list-repo-url")
|
|
847
|
+
|
|
848
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
849
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
850
|
+
|
|
851
|
+
if result.returncode == 0:
|
|
852
|
+
charts = json.loads(result.stdout) if result.stdout.strip() else []
|
|
853
|
+
return {
|
|
854
|
+
"success": True,
|
|
855
|
+
"charts": charts[:max_results],
|
|
856
|
+
"count": len(charts[:max_results]),
|
|
857
|
+
"source": "Artifact Hub (artifacthub.io)"
|
|
858
|
+
}
|
|
859
|
+
else:
|
|
860
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
861
|
+
except Exception as e:
|
|
862
|
+
logger.error(f"Error searching Artifact Hub: {e}")
|
|
863
|
+
return {"success": False, "error": str(e)}
|
|
864
|
+
|
|
865
|
+
@server.tool(
|
|
866
|
+
annotations=ToolAnnotations(
|
|
867
|
+
title="Helm Repo List",
|
|
868
|
+
readOnlyHint=True,
|
|
869
|
+
),
|
|
870
|
+
)
|
|
871
|
+
def helm_repo_list() -> Dict[str, Any]:
|
|
872
|
+
"""List all configured Helm repositories."""
|
|
873
|
+
if not check_helm_fn():
|
|
874
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
875
|
+
|
|
876
|
+
try:
|
|
877
|
+
cmd = ["helm", "repo", "list", "--output", "json"]
|
|
878
|
+
|
|
879
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
880
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
881
|
+
|
|
882
|
+
if result.returncode == 0:
|
|
883
|
+
repos = json.loads(result.stdout) if result.stdout.strip() else []
|
|
884
|
+
return {
|
|
885
|
+
"success": True,
|
|
886
|
+
"repositories": repos,
|
|
887
|
+
"count": len(repos)
|
|
888
|
+
}
|
|
889
|
+
else:
|
|
890
|
+
if "no repositories to show" in result.stderr.lower():
|
|
891
|
+
return {"success": True, "repositories": [], "count": 0}
|
|
892
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
893
|
+
except Exception as e:
|
|
894
|
+
logger.error(f"Error listing repos: {e}")
|
|
895
|
+
return {"success": False, "error": str(e)}
|
|
896
|
+
|
|
897
|
+
@server.tool(
|
|
898
|
+
annotations=ToolAnnotations(
|
|
899
|
+
title="Helm Repo Add",
|
|
900
|
+
destructiveHint=False,
|
|
901
|
+
),
|
|
902
|
+
)
|
|
903
|
+
def helm_repo_add(
|
|
904
|
+
name: str,
|
|
905
|
+
url: str,
|
|
906
|
+
username: Optional[str] = None,
|
|
907
|
+
password: Optional[str] = None,
|
|
908
|
+
force_update: bool = False,
|
|
909
|
+
pass_credentials: bool = False
|
|
910
|
+
) -> Dict[str, Any]:
|
|
911
|
+
"""Add a Helm chart repository.
|
|
912
|
+
|
|
913
|
+
Args:
|
|
914
|
+
name: Repository name (local alias)
|
|
915
|
+
url: Repository URL
|
|
916
|
+
username: Username for basic auth
|
|
917
|
+
password: Password for basic auth
|
|
918
|
+
force_update: Replace existing repo with same name
|
|
919
|
+
pass_credentials: Pass credentials to all domains
|
|
920
|
+
"""
|
|
921
|
+
if not check_helm_fn():
|
|
922
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
923
|
+
|
|
924
|
+
try:
|
|
925
|
+
cmd = ["helm", "repo", "add", name, url]
|
|
926
|
+
|
|
927
|
+
if username:
|
|
928
|
+
cmd.extend(["--username", username])
|
|
929
|
+
if password:
|
|
930
|
+
cmd.extend(["--password", password])
|
|
931
|
+
if force_update:
|
|
932
|
+
cmd.append("--force-update")
|
|
933
|
+
if pass_credentials:
|
|
934
|
+
cmd.append("--pass-credentials")
|
|
935
|
+
|
|
936
|
+
logger.debug(f"Running command: {' '.join(cmd[:4])}...")
|
|
937
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
938
|
+
|
|
939
|
+
if result.returncode == 0:
|
|
940
|
+
return {
|
|
941
|
+
"success": True,
|
|
942
|
+
"message": f"Repository '{name}' added successfully",
|
|
943
|
+
"details": result.stdout.strip()
|
|
944
|
+
}
|
|
945
|
+
else:
|
|
946
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
947
|
+
except Exception as e:
|
|
948
|
+
logger.error(f"Error adding repo: {e}")
|
|
949
|
+
return {"success": False, "error": str(e)}
|
|
950
|
+
|
|
951
|
+
@server.tool(
|
|
952
|
+
annotations=ToolAnnotations(
|
|
953
|
+
title="Helm Repo Remove",
|
|
954
|
+
destructiveHint=True,
|
|
955
|
+
),
|
|
956
|
+
)
|
|
957
|
+
def helm_repo_remove(name: str) -> Dict[str, Any]:
|
|
958
|
+
"""Remove a Helm chart repository.
|
|
959
|
+
|
|
960
|
+
Args:
|
|
961
|
+
name: Repository name to remove
|
|
962
|
+
"""
|
|
963
|
+
if not check_helm_fn():
|
|
964
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
965
|
+
|
|
966
|
+
try:
|
|
967
|
+
cmd = ["helm", "repo", "remove", name]
|
|
968
|
+
|
|
969
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
970
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
971
|
+
|
|
972
|
+
if result.returncode == 0:
|
|
973
|
+
return {
|
|
974
|
+
"success": True,
|
|
975
|
+
"message": f"Repository '{name}' removed successfully",
|
|
976
|
+
"details": result.stdout.strip()
|
|
977
|
+
}
|
|
978
|
+
else:
|
|
979
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
980
|
+
except Exception as e:
|
|
981
|
+
logger.error(f"Error removing repo: {e}")
|
|
982
|
+
return {"success": False, "error": str(e)}
|
|
983
|
+
|
|
984
|
+
@server.tool(
|
|
985
|
+
annotations=ToolAnnotations(
|
|
986
|
+
title="Helm Repo Update",
|
|
987
|
+
readOnlyHint=False,
|
|
988
|
+
),
|
|
989
|
+
)
|
|
990
|
+
def helm_repo_update(repos: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
991
|
+
"""Update Helm repository indexes.
|
|
992
|
+
|
|
993
|
+
Args:
|
|
994
|
+
repos: Specific repositories to update (default: all)
|
|
995
|
+
"""
|
|
996
|
+
if not check_helm_fn():
|
|
997
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
998
|
+
|
|
999
|
+
try:
|
|
1000
|
+
cmd = ["helm", "repo", "update"]
|
|
1001
|
+
|
|
1002
|
+
if repos:
|
|
1003
|
+
cmd.extend(repos)
|
|
1004
|
+
|
|
1005
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1006
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
1007
|
+
|
|
1008
|
+
if result.returncode == 0:
|
|
1009
|
+
return {
|
|
1010
|
+
"success": True,
|
|
1011
|
+
"message": "Repositories updated successfully",
|
|
1012
|
+
"details": result.stdout.strip()
|
|
1013
|
+
}
|
|
1014
|
+
else:
|
|
1015
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1016
|
+
except Exception as e:
|
|
1017
|
+
logger.error(f"Error updating repos: {e}")
|
|
1018
|
+
return {"success": False, "error": str(e)}
|
|
1019
|
+
|
|
1020
|
+
@server.tool(
|
|
1021
|
+
annotations=ToolAnnotations(
|
|
1022
|
+
title="Helm Rollback",
|
|
1023
|
+
destructiveHint=True,
|
|
1024
|
+
),
|
|
1025
|
+
)
|
|
1026
|
+
def helm_rollback(
|
|
1027
|
+
release_name: str,
|
|
1028
|
+
revision: int,
|
|
1029
|
+
namespace: str = "default",
|
|
1030
|
+
force: bool = False,
|
|
1031
|
+
recreate_pods: bool = False,
|
|
1032
|
+
cleanup_on_fail: bool = False,
|
|
1033
|
+
wait: bool = False,
|
|
1034
|
+
timeout: str = "5m0s"
|
|
1035
|
+
) -> Dict[str, Any]:
|
|
1036
|
+
"""Rollback a Helm release to a previous revision.
|
|
1037
|
+
|
|
1038
|
+
Args:
|
|
1039
|
+
release_name: Name of the release
|
|
1040
|
+
revision: Revision number to rollback to (use helm_history to see revisions)
|
|
1041
|
+
namespace: Kubernetes namespace
|
|
1042
|
+
force: Force resource updates through delete/recreate
|
|
1043
|
+
recreate_pods: Force pod restarts
|
|
1044
|
+
cleanup_on_fail: Delete newly created resources on failure
|
|
1045
|
+
wait: Wait until all resources are ready
|
|
1046
|
+
timeout: Timeout for waiting
|
|
1047
|
+
"""
|
|
1048
|
+
if non_destructive:
|
|
1049
|
+
return {"success": False, "error": "Blocked: non-destructive mode"}
|
|
1050
|
+
if not check_helm_fn():
|
|
1051
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1052
|
+
|
|
1053
|
+
try:
|
|
1054
|
+
cmd = ["helm", "rollback", release_name, str(revision), "-n", namespace]
|
|
1055
|
+
|
|
1056
|
+
if force:
|
|
1057
|
+
cmd.append("--force")
|
|
1058
|
+
if recreate_pods:
|
|
1059
|
+
cmd.append("--recreate-pods")
|
|
1060
|
+
if cleanup_on_fail:
|
|
1061
|
+
cmd.append("--cleanup-on-fail")
|
|
1062
|
+
if wait:
|
|
1063
|
+
cmd.append("--wait")
|
|
1064
|
+
cmd.extend(["--timeout", timeout])
|
|
1065
|
+
|
|
1066
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1067
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
|
1068
|
+
|
|
1069
|
+
if result.returncode == 0:
|
|
1070
|
+
return {
|
|
1071
|
+
"success": True,
|
|
1072
|
+
"message": f"Release '{release_name}' rolled back to revision {revision}",
|
|
1073
|
+
"details": result.stdout.strip()
|
|
1074
|
+
}
|
|
1075
|
+
else:
|
|
1076
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1077
|
+
except Exception as e:
|
|
1078
|
+
logger.error(f"Error rolling back: {e}")
|
|
1079
|
+
return {"success": False, "error": str(e)}
|
|
1080
|
+
|
|
1081
|
+
@server.tool(
|
|
1082
|
+
annotations=ToolAnnotations(
|
|
1083
|
+
title="Helm Test",
|
|
1084
|
+
readOnlyHint=False,
|
|
1085
|
+
),
|
|
1086
|
+
)
|
|
1087
|
+
def helm_test(
|
|
1088
|
+
release_name: str,
|
|
1089
|
+
namespace: str = "default",
|
|
1090
|
+
timeout: str = "5m0s",
|
|
1091
|
+
logs: bool = True,
|
|
1092
|
+
filter: Optional[str] = None
|
|
1093
|
+
) -> Dict[str, Any]:
|
|
1094
|
+
"""Run tests for a Helm release.
|
|
1095
|
+
|
|
1096
|
+
Args:
|
|
1097
|
+
release_name: Name of the release
|
|
1098
|
+
namespace: Kubernetes namespace
|
|
1099
|
+
timeout: Timeout for tests
|
|
1100
|
+
logs: Show test pod logs
|
|
1101
|
+
filter: Filter tests by name
|
|
1102
|
+
"""
|
|
1103
|
+
if not check_helm_fn():
|
|
1104
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1105
|
+
|
|
1106
|
+
try:
|
|
1107
|
+
cmd = ["helm", "test", release_name, "-n", namespace, "--timeout", timeout]
|
|
1108
|
+
|
|
1109
|
+
if logs:
|
|
1110
|
+
cmd.append("--logs")
|
|
1111
|
+
if filter:
|
|
1112
|
+
cmd.extend(["--filter", filter])
|
|
1113
|
+
|
|
1114
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1115
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
|
1116
|
+
|
|
1117
|
+
if result.returncode == 0:
|
|
1118
|
+
return {
|
|
1119
|
+
"success": True,
|
|
1120
|
+
"message": f"Tests passed for release '{release_name}'",
|
|
1121
|
+
"output": result.stdout.strip()
|
|
1122
|
+
}
|
|
1123
|
+
else:
|
|
1124
|
+
return {
|
|
1125
|
+
"success": False,
|
|
1126
|
+
"error": "Tests failed",
|
|
1127
|
+
"output": result.stdout.strip(),
|
|
1128
|
+
"stderr": result.stderr.strip()
|
|
1129
|
+
}
|
|
1130
|
+
except Exception as e:
|
|
1131
|
+
logger.error(f"Error running tests: {e}")
|
|
1132
|
+
return {"success": False, "error": str(e)}
|
|
1133
|
+
|
|
1134
|
+
@server.tool(
|
|
1135
|
+
annotations=ToolAnnotations(
|
|
1136
|
+
title="Helm Lint",
|
|
1137
|
+
readOnlyHint=True,
|
|
1138
|
+
),
|
|
1139
|
+
)
|
|
1140
|
+
def helm_lint(
|
|
1141
|
+
chart_path: str,
|
|
1142
|
+
values: Optional[str] = None,
|
|
1143
|
+
strict: bool = False,
|
|
1144
|
+
with_subcharts: bool = False
|
|
1145
|
+
) -> Dict[str, Any]:
|
|
1146
|
+
"""Lint a Helm chart for issues.
|
|
1147
|
+
|
|
1148
|
+
Args:
|
|
1149
|
+
chart_path: Path to the chart directory
|
|
1150
|
+
values: Values file path or --set values
|
|
1151
|
+
strict: Fail on lint warnings
|
|
1152
|
+
with_subcharts: Lint dependent charts
|
|
1153
|
+
"""
|
|
1154
|
+
if not check_helm_fn():
|
|
1155
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1156
|
+
|
|
1157
|
+
try:
|
|
1158
|
+
cmd = ["helm", "lint", chart_path]
|
|
1159
|
+
|
|
1160
|
+
if values:
|
|
1161
|
+
if values.endswith(('.yaml', '.yml', '.json')):
|
|
1162
|
+
cmd.extend(["-f", values])
|
|
1163
|
+
else:
|
|
1164
|
+
cmd.extend(["--set", values])
|
|
1165
|
+
if strict:
|
|
1166
|
+
cmd.append("--strict")
|
|
1167
|
+
if with_subcharts:
|
|
1168
|
+
cmd.append("--with-subcharts")
|
|
1169
|
+
|
|
1170
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1171
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
1172
|
+
|
|
1173
|
+
if result.returncode == 0:
|
|
1174
|
+
return {
|
|
1175
|
+
"success": True,
|
|
1176
|
+
"message": "Chart linting passed",
|
|
1177
|
+
"output": result.stdout.strip()
|
|
1178
|
+
}
|
|
1179
|
+
else:
|
|
1180
|
+
return {
|
|
1181
|
+
"success": False,
|
|
1182
|
+
"error": "Chart linting failed",
|
|
1183
|
+
"output": result.stdout.strip(),
|
|
1184
|
+
"stderr": result.stderr.strip()
|
|
1185
|
+
}
|
|
1186
|
+
except Exception as e:
|
|
1187
|
+
logger.error(f"Error linting chart: {e}")
|
|
1188
|
+
return {"success": False, "error": str(e)}
|
|
1189
|
+
|
|
1190
|
+
@server.tool(
|
|
1191
|
+
annotations=ToolAnnotations(
|
|
1192
|
+
title="Helm Package",
|
|
1193
|
+
readOnlyHint=False,
|
|
1194
|
+
),
|
|
1195
|
+
)
|
|
1196
|
+
def helm_package(
|
|
1197
|
+
chart_path: str,
|
|
1198
|
+
destination: Optional[str] = None,
|
|
1199
|
+
version: Optional[str] = None,
|
|
1200
|
+
app_version: Optional[str] = None,
|
|
1201
|
+
dependency_update: bool = False
|
|
1202
|
+
) -> Dict[str, Any]:
|
|
1203
|
+
"""Package a Helm chart into a versioned archive.
|
|
1204
|
+
|
|
1205
|
+
Args:
|
|
1206
|
+
chart_path: Path to the chart directory
|
|
1207
|
+
destination: Output directory for the package
|
|
1208
|
+
version: Override the chart version
|
|
1209
|
+
app_version: Override the app version
|
|
1210
|
+
dependency_update: Update dependencies before packaging
|
|
1211
|
+
"""
|
|
1212
|
+
if not check_helm_fn():
|
|
1213
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1214
|
+
|
|
1215
|
+
try:
|
|
1216
|
+
cmd = ["helm", "package", chart_path]
|
|
1217
|
+
|
|
1218
|
+
if destination:
|
|
1219
|
+
cmd.extend(["--destination", destination])
|
|
1220
|
+
if version:
|
|
1221
|
+
cmd.extend(["--version", version])
|
|
1222
|
+
if app_version:
|
|
1223
|
+
cmd.extend(["--app-version", app_version])
|
|
1224
|
+
if dependency_update:
|
|
1225
|
+
cmd.append("--dependency-update")
|
|
1226
|
+
|
|
1227
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1228
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
1229
|
+
|
|
1230
|
+
if result.returncode == 0:
|
|
1231
|
+
return {
|
|
1232
|
+
"success": True,
|
|
1233
|
+
"message": "Chart packaged successfully",
|
|
1234
|
+
"output": result.stdout.strip()
|
|
1235
|
+
}
|
|
1236
|
+
else:
|
|
1237
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1238
|
+
except Exception as e:
|
|
1239
|
+
logger.error(f"Error packaging chart: {e}")
|
|
1240
|
+
return {"success": False, "error": str(e)}
|
|
1241
|
+
|
|
1242
|
+
@server.tool(
|
|
1243
|
+
annotations=ToolAnnotations(
|
|
1244
|
+
title="Helm Dependency Update",
|
|
1245
|
+
readOnlyHint=False,
|
|
1246
|
+
),
|
|
1247
|
+
)
|
|
1248
|
+
def helm_dependency_update(chart_path: str, skip_refresh: bool = False) -> Dict[str, Any]:
|
|
1249
|
+
"""Update chart dependencies (download from Chart.yaml).
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
chart_path: Path to the chart directory
|
|
1253
|
+
skip_refresh: Don't refresh repo cache
|
|
1254
|
+
"""
|
|
1255
|
+
if not check_helm_fn():
|
|
1256
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1257
|
+
|
|
1258
|
+
try:
|
|
1259
|
+
cmd = ["helm", "dependency", "update", chart_path]
|
|
1260
|
+
|
|
1261
|
+
if skip_refresh:
|
|
1262
|
+
cmd.append("--skip-refresh")
|
|
1263
|
+
|
|
1264
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1265
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
1266
|
+
|
|
1267
|
+
if result.returncode == 0:
|
|
1268
|
+
return {
|
|
1269
|
+
"success": True,
|
|
1270
|
+
"message": "Dependencies updated successfully",
|
|
1271
|
+
"output": result.stdout.strip()
|
|
1272
|
+
}
|
|
1273
|
+
else:
|
|
1274
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1275
|
+
except Exception as e:
|
|
1276
|
+
logger.error(f"Error updating dependencies: {e}")
|
|
1277
|
+
return {"success": False, "error": str(e)}
|
|
1278
|
+
|
|
1279
|
+
@server.tool(
|
|
1280
|
+
annotations=ToolAnnotations(
|
|
1281
|
+
title="Helm Dependency List",
|
|
1282
|
+
readOnlyHint=True,
|
|
1283
|
+
),
|
|
1284
|
+
)
|
|
1285
|
+
def helm_dependency_list(chart_path: str) -> Dict[str, Any]:
|
|
1286
|
+
"""List chart dependencies.
|
|
1287
|
+
|
|
1288
|
+
Args:
|
|
1289
|
+
chart_path: Path to the chart directory
|
|
1290
|
+
"""
|
|
1291
|
+
if not check_helm_fn():
|
|
1292
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1293
|
+
|
|
1294
|
+
try:
|
|
1295
|
+
cmd = ["helm", "dependency", "list", chart_path]
|
|
1296
|
+
|
|
1297
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1298
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
1299
|
+
|
|
1300
|
+
if result.returncode == 0:
|
|
1301
|
+
return {
|
|
1302
|
+
"success": True,
|
|
1303
|
+
"dependencies": result.stdout.strip()
|
|
1304
|
+
}
|
|
1305
|
+
else:
|
|
1306
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1307
|
+
except Exception as e:
|
|
1308
|
+
logger.error(f"Error listing dependencies: {e}")
|
|
1309
|
+
return {"success": False, "error": str(e)}
|
|
1310
|
+
|
|
1311
|
+
@server.tool(
|
|
1312
|
+
annotations=ToolAnnotations(
|
|
1313
|
+
title="Helm Dependency Build",
|
|
1314
|
+
readOnlyHint=False,
|
|
1315
|
+
),
|
|
1316
|
+
)
|
|
1317
|
+
def helm_dependency_build(chart_path: str, skip_refresh: bool = False) -> Dict[str, Any]:
|
|
1318
|
+
"""Build chart dependencies from Chart.lock.
|
|
1319
|
+
|
|
1320
|
+
Args:
|
|
1321
|
+
chart_path: Path to the chart directory
|
|
1322
|
+
skip_refresh: Don't refresh repo cache
|
|
1323
|
+
"""
|
|
1324
|
+
if not check_helm_fn():
|
|
1325
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1326
|
+
|
|
1327
|
+
try:
|
|
1328
|
+
cmd = ["helm", "dependency", "build", chart_path]
|
|
1329
|
+
|
|
1330
|
+
if skip_refresh:
|
|
1331
|
+
cmd.append("--skip-refresh")
|
|
1332
|
+
|
|
1333
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1334
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
1335
|
+
|
|
1336
|
+
if result.returncode == 0:
|
|
1337
|
+
return {
|
|
1338
|
+
"success": True,
|
|
1339
|
+
"message": "Dependencies built successfully",
|
|
1340
|
+
"output": result.stdout.strip()
|
|
1341
|
+
}
|
|
1342
|
+
else:
|
|
1343
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1344
|
+
except Exception as e:
|
|
1345
|
+
logger.error(f"Error building dependencies: {e}")
|
|
1346
|
+
return {"success": False, "error": str(e)}
|
|
1347
|
+
|
|
1348
|
+
@server.tool(
|
|
1349
|
+
annotations=ToolAnnotations(
|
|
1350
|
+
title="Helm Pull",
|
|
1351
|
+
readOnlyHint=False,
|
|
1352
|
+
),
|
|
1353
|
+
)
|
|
1354
|
+
def helm_pull(
|
|
1355
|
+
chart: str,
|
|
1356
|
+
repo: Optional[str] = None,
|
|
1357
|
+
version: Optional[str] = None,
|
|
1358
|
+
destination: Optional[str] = None,
|
|
1359
|
+
untar: bool = True
|
|
1360
|
+
) -> Dict[str, Any]:
|
|
1361
|
+
"""Download a chart from a repository.
|
|
1362
|
+
|
|
1363
|
+
Args:
|
|
1364
|
+
chart: Chart reference (e.g., 'bitnami/nginx')
|
|
1365
|
+
repo: Repository URL
|
|
1366
|
+
version: Specific chart version
|
|
1367
|
+
destination: Download directory
|
|
1368
|
+
untar: Extract the chart archive
|
|
1369
|
+
"""
|
|
1370
|
+
if not check_helm_fn():
|
|
1371
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1372
|
+
|
|
1373
|
+
try:
|
|
1374
|
+
cmd = ["helm", "pull", chart]
|
|
1375
|
+
|
|
1376
|
+
if repo:
|
|
1377
|
+
cmd.extend(["--repo", repo])
|
|
1378
|
+
if version:
|
|
1379
|
+
cmd.extend(["--version", version])
|
|
1380
|
+
if destination:
|
|
1381
|
+
cmd.extend(["--destination", destination])
|
|
1382
|
+
if untar:
|
|
1383
|
+
cmd.append("--untar")
|
|
1384
|
+
|
|
1385
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1386
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
1387
|
+
|
|
1388
|
+
if result.returncode == 0:
|
|
1389
|
+
return {
|
|
1390
|
+
"success": True,
|
|
1391
|
+
"message": f"Chart '{chart}' downloaded successfully",
|
|
1392
|
+
"output": result.stdout.strip() if result.stdout.strip() else "Download complete"
|
|
1393
|
+
}
|
|
1394
|
+
else:
|
|
1395
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1396
|
+
except Exception as e:
|
|
1397
|
+
logger.error(f"Error pulling chart: {e}")
|
|
1398
|
+
return {"success": False, "error": str(e)}
|
|
1399
|
+
|
|
1400
|
+
@server.tool(
|
|
1401
|
+
annotations=ToolAnnotations(
|
|
1402
|
+
title="Helm Create",
|
|
1403
|
+
readOnlyHint=False,
|
|
1404
|
+
),
|
|
1405
|
+
)
|
|
1406
|
+
def helm_create(name: str, starter: Optional[str] = None) -> Dict[str, Any]:
|
|
1407
|
+
"""Create a new Helm chart with the given name.
|
|
1408
|
+
|
|
1409
|
+
Args:
|
|
1410
|
+
name: Name of the chart to create
|
|
1411
|
+
starter: Name of the starter chart to use
|
|
1412
|
+
"""
|
|
1413
|
+
if not check_helm_fn():
|
|
1414
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1415
|
+
|
|
1416
|
+
try:
|
|
1417
|
+
cmd = ["helm", "create", name]
|
|
1418
|
+
|
|
1419
|
+
if starter:
|
|
1420
|
+
cmd.extend(["--starter", starter])
|
|
1421
|
+
|
|
1422
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1423
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
1424
|
+
|
|
1425
|
+
if result.returncode == 0:
|
|
1426
|
+
return {
|
|
1427
|
+
"success": True,
|
|
1428
|
+
"message": f"Chart '{name}' created successfully",
|
|
1429
|
+
"output": result.stdout.strip()
|
|
1430
|
+
}
|
|
1431
|
+
else:
|
|
1432
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1433
|
+
except Exception as e:
|
|
1434
|
+
logger.error(f"Error creating chart: {e}")
|
|
1435
|
+
return {"success": False, "error": str(e)}
|
|
1436
|
+
|
|
1437
|
+
@server.tool(
|
|
1438
|
+
annotations=ToolAnnotations(
|
|
1439
|
+
title="Helm Version",
|
|
1440
|
+
readOnlyHint=True,
|
|
1441
|
+
),
|
|
1442
|
+
)
|
|
1443
|
+
def helm_version() -> Dict[str, Any]:
|
|
1444
|
+
"""Get the Helm client version information."""
|
|
1445
|
+
if not check_helm_fn():
|
|
1446
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1447
|
+
|
|
1448
|
+
try:
|
|
1449
|
+
cmd = ["helm", "version", "--short"]
|
|
1450
|
+
|
|
1451
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1452
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
1453
|
+
|
|
1454
|
+
if result.returncode == 0:
|
|
1455
|
+
return {
|
|
1456
|
+
"success": True,
|
|
1457
|
+
"version": result.stdout.strip()
|
|
1458
|
+
}
|
|
1459
|
+
else:
|
|
1460
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1461
|
+
except Exception as e:
|
|
1462
|
+
logger.error(f"Error getting Helm version: {e}")
|
|
1463
|
+
return {"success": False, "error": str(e)}
|
|
1464
|
+
|
|
1465
|
+
@server.tool(
|
|
1466
|
+
annotations=ToolAnnotations(
|
|
1467
|
+
title="Helm Environment",
|
|
1468
|
+
readOnlyHint=True,
|
|
1469
|
+
),
|
|
1470
|
+
)
|
|
1471
|
+
def helm_env() -> Dict[str, Any]:
|
|
1472
|
+
"""Get Helm environment information (paths, settings)."""
|
|
1473
|
+
if not check_helm_fn():
|
|
1474
|
+
return {"success": False, "error": "Helm is not available on this system"}
|
|
1475
|
+
|
|
1476
|
+
try:
|
|
1477
|
+
cmd = ["helm", "env"]
|
|
1478
|
+
|
|
1479
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
1480
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
1481
|
+
|
|
1482
|
+
if result.returncode == 0:
|
|
1483
|
+
env_dict = {}
|
|
1484
|
+
for line in result.stdout.strip().split('\n'):
|
|
1485
|
+
if '=' in line:
|
|
1486
|
+
key, value = line.split('=', 1)
|
|
1487
|
+
env_dict[key] = value.strip('"')
|
|
1488
|
+
return {
|
|
1489
|
+
"success": True,
|
|
1490
|
+
"environment": env_dict,
|
|
1491
|
+
"raw": result.stdout.strip()
|
|
1492
|
+
}
|
|
1493
|
+
else:
|
|
1494
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1495
|
+
except Exception as e:
|
|
1496
|
+
logger.error(f"Error getting Helm environment: {e}")
|
|
1497
|
+
return {"success": False, "error": str(e)}
|
|
1498
|
+
|
|
1499
|
+
@server.tool(
|
|
1500
|
+
annotations=ToolAnnotations(
|
|
1501
|
+
title="Helm Template",
|
|
1502
|
+
readOnlyHint=True,
|
|
1503
|
+
),
|
|
1504
|
+
)
|
|
1505
|
+
def helm_template(
|
|
1506
|
+
chart: str,
|
|
1507
|
+
name: str,
|
|
1508
|
+
namespace: str = "default",
|
|
1509
|
+
repo: Optional[str] = None,
|
|
1510
|
+
values: Optional[str] = None
|
|
1511
|
+
) -> Dict[str, Any]:
|
|
1512
|
+
"""Render Helm chart templates locally without installing."""
|
|
1513
|
+
try:
|
|
1514
|
+
cmd = ["helm", "template", name, chart, "-n", namespace]
|
|
1515
|
+
if repo:
|
|
1516
|
+
cmd.extend(["--repo", repo])
|
|
1517
|
+
if values:
|
|
1518
|
+
cmd.extend(["--set", values])
|
|
1519
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
1520
|
+
if result.returncode == 0:
|
|
1521
|
+
return {"success": True, "manifest": result.stdout}
|
|
1522
|
+
else:
|
|
1523
|
+
return {"success": False, "error": result.stderr.strip()}
|
|
1524
|
+
except Exception as e:
|
|
1525
|
+
logger.error(f"Error templating chart: {e}")
|
|
1526
|
+
return {"success": False, "error": str(e)}
|
|
1527
|
+
|
|
1528
|
+
@server.tool(
|
|
1529
|
+
annotations=ToolAnnotations(
|
|
1530
|
+
title="Helm Template Apply",
|
|
1531
|
+
destructiveHint=True,
|
|
1532
|
+
),
|
|
1533
|
+
)
|
|
1534
|
+
def helm_template_apply(
|
|
1535
|
+
chart: str,
|
|
1536
|
+
name: str,
|
|
1537
|
+
namespace: str = "default",
|
|
1538
|
+
repo: Optional[str] = None,
|
|
1539
|
+
values: Optional[str] = None
|
|
1540
|
+
) -> Dict[str, Any]:
|
|
1541
|
+
"""Render and apply Helm chart (bypasses Tiller/auth issues)."""
|
|
1542
|
+
if non_destructive:
|
|
1543
|
+
return {"success": False, "error": "Operation blocked: non-destructive mode enabled"}
|
|
1544
|
+
try:
|
|
1545
|
+
cmd = ["helm", "template", name, chart, "-n", namespace]
|
|
1546
|
+
if repo:
|
|
1547
|
+
cmd.extend(["--repo", repo])
|
|
1548
|
+
if values:
|
|
1549
|
+
cmd.extend(["--set", values])
|
|
1550
|
+
template_result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
1551
|
+
if template_result.returncode != 0:
|
|
1552
|
+
return {"success": False, "error": template_result.stderr.strip()}
|
|
1553
|
+
apply_cmd = ["kubectl", "apply", "-f", "-", "-n", namespace]
|
|
1554
|
+
apply_result = subprocess.run(apply_cmd, input=template_result.stdout, capture_output=True, text=True, timeout=60)
|
|
1555
|
+
if apply_result.returncode == 0:
|
|
1556
|
+
return {"success": True, "output": apply_result.stdout.strip()}
|
|
1557
|
+
else:
|
|
1558
|
+
return {"success": False, "error": apply_result.stderr.strip()}
|
|
1559
|
+
except Exception as e:
|
|
1560
|
+
logger.error(f"Error applying template: {e}")
|
|
1561
|
+
return {"success": False, "error": str(e)}
|