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
A. F. Dudley 2026-03-08 04:32:20 +00:00
parent 1da69cf739
commit 14f423ea0c
1 changed files with 26 additions and 1 deletions

View File

@ -202,6 +202,10 @@ class K8sDeployer(Deployer):
print(f"ConfigMap created: {resp}")
except ApiException as e:
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(
name=cfg_map.metadata.name,
namespace=self.k8s_namespace,
@ -225,6 +229,13 @@ class K8sDeployer(Deployer):
print("Deployment created:")
except ApiException as e:
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(
client.V1Deployment,
self.apps_api.replace_namespaced_deployment(
@ -246,7 +257,11 @@ class K8sDeployer(Deployer):
print(f"{meta.namespace} {meta.name} {meta.generation} {img}")
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:
resp = self.core_api.create_namespaced_service(
namespace=self.k8s_namespace, body=service
@ -255,6 +270,12 @@ class K8sDeployer(Deployer):
print(f"{kind} created: {resp}")
except ApiException as e:
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(
name=service.metadata.name,
namespace=self.k8s_namespace,
@ -275,6 +296,10 @@ class K8sDeployer(Deployer):
print(f"Ingress created: {resp}")
except ApiException as e:
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(
name=ingress.metadata.name,
namespace=self.k8s_namespace,