From 4f432aa995b4eb21be17215001f4e970e6484713 Mon Sep 17 00:00:00 2001 From: henry3260 Date: Fri, 4 Sep 2026 23:51:48 +0800 Subject: [PATCH] Fix XCom sidecar helper mutating the caller's pod volumes add_xcom_sidecar deep-copies the input pod so callers keep an untouched object, but it then pointed the copy's volumes back at the original pod's list before inserting the xcom volume. Any caller reusing the same pod object accumulated one extra "xcom" volume per call, which the Kubernetes API rejects as a duplicate. KubernetesPodOperator was only shielded because reconcile_pods happens to deep-copy first. The default V1Volume and V1VolumeMount were also inserted as the shared module-level instances, so editing them on one pod silently changed every other pod built by the helper and the defaults themselves. --- .../cncf/kubernetes/utils/xcom_sidecar.py | 6 +++--- .../cncf/kubernetes/utils/test_xcom_sidecar.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py index 5bf26975b32c3..5b6f34d8bd988 100644 --- a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py +++ b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py @@ -62,10 +62,10 @@ def add_xcom_sidecar( ) -> k8s.V1Pod: """Add sidecar.""" pod_cp = copy.deepcopy(pod) - pod_cp.spec.volumes = pod.spec.volumes or [] - pod_cp.spec.volumes.insert(0, PodDefaults.VOLUME) + pod_cp.spec.volumes = pod_cp.spec.volumes or [] + pod_cp.spec.volumes.insert(0, copy.deepcopy(PodDefaults.VOLUME)) pod_cp.spec.containers[0].volume_mounts = pod_cp.spec.containers[0].volume_mounts or [] - pod_cp.spec.containers[0].volume_mounts.insert(0, PodDefaults.VOLUME_MOUNT) + pod_cp.spec.containers[0].volume_mounts.insert(0, copy.deepcopy(PodDefaults.VOLUME_MOUNT)) sidecar = copy.deepcopy(PodDefaults.SIDECAR_CONTAINER) sidecar.image = sidecar_container_image or PodDefaults.SIDECAR_CONTAINER.image if sidecar_container_resources: diff --git a/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_xcom_sidecar.py b/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_xcom_sidecar.py index 7fc9e9b4796ab..34b04673923d2 100644 --- a/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_xcom_sidecar.py +++ b/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/utils/test_xcom_sidecar.py @@ -69,3 +69,20 @@ def test_add_xcom_sidecar_empty_security_context(): def test_add_xcom_sidecar_does_not_mutate_shared_default(): add_xcom_sidecar(_base_pod(), sidecar_container_security_context={"readOnlyRootFilesystem": True}) assert PodDefaults.SIDECAR_CONTAINER.security_context is None + + +def test_add_xcom_sidecar_does_not_mutate_input_pod_volumes(): + pod = _base_pod() + pod.spec.volumes = [k8s.V1Volume(name="data", empty_dir=k8s.V1EmptyDirVolumeSource())] + + result = add_xcom_sidecar(pod) + + assert [v.name for v in pod.spec.volumes] == ["data"] + assert [v.name for v in result.spec.volumes] == ["xcom", "data"] + + +def test_add_xcom_sidecar_copies_default_volume_and_mount(): + result = add_xcom_sidecar(_base_pod()) + + assert result.spec.volumes[0] is not PodDefaults.VOLUME + assert result.spec.containers[0].volume_mounts[0] is not PodDefaults.VOLUME_MOUNT