dory-processor-sdk 0.0.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.
- dory/__init__.py +101 -0
- dory/auth/__init__.py +10 -0
- dory/auth/oauth2.py +153 -0
- dory/auto_instrument.py +142 -0
- dory/cli/__init__.py +5 -0
- dory/cli/main.py +137 -0
- dory/cli/templates.py +123 -0
- dory/config/__init__.py +23 -0
- dory/config/defaults.py +24 -0
- dory/config/loader.py +430 -0
- dory/config/presets.py +73 -0
- dory/config/schema.py +84 -0
- dory/core/__init__.py +27 -0
- dory/core/app.py +434 -0
- dory/core/context.py +209 -0
- dory/core/lifecycle.py +214 -0
- dory/core/meta.py +121 -0
- dory/core/modes.py +479 -0
- dory/core/processor.py +564 -0
- dory/core/signals.py +122 -0
- dory/decorators.py +142 -0
- dory/edge/__init__.py +88 -0
- dory/edge/adaptive.py +644 -0
- dory/edge/detector.py +546 -0
- dory/edge/fencing.py +488 -0
- dory/edge/heartbeat.py +598 -0
- dory/edge/role.py +419 -0
- dory/errors/__init__.py +139 -0
- dory/errors/classification.py +362 -0
- dory/errors/codes.py +498 -0
- dory/geo/__init__.py +40 -0
- dory/geo/geolocalizer.py +1034 -0
- dory/health/__init__.py +12 -0
- dory/health/probes.py +210 -0
- dory/health/server.py +635 -0
- dory/k8s/__init__.py +80 -0
- dory/k8s/annotation_watcher.py +184 -0
- dory/k8s/client.py +251 -0
- dory/k8s/labels.py +505 -0
- dory/k8s/pod_metadata.py +182 -0
- dory/logging/__init__.py +9 -0
- dory/logging/logger.py +148 -0
- dory/metrics/__init__.py +7 -0
- dory/metrics/collector.py +301 -0
- dory/middleware/__init__.py +46 -0
- dory/middleware/connection_tracker.py +608 -0
- dory/middleware/request_id.py +325 -0
- dory/middleware/request_tracker.py +511 -0
- dory/migration/__init__.py +33 -0
- dory/migration/configmap.py +232 -0
- dory/migration/s3_store.py +594 -0
- dory/migration/serialization.py +135 -0
- dory/migration/state_manager.py +286 -0
- dory/migration/transfer.py +382 -0
- dory/monitoring/__init__.py +29 -0
- dory/monitoring/opentelemetry.py +489 -0
- dory/output/__init__.py +31 -0
- dory/output/envelope.py +137 -0
- dory/output/formatter.py +113 -0
- dory/output/rabbitmq.py +632 -0
- dory/output/routing.py +318 -0
- dory/output/validator.py +199 -0
- dory/py.typed +2 -0
- dory/recovery/__init__.py +60 -0
- dory/recovery/golden_image.py +487 -0
- dory/recovery/golden_snapshot.py +713 -0
- dory/recovery/golden_validator.py +518 -0
- dory/recovery/partial_recovery.py +482 -0
- dory/recovery/recovery_decision.py +242 -0
- dory/recovery/restart_detector.py +142 -0
- dory/recovery/state_validator.py +183 -0
- dory/resilience/__init__.py +45 -0
- dory/resilience/circuit_breaker.py +457 -0
- dory/resilience/retry.py +389 -0
- dory/simple.py +342 -0
- dory/types.py +68 -0
- dory/utils/__init__.py +31 -0
- dory/utils/errors.py +59 -0
- dory/utils/retry.py +115 -0
- dory/utils/timeout.py +80 -0
- dory_processor_sdk-0.0.1.dist-info/METADATA +424 -0
- dory_processor_sdk-0.0.1.dist-info/RECORD +86 -0
- dory_processor_sdk-0.0.1.dist-info/WHEEL +5 -0
- dory_processor_sdk-0.0.1.dist-info/entry_points.txt +2 -0
- dory_processor_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
- dory_processor_sdk-0.0.1.dist-info/top_level.txt +1 -0
dory/k8s/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Kubernetes integration utilities."""
|
|
2
|
+
|
|
3
|
+
from dory.k8s.client import K8sClient
|
|
4
|
+
from dory.k8s.pod_metadata import PodMetadata
|
|
5
|
+
from dory.k8s.annotation_watcher import AnnotationWatcher
|
|
6
|
+
from dory.k8s.labels import (
|
|
7
|
+
# Label keys
|
|
8
|
+
LABEL_MANAGED_BY,
|
|
9
|
+
LABEL_APP_NAME,
|
|
10
|
+
LABEL_PROCESSOR_ID,
|
|
11
|
+
LABEL_WORKLOAD_TYPE,
|
|
12
|
+
LABEL_WORKLOAD_LOCATION,
|
|
13
|
+
LABEL_NODE_TYPE,
|
|
14
|
+
LABEL_MIGRATED_FROM_EDGE,
|
|
15
|
+
LABEL_ORIGINAL_NODE,
|
|
16
|
+
# Label values
|
|
17
|
+
VALUE_ORCHESTRATOR_NAME,
|
|
18
|
+
VALUE_EDGE,
|
|
19
|
+
VALUE_MANAGED,
|
|
20
|
+
# Enums
|
|
21
|
+
WorkloadLocation,
|
|
22
|
+
NodeType,
|
|
23
|
+
# Builder
|
|
24
|
+
DoryLabels,
|
|
25
|
+
# Utilities
|
|
26
|
+
get_label,
|
|
27
|
+
get_app_name,
|
|
28
|
+
get_processor_id,
|
|
29
|
+
get_workload_location,
|
|
30
|
+
is_managed_by_dory,
|
|
31
|
+
is_edge_workload,
|
|
32
|
+
is_migrated_from_edge,
|
|
33
|
+
get_original_node,
|
|
34
|
+
detect_workload_context,
|
|
35
|
+
edge_node_selector,
|
|
36
|
+
managed_node_selector,
|
|
37
|
+
edge_toleration,
|
|
38
|
+
get_contract_documentation,
|
|
39
|
+
CONTRACT_VERSION,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Core
|
|
44
|
+
"K8sClient",
|
|
45
|
+
"PodMetadata",
|
|
46
|
+
"AnnotationWatcher",
|
|
47
|
+
# Label keys
|
|
48
|
+
"LABEL_MANAGED_BY",
|
|
49
|
+
"LABEL_APP_NAME",
|
|
50
|
+
"LABEL_PROCESSOR_ID",
|
|
51
|
+
"LABEL_WORKLOAD_TYPE",
|
|
52
|
+
"LABEL_WORKLOAD_LOCATION",
|
|
53
|
+
"LABEL_NODE_TYPE",
|
|
54
|
+
"LABEL_MIGRATED_FROM_EDGE",
|
|
55
|
+
"LABEL_ORIGINAL_NODE",
|
|
56
|
+
# Label values
|
|
57
|
+
"VALUE_ORCHESTRATOR_NAME",
|
|
58
|
+
"VALUE_EDGE",
|
|
59
|
+
"VALUE_MANAGED",
|
|
60
|
+
# Enums
|
|
61
|
+
"WorkloadLocation",
|
|
62
|
+
"NodeType",
|
|
63
|
+
# Builder
|
|
64
|
+
"DoryLabels",
|
|
65
|
+
# Utilities
|
|
66
|
+
"get_label",
|
|
67
|
+
"get_app_name",
|
|
68
|
+
"get_processor_id",
|
|
69
|
+
"get_workload_location",
|
|
70
|
+
"is_managed_by_dory",
|
|
71
|
+
"is_edge_workload",
|
|
72
|
+
"is_migrated_from_edge",
|
|
73
|
+
"get_original_node",
|
|
74
|
+
"detect_workload_context",
|
|
75
|
+
"edge_node_selector",
|
|
76
|
+
"managed_node_selector",
|
|
77
|
+
"edge_toleration",
|
|
78
|
+
"get_contract_documentation",
|
|
79
|
+
"CONTRACT_VERSION",
|
|
80
|
+
]
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Annotation watcher for migration signals.
|
|
3
|
+
|
|
4
|
+
Watches pod annotations for migration-related signals
|
|
5
|
+
from the orchestrator.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Callable, Any
|
|
11
|
+
|
|
12
|
+
from dory.k8s.client import K8sClient
|
|
13
|
+
from dory.utils.errors import DoryK8sError
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AnnotationWatcher:
|
|
19
|
+
"""
|
|
20
|
+
Watches pod annotations for orchestrator signals.
|
|
21
|
+
|
|
22
|
+
Monitors annotations:
|
|
23
|
+
- dory.io/migration: "true" when migration imminent
|
|
24
|
+
- dory.io/shutdown: "true" when shutdown requested
|
|
25
|
+
- dory.io/snapshot: "true" when snapshot requested
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
MIGRATION_ANNOTATION = "dory.io/migration"
|
|
29
|
+
SHUTDOWN_ANNOTATION = "dory.io/shutdown"
|
|
30
|
+
SNAPSHOT_ANNOTATION = "dory.io/snapshot"
|
|
31
|
+
DEADLINE_ANNOTATION = "dory.io/migration-deadline"
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
k8s_client: K8sClient,
|
|
36
|
+
pod_name: str,
|
|
37
|
+
poll_interval: float = 5.0,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize annotation watcher.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
k8s_client: Kubernetes client
|
|
44
|
+
pod_name: Name of pod to watch
|
|
45
|
+
poll_interval: Seconds between polls
|
|
46
|
+
"""
|
|
47
|
+
self._k8s_client = k8s_client
|
|
48
|
+
self._pod_name = pod_name
|
|
49
|
+
self._poll_interval = poll_interval
|
|
50
|
+
|
|
51
|
+
self._running = False
|
|
52
|
+
self._watch_task: asyncio.Task | None = None
|
|
53
|
+
|
|
54
|
+
# Callbacks
|
|
55
|
+
self._on_migration: Callable[[], Any] | None = None
|
|
56
|
+
self._on_shutdown: Callable[[], Any] | None = None
|
|
57
|
+
self._on_snapshot: Callable[[], Any] | None = None
|
|
58
|
+
|
|
59
|
+
# State tracking
|
|
60
|
+
self._last_annotations: dict[str, str] = {}
|
|
61
|
+
|
|
62
|
+
def on_migration(self, callback: Callable[[], Any]) -> None:
|
|
63
|
+
"""Set callback for migration signal."""
|
|
64
|
+
self._on_migration = callback
|
|
65
|
+
|
|
66
|
+
def on_shutdown(self, callback: Callable[[], Any]) -> None:
|
|
67
|
+
"""Set callback for shutdown signal."""
|
|
68
|
+
self._on_shutdown = callback
|
|
69
|
+
|
|
70
|
+
def on_snapshot(self, callback: Callable[[], Any]) -> None:
|
|
71
|
+
"""Set callback for snapshot signal."""
|
|
72
|
+
self._on_snapshot = callback
|
|
73
|
+
|
|
74
|
+
async def start(self) -> None:
|
|
75
|
+
"""Start watching annotations."""
|
|
76
|
+
if self._running:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
self._running = True
|
|
80
|
+
self._watch_task = asyncio.create_task(self._watch_loop())
|
|
81
|
+
logger.info(f"Started annotation watcher for pod {self._pod_name}")
|
|
82
|
+
|
|
83
|
+
async def stop(self) -> None:
|
|
84
|
+
"""Stop watching annotations."""
|
|
85
|
+
self._running = False
|
|
86
|
+
|
|
87
|
+
if self._watch_task:
|
|
88
|
+
self._watch_task.cancel()
|
|
89
|
+
try:
|
|
90
|
+
await self._watch_task
|
|
91
|
+
except asyncio.CancelledError:
|
|
92
|
+
pass
|
|
93
|
+
self._watch_task = None
|
|
94
|
+
|
|
95
|
+
logger.info("Annotation watcher stopped")
|
|
96
|
+
|
|
97
|
+
async def _watch_loop(self) -> None:
|
|
98
|
+
"""Main watch loop."""
|
|
99
|
+
while self._running:
|
|
100
|
+
try:
|
|
101
|
+
await self._check_annotations()
|
|
102
|
+
except DoryK8sError as e:
|
|
103
|
+
logger.warning(f"Failed to check annotations: {e}")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Unexpected error in annotation watcher: {e}")
|
|
106
|
+
|
|
107
|
+
await asyncio.sleep(self._poll_interval)
|
|
108
|
+
|
|
109
|
+
async def _check_annotations(self) -> None:
|
|
110
|
+
"""Check annotations for changes."""
|
|
111
|
+
try:
|
|
112
|
+
annotations = await self._k8s_client.get_pod_annotations(self._pod_name)
|
|
113
|
+
except DoryK8sError:
|
|
114
|
+
# Pod might not exist yet or API unavailable
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Check migration annotation
|
|
118
|
+
if self._annotation_changed(self.MIGRATION_ANNOTATION, annotations, "true"):
|
|
119
|
+
logger.info("Migration signal detected")
|
|
120
|
+
if self._on_migration:
|
|
121
|
+
await self._invoke_callback(self._on_migration)
|
|
122
|
+
|
|
123
|
+
# Check shutdown annotation
|
|
124
|
+
if self._annotation_changed(self.SHUTDOWN_ANNOTATION, annotations, "true"):
|
|
125
|
+
logger.info("Shutdown signal detected")
|
|
126
|
+
if self._on_shutdown:
|
|
127
|
+
await self._invoke_callback(self._on_shutdown)
|
|
128
|
+
|
|
129
|
+
# Check snapshot annotation
|
|
130
|
+
if self._annotation_changed(self.SNAPSHOT_ANNOTATION, annotations, "true"):
|
|
131
|
+
logger.info("Snapshot signal detected")
|
|
132
|
+
if self._on_snapshot:
|
|
133
|
+
await self._invoke_callback(self._on_snapshot)
|
|
134
|
+
# Clear snapshot annotation after processing
|
|
135
|
+
await self._clear_annotation(self.SNAPSHOT_ANNOTATION)
|
|
136
|
+
|
|
137
|
+
self._last_annotations = annotations
|
|
138
|
+
|
|
139
|
+
def _annotation_changed(
|
|
140
|
+
self,
|
|
141
|
+
key: str,
|
|
142
|
+
new_annotations: dict[str, str],
|
|
143
|
+
trigger_value: str,
|
|
144
|
+
) -> bool:
|
|
145
|
+
"""Check if annotation changed to trigger value."""
|
|
146
|
+
old_value = self._last_annotations.get(key)
|
|
147
|
+
new_value = new_annotations.get(key)
|
|
148
|
+
|
|
149
|
+
return old_value != new_value and new_value == trigger_value
|
|
150
|
+
|
|
151
|
+
async def _invoke_callback(self, callback: Callable[[], Any]) -> None:
|
|
152
|
+
"""Invoke callback, handling async/sync."""
|
|
153
|
+
try:
|
|
154
|
+
if asyncio.iscoroutinefunction(callback):
|
|
155
|
+
await callback()
|
|
156
|
+
else:
|
|
157
|
+
callback()
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Callback error: {e}")
|
|
160
|
+
|
|
161
|
+
async def _clear_annotation(self, key: str) -> None:
|
|
162
|
+
"""Clear an annotation after processing."""
|
|
163
|
+
try:
|
|
164
|
+
await self._k8s_client.patch_pod_annotations(
|
|
165
|
+
self._pod_name,
|
|
166
|
+
{key: None}, # Setting to None removes the annotation
|
|
167
|
+
)
|
|
168
|
+
except DoryK8sError as e:
|
|
169
|
+
logger.warning(f"Failed to clear annotation {key}: {e}")
|
|
170
|
+
|
|
171
|
+
def get_migration_deadline(self) -> float | None:
|
|
172
|
+
"""
|
|
173
|
+
Get migration deadline from annotations.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Unix timestamp of deadline, or None
|
|
177
|
+
"""
|
|
178
|
+
deadline_str = self._last_annotations.get(self.DEADLINE_ANNOTATION)
|
|
179
|
+
if deadline_str:
|
|
180
|
+
try:
|
|
181
|
+
return float(deadline_str)
|
|
182
|
+
except ValueError:
|
|
183
|
+
pass
|
|
184
|
+
return None
|
dory/k8s/client.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kubernetes client wrapper.
|
|
3
|
+
|
|
4
|
+
Provides simplified interface to Kubernetes API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from dory.utils.errors import DoryK8sError
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Optional kubernetes import
|
|
15
|
+
try:
|
|
16
|
+
from kubernetes import client, config
|
|
17
|
+
from kubernetes.client.rest import ApiException
|
|
18
|
+
K8S_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
K8S_AVAILABLE = False
|
|
21
|
+
client = None
|
|
22
|
+
config = None
|
|
23
|
+
ApiException = Exception
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class K8sClient:
|
|
27
|
+
"""
|
|
28
|
+
Kubernetes API client wrapper.
|
|
29
|
+
|
|
30
|
+
Handles configuration loading and provides
|
|
31
|
+
simplified access to common operations.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, namespace: str | None = None):
|
|
35
|
+
"""
|
|
36
|
+
Initialize Kubernetes client.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
namespace: Kubernetes namespace (auto-detected if not provided)
|
|
40
|
+
"""
|
|
41
|
+
self._namespace = namespace
|
|
42
|
+
self._core_api: Any = None
|
|
43
|
+
self._initialized = False
|
|
44
|
+
|
|
45
|
+
def _ensure_initialized(self) -> None:
|
|
46
|
+
"""Initialize Kubernetes client if not already done."""
|
|
47
|
+
if self._initialized:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if not K8S_AVAILABLE:
|
|
51
|
+
raise DoryK8sError(
|
|
52
|
+
"Kubernetes client not available. "
|
|
53
|
+
"Install with: pip install kubernetes"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# Try in-cluster config first
|
|
58
|
+
config.load_incluster_config()
|
|
59
|
+
logger.debug("Using in-cluster Kubernetes config")
|
|
60
|
+
except config.ConfigException:
|
|
61
|
+
try:
|
|
62
|
+
# Fall back to kubeconfig
|
|
63
|
+
config.load_kube_config()
|
|
64
|
+
logger.debug("Using kubeconfig")
|
|
65
|
+
except config.ConfigException as e:
|
|
66
|
+
raise DoryK8sError(f"Failed to load Kubernetes config: {e}", cause=e)
|
|
67
|
+
|
|
68
|
+
self._core_api = client.CoreV1Api()
|
|
69
|
+
|
|
70
|
+
# Auto-detect namespace if not provided
|
|
71
|
+
if not self._namespace:
|
|
72
|
+
import os
|
|
73
|
+
self._namespace = os.environ.get("POD_NAMESPACE", "default")
|
|
74
|
+
|
|
75
|
+
self._initialized = True
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def namespace(self) -> str:
|
|
79
|
+
"""Get current namespace."""
|
|
80
|
+
self._ensure_initialized()
|
|
81
|
+
return self._namespace
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def core_api(self):
|
|
85
|
+
"""Get CoreV1Api client."""
|
|
86
|
+
self._ensure_initialized()
|
|
87
|
+
return self._core_api
|
|
88
|
+
|
|
89
|
+
async def get_pod(self, name: str) -> dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
Get pod details.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
name: Pod name
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Pod details as dictionary
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
DoryK8sError: If operation fails
|
|
101
|
+
"""
|
|
102
|
+
self._ensure_initialized()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
pod = self._core_api.read_namespaced_pod(
|
|
106
|
+
name=name,
|
|
107
|
+
namespace=self._namespace,
|
|
108
|
+
)
|
|
109
|
+
return pod.to_dict()
|
|
110
|
+
|
|
111
|
+
except ApiException as e:
|
|
112
|
+
if e.status == 404:
|
|
113
|
+
raise DoryK8sError(f"Pod not found: {name}", cause=e)
|
|
114
|
+
raise DoryK8sError(f"Failed to get pod {name}: {e}", cause=e)
|
|
115
|
+
|
|
116
|
+
async def get_pod_annotations(self, name: str) -> dict[str, str]:
|
|
117
|
+
"""
|
|
118
|
+
Get pod annotations.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name: Pod name
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Annotations dictionary
|
|
125
|
+
"""
|
|
126
|
+
pod = await self.get_pod(name)
|
|
127
|
+
return pod.get("metadata", {}).get("annotations", {})
|
|
128
|
+
|
|
129
|
+
async def patch_pod_annotations(
|
|
130
|
+
self,
|
|
131
|
+
name: str,
|
|
132
|
+
annotations: dict[str, str],
|
|
133
|
+
) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Patch pod annotations.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
name: Pod name
|
|
139
|
+
annotations: Annotations to add/update
|
|
140
|
+
"""
|
|
141
|
+
self._ensure_initialized()
|
|
142
|
+
|
|
143
|
+
body = {
|
|
144
|
+
"metadata": {
|
|
145
|
+
"annotations": annotations,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
self._core_api.patch_namespaced_pod(
|
|
151
|
+
name=name,
|
|
152
|
+
namespace=self._namespace,
|
|
153
|
+
body=body,
|
|
154
|
+
)
|
|
155
|
+
logger.debug(f"Patched annotations on pod {name}")
|
|
156
|
+
|
|
157
|
+
except ApiException as e:
|
|
158
|
+
raise DoryK8sError(f"Failed to patch pod {name}: {e}", cause=e)
|
|
159
|
+
|
|
160
|
+
async def get_configmap(self, name: str) -> dict[str, str] | None:
|
|
161
|
+
"""
|
|
162
|
+
Get ConfigMap data.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
name: ConfigMap name
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
ConfigMap data, or None if not found
|
|
169
|
+
"""
|
|
170
|
+
self._ensure_initialized()
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
cm = self._core_api.read_namespaced_config_map(
|
|
174
|
+
name=name,
|
|
175
|
+
namespace=self._namespace,
|
|
176
|
+
)
|
|
177
|
+
return cm.data or {}
|
|
178
|
+
|
|
179
|
+
except ApiException as e:
|
|
180
|
+
if e.status == 404:
|
|
181
|
+
return None
|
|
182
|
+
raise DoryK8sError(f"Failed to get ConfigMap {name}: {e}", cause=e)
|
|
183
|
+
|
|
184
|
+
async def create_or_update_configmap(
|
|
185
|
+
self,
|
|
186
|
+
name: str,
|
|
187
|
+
data: dict[str, str],
|
|
188
|
+
labels: dict[str, str] | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Create or update a ConfigMap.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
name: ConfigMap name
|
|
195
|
+
data: ConfigMap data
|
|
196
|
+
labels: Optional labels
|
|
197
|
+
"""
|
|
198
|
+
self._ensure_initialized()
|
|
199
|
+
|
|
200
|
+
configmap = client.V1ConfigMap(
|
|
201
|
+
metadata=client.V1ObjectMeta(
|
|
202
|
+
name=name,
|
|
203
|
+
namespace=self._namespace,
|
|
204
|
+
labels=labels,
|
|
205
|
+
),
|
|
206
|
+
data=data,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
# Try create first
|
|
211
|
+
self._core_api.create_namespaced_config_map(
|
|
212
|
+
namespace=self._namespace,
|
|
213
|
+
body=configmap,
|
|
214
|
+
)
|
|
215
|
+
logger.debug(f"Created ConfigMap {name}")
|
|
216
|
+
|
|
217
|
+
except ApiException as e:
|
|
218
|
+
if e.status == 409:
|
|
219
|
+
# Already exists, update
|
|
220
|
+
self._core_api.replace_namespaced_config_map(
|
|
221
|
+
name=name,
|
|
222
|
+
namespace=self._namespace,
|
|
223
|
+
body=configmap,
|
|
224
|
+
)
|
|
225
|
+
logger.debug(f"Updated ConfigMap {name}")
|
|
226
|
+
else:
|
|
227
|
+
raise DoryK8sError(f"Failed to create ConfigMap {name}: {e}", cause=e)
|
|
228
|
+
|
|
229
|
+
async def delete_configmap(self, name: str) -> bool:
|
|
230
|
+
"""
|
|
231
|
+
Delete a ConfigMap.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
name: ConfigMap name
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
True if deleted, False if not found
|
|
238
|
+
"""
|
|
239
|
+
self._ensure_initialized()
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
self._core_api.delete_namespaced_config_map(
|
|
243
|
+
name=name,
|
|
244
|
+
namespace=self._namespace,
|
|
245
|
+
)
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
except ApiException as e:
|
|
249
|
+
if e.status == 404:
|
|
250
|
+
return False
|
|
251
|
+
raise DoryK8sError(f"Failed to delete ConfigMap {name}: {e}", cause=e)
|