kubectl-mcp-server 1.19.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.19.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/METADATA +88 -18
- {kubectl_mcp_server-1.19.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/RECORD +24 -22
- {kubectl_mcp_server-1.19.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/WHEEL +1 -1
- kubectl_mcp_tool/k8s_config.py +296 -277
- kubectl_mcp_tool/mcp_server.py +30 -0
- kubectl_mcp_tool/providers.py +347 -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.19.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.19.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.19.0.dist-info → kubectl_mcp_server-1.19.1.dist-info}/top_level.txt +0 -0
kubectl_mcp_tool/k8s_config.py
CHANGED
|
@@ -6,14 +6,214 @@ Supports multi-cluster operations with context targeting.
|
|
|
6
6
|
|
|
7
7
|
This module provides context-aware client creation for multi-cluster support.
|
|
8
8
|
All get_*_client() functions accept an optional 'context' parameter.
|
|
9
|
+
|
|
10
|
+
Environment Variables:
|
|
11
|
+
MCP_K8S_PROVIDER: Provider type (kubeconfig, in-cluster, single)
|
|
12
|
+
MCP_K8S_KUBECONFIG: Path to kubeconfig file
|
|
13
|
+
MCP_K8S_CONTEXT: Default context for single provider
|
|
14
|
+
MCP_K8S_QPS: API rate limit (default: 100)
|
|
15
|
+
MCP_K8S_BURST: API burst limit (default: 200)
|
|
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
|
|
9
19
|
"""
|
|
10
20
|
|
|
11
21
|
import os
|
|
12
22
|
import logging
|
|
13
|
-
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
from typing import Optional, Any, List, Dict, Callable
|
|
14
26
|
|
|
15
27
|
logger = logging.getLogger("mcp-server")
|
|
16
28
|
|
|
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
|
+
|
|
201
|
+
try:
|
|
202
|
+
from .providers import (
|
|
203
|
+
KubernetesProvider,
|
|
204
|
+
ProviderConfig,
|
|
205
|
+
ProviderType,
|
|
206
|
+
UnknownContextError,
|
|
207
|
+
get_provider,
|
|
208
|
+
get_context_names,
|
|
209
|
+
get_current_context as provider_get_current_context,
|
|
210
|
+
validate_context,
|
|
211
|
+
)
|
|
212
|
+
_HAS_PROVIDER = True
|
|
213
|
+
except ImportError:
|
|
214
|
+
_HAS_PROVIDER = False
|
|
215
|
+
logger.debug("Provider module not available, using basic config")
|
|
216
|
+
|
|
17
217
|
_config_loaded = False
|
|
18
218
|
_original_load_kube_config = None
|
|
19
219
|
|
|
@@ -35,7 +235,6 @@ def load_kubernetes_config(context: str = ""):
|
|
|
35
235
|
from kubernetes import config
|
|
36
236
|
from kubernetes.config.config_exception import ConfigException
|
|
37
237
|
|
|
38
|
-
# Try in-cluster config first (for pods running in Kubernetes)
|
|
39
238
|
try:
|
|
40
239
|
config.load_incluster_config()
|
|
41
240
|
logger.info("Loaded in-cluster Kubernetes configuration")
|
|
@@ -44,7 +243,6 @@ def load_kubernetes_config(context: str = ""):
|
|
|
44
243
|
except ConfigException:
|
|
45
244
|
logger.debug("Not running in-cluster, trying kubeconfig...")
|
|
46
245
|
|
|
47
|
-
# Fall back to kubeconfig file
|
|
48
246
|
try:
|
|
49
247
|
kubeconfig_path = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
50
248
|
kubeconfig_path = os.path.expanduser(kubeconfig_path)
|
|
@@ -72,7 +270,6 @@ def _patched_load_kube_config(*args, **kwargs):
|
|
|
72
270
|
|
|
73
271
|
from kubernetes.config.config_exception import ConfigException
|
|
74
272
|
|
|
75
|
-
# Try in-cluster config first
|
|
76
273
|
try:
|
|
77
274
|
from kubernetes import config
|
|
78
275
|
config.load_incluster_config()
|
|
@@ -82,7 +279,6 @@ def _patched_load_kube_config(*args, **kwargs):
|
|
|
82
279
|
except ConfigException:
|
|
83
280
|
pass
|
|
84
281
|
|
|
85
|
-
# Fall back to original behavior
|
|
86
282
|
if _original_load_kube_config:
|
|
87
283
|
_original_load_kube_config(*args, **kwargs)
|
|
88
284
|
_config_loaded = True
|
|
@@ -106,7 +302,6 @@ def patch_kubernetes_config():
|
|
|
106
302
|
logger.debug("kubernetes package not available for patching")
|
|
107
303
|
|
|
108
304
|
|
|
109
|
-
# Auto-patch when this module is imported
|
|
110
305
|
patch_kubernetes_config()
|
|
111
306
|
|
|
112
307
|
|
|
@@ -114,23 +309,37 @@ def _load_config_for_context(context: str = "") -> Any:
|
|
|
114
309
|
"""
|
|
115
310
|
Load kubernetes config for a specific context and return ApiClient.
|
|
116
311
|
|
|
312
|
+
Uses the provider module for caching when available, unless stateless
|
|
313
|
+
mode is enabled.
|
|
314
|
+
|
|
117
315
|
Args:
|
|
118
316
|
context: Context name (empty for default)
|
|
119
317
|
|
|
120
318
|
Returns:
|
|
121
319
|
kubernetes.client.ApiClient configured for the context
|
|
320
|
+
|
|
321
|
+
Raises:
|
|
322
|
+
UnknownContextError: If context is not found (when provider available)
|
|
323
|
+
RuntimeError: If config cannot be loaded
|
|
122
324
|
"""
|
|
325
|
+
if not _stateless_mode and _HAS_PROVIDER:
|
|
326
|
+
try:
|
|
327
|
+
provider = get_provider()
|
|
328
|
+
return provider.get_api_client(context)
|
|
329
|
+
except UnknownContextError:
|
|
330
|
+
raise
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.warning(f"Provider failed, falling back to basic config: {e}")
|
|
333
|
+
|
|
123
334
|
from kubernetes import client, config
|
|
124
335
|
from kubernetes.config.config_exception import ConfigException
|
|
125
336
|
|
|
126
|
-
# Try in-cluster first
|
|
127
337
|
try:
|
|
128
338
|
config.load_incluster_config()
|
|
129
339
|
return client.ApiClient()
|
|
130
340
|
except ConfigException:
|
|
131
341
|
pass
|
|
132
342
|
|
|
133
|
-
# Load kubeconfig with optional context
|
|
134
343
|
kubeconfig_path = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
135
344
|
kubeconfig_path = os.path.expanduser(kubeconfig_path)
|
|
136
345
|
|
|
@@ -151,309 +360,119 @@ def _load_config_for_context(context: str = "") -> Any:
|
|
|
151
360
|
return client.ApiClient(configuration=api_config)
|
|
152
361
|
|
|
153
362
|
|
|
154
|
-
def
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
context: Optional context name for multi-cluster support
|
|
159
|
-
|
|
160
|
-
Returns:
|
|
161
|
-
kubernetes.client.CoreV1Api: Configured Kubernetes client
|
|
162
|
-
|
|
163
|
-
Raises:
|
|
164
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
165
|
-
"""
|
|
363
|
+
def _get_client(context: str, client_class):
|
|
364
|
+
"""Helper to create a configured Kubernetes API client."""
|
|
166
365
|
from kubernetes import client
|
|
167
|
-
|
|
168
366
|
try:
|
|
169
367
|
api_client = _load_config_for_context(context)
|
|
170
|
-
return
|
|
368
|
+
return client_class(api_client=api_client)
|
|
171
369
|
except Exception as e:
|
|
172
370
|
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
173
371
|
|
|
174
372
|
|
|
175
|
-
def
|
|
176
|
-
"""Get a configured Kubernetes
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
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)
|
|
180
377
|
|
|
181
|
-
Returns:
|
|
182
|
-
kubernetes.client.AppsV1Api: Configured Apps API client
|
|
183
378
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"""
|
|
379
|
+
def get_apps_client(context: str = ""):
|
|
380
|
+
"""Get a configured Kubernetes Apps API client."""
|
|
187
381
|
from kubernetes import client
|
|
188
|
-
|
|
189
|
-
try:
|
|
190
|
-
api_client = _load_config_for_context(context)
|
|
191
|
-
return client.AppsV1Api(api_client=api_client)
|
|
192
|
-
except Exception as e:
|
|
193
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
382
|
+
return _get_client(context, client.AppsV1Api)
|
|
194
383
|
|
|
195
384
|
|
|
196
385
|
def get_rbac_client(context: str = ""):
|
|
197
|
-
"""Get a configured Kubernetes RBAC API client.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
context: Optional context name for multi-cluster support
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
kubernetes.client.RbacAuthorizationV1Api: Configured RBAC client
|
|
204
|
-
|
|
205
|
-
Raises:
|
|
206
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
207
|
-
"""
|
|
386
|
+
"""Get a configured Kubernetes RBAC API client."""
|
|
208
387
|
from kubernetes import client
|
|
209
|
-
|
|
210
|
-
try:
|
|
211
|
-
api_client = _load_config_for_context(context)
|
|
212
|
-
return client.RbacAuthorizationV1Api(api_client=api_client)
|
|
213
|
-
except Exception as e:
|
|
214
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
388
|
+
return _get_client(context, client.RbacAuthorizationV1Api)
|
|
215
389
|
|
|
216
390
|
|
|
217
391
|
def get_networking_client(context: str = ""):
|
|
218
|
-
"""Get a configured Kubernetes Networking API client.
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
context: Optional context name for multi-cluster support
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
kubernetes.client.NetworkingV1Api: Configured Networking client
|
|
225
|
-
|
|
226
|
-
Raises:
|
|
227
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
228
|
-
"""
|
|
392
|
+
"""Get a configured Kubernetes Networking API client."""
|
|
229
393
|
from kubernetes import client
|
|
230
|
-
|
|
231
|
-
try:
|
|
232
|
-
api_client = _load_config_for_context(context)
|
|
233
|
-
return client.NetworkingV1Api(api_client=api_client)
|
|
234
|
-
except Exception as e:
|
|
235
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
394
|
+
return _get_client(context, client.NetworkingV1Api)
|
|
236
395
|
|
|
237
396
|
|
|
238
397
|
def get_storage_client(context: str = ""):
|
|
239
|
-
"""Get a configured Kubernetes Storage API client.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
context: Optional context name for multi-cluster support
|
|
243
|
-
|
|
244
|
-
Returns:
|
|
245
|
-
kubernetes.client.StorageV1Api: Configured Storage client
|
|
246
|
-
|
|
247
|
-
Raises:
|
|
248
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
249
|
-
"""
|
|
398
|
+
"""Get a configured Kubernetes Storage API client."""
|
|
250
399
|
from kubernetes import client
|
|
251
|
-
|
|
252
|
-
try:
|
|
253
|
-
api_client = _load_config_for_context(context)
|
|
254
|
-
return client.StorageV1Api(api_client=api_client)
|
|
255
|
-
except Exception as e:
|
|
256
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
400
|
+
return _get_client(context, client.StorageV1Api)
|
|
257
401
|
|
|
258
402
|
|
|
259
403
|
def get_batch_client(context: str = ""):
|
|
260
|
-
"""Get a configured Kubernetes Batch API client.
|
|
261
|
-
|
|
262
|
-
Args:
|
|
263
|
-
context: Optional context name for multi-cluster support
|
|
264
|
-
|
|
265
|
-
Returns:
|
|
266
|
-
kubernetes.client.BatchV1Api: Configured Batch client
|
|
267
|
-
|
|
268
|
-
Raises:
|
|
269
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
270
|
-
"""
|
|
404
|
+
"""Get a configured Kubernetes Batch API client."""
|
|
271
405
|
from kubernetes import client
|
|
272
|
-
|
|
273
|
-
try:
|
|
274
|
-
api_client = _load_config_for_context(context)
|
|
275
|
-
return client.BatchV1Api(api_client=api_client)
|
|
276
|
-
except Exception as e:
|
|
277
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
406
|
+
return _get_client(context, client.BatchV1Api)
|
|
278
407
|
|
|
279
408
|
|
|
280
409
|
def get_autoscaling_client(context: str = ""):
|
|
281
|
-
"""Get a configured Kubernetes Autoscaling API client.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
context: Optional context name for multi-cluster support
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
kubernetes.client.AutoscalingV1Api: Configured Autoscaling client
|
|
288
|
-
|
|
289
|
-
Raises:
|
|
290
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
291
|
-
"""
|
|
410
|
+
"""Get a configured Kubernetes Autoscaling API client."""
|
|
292
411
|
from kubernetes import client
|
|
293
|
-
|
|
294
|
-
try:
|
|
295
|
-
api_client = _load_config_for_context(context)
|
|
296
|
-
return client.AutoscalingV1Api(api_client=api_client)
|
|
297
|
-
except Exception as e:
|
|
298
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
412
|
+
return _get_client(context, client.AutoscalingV1Api)
|
|
299
413
|
|
|
300
414
|
|
|
301
415
|
def get_policy_client(context: str = ""):
|
|
302
|
-
"""Get a configured Kubernetes Policy API client.
|
|
303
|
-
|
|
304
|
-
Args:
|
|
305
|
-
context: Optional context name for multi-cluster support
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
kubernetes.client.PolicyV1Api: Configured Policy client
|
|
309
|
-
|
|
310
|
-
Raises:
|
|
311
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
312
|
-
"""
|
|
416
|
+
"""Get a configured Kubernetes Policy API client."""
|
|
313
417
|
from kubernetes import client
|
|
314
|
-
|
|
315
|
-
try:
|
|
316
|
-
api_client = _load_config_for_context(context)
|
|
317
|
-
return client.PolicyV1Api(api_client=api_client)
|
|
318
|
-
except Exception as e:
|
|
319
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
418
|
+
return _get_client(context, client.PolicyV1Api)
|
|
320
419
|
|
|
321
420
|
|
|
322
421
|
def get_custom_objects_client(context: str = ""):
|
|
323
|
-
"""Get a configured Kubernetes Custom Objects API client.
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
context: Optional context name for multi-cluster support
|
|
327
|
-
|
|
328
|
-
Returns:
|
|
329
|
-
kubernetes.client.CustomObjectsApi: Configured Custom Objects client
|
|
330
|
-
|
|
331
|
-
Raises:
|
|
332
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
333
|
-
"""
|
|
422
|
+
"""Get a configured Kubernetes Custom Objects API client."""
|
|
334
423
|
from kubernetes import client
|
|
335
|
-
|
|
336
|
-
try:
|
|
337
|
-
api_client = _load_config_for_context(context)
|
|
338
|
-
return client.CustomObjectsApi(api_client=api_client)
|
|
339
|
-
except Exception as e:
|
|
340
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
424
|
+
return _get_client(context, client.CustomObjectsApi)
|
|
341
425
|
|
|
342
426
|
|
|
343
427
|
def get_version_client(context: str = ""):
|
|
344
|
-
"""Get a configured Kubernetes Version API client.
|
|
345
|
-
|
|
346
|
-
Args:
|
|
347
|
-
context: Optional context name for multi-cluster support
|
|
348
|
-
|
|
349
|
-
Returns:
|
|
350
|
-
kubernetes.client.VersionApi: Configured Version client
|
|
351
|
-
|
|
352
|
-
Raises:
|
|
353
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
354
|
-
"""
|
|
428
|
+
"""Get a configured Kubernetes Version API client."""
|
|
355
429
|
from kubernetes import client
|
|
356
|
-
|
|
357
|
-
try:
|
|
358
|
-
api_client = _load_config_for_context(context)
|
|
359
|
-
return client.VersionApi(api_client=api_client)
|
|
360
|
-
except Exception as e:
|
|
361
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
430
|
+
return _get_client(context, client.VersionApi)
|
|
362
431
|
|
|
363
432
|
|
|
364
433
|
def get_admissionregistration_client(context: str = ""):
|
|
365
|
-
"""Get a configured Kubernetes Admission Registration API client.
|
|
366
|
-
|
|
367
|
-
Args:
|
|
368
|
-
context: Optional context name for multi-cluster support
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
kubernetes.client.AdmissionregistrationV1Api: Configured Admission client
|
|
372
|
-
|
|
373
|
-
Raises:
|
|
374
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
375
|
-
"""
|
|
434
|
+
"""Get a configured Kubernetes Admission Registration API client."""
|
|
376
435
|
from kubernetes import client
|
|
377
|
-
|
|
378
|
-
try:
|
|
379
|
-
api_client = _load_config_for_context(context)
|
|
380
|
-
return client.AdmissionregistrationV1Api(api_client=api_client)
|
|
381
|
-
except Exception as e:
|
|
382
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
436
|
+
return _get_client(context, client.AdmissionregistrationV1Api)
|
|
383
437
|
|
|
384
438
|
|
|
385
439
|
def get_apiextensions_client(context: str = ""):
|
|
386
|
-
"""Get a configured Kubernetes API Extensions client.
|
|
387
|
-
|
|
388
|
-
Args:
|
|
389
|
-
context: Optional context name for multi-cluster support
|
|
390
|
-
|
|
391
|
-
Returns:
|
|
392
|
-
kubernetes.client.ApiextensionsV1Api: Configured API Extensions client
|
|
393
|
-
|
|
394
|
-
Raises:
|
|
395
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
396
|
-
"""
|
|
440
|
+
"""Get a configured Kubernetes API Extensions client."""
|
|
397
441
|
from kubernetes import client
|
|
398
|
-
|
|
399
|
-
try:
|
|
400
|
-
api_client = _load_config_for_context(context)
|
|
401
|
-
return client.ApiextensionsV1Api(api_client=api_client)
|
|
402
|
-
except Exception as e:
|
|
403
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
442
|
+
return _get_client(context, client.ApiextensionsV1Api)
|
|
404
443
|
|
|
405
444
|
|
|
406
445
|
def get_coordination_client(context: str = ""):
|
|
407
|
-
"""Get a configured Kubernetes Coordination API client.
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
context: Optional context name for multi-cluster support
|
|
411
|
-
|
|
412
|
-
Returns:
|
|
413
|
-
kubernetes.client.CoordinationV1Api: Configured Coordination client
|
|
414
|
-
|
|
415
|
-
Raises:
|
|
416
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
417
|
-
"""
|
|
446
|
+
"""Get a configured Kubernetes Coordination API client."""
|
|
418
447
|
from kubernetes import client
|
|
419
|
-
|
|
420
|
-
try:
|
|
421
|
-
api_client = _load_config_for_context(context)
|
|
422
|
-
return client.CoordinationV1Api(api_client=api_client)
|
|
423
|
-
except Exception as e:
|
|
424
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
448
|
+
return _get_client(context, client.CoordinationV1Api)
|
|
425
449
|
|
|
426
450
|
|
|
427
451
|
def get_events_client(context: str = ""):
|
|
428
|
-
"""Get a configured Kubernetes Events API client.
|
|
429
|
-
|
|
430
|
-
Args:
|
|
431
|
-
context: Optional context name for multi-cluster support
|
|
432
|
-
|
|
433
|
-
Returns:
|
|
434
|
-
kubernetes.client.EventsV1Api: Configured Events client
|
|
435
|
-
|
|
436
|
-
Raises:
|
|
437
|
-
RuntimeError: If Kubernetes config cannot be loaded
|
|
438
|
-
"""
|
|
452
|
+
"""Get a configured Kubernetes Events API client."""
|
|
439
453
|
from kubernetes import client
|
|
454
|
+
return _get_client(context, client.EventsV1Api)
|
|
440
455
|
|
|
441
|
-
try:
|
|
442
|
-
api_client = _load_config_for_context(context)
|
|
443
|
-
return client.EventsV1Api(api_client=api_client)
|
|
444
|
-
except Exception as e:
|
|
445
|
-
raise RuntimeError(f"Invalid kube-config. Context: {context or 'default'}. Error: {e}")
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
# Utility functions for context management
|
|
449
456
|
|
|
450
457
|
def list_contexts() -> list:
|
|
451
|
-
"""
|
|
452
|
-
|
|
458
|
+
"""List all available kubeconfig contexts."""
|
|
459
|
+
if _HAS_PROVIDER:
|
|
460
|
+
try:
|
|
461
|
+
provider = get_provider()
|
|
462
|
+
contexts = provider.list_contexts()
|
|
463
|
+
return [
|
|
464
|
+
{
|
|
465
|
+
"name": ctx.name,
|
|
466
|
+
"cluster": ctx.cluster,
|
|
467
|
+
"user": ctx.user,
|
|
468
|
+
"namespace": ctx.namespace,
|
|
469
|
+
"active": ctx.is_active
|
|
470
|
+
}
|
|
471
|
+
for ctx in contexts
|
|
472
|
+
]
|
|
473
|
+
except Exception as e:
|
|
474
|
+
logger.warning(f"Provider list_contexts failed: {e}")
|
|
453
475
|
|
|
454
|
-
Returns:
|
|
455
|
-
List of context dictionaries with name, cluster, user, namespace
|
|
456
|
-
"""
|
|
457
476
|
from kubernetes import config
|
|
458
477
|
|
|
459
478
|
try:
|
|
@@ -478,14 +497,14 @@ def list_contexts() -> list:
|
|
|
478
497
|
|
|
479
498
|
|
|
480
499
|
def get_active_context() -> Optional[str]:
|
|
481
|
-
"""
|
|
482
|
-
|
|
500
|
+
"""Get the current active context name."""
|
|
501
|
+
if _HAS_PROVIDER:
|
|
502
|
+
try:
|
|
503
|
+
return provider_get_current_context()
|
|
504
|
+
except Exception as e:
|
|
505
|
+
logger.warning(f"Provider get_current_context failed: {e}")
|
|
483
506
|
|
|
484
|
-
Returns:
|
|
485
|
-
Active context name or None
|
|
486
|
-
"""
|
|
487
507
|
from kubernetes import config
|
|
488
|
-
|
|
489
508
|
try:
|
|
490
509
|
kubeconfig_path = os.environ.get('KUBECONFIG', '~/.kube/config')
|
|
491
510
|
kubeconfig_path = os.path.expanduser(kubeconfig_path)
|
|
@@ -498,33 +517,33 @@ def get_active_context() -> Optional[str]:
|
|
|
498
517
|
|
|
499
518
|
|
|
500
519
|
def context_exists(context: str) -> bool:
|
|
501
|
-
"""
|
|
502
|
-
Check if a context exists in kubeconfig.
|
|
503
|
-
|
|
504
|
-
Args:
|
|
505
|
-
context: Context name to check
|
|
506
|
-
|
|
507
|
-
Returns:
|
|
508
|
-
True if context exists
|
|
509
|
-
"""
|
|
520
|
+
"""Check if a context exists in kubeconfig."""
|
|
510
521
|
contexts = list_contexts()
|
|
511
522
|
return any(ctx["name"] == context for ctx in contexts)
|
|
512
523
|
|
|
513
524
|
|
|
514
525
|
def _get_kubectl_context_args(context: str = "") -> list:
|
|
515
|
-
"""
|
|
516
|
-
Get kubectl command arguments for specifying a context.
|
|
517
|
-
|
|
518
|
-
This utility function returns the appropriate --context flag arguments
|
|
519
|
-
for kubectl commands when targeting a specific cluster.
|
|
520
|
-
|
|
521
|
-
Args:
|
|
522
|
-
context: Context name (empty string for default context)
|
|
523
|
-
|
|
524
|
-
Returns:
|
|
525
|
-
List of command arguments, e.g., ["--context", "my-cluster"]
|
|
526
|
-
or empty list if no context specified
|
|
527
|
-
"""
|
|
526
|
+
"""Get kubectl command arguments for specifying a context."""
|
|
528
527
|
if context and context.strip():
|
|
529
528
|
return ["--context", context.strip()]
|
|
530
529
|
return []
|
|
530
|
+
|
|
531
|
+
|
|
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
|
+
|
|
543
|
+
if _HAS_PROVIDER:
|
|
544
|
+
__all__ = _BASE_EXPORTS + [
|
|
545
|
+
"KubernetesProvider", "ProviderConfig", "ProviderType",
|
|
546
|
+
"UnknownContextError", "get_provider", "validate_context",
|
|
547
|
+
]
|
|
548
|
+
else:
|
|
549
|
+
__all__ = _BASE_EXPORTS
|