kubectl-mcp-server 1.18.0__py3-none-any.whl → 1.19.1__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.
@@ -71,6 +71,7 @@ from kubectl_mcp_tool.tools import (
71
71
  register_pod_tools,
72
72
  register_core_tools,
73
73
  register_cluster_tools,
74
+ register_multicluster_tools,
74
75
  register_deployment_tools,
75
76
  register_security_tools,
76
77
  register_networking_tools,
@@ -314,6 +315,7 @@ class MCPServer:
314
315
  register_pod_tools(self.server, self.non_destructive)
315
316
  register_core_tools(self.server, self.non_destructive)
316
317
  register_cluster_tools(self.server, self.non_destructive)
318
+ register_multicluster_tools(self.server, self.non_destructive)
317
319
  register_deployment_tools(self.server, self.non_destructive)
318
320
  register_security_tools(self.server, self.non_destructive)
319
321
  register_networking_tools(self.server, self.non_destructive)
@@ -741,8 +743,36 @@ if __name__ == "__main__":
741
743
  action="store_true",
742
744
  help="Disable destructive operations (allow create/update, block delete).",
743
745
  )
746
+ parser.add_argument(
747
+ "--stateless",
748
+ action="store_true",
749
+ help="Enable stateless mode (don't cache API clients, reload config each request). Useful for serverless environments.",
750
+ )
751
+ parser.add_argument(
752
+ "--watch-kubeconfig",
753
+ action="store_true",
754
+ help="Watch kubeconfig files for changes and auto-reload. Useful when credentials are refreshed externally.",
755
+ )
756
+ parser.add_argument(
757
+ "--watch-interval",
758
+ type=float,
759
+ default=5.0,
760
+ help="Interval in seconds for kubeconfig watch checks. Default: 5.0.",
761
+ )
744
762
  args = parser.parse_args()
745
763
 
764
+ # Configure stateless mode if requested
765
+ if args.stateless or os.environ.get("MCP_STATELESS_MODE", "").lower() in ("true", "1", "yes"):
766
+ from kubectl_mcp_tool.k8s_config import set_stateless_mode
767
+ set_stateless_mode(True)
768
+ logger.info("Stateless mode enabled via CLI flag")
769
+
770
+ # Configure kubeconfig watching if requested
771
+ if args.watch_kubeconfig or os.environ.get("MCP_KUBECONFIG_WATCH", "").lower() in ("true", "1", "yes"):
772
+ from kubectl_mcp_tool.k8s_config import enable_kubeconfig_watch
773
+ enable_kubeconfig_watch(check_interval=args.watch_interval)
774
+ logger.info(f"Kubeconfig watching enabled (interval: {args.watch_interval}s)")
775
+
746
776
  server_name = "kubectl_mcp_server"
747
777
  mcp_server = MCPServer(
748
778
  name=server_name,
@@ -1,7 +1,7 @@
1
1
  from .helm import register_helm_tools
2
2
  from .pods import register_pod_tools
3
3
  from .core import register_core_tools
4
- from .cluster import register_cluster_tools
4
+ from .cluster import register_cluster_tools, register_multicluster_tools
5
5
  from .deployments import register_deployment_tools
6
6
  from .security import register_security_tools
7
7
  from .networking import register_networking_tools
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "register_pod_tools",
28
28
  "register_core_tools",
29
29
  "register_cluster_tools",
30
+ "register_multicluster_tools",
30
31
  "register_deployment_tools",
31
32
  "register_security_tools",
32
33
  "register_networking_tools",
@@ -1,11 +1,8 @@
1
- """Backup toolset for kubectl-mcp-server.
2
-
3
- Provides tools for managing Velero backups and restores.
4
- """
1
+ """Backup toolset for kubectl-mcp-server (Velero backups and restores)."""
5
2
 
6
3
  import subprocess
7
4
  import json
8
- from typing import Dict, Any, List, Optional
5
+ from typing import Dict, Any, List
9
6
  from datetime import datetime
10
7
 
11
8
  try:
@@ -15,8 +12,8 @@ except ImportError:
15
12
  from mcp.server.fastmcp import FastMCP
16
13
  from mcp.types import ToolAnnotations
17
14
 
18
- from ..k8s_config import _get_kubectl_context_args
19
15
  from ..crd_detector import crd_exists
16
+ from .utils import run_kubectl, get_resources
20
17
 
21
18
 
22
19
  VELERO_BACKUP_CRD = "backups.velero.io"
@@ -26,40 +23,6 @@ VELERO_BSL_CRD = "backupstoragelocations.velero.io"
26
23
  VELERO_VSL_CRD = "volumesnapshotlocations.velero.io"
27
24
 
28
25
 
29
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
30
- """Run kubectl command and return result."""
31
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
32
- try:
33
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
34
- if result.returncode == 0:
35
- return {"success": True, "output": result.stdout}
36
- return {"success": False, "error": result.stderr}
37
- except subprocess.TimeoutExpired:
38
- return {"success": False, "error": "Command timed out"}
39
- except Exception as e:
40
- return {"success": False, "error": str(e)}
41
-
42
-
43
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
44
- """Get Kubernetes resources of a specific kind."""
45
- args = ["get", kind, "-o", "json"]
46
- if namespace:
47
- args.extend(["-n", namespace])
48
- else:
49
- args.append("-A")
50
- if label_selector:
51
- args.extend(["-l", label_selector])
52
-
53
- result = _run_kubectl(args, context)
54
- if result["success"]:
55
- try:
56
- data = json.loads(result["output"])
57
- return data.get("items", [])
58
- except json.JSONDecodeError:
59
- return []
60
- return []
61
-
62
-
63
26
  def _velero_cli_available() -> bool:
64
27
  """Check if velero CLI is available."""
65
28
  try:
@@ -112,7 +75,7 @@ def backup_list(
112
75
  }
113
76
 
114
77
  backups = []
115
- for item in _get_resources("backups.velero.io", namespace, context, label_selector):
78
+ for item in get_resources("backups.velero.io", namespace, context, label_selector):
116
79
  status = item.get("status", {})
117
80
  spec = item.get("spec", {})
118
81
  progress = status.get("progress", {})
@@ -165,7 +128,7 @@ def backup_get(
165
128
  return {"success": False, "error": "Velero is not installed"}
166
129
 
167
130
  args = ["get", "backups.velero.io", name, "-n", namespace, "-o", "json"]
168
- result = _run_kubectl(args, context)
131
+ result = run_kubectl(args, context)
169
132
 
170
133
  if result["success"]:
171
134
  try:
@@ -341,7 +304,7 @@ def backup_delete(
341
304
  return result
342
305
 
343
306
  args = ["delete", "backups.velero.io", name, "-n", namespace]
344
- result = _run_kubectl(args, context)
307
+ result = run_kubectl(args, context)
345
308
 
346
309
  if result["success"]:
347
310
  return {
@@ -375,7 +338,7 @@ def restore_list(
375
338
  }
376
339
 
377
340
  restores = []
378
- for item in _get_resources("restores.velero.io", namespace, context, label_selector):
341
+ for item in get_resources("restores.velero.io", namespace, context, label_selector):
379
342
  status = item.get("status", {})
380
343
  spec = item.get("spec", {})
381
344
  progress = status.get("progress", {})
@@ -536,7 +499,7 @@ def restore_get(
536
499
  return {"success": False, "error": "Velero is not installed"}
537
500
 
538
501
  args = ["get", "restores.velero.io", name, "-n", namespace, "-o", "json"]
539
- result = _run_kubectl(args, context)
502
+ result = run_kubectl(args, context)
540
503
 
541
504
  if result["success"]:
542
505
  try:
@@ -572,7 +535,7 @@ def backup_locations_list(
572
535
  }
573
536
 
574
537
  locations = []
575
- for item in _get_resources("backupstoragelocations.velero.io", namespace, context):
538
+ for item in get_resources("backupstoragelocations.velero.io", namespace, context):
576
539
  status = item.get("status", {})
577
540
  spec = item.get("spec", {})
578
541
 
@@ -615,7 +578,7 @@ def backup_schedules_list(
615
578
  }
616
579
 
617
580
  schedules = []
618
- for item in _get_resources("schedules.velero.io", namespace, context):
581
+ for item in get_resources("schedules.velero.io", namespace, context):
619
582
  status = item.get("status", {})
620
583
  spec = item.get("spec", {})
621
584
  template = spec.get("template", {})
@@ -1,7 +1,4 @@
1
- """Cluster API (CAPI) toolset for kubectl-mcp-server.
2
-
3
- Provides tools for managing Cluster API clusters and machine lifecycle.
4
- """
1
+ """Cluster API (CAPI) toolset for kubectl-mcp-server."""
5
2
 
6
3
  import subprocess
7
4
  import json
@@ -14,11 +11,10 @@ except ImportError:
14
11
  from mcp.server.fastmcp import FastMCP
15
12
  from mcp.types import ToolAnnotations
16
13
 
17
- from ..k8s_config import _get_kubectl_context_args
18
14
  from ..crd_detector import crd_exists
15
+ from .utils import run_kubectl, get_resources
19
16
 
20
17
 
21
- # Cluster API CRDs
22
18
  CLUSTER_CRD = "clusters.cluster.x-k8s.io"
23
19
  MACHINE_CRD = "machines.cluster.x-k8s.io"
24
20
  MACHINEDEPLOYMENT_CRD = "machinedeployments.cluster.x-k8s.io"
@@ -28,40 +24,6 @@ MACHINEHEALTHCHECK_CRD = "machinehealthchecks.cluster.x-k8s.io"
28
24
  CLUSTERCLASS_CRD = "clusterclasses.cluster.x-k8s.io"
29
25
 
30
26
 
31
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
32
- """Run kubectl command and return result."""
33
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
34
- try:
35
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
36
- if result.returncode == 0:
37
- return {"success": True, "output": result.stdout}
38
- return {"success": False, "error": result.stderr}
39
- except subprocess.TimeoutExpired:
40
- return {"success": False, "error": "Command timed out"}
41
- except Exception as e:
42
- return {"success": False, "error": str(e)}
43
-
44
-
45
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
46
- """Get Kubernetes resources of a specific kind."""
47
- args = ["get", kind, "-o", "json"]
48
- if namespace:
49
- args.extend(["-n", namespace])
50
- else:
51
- args.append("-A")
52
- if label_selector:
53
- args.extend(["-l", label_selector])
54
-
55
- result = _run_kubectl(args, context)
56
- if result["success"]:
57
- try:
58
- data = json.loads(result["output"])
59
- return data.get("items", [])
60
- except json.JSONDecodeError:
61
- return []
62
- return []
63
-
64
-
65
27
  def _clusterctl_available() -> bool:
66
28
  """Check if clusterctl CLI is available."""
67
29
  try:
@@ -93,12 +55,10 @@ def capi_clusters_list(
93
55
  }
94
56
 
95
57
  clusters = []
96
- for item in _get_resources("clusters.cluster.x-k8s.io", namespace, context, label_selector):
58
+ for item in get_resources("clusters.cluster.x-k8s.io", namespace, context, label_selector):
97
59
  status = item.get("status", {})
98
60
  spec = item.get("spec", {})
99
61
  conditions = status.get("conditions", [])
100
-
101
- # Find key conditions
102
62
  ready_cond = next((c for c in conditions if c.get("type") == "Ready"), {})
103
63
  infra_ready = next((c for c in conditions if c.get("type") == "InfrastructureReady"), {})
104
64
  cp_ready = next((c for c in conditions if c.get("type") == "ControlPlaneReady"), {})
@@ -120,7 +80,6 @@ def capi_clusters_list(
120
80
  "failure_message": status.get("failureMessage"),
121
81
  })
122
82
 
123
- # Summary
124
83
  ready = sum(1 for c in clusters if c["ready"])
125
84
  provisioning = sum(1 for c in clusters if c["phase"] == "Provisioning")
126
85
 
@@ -152,7 +111,7 @@ def capi_cluster_get(
152
111
  return {"success": False, "error": "Cluster API is not installed"}
153
112
 
154
113
  args = ["get", "clusters.cluster.x-k8s.io", name, "-n", namespace, "-o", "json"]
155
- result = _run_kubectl(args, context)
114
+ result = run_kubectl(args, context)
156
115
 
157
116
  if result["success"]:
158
117
  try:
@@ -191,14 +150,13 @@ def capi_machines_list(
191
150
  "error": "Cluster API is not installed (machines.cluster.x-k8s.io CRD not found)"
192
151
  }
193
152
 
194
- # Build label selector
195
153
  selector = label_selector
196
154
  if cluster_name:
197
155
  cluster_label = f"cluster.x-k8s.io/cluster-name={cluster_name}"
198
156
  selector = f"{selector},{cluster_label}" if selector else cluster_label
199
157
 
200
158
  machines = []
201
- for item in _get_resources("machines.cluster.x-k8s.io", namespace, context, selector):
159
+ for item in get_resources("machines.cluster.x-k8s.io", namespace, context, selector):
202
160
  status = item.get("status", {})
203
161
  spec = item.get("spec", {})
204
162
  conditions = status.get("conditions", [])
@@ -223,7 +181,6 @@ def capi_machines_list(
223
181
  "failure_message": status.get("failureMessage"),
224
182
  })
225
183
 
226
- # Summary
227
184
  ready = sum(1 for m in machines if m["ready"])
228
185
  running = sum(1 for m in machines if m["phase"] == "Running")
229
186
 
@@ -255,7 +212,7 @@ def capi_machine_get(
255
212
  return {"success": False, "error": "Cluster API is not installed"}
256
213
 
257
214
  args = ["get", "machines.cluster.x-k8s.io", name, "-n", namespace, "-o", "json"]
258
- result = _run_kubectl(args, context)
215
+ result = run_kubectl(args, context)
259
216
 
260
217
  if result["success"]:
261
218
  try:
@@ -294,14 +251,13 @@ def capi_machinedeployments_list(
294
251
  "error": "MachineDeployments CRD not found"
295
252
  }
296
253
 
297
- # Build label selector
298
254
  selector = label_selector
299
255
  if cluster_name:
300
256
  cluster_label = f"cluster.x-k8s.io/cluster-name={cluster_name}"
301
257
  selector = f"{selector},{cluster_label}" if selector else cluster_label
302
258
 
303
259
  deployments = []
304
- for item in _get_resources("machinedeployments.cluster.x-k8s.io", namespace, context, selector):
260
+ for item in get_resources("machinedeployments.cluster.x-k8s.io", namespace, context, selector):
305
261
  status = item.get("status", {})
306
262
  spec = item.get("spec", {})
307
263
  conditions = status.get("conditions", [])
@@ -361,7 +317,7 @@ def capi_machinedeployment_scale(
361
317
  "-n", namespace,
362
318
  f"--replicas={replicas}"
363
319
  ]
364
- result = _run_kubectl(args, context)
320
+ result = run_kubectl(args, context)
365
321
 
366
322
  if result["success"]:
367
323
  return {
@@ -402,7 +358,7 @@ def capi_machinesets_list(
402
358
  selector = f"{selector},{cluster_label}" if selector else cluster_label
403
359
 
404
360
  machinesets = []
405
- for item in _get_resources("machinesets.cluster.x-k8s.io", namespace, context, selector):
361
+ for item in get_resources("machinesets.cluster.x-k8s.io", namespace, context, selector):
406
362
  status = item.get("status", {})
407
363
  spec = item.get("spec", {})
408
364
 
@@ -457,7 +413,7 @@ def capi_machinehealthchecks_list(
457
413
  selector = f"{selector},{cluster_label}" if selector else cluster_label
458
414
 
459
415
  healthchecks = []
460
- for item in _get_resources("machinehealthchecks.cluster.x-k8s.io", namespace, context, selector):
416
+ for item in get_resources("machinehealthchecks.cluster.x-k8s.io", namespace, context, selector):
461
417
  status = item.get("status", {})
462
418
  spec = item.get("spec", {})
463
419
  conditions = status.get("conditions", [])
@@ -506,7 +462,7 @@ def capi_clusterclasses_list(
506
462
  }
507
463
 
508
464
  classes = []
509
- for item in _get_resources("clusterclasses.cluster.x-k8s.io", namespace, context, label_selector):
465
+ for item in get_resources("clusterclasses.cluster.x-k8s.io", namespace, context, label_selector):
510
466
  spec = item.get("spec", {})
511
467
  status = item.get("status", {})
512
468
  conditions = status.get("conditions", [])
@@ -559,7 +515,7 @@ def capi_cluster_kubeconfig(
559
515
  # CAPI stores kubeconfig in a secret named <cluster-name>-kubeconfig
560
516
  secret_name = f"{name}-kubeconfig"
561
517
  args = ["get", "secret", secret_name, "-n", namespace, "-o", "json"]
562
- result = _run_kubectl(args, context)
518
+ result = run_kubectl(args, context)
563
519
 
564
520
  if result["success"]:
565
521
  try:
@@ -1,12 +1,8 @@
1
- """Cert-Manager toolset for kubectl-mcp-server.
1
+ """Cert-Manager toolset for kubectl-mcp-server."""
2
2
 
3
- Provides tools for managing certificates via cert-manager.
4
- """
5
-
6
- import subprocess
7
3
  import json
8
4
  from typing import Dict, Any, List, Optional
9
- from datetime import datetime, timezone
5
+ from datetime import datetime
10
6
 
11
7
  try:
12
8
  from fastmcp import FastMCP
@@ -15,8 +11,8 @@ except ImportError:
15
11
  from mcp.server.fastmcp import FastMCP
16
12
  from mcp.types import ToolAnnotations
17
13
 
18
- from ..k8s_config import _get_kubectl_context_args
19
14
  from ..crd_detector import crd_exists
15
+ from .utils import run_kubectl
20
16
 
21
17
 
22
18
  CERTIFICATE_CRD = "certificates.cert-manager.io"
@@ -27,27 +23,13 @@ ORDER_CRD = "orders.acme.cert-manager.io"
27
23
  CHALLENGE_CRD = "challenges.acme.cert-manager.io"
28
24
 
29
25
 
30
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
31
- """Run kubectl command and return result."""
32
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
33
- try:
34
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
35
- if result.returncode == 0:
36
- return {"success": True, "output": result.stdout}
37
- return {"success": False, "error": result.stderr}
38
- except subprocess.TimeoutExpired:
39
- return {"success": False, "error": "Command timed out"}
40
- except Exception as e:
41
- return {"success": False, "error": str(e)}
42
-
43
-
44
26
  class CertsResourceError(Exception):
45
27
  """Exception raised when fetching cert-manager resources fails."""
46
28
  pass
47
29
 
48
30
 
49
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
50
- """Get Kubernetes resources of a specific kind.
31
+ def _get_certs_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
32
+ """Get cert-manager resources with error handling.
51
33
 
52
34
  Raises:
53
35
  CertsResourceError: If kubectl command fails or output cannot be parsed
@@ -60,7 +42,7 @@ def _get_resources(kind: str, namespace: str = "", context: str = "", label_sele
60
42
  if label_selector:
61
43
  args.extend(["-l", label_selector])
62
44
 
63
- result = _run_kubectl(args, context)
45
+ result = run_kubectl(args, context)
64
46
  if not result["success"]:
65
47
  error_msg = result.get("error", "Unknown error")
66
48
  raise CertsResourceError(
@@ -115,7 +97,7 @@ def certs_list(
115
97
  }
116
98
 
117
99
  try:
118
- resources = _get_resources("certificates.cert-manager.io", namespace, context, label_selector)
100
+ resources = _get_certs_resources("certificates.cert-manager.io", namespace, context, label_selector)
119
101
  except CertsResourceError as e:
120
102
  return {"success": False, "error": str(e)}
121
103
 
@@ -214,7 +196,7 @@ def certs_issuers_list(
214
196
 
215
197
  if crd_exists(ISSUER_CRD, context):
216
198
  try:
217
- issuer_resources = _get_resources("issuers.cert-manager.io", namespace, context)
199
+ issuer_resources = _get_certs_resources("issuers.cert-manager.io", namespace, context)
218
200
  except CertsResourceError as e:
219
201
  return {"success": False, "error": str(e)}
220
202
 
@@ -248,7 +230,7 @@ def certs_issuers_list(
248
230
 
249
231
  if include_cluster_issuers and crd_exists(CLUSTER_ISSUER_CRD, context):
250
232
  try:
251
- cluster_issuer_resources = _get_resources("clusterissuers.cert-manager.io", "", context)
233
+ cluster_issuer_resources = _get_certs_resources("clusterissuers.cert-manager.io", "", context)
252
234
  except CertsResourceError as e:
253
235
  return {"success": False, "error": str(e)}
254
236
 
@@ -528,7 +510,7 @@ def certs_challenges_list(
528
510
  }
529
511
 
530
512
  challenges = []
531
- for item in _get_resources("challenges.acme.cert-manager.io", namespace, context, label_selector):
513
+ for item in _get_certs_resources("challenges.acme.cert-manager.io", namespace, context, label_selector):
532
514
  status = item.get("status", {})
533
515
  spec = item.get("spec", {})
534
516
 
@@ -576,7 +558,7 @@ def certs_requests_list(
576
558
  }
577
559
 
578
560
  requests = []
579
- for item in _get_resources("certificaterequests.cert-manager.io", namespace, context, label_selector):
561
+ for item in _get_certs_resources("certificaterequests.cert-manager.io", namespace, context, label_selector):
580
562
  status = item.get("status", {})
581
563
  conditions = status.get("conditions", [])
582
564
  ready_cond = _get_condition(conditions, "Ready")
@@ -1,11 +1,8 @@
1
- """Cilium/Hubble network toolset for kubectl-mcp-server.
2
-
3
- Provides tools for managing Cilium network policies and observability.
4
- """
1
+ """Cilium/Hubble network toolset for kubectl-mcp-server."""
5
2
 
6
3
  import subprocess
7
4
  import json
8
- from typing import Dict, Any, List, Optional
5
+ from typing import Dict, Any, List
9
6
 
10
7
  try:
11
8
  from fastmcp import FastMCP
@@ -14,8 +11,8 @@ except ImportError:
14
11
  from mcp.server.fastmcp import FastMCP
15
12
  from mcp.types import ToolAnnotations
16
13
 
17
- from ..k8s_config import _get_kubectl_context_args
18
14
  from ..crd_detector import crd_exists
15
+ from .utils import run_kubectl, get_resources
19
16
 
20
17
 
21
18
  CILIUM_NETWORK_POLICY_CRD = "ciliumnetworkpolicies.cilium.io"
@@ -25,40 +22,6 @@ CILIUM_IDENTITY_CRD = "ciliumidentities.cilium.io"
25
22
  CILIUM_NODE_CRD = "ciliumnodes.cilium.io"
26
23
 
27
24
 
28
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
29
- """Run kubectl command and return result."""
30
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
31
- try:
32
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
33
- if result.returncode == 0:
34
- return {"success": True, "output": result.stdout}
35
- return {"success": False, "error": result.stderr}
36
- except subprocess.TimeoutExpired:
37
- return {"success": False, "error": "Command timed out"}
38
- except Exception as e:
39
- return {"success": False, "error": str(e)}
40
-
41
-
42
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
43
- """Get Kubernetes resources of a specific kind."""
44
- args = ["get", kind, "-o", "json"]
45
- if namespace:
46
- args.extend(["-n", namespace])
47
- else:
48
- args.append("-A")
49
- if label_selector:
50
- args.extend(["-l", label_selector])
51
-
52
- result = _run_kubectl(args, context)
53
- if result["success"]:
54
- try:
55
- data = json.loads(result["output"])
56
- return data.get("items", [])
57
- except json.JSONDecodeError:
58
- return []
59
- return []
60
-
61
-
62
25
  def _cilium_cli_available() -> bool:
63
26
  """Check if cilium CLI is available."""
64
27
  try:
@@ -95,7 +58,7 @@ def cilium_policies_list(
95
58
  policies = []
96
59
 
97
60
  if crd_exists(CILIUM_NETWORK_POLICY_CRD, context):
98
- for item in _get_resources("ciliumnetworkpolicies.cilium.io", namespace, context):
61
+ for item in get_resources("ciliumnetworkpolicies.cilium.io", namespace, context):
99
62
  spec = item.get("spec", {})
100
63
  status = item.get("status", {})
101
64
 
@@ -123,7 +86,7 @@ def cilium_policies_list(
123
86
  })
124
87
 
125
88
  if include_clusterwide and crd_exists(CILIUM_CLUSTERWIDE_POLICY_CRD, context):
126
- for item in _get_resources("ciliumclusterwidenetworkpolicies.cilium.io", "", context):
89
+ for item in get_resources("ciliumclusterwidenetworkpolicies.cilium.io", "", context):
127
90
  spec = item.get("spec", {})
128
91
  status = item.get("status", {})
129
92
 
@@ -182,7 +145,7 @@ def cilium_policy_get(
182
145
  if not crd_exists(crd, context):
183
146
  return {"success": False, "error": f"{crd} not found"}
184
147
 
185
- result = _run_kubectl(args, context)
148
+ result = run_kubectl(args, context)
186
149
 
187
150
  if result["success"]:
188
151
  try:
@@ -220,7 +183,7 @@ def cilium_endpoints_list(
220
183
  }
221
184
 
222
185
  endpoints = []
223
- for item in _get_resources("ciliumendpoints.cilium.io", namespace, context, label_selector):
186
+ for item in get_resources("ciliumendpoints.cilium.io", namespace, context, label_selector):
224
187
  status = item.get("status", {})
225
188
  networking = status.get("networking", {})
226
189
  identity = status.get("identity", {})
@@ -275,7 +238,7 @@ def cilium_identities_list(
275
238
  }
276
239
 
277
240
  identities = []
278
- for item in _get_resources("ciliumidentities.cilium.io", "", context, label_selector):
241
+ for item in get_resources("ciliumidentities.cilium.io", "", context, label_selector):
279
242
  security_labels = item.get("security-labels", {})
280
243
 
281
244
  identities.append({
@@ -308,7 +271,7 @@ def cilium_nodes_list(context: str = "") -> Dict[str, Any]:
308
271
  }
309
272
 
310
273
  nodes = []
311
- for item in _get_resources("ciliumnodes.cilium.io", "", context):
274
+ for item in get_resources("ciliumnodes.cilium.io", "", context):
312
275
  spec = item.get("spec", {})
313
276
  status = item.get("status", {})
314
277
 
@@ -363,7 +326,7 @@ def cilium_status(context: str = "") -> Dict[str, Any]:
363
326
  # Fallback to kubectl
364
327
  # Check Cilium pods status
365
328
  args = ["get", "pods", "-n", "kube-system", "-l", "k8s-app=cilium", "-o", "json"]
366
- result = _run_kubectl(args, context)
329
+ result = run_kubectl(args, context)
367
330
 
368
331
  if not result["success"]:
369
332
  return {"success": False, "error": result.get("error", "Failed to get Cilium status")}