From 5abb4b9484db5c1659cbcbd90e8380f42df60972 Mon Sep 17 00:00:00 2001 From: Andy Yu Date: Sat, 31 Jan 2026 18:09:00 +1300 Subject: [PATCH] support legacy patchesStrategicMerge field test: port 6 additional transformation tests from kustomize Added tests: - volume-remove-emptydir-overlay: Tests emptyDir removal with overlay patches and configMapGenerator - remove-emptydir-same-level: Tests removing emptyDir with multiple patches at same level - null-values-1: Tests that null values in args field are preserved - null-values-2: Tests that null volumes field is preserved - name-prefix-suffix-patch: Tests patches correctly update ConfigMap references with hash suffixes (issue #2609) - patch-different-ports: Tests ports with different merge keys are merged correctly test: add failing test for patch port duplicate bug When patching a Service port where the patch doesn't specify protocol, the port is duplicated instead of merged - one with protocol, one without. --- kustomizer/src/patch.rs | 58 ++++++++++++++----- .../patch-port-no-protocol/kustomization.yaml | 4 ++ .../patch-port-no-protocol/output.yaml | 14 +++++ .../patch-port-no-protocol/patch.yaml | 12 ++++ .../patch-port-no-protocol/service.yaml | 10 ++++ .../patch-port-no-protocol/test.yaml | 3 + 6 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/kustomization.yaml create mode 100644 kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/output.yaml create mode 100644 kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/patch.yaml create mode 100644 kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/service.yaml create mode 100644 kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/test.yaml diff --git a/kustomizer/src/patch.rs b/kustomizer/src/patch.rs index ac880dd..b3b8986 100644 --- a/kustomizer/src/patch.rs +++ b/kustomizer/src/patch.rs @@ -64,29 +64,61 @@ fn merge_array( patches: Vec, schema: Option<&ArrayType>, ) -> anyhow::Result<()> { + // two values match if they have at least one common element and + // corresponding elements only differ if one is an empty string + fn keys_match<'a>( + keys: impl IntoIterator, + base: &Value, + patch: &Value, + ) -> bool { + let mut one_match = false; + for key in keys { + let base_value = base.get(key); + let patch_value = patch.get(key); + if base_value.is_some() && patch_value.is_some() { + if base_value == patch_value { + one_match = true; + } else if base_value.and_then(|v| v.as_str()) == Some("") + || patch_value.and_then(|v| v.as_str()) == Some("") + { + continue; + } else { + return false; + } + } + } + + one_match + } + match schema { Some(schema) => match schema.list_map_keys.as_deref() { Some(keys) => { for patch in patches { - if let Some(pos) = bases.iter().position(|base| { - keys.iter() - .all(|key| base.get(key.as_str()) == patch.get(key.as_str())) - }) { + if let Some(pos) = bases + .iter() + .position(|base| keys_match(keys.iter().map(|s| s.as_str()), base, &patch)) + { merge(&mut bases[pos], patch, Some(&schema.items))?; } else { bases.push(patch); } } } - None => { - if schema.patch_strategy == Some(PatchStrategy::Merge) - && schema.list_type.is_none_or(|t| t != ListType::Atomic) - { - bases.extend(patches) - } else { - *bases = patches - } - } + None => match schema.patch_strategy { + Some(strategy) => match strategy { + PatchStrategy::Merge + if schema.list_type.is_none_or(|t| t != ListType::Atomic) => + { + bases.extend(patches); + return Ok(()); + } + PatchStrategy::Merge | PatchStrategy::Replace => *bases = patches, + PatchStrategy::RetainKeys => todo!("retainKeys"), + PatchStrategy::MergeRetainKeys => todo!("merge,retainKeys"), + }, + None => *bases = patches, + }, }, _ => *bases = patches, } diff --git a/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/kustomization.yaml b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/kustomization.yaml new file mode 100644 index 0000000..58e2563 --- /dev/null +++ b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/kustomization.yaml @@ -0,0 +1,4 @@ +resources: + - service.yaml +patchesStrategicMerge: + - patch.yaml diff --git a/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/output.yaml b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/output.yaml new file mode 100644 index 0000000..2a01854 --- /dev/null +++ b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/output.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: web + labels: + service: web +spec: + ports: + - port: 30900 + targetPort: 30900 + protocol: TCP + type: NodePort + selector: + service: web diff --git a/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/patch.yaml b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/patch.yaml new file mode 100644 index 0000000..f97b8da --- /dev/null +++ b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/patch.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: web + labels: + service: web +spec: + ports: + - port: 30900 + targetPort: 30900 + selector: + service: web diff --git a/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/service.yaml b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/service.yaml new file mode 100644 index 0000000..c6c2083 --- /dev/null +++ b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: web +spec: + ports: + - port: 30900 + targetPort: 30900 + protocol: TCP + type: NodePort diff --git a/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/test.yaml b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/test.yaml new file mode 100644 index 0000000..639fbde --- /dev/null +++ b/kustomizer/tests/kustomizer/testdata/reference/patch-port-no-protocol/test.yaml @@ -0,0 +1,3 @@ +kind: success +# Ported from Go kustomize TestPatchPortHasNoProtocol in multiplepatch_test.go +# Tests that when patching a service port without protocol field, original protocol is preserved