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.
- {kubectl_mcp_server-1.18.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/METADATA +41 -18
- {kubectl_mcp_server-1.18.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/RECORD +24 -23
- {kubectl_mcp_server-1.18.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/WHEEL +1 -1
- kubectl_mcp_tool/__init__.py +1 -1
- kubectl_mcp_tool/k8s_config.py +233 -340
- kubectl_mcp_tool/mcp_server.py +30 -0
- kubectl_mcp_tool/tools/__init__.py +2 -1
- kubectl_mcp_tool/tools/backup.py +10 -47
- kubectl_mcp_tool/tools/capi.py +12 -56
- kubectl_mcp_tool/tools/certs.py +11 -29
- kubectl_mcp_tool/tools/cilium.py +10 -47
- kubectl_mcp_tool/tools/cluster.py +489 -9
- kubectl_mcp_tool/tools/gitops.py +12 -51
- kubectl_mcp_tool/tools/keda.py +9 -47
- kubectl_mcp_tool/tools/kiali.py +10 -50
- kubectl_mcp_tool/tools/kubevirt.py +11 -49
- kubectl_mcp_tool/tools/pods.py +93 -0
- kubectl_mcp_tool/tools/policy.py +11 -49
- kubectl_mcp_tool/tools/rollouts.py +11 -65
- kubectl_mcp_tool/tools/utils.py +41 -0
- tests/test_tools.py +44 -11
- {kubectl_mcp_server-1.18.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.18.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.18.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/top_level.txt +0 -0
kubectl_mcp_tool/k8s_config.py
CHANGED
|
@@ -14,15 +14,190 @@ Environment Variables:
|
|
|
14
14
|
MCP_K8S_QPS: API rate limit (default: 100)
|
|
15
15
|
MCP_K8S_BURST: API burst limit (default: 200)
|
|
16
16
|
MCP_K8S_TIMEOUT: Request timeout in seconds (default: 30)
|
|
17
|
+
MCP_STATELESS_MODE: If "true", don't cache API clients (reload config each request)
|
|
18
|
+
MCP_KUBECONFIG_WATCH: If "true", auto-detect kubeconfig file changes
|
|
17
19
|
"""
|
|
18
20
|
|
|
19
21
|
import os
|
|
20
22
|
import logging
|
|
21
|
-
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
from typing import Optional, Any, List, Dict, Callable
|
|
22
26
|
|
|
23
27
|
logger = logging.getLogger("mcp-server")
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
_stateless_mode = os.environ.get("MCP_STATELESS_MODE", "").lower() in ("true", "1", "yes")
|
|
30
|
+
_kubeconfig_watcher: Optional["KubeconfigWatcher"] = None
|
|
31
|
+
_kubeconfig_last_mtime: Dict[str, float] = {}
|
|
32
|
+
_config_change_callbacks: List[Callable[[], None]] = []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class KubeconfigWatcher:
|
|
36
|
+
"""Watch kubeconfig files for changes and trigger reloads.
|
|
37
|
+
|
|
38
|
+
This class monitors kubeconfig files and automatically invalidates
|
|
39
|
+
cached configurations when changes are detected.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, check_interval: float = 5.0):
|
|
43
|
+
"""Initialize the kubeconfig watcher.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
check_interval: How often to check for changes (seconds)
|
|
47
|
+
"""
|
|
48
|
+
self._check_interval = check_interval
|
|
49
|
+
self._running = False
|
|
50
|
+
self._thread: Optional[threading.Thread] = None
|
|
51
|
+
self._watched_files: Dict[str, float] = {}
|
|
52
|
+
self._lock = threading.Lock()
|
|
53
|
+
|
|
54
|
+
def start(self):
|
|
55
|
+
"""Start watching for kubeconfig changes."""
|
|
56
|
+
if self._running:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
self._running = True
|
|
60
|
+
self._thread = threading.Thread(target=self._watch_loop, daemon=True)
|
|
61
|
+
self._thread.start()
|
|
62
|
+
logger.info("Kubeconfig watcher started")
|
|
63
|
+
|
|
64
|
+
def stop(self):
|
|
65
|
+
"""Stop watching for kubeconfig changes."""
|
|
66
|
+
self._running = False
|
|
67
|
+
if self._thread:
|
|
68
|
+
self._thread.join(timeout=2.0)
|
|
69
|
+
self._thread = None
|
|
70
|
+
logger.info("Kubeconfig watcher stopped")
|
|
71
|
+
|
|
72
|
+
def add_file(self, filepath: str):
|
|
73
|
+
"""Add a file to watch.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
filepath: Path to kubeconfig file
|
|
77
|
+
"""
|
|
78
|
+
filepath = os.path.expanduser(filepath)
|
|
79
|
+
if os.path.exists(filepath):
|
|
80
|
+
with self._lock:
|
|
81
|
+
self._watched_files[filepath] = os.path.getmtime(filepath)
|
|
82
|
+
|
|
83
|
+
def _watch_loop(self):
|
|
84
|
+
"""Main watch loop - check for file changes periodically."""
|
|
85
|
+
while self._running:
|
|
86
|
+
try:
|
|
87
|
+
self._check_files()
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.debug(f"Kubeconfig watch error: {e}")
|
|
90
|
+
time.sleep(self._check_interval)
|
|
91
|
+
|
|
92
|
+
def _check_files(self):
|
|
93
|
+
"""Check all watched files for changes."""
|
|
94
|
+
with self._lock:
|
|
95
|
+
for filepath, last_mtime in list(self._watched_files.items()):
|
|
96
|
+
try:
|
|
97
|
+
if not os.path.exists(filepath):
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
current_mtime = os.path.getmtime(filepath)
|
|
101
|
+
if current_mtime != last_mtime:
|
|
102
|
+
self._watched_files[filepath] = current_mtime
|
|
103
|
+
logger.info(f"Kubeconfig changed: {filepath}")
|
|
104
|
+
self._on_config_changed()
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.debug(f"Error checking {filepath}: {e}")
|
|
107
|
+
|
|
108
|
+
def _on_config_changed(self):
|
|
109
|
+
"""Handle kubeconfig file change - invalidate caches."""
|
|
110
|
+
global _config_loaded
|
|
111
|
+
_config_loaded = False
|
|
112
|
+
|
|
113
|
+
if _HAS_PROVIDER:
|
|
114
|
+
try:
|
|
115
|
+
provider = get_provider()
|
|
116
|
+
if hasattr(provider, 'invalidate_cache'):
|
|
117
|
+
provider.invalidate_cache()
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
for callback in _config_change_callbacks:
|
|
122
|
+
try:
|
|
123
|
+
callback()
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.debug(f"Config change callback error: {e}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def enable_kubeconfig_watch(check_interval: float = 5.0):
|
|
129
|
+
"""Enable automatic kubeconfig file watching.
|
|
130
|
+
|
|
131
|
+
When enabled, the server will automatically detect changes to kubeconfig
|
|
132
|
+
files and reload the configuration. This is useful when:
|
|
133
|
+
- Cloud provider CLIs update credentials (aws, gcloud, az)
|
|
134
|
+
- Users switch contexts using external tools
|
|
135
|
+
- Kubeconfig files are mounted dynamically (e.g., in Kubernetes)
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
check_interval: How often to check for changes (seconds)
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
enable_kubeconfig_watch(check_interval=10.0)
|
|
142
|
+
"""
|
|
143
|
+
global _kubeconfig_watcher
|
|
144
|
+
|
|
145
|
+
if _kubeconfig_watcher is not None:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
_kubeconfig_watcher = KubeconfigWatcher(check_interval=check_interval)
|
|
149
|
+
kubeconfig_env = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
150
|
+
for path in kubeconfig_env.split(os.pathsep):
|
|
151
|
+
_kubeconfig_watcher.add_file(path)
|
|
152
|
+
_kubeconfig_watcher.start()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def disable_kubeconfig_watch():
|
|
156
|
+
"""Disable kubeconfig file watching."""
|
|
157
|
+
global _kubeconfig_watcher
|
|
158
|
+
|
|
159
|
+
if _kubeconfig_watcher is not None:
|
|
160
|
+
_kubeconfig_watcher.stop()
|
|
161
|
+
_kubeconfig_watcher = None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def on_config_change(callback: Callable[[], None]):
|
|
165
|
+
"""Register a callback to be called when kubeconfig changes.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
callback: Function to call when config changes
|
|
169
|
+
"""
|
|
170
|
+
_config_change_callbacks.append(callback)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def is_stateless_mode() -> bool:
|
|
174
|
+
"""Check if stateless mode is enabled.
|
|
175
|
+
|
|
176
|
+
In stateless mode, API clients are not cached and configuration
|
|
177
|
+
is reloaded on each request. This is useful for:
|
|
178
|
+
- Serverless environments (Lambda, Cloud Functions)
|
|
179
|
+
- Environments where credentials may change frequently
|
|
180
|
+
- Security-conscious deployments
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
True if stateless mode is enabled
|
|
184
|
+
"""
|
|
185
|
+
return _stateless_mode
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def set_stateless_mode(enabled: bool):
|
|
189
|
+
"""Enable or disable stateless mode.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
enabled: True to enable stateless mode
|
|
193
|
+
"""
|
|
194
|
+
global _stateless_mode
|
|
195
|
+
_stateless_mode = enabled
|
|
196
|
+
if enabled:
|
|
197
|
+
logger.info("Stateless mode enabled - API clients will not be cached")
|
|
198
|
+
else:
|
|
199
|
+
logger.info("Stateless mode disabled - API clients will be cached")
|
|
200
|
+
|
|
26
201
|
try:
|
|
27
202
|
from .providers import (
|
|
28
203
|
KubernetesProvider,
|
|
@@ -60,7 +235,6 @@ def load_kubernetes_config(context: str = ""):
|
|
|
60
235
|
from kubernetes import config
|
|
61
236
|
from kubernetes.config.config_exception import ConfigException
|
|
62
237
|
|
|
63
|
-
# Try in-cluster config first (for pods running in Kubernetes)
|
|
64
238
|
try:
|
|
65
239
|
config.load_incluster_config()
|
|
66
240
|
logger.info("Loaded in-cluster Kubernetes configuration")
|
|
@@ -69,7 +243,6 @@ def load_kubernetes_config(context: str = ""):
|
|
|
69
243
|
except ConfigException:
|
|
70
244
|
logger.debug("Not running in-cluster, trying kubeconfig...")
|
|
71
245
|
|
|
72
|
-
# Fall back to kubeconfig file
|
|
73
246
|
try:
|
|
74
247
|
kubeconfig_path = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
75
248
|
kubeconfig_path = os.path.expanduser(kubeconfig_path)
|
|
@@ -97,7 +270,6 @@ def _patched_load_kube_config(*args, **kwargs):
|
|
|
97
270
|
|
|
98
271
|
from kubernetes.config.config_exception import ConfigException
|
|
99
272
|
|
|
100
|
-
# Try in-cluster config first
|
|
101
273
|
try:
|
|
102
274
|
from kubernetes import config
|
|
103
275
|
config.load_incluster_config()
|
|
@@ -107,7 +279,6 @@ def _patched_load_kube_config(*args, **kwargs):
|
|
|
107
279
|
except ConfigException:
|
|
108
280
|
pass
|
|
109
281
|
|
|
110
|
-
# Fall back to original behavior
|
|
111
282
|
if _original_load_kube_config:
|
|
112
283
|
_original_load_kube_config(*args, **kwargs)
|
|
113
284
|
_config_loaded = True
|
|
@@ -131,7 +302,6 @@ def patch_kubernetes_config():
|
|
|
131
302
|
logger.debug("kubernetes package not available for patching")
|
|
132
303
|
|
|
133
304
|
|
|
134
|
-
# Auto-patch when this module is imported
|
|
135
305
|
patch_kubernetes_config()
|
|
136
306
|
|
|
137
307
|
|
|
@@ -139,7 +309,8 @@ def _load_config_for_context(context: str = "") -> Any:
|
|
|
139
309
|
"""
|
|
140
310
|
Load kubernetes config for a specific context and return ApiClient.
|
|
141
311
|
|
|
142
|
-
Uses the provider module for caching when available
|
|
312
|
+
Uses the provider module for caching when available, unless stateless
|
|
313
|
+
mode is enabled.
|
|
143
314
|
|
|
144
315
|
Args:
|
|
145
316
|
context: Context name (empty for default)
|
|
@@ -151,8 +322,7 @@ def _load_config_for_context(context: str = "") -> Any:
|
|
|
151
322
|
UnknownContextError: If context is not found (when provider available)
|
|
152
323
|
RuntimeError: If config cannot be loaded
|
|
153
324
|
"""
|
|
154
|
-
|
|
155
|
-
if _HAS_PROVIDER:
|
|
325
|
+
if not _stateless_mode and _HAS_PROVIDER:
|
|
156
326
|
try:
|
|
157
327
|
provider = get_provider()
|
|
158
328
|
return provider.get_api_client(context)
|
|
@@ -161,18 +331,15 @@ def _load_config_for_context(context: str = "") -> Any:
|
|
|
161
331
|
except Exception as e:
|
|
162
332
|
logger.warning(f"Provider failed, falling back to basic config: {e}")
|
|
163
333
|
|
|
164
|
-
# Fallback to basic config loading
|
|
165
334
|
from kubernetes import client, config
|
|
166
335
|
from kubernetes.config.config_exception import ConfigException
|
|
167
336
|
|
|
168
|
-
# Try in-cluster first
|
|
169
337
|
try:
|
|
170
338
|
config.load_incluster_config()
|
|
171
339
|
return client.ApiClient()
|
|
172
340
|
except ConfigException:
|
|
173
341
|
pass
|
|
174
342
|
|
|
175
|
-
# Load kubeconfig with optional context
|
|
176
343
|
kubeconfig_path = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
177
344
|
kubeconfig_path = os.path.expanduser(kubeconfig_path)
|
|
178
345
|
|
|
@@ -193,310 +360,102 @@ def _load_config_for_context(context: str = "") -> Any:
|
|
|
193
360
|
return client.ApiClient(configuration=api_config)
|
|
194
361
|
|
|
195
362
|
|
|
196
|
-
def
|
|
197
|
-
"""
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
context: Optional context name for multi-cluster support
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
kubernetes.client.CoreV1Api: Configured Kubernetes client
|
|
204
|
-
|
|
205
|
-
Raises:
|
|
206
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
207
|
-
"""
|
|
363
|
+
def _get_client(context: str, client_class):
|
|
364
|
+
"""Helper to create a configured Kubernetes API client."""
|
|
208
365
|
from kubernetes import client
|
|
209
|
-
|
|
210
366
|
try:
|
|
211
367
|
api_client = _load_config_for_context(context)
|
|
212
|
-
return
|
|
368
|
+
return client_class(api_client=api_client)
|
|
213
369
|
except Exception as e:
|
|
214
370
|
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
215
371
|
|
|
216
372
|
|
|
217
|
-
def
|
|
218
|
-
"""Get a configured Kubernetes
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
context: Optional context name for multi-cluster support
|
|
373
|
+
def get_k8s_client(context: str = ""):
|
|
374
|
+
"""Get a configured Kubernetes Core API client."""
|
|
375
|
+
from kubernetes import client
|
|
376
|
+
return _get_client(context, client.CoreV1Api)
|
|
222
377
|
|
|
223
|
-
Returns:
|
|
224
|
-
kubernetes.client.AppsV1Api: Configured Apps API client
|
|
225
378
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"""
|
|
379
|
+
def get_apps_client(context: str = ""):
|
|
380
|
+
"""Get a configured Kubernetes Apps API client."""
|
|
229
381
|
from kubernetes import client
|
|
230
|
-
|
|
231
|
-
try:
|
|
232
|
-
api_client = _load_config_for_context(context)
|
|
233
|
-
return client.AppsV1Api(api_client=api_client)
|
|
234
|
-
except Exception as e:
|
|
235
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
382
|
+
return _get_client(context, client.AppsV1Api)
|
|
236
383
|
|
|
237
384
|
|
|
238
385
|
def get_rbac_client(context: str = ""):
|
|
239
|
-
"""Get a configured Kubernetes RBAC API client.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
context: Optional context name for multi-cluster support
|
|
243
|
-
|
|
244
|
-
Returns:
|
|
245
|
-
kubernetes.client.RbacAuthorizationV1Api: Configured RBAC client
|
|
246
|
-
|
|
247
|
-
Raises:
|
|
248
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
249
|
-
"""
|
|
386
|
+
"""Get a configured Kubernetes RBAC API client."""
|
|
250
387
|
from kubernetes import client
|
|
251
|
-
|
|
252
|
-
try:
|
|
253
|
-
api_client = _load_config_for_context(context)
|
|
254
|
-
return client.RbacAuthorizationV1Api(api_client=api_client)
|
|
255
|
-
except Exception as e:
|
|
256
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
388
|
+
return _get_client(context, client.RbacAuthorizationV1Api)
|
|
257
389
|
|
|
258
390
|
|
|
259
391
|
def get_networking_client(context: str = ""):
|
|
260
|
-
"""Get a configured Kubernetes Networking API client.
|
|
261
|
-
|
|
262
|
-
Args:
|
|
263
|
-
context: Optional context name for multi-cluster support
|
|
264
|
-
|
|
265
|
-
Returns:
|
|
266
|
-
kubernetes.client.NetworkingV1Api: Configured Networking client
|
|
267
|
-
|
|
268
|
-
Raises:
|
|
269
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
270
|
-
"""
|
|
392
|
+
"""Get a configured Kubernetes Networking API client."""
|
|
271
393
|
from kubernetes import client
|
|
272
|
-
|
|
273
|
-
try:
|
|
274
|
-
api_client = _load_config_for_context(context)
|
|
275
|
-
return client.NetworkingV1Api(api_client=api_client)
|
|
276
|
-
except Exception as e:
|
|
277
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
394
|
+
return _get_client(context, client.NetworkingV1Api)
|
|
278
395
|
|
|
279
396
|
|
|
280
397
|
def get_storage_client(context: str = ""):
|
|
281
|
-
"""Get a configured Kubernetes Storage API client.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
context: Optional context name for multi-cluster support
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
kubernetes.client.StorageV1Api: Configured Storage client
|
|
288
|
-
|
|
289
|
-
Raises:
|
|
290
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
291
|
-
"""
|
|
398
|
+
"""Get a configured Kubernetes Storage API client."""
|
|
292
399
|
from kubernetes import client
|
|
293
|
-
|
|
294
|
-
try:
|
|
295
|
-
api_client = _load_config_for_context(context)
|
|
296
|
-
return client.StorageV1Api(api_client=api_client)
|
|
297
|
-
except Exception as e:
|
|
298
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
400
|
+
return _get_client(context, client.StorageV1Api)
|
|
299
401
|
|
|
300
402
|
|
|
301
403
|
def get_batch_client(context: str = ""):
|
|
302
|
-
"""Get a configured Kubernetes Batch API client.
|
|
303
|
-
|
|
304
|
-
Args:
|
|
305
|
-
context: Optional context name for multi-cluster support
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
kubernetes.client.BatchV1Api: Configured Batch client
|
|
309
|
-
|
|
310
|
-
Raises:
|
|
311
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
312
|
-
"""
|
|
404
|
+
"""Get a configured Kubernetes Batch API client."""
|
|
313
405
|
from kubernetes import client
|
|
314
|
-
|
|
315
|
-
try:
|
|
316
|
-
api_client = _load_config_for_context(context)
|
|
317
|
-
return client.BatchV1Api(api_client=api_client)
|
|
318
|
-
except Exception as e:
|
|
319
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
406
|
+
return _get_client(context, client.BatchV1Api)
|
|
320
407
|
|
|
321
408
|
|
|
322
409
|
def get_autoscaling_client(context: str = ""):
|
|
323
|
-
"""Get a configured Kubernetes Autoscaling API client.
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
context: Optional context name for multi-cluster support
|
|
327
|
-
|
|
328
|
-
Returns:
|
|
329
|
-
kubernetes.client.AutoscalingV1Api: Configured Autoscaling client
|
|
330
|
-
|
|
331
|
-
Raises:
|
|
332
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
333
|
-
"""
|
|
410
|
+
"""Get a configured Kubernetes Autoscaling API client."""
|
|
334
411
|
from kubernetes import client
|
|
335
|
-
|
|
336
|
-
try:
|
|
337
|
-
api_client = _load_config_for_context(context)
|
|
338
|
-
return client.AutoscalingV1Api(api_client=api_client)
|
|
339
|
-
except Exception as e:
|
|
340
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
412
|
+
return _get_client(context, client.AutoscalingV1Api)
|
|
341
413
|
|
|
342
414
|
|
|
343
415
|
def get_policy_client(context: str = ""):
|
|
344
|
-
"""Get a configured Kubernetes Policy API client.
|
|
345
|
-
|
|
346
|
-
Args:
|
|
347
|
-
context: Optional context name for multi-cluster support
|
|
348
|
-
|
|
349
|
-
Returns:
|
|
350
|
-
kubernetes.client.PolicyV1Api: Configured Policy client
|
|
351
|
-
|
|
352
|
-
Raises:
|
|
353
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
354
|
-
"""
|
|
416
|
+
"""Get a configured Kubernetes Policy API client."""
|
|
355
417
|
from kubernetes import client
|
|
356
|
-
|
|
357
|
-
try:
|
|
358
|
-
api_client = _load_config_for_context(context)
|
|
359
|
-
return client.PolicyV1Api(api_client=api_client)
|
|
360
|
-
except Exception as e:
|
|
361
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
418
|
+
return _get_client(context, client.PolicyV1Api)
|
|
362
419
|
|
|
363
420
|
|
|
364
421
|
def get_custom_objects_client(context: str = ""):
|
|
365
|
-
"""Get a configured Kubernetes Custom Objects API client.
|
|
366
|
-
|
|
367
|
-
Args:
|
|
368
|
-
context: Optional context name for multi-cluster support
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
kubernetes.client.CustomObjectsApi: Configured Custom Objects client
|
|
372
|
-
|
|
373
|
-
Raises:
|
|
374
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
375
|
-
"""
|
|
422
|
+
"""Get a configured Kubernetes Custom Objects API client."""
|
|
376
423
|
from kubernetes import client
|
|
377
|
-
|
|
378
|
-
try:
|
|
379
|
-
api_client = _load_config_for_context(context)
|
|
380
|
-
return client.CustomObjectsApi(api_client=api_client)
|
|
381
|
-
except Exception as e:
|
|
382
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
424
|
+
return _get_client(context, client.CustomObjectsApi)
|
|
383
425
|
|
|
384
426
|
|
|
385
427
|
def get_version_client(context: str = ""):
|
|
386
|
-
"""Get a configured Kubernetes Version API client.
|
|
387
|
-
|
|
388
|
-
Args:
|
|
389
|
-
context: Optional context name for multi-cluster support
|
|
390
|
-
|
|
391
|
-
Returns:
|
|
392
|
-
kubernetes.client.VersionApi: Configured Version client
|
|
393
|
-
|
|
394
|
-
Raises:
|
|
395
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
396
|
-
"""
|
|
428
|
+
"""Get a configured Kubernetes Version API client."""
|
|
397
429
|
from kubernetes import client
|
|
398
|
-
|
|
399
|
-
try:
|
|
400
|
-
api_client = _load_config_for_context(context)
|
|
401
|
-
return client.VersionApi(api_client=api_client)
|
|
402
|
-
except Exception as e:
|
|
403
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
430
|
+
return _get_client(context, client.VersionApi)
|
|
404
431
|
|
|
405
432
|
|
|
406
433
|
def get_admissionregistration_client(context: str = ""):
|
|
407
|
-
"""Get a configured Kubernetes Admission Registration API client.
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
context: Optional context name for multi-cluster support
|
|
411
|
-
|
|
412
|
-
Returns:
|
|
413
|
-
kubernetes.client.AdmissionregistrationV1Api: Configured Admission client
|
|
414
|
-
|
|
415
|
-
Raises:
|
|
416
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
417
|
-
"""
|
|
434
|
+
"""Get a configured Kubernetes Admission Registration API client."""
|
|
418
435
|
from kubernetes import client
|
|
419
|
-
|
|
420
|
-
try:
|
|
421
|
-
api_client = _load_config_for_context(context)
|
|
422
|
-
return client.AdmissionregistrationV1Api(api_client=api_client)
|
|
423
|
-
except Exception as e:
|
|
424
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
436
|
+
return _get_client(context, client.AdmissionregistrationV1Api)
|
|
425
437
|
|
|
426
438
|
|
|
427
439
|
def get_apiextensions_client(context: str = ""):
|
|
428
|
-
"""Get a configured Kubernetes API Extensions client.
|
|
429
|
-
|
|
430
|
-
Args:
|
|
431
|
-
context: Optional context name for multi-cluster support
|
|
432
|
-
|
|
433
|
-
Returns:
|
|
434
|
-
kubernetes.client.ApiextensionsV1Api: Configured API Extensions client
|
|
435
|
-
|
|
436
|
-
Raises:
|
|
437
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
438
|
-
"""
|
|
440
|
+
"""Get a configured Kubernetes API Extensions client."""
|
|
439
441
|
from kubernetes import client
|
|
440
|
-
|
|
441
|
-
try:
|
|
442
|
-
api_client = _load_config_for_context(context)
|
|
443
|
-
return client.ApiextensionsV1Api(api_client=api_client)
|
|
444
|
-
except Exception as e:
|
|
445
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
442
|
+
return _get_client(context, client.ApiextensionsV1Api)
|
|
446
443
|
|
|
447
444
|
|
|
448
445
|
def get_coordination_client(context: str = ""):
|
|
449
|
-
"""Get a configured Kubernetes Coordination API client.
|
|
450
|
-
|
|
451
|
-
Args:
|
|
452
|
-
context: Optional context name for multi-cluster support
|
|
453
|
-
|
|
454
|
-
Returns:
|
|
455
|
-
kubernetes.client.CoordinationV1Api: Configured Coordination client
|
|
456
|
-
|
|
457
|
-
Raises:
|
|
458
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
459
|
-
"""
|
|
446
|
+
"""Get a configured Kubernetes Coordination API client."""
|
|
460
447
|
from kubernetes import client
|
|
461
|
-
|
|
462
|
-
try:
|
|
463
|
-
api_client = _load_config_for_context(context)
|
|
464
|
-
return client.CoordinationV1Api(api_client=api_client)
|
|
465
|
-
except Exception as e:
|
|
466
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
448
|
+
return _get_client(context, client.CoordinationV1Api)
|
|
467
449
|
|
|
468
450
|
|
|
469
451
|
def get_events_client(context: str = ""):
|
|
470
|
-
"""Get a configured Kubernetes Events API client.
|
|
471
|
-
|
|
472
|
-
Args:
|
|
473
|
-
context: Optional context name for multi-cluster support
|
|
474
|
-
|
|
475
|
-
Returns:
|
|
476
|
-
kubernetes.client.EventsV1Api: Configured Events client
|
|
477
|
-
|
|
478
|
-
Raises:
|
|
479
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
480
|
-
"""
|
|
452
|
+
"""Get a configured Kubernetes Events API client."""
|
|
481
453
|
from kubernetes import client
|
|
454
|
+
return _get_client(context, client.EventsV1Api)
|
|
482
455
|
|
|
483
|
-
try:
|
|
484
|
-
api_client = _load_config_for_context(context)
|
|
485
|
-
return client.EventsV1Api(api_client=api_client)
|
|
486
|
-
except Exception as e:
|
|
487
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
# Utility functions for context management
|
|
491
456
|
|
|
492
457
|
def list_contexts() -> list:
|
|
493
|
-
"""
|
|
494
|
-
List all available kubeconfig contexts.
|
|
495
|
-
|
|
496
|
-
Returns:
|
|
497
|
-
List of context dictionaries with name, cluster, user, namespace
|
|
498
|
-
"""
|
|
499
|
-
# Use provider if available
|
|
458
|
+
"""List all available kubeconfig contexts."""
|
|
500
459
|
if _HAS_PROVIDER:
|
|
501
460
|
try:
|
|
502
461
|
provider = get_provider()
|
|
@@ -514,7 +473,6 @@ def list_contexts() -> list:
|
|
|
514
473
|
except Exception as e:
|
|
515
474
|
logger.warning(f"Provider list_contexts failed: {e}")
|
|
516
475
|
|
|
517
|
-
# Fallback to direct kubeconfig reading
|
|
518
476
|
from kubernetes import config
|
|
519
477
|
|
|
520
478
|
try:
|
|
@@ -539,22 +497,14 @@ def list_contexts() -> list:
|
|
|
539
497
|
|
|
540
498
|
|
|
541
499
|
def get_active_context() -> Optional[str]:
|
|
542
|
-
"""
|
|
543
|
-
Get the current active context name.
|
|
544
|
-
|
|
545
|
-
Returns:
|
|
546
|
-
Active context name or None
|
|
547
|
-
"""
|
|
548
|
-
# Use provider if available
|
|
500
|
+
"""Get the current active context name."""
|
|
549
501
|
if _HAS_PROVIDER:
|
|
550
502
|
try:
|
|
551
503
|
return provider_get_current_context()
|
|
552
504
|
except Exception as e:
|
|
553
505
|
logger.warning(f"Provider get_current_context failed: {e}")
|
|
554
506
|
|
|
555
|
-
# Fallback to direct kubeconfig reading
|
|
556
507
|
from kubernetes import config
|
|
557
|
-
|
|
558
508
|
try:
|
|
559
509
|
kubeconfig_path = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
560
510
|
kubeconfig_path = os.path.expanduser(kubeconfig_path)
|
|
@@ -567,90 +517,33 @@ def get_active_context() -> Optional[str]:
|
|
|
567
517
|
|
|
568
518
|
|
|
569
519
|
def context_exists(context: str) -> bool:
|
|
570
|
-
"""
|
|
571
|
-
Check if a context exists in kubeconfig.
|
|
572
|
-
|
|
573
|
-
Args:
|
|
574
|
-
context: Context name to check
|
|
575
|
-
|
|
576
|
-
Returns:
|
|
577
|
-
True if context exists
|
|
578
|
-
"""
|
|
520
|
+
"""Check if a context exists in kubeconfig."""
|
|
579
521
|
contexts = list_contexts()
|
|
580
522
|
return any(ctx["name"] == context for ctx in contexts)
|
|
581
523
|
|
|
582
524
|
|
|
583
525
|
def _get_kubectl_context_args(context: str = "") -> list:
|
|
584
|
-
"""
|
|
585
|
-
Get kubectl command arguments for specifying a context.
|
|
586
|
-
|
|
587
|
-
This utility function returns the appropriate --context flag arguments
|
|
588
|
-
for kubectl commands when targeting a specific cluster.
|
|
589
|
-
|
|
590
|
-
Args:
|
|
591
|
-
context: Context name (empty string for default context)
|
|
592
|
-
|
|
593
|
-
Returns:
|
|
594
|
-
List of command arguments, e.g., ["--context", "my-cluster"]
|
|
595
|
-
or empty list if no context specified
|
|
596
|
-
"""
|
|
526
|
+
"""Get kubectl command arguments for specifying a context."""
|
|
597
527
|
if context and context.strip():
|
|
598
528
|
return ["--context", context.strip()]
|
|
599
529
|
return []
|
|
600
530
|
|
|
601
531
|
|
|
602
|
-
|
|
532
|
+
_BASE_EXPORTS = [
|
|
533
|
+
"get_k8s_client", "get_apps_client", "get_rbac_client", "get_networking_client",
|
|
534
|
+
"get_storage_client", "get_batch_client", "get_autoscaling_client", "get_policy_client",
|
|
535
|
+
"get_custom_objects_client", "get_version_client", "get_admissionregistration_client",
|
|
536
|
+
"get_apiextensions_client", "get_coordination_client", "get_events_client",
|
|
537
|
+
"load_kubernetes_config", "patch_kubernetes_config",
|
|
538
|
+
"list_contexts", "get_active_context", "context_exists",
|
|
539
|
+
"enable_kubeconfig_watch", "disable_kubeconfig_watch", "on_config_change", "KubeconfigWatcher",
|
|
540
|
+
"is_stateless_mode", "set_stateless_mode",
|
|
541
|
+
]
|
|
542
|
+
|
|
603
543
|
if _HAS_PROVIDER:
|
|
604
|
-
__all__ = [
|
|
605
|
-
|
|
606
|
-
"
|
|
607
|
-
"get_apps_client",
|
|
608
|
-
"get_rbac_client",
|
|
609
|
-
"get_networking_client",
|
|
610
|
-
"get_storage_client",
|
|
611
|
-
"get_batch_client",
|
|
612
|
-
"get_autoscaling_client",
|
|
613
|
-
"get_policy_client",
|
|
614
|
-
"get_custom_objects_client",
|
|
615
|
-
"get_version_client",
|
|
616
|
-
"get_admissionregistration_client",
|
|
617
|
-
"get_apiextensions_client",
|
|
618
|
-
"get_coordination_client",
|
|
619
|
-
"get_events_client",
|
|
620
|
-
# Config functions
|
|
621
|
-
"load_kubernetes_config",
|
|
622
|
-
"patch_kubernetes_config",
|
|
623
|
-
# Context functions
|
|
624
|
-
"list_contexts",
|
|
625
|
-
"get_active_context",
|
|
626
|
-
"context_exists",
|
|
627
|
-
# Provider types (when available)
|
|
628
|
-
"KubernetesProvider",
|
|
629
|
-
"ProviderConfig",
|
|
630
|
-
"ProviderType",
|
|
631
|
-
"UnknownContextError",
|
|
632
|
-
"get_provider",
|
|
633
|
-
"validate_context",
|
|
544
|
+
__all__ = _BASE_EXPORTS + [
|
|
545
|
+
"KubernetesProvider", "ProviderConfig", "ProviderType",
|
|
546
|
+
"UnknownContextError", "get_provider", "validate_context",
|
|
634
547
|
]
|
|
635
548
|
else:
|
|
636
|
-
__all__ =
|
|
637
|
-
"get_k8s_client",
|
|
638
|
-
"get_apps_client",
|
|
639
|
-
"get_rbac_client",
|
|
640
|
-
"get_networking_client",
|
|
641
|
-
"get_storage_client",
|
|
642
|
-
"get_batch_client",
|
|
643
|
-
"get_autoscaling_client",
|
|
644
|
-
"get_policy_client",
|
|
645
|
-
"get_custom_objects_client",
|
|
646
|
-
"get_version_client",
|
|
647
|
-
"get_admissionregistration_client",
|
|
648
|
-
"get_apiextensions_client",
|
|
649
|
-
"get_coordination_client",
|
|
650
|
-
"get_events_client",
|
|
651
|
-
"load_kubernetes_config",
|
|
652
|
-
"patch_kubernetes_config",
|
|
653
|
-
"list_contexts",
|
|
654
|
-
"get_active_context",
|
|
655
|
-
"context_exists",
|
|
656
|
-
]
|
|
549
|
+
__all__ = _BASE_EXPORTS
|