fix(k8s): read existing resourceVersion/clusterIP before replace
K8s PUT (replace) operations require metadata.resourceVersion for optimistic concurrency control. Services additionally have immutable spec.clusterIP that must be preserved from the existing object. On 409 conflict, all _ensure_* methods now read the existing resource first and copy resourceVersion (and clusterIP for Services) into the body before calling replace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>fix/kind-mount-propagation
parent
1da69cf739
commit
14f423ea0c
|
|
@ -202,6 +202,10 @@ class K8sDeployer(Deployer):
|
||||||
print(f"ConfigMap created: {resp}")
|
print(f"ConfigMap created: {resp}")
|
||||||
except ApiException as e:
|
except ApiException as e:
|
||||||
if e.status == 409:
|
if e.status == 409:
|
||||||
|
existing = self.core_api.read_namespaced_config_map(
|
||||||
|
name=cfg_map.metadata.name, namespace=self.k8s_namespace
|
||||||
|
)
|
||||||
|
cfg_map.metadata.resource_version = existing.metadata.resource_version
|
||||||
resp = self.core_api.replace_namespaced_config_map(
|
resp = self.core_api.replace_namespaced_config_map(
|
||||||
name=cfg_map.metadata.name,
|
name=cfg_map.metadata.name,
|
||||||
namespace=self.k8s_namespace,
|
namespace=self.k8s_namespace,
|
||||||
|
|
@ -225,6 +229,13 @@ class K8sDeployer(Deployer):
|
||||||
print("Deployment created:")
|
print("Deployment created:")
|
||||||
except ApiException as e:
|
except ApiException as e:
|
||||||
if e.status == 409:
|
if e.status == 409:
|
||||||
|
existing = self.apps_api.read_namespaced_deployment(
|
||||||
|
name=deployment.metadata.name,
|
||||||
|
namespace=self.k8s_namespace,
|
||||||
|
)
|
||||||
|
deployment.metadata.resource_version = (
|
||||||
|
existing.metadata.resource_version
|
||||||
|
)
|
||||||
resp = cast(
|
resp = cast(
|
||||||
client.V1Deployment,
|
client.V1Deployment,
|
||||||
self.apps_api.replace_namespaced_deployment(
|
self.apps_api.replace_namespaced_deployment(
|
||||||
|
|
@ -246,7 +257,11 @@ class K8sDeployer(Deployer):
|
||||||
print(f"{meta.namespace} {meta.name} {meta.generation} {img}")
|
print(f"{meta.namespace} {meta.name} {meta.generation} {img}")
|
||||||
|
|
||||||
def _ensure_service(self, service, kind: str = "Service"):
|
def _ensure_service(self, service, kind: str = "Service"):
|
||||||
"""Create or replace a Service (idempotent)."""
|
"""Create or replace a Service (idempotent).
|
||||||
|
|
||||||
|
Services have immutable fields (spec.clusterIP) that must be
|
||||||
|
preserved from the existing object on replace.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
resp = self.core_api.create_namespaced_service(
|
resp = self.core_api.create_namespaced_service(
|
||||||
namespace=self.k8s_namespace, body=service
|
namespace=self.k8s_namespace, body=service
|
||||||
|
|
@ -255,6 +270,12 @@ class K8sDeployer(Deployer):
|
||||||
print(f"{kind} created: {resp}")
|
print(f"{kind} created: {resp}")
|
||||||
except ApiException as e:
|
except ApiException as e:
|
||||||
if e.status == 409:
|
if e.status == 409:
|
||||||
|
existing = self.core_api.read_namespaced_service(
|
||||||
|
name=service.metadata.name, namespace=self.k8s_namespace
|
||||||
|
)
|
||||||
|
service.metadata.resource_version = existing.metadata.resource_version
|
||||||
|
if existing.spec.cluster_ip:
|
||||||
|
service.spec.cluster_ip = existing.spec.cluster_ip
|
||||||
resp = self.core_api.replace_namespaced_service(
|
resp = self.core_api.replace_namespaced_service(
|
||||||
name=service.metadata.name,
|
name=service.metadata.name,
|
||||||
namespace=self.k8s_namespace,
|
namespace=self.k8s_namespace,
|
||||||
|
|
@ -275,6 +296,10 @@ class K8sDeployer(Deployer):
|
||||||
print(f"Ingress created: {resp}")
|
print(f"Ingress created: {resp}")
|
||||||
except ApiException as e:
|
except ApiException as e:
|
||||||
if e.status == 409:
|
if e.status == 409:
|
||||||
|
existing = self.networking_api.read_namespaced_ingress(
|
||||||
|
name=ingress.metadata.name, namespace=self.k8s_namespace
|
||||||
|
)
|
||||||
|
ingress.metadata.resource_version = existing.metadata.resource_version
|
||||||
resp = self.networking_api.replace_namespaced_ingress(
|
resp = self.networking_api.replace_namespaced_ingress(
|
||||||
name=ingress.metadata.name,
|
name=ingress.metadata.name,
|
||||||
namespace=self.k8s_namespace,
|
namespace=self.k8s_namespace,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue