Repository: karmada-io/karmada (2026-04-04T21:45:20Z)

Issue:
fix `FieldOverrider` YAML/JSON log swap and reject empty field overriders
**What happened**:

1. In `applyFieldOverriders` (`pkg/util/overridemanager/overridemanager.go`), YAML patch failures are logged as *"Error applying raw JSON patch"* and JSON patch failures as *"Error applying raw YAML patch"*, the messages are reversed.
2. A `FieldOverrider` can pass validation with only `fieldPath` set and both `yaml` and `json` empty. In that case the code still runs `pointer.Set` with an empty string and **clears the target field** without returning an error.

**What you expected to happen**:

1. Log lines should match the branch (YAML vs JSON) so operators can debug overrides quickly.
2. Either admission should reject a `FieldOverrider` with no YAML and no JSON, or the runtime should return an error instead of silently wiping the field.

**How to reproduce it (as minimally and precisely as possible)**:

**A.  Logs**

- Trigger a failing YAML field override (invalid patch on a string field at `fieldPath`) and check controller logs: the error line says “JSON patch”.
- Trigger a failing JSON field override similarly: the error line says “YAML patch”.

**B.  Silent clear**

- Create an `OverridePolicy` (or `ClusterOverridePolicy`) with `fieldOverrider` containing a valid `fieldPath` pointing at a string field (e.g. a ConfigMap data key) and **omit** both `yaml` and `json` (or use empty arrays).
- Apply the policy to a matching resource and observe propagation: the field at `fieldPath` becomes empty with no admission or apply error.

**Environment**:
- Karmada version:
- kubectl-karmada or karmadactl version (the result of `kubectl-karmada version` or `karmadactl version`):
- Others:



Discussion:
/assign
/assign
@arushdwivedi, I am already assigned to this issue and opened PR. 
/remove-kind bug
/kind feature



Patch:
diff --git a/pkg/util/overridemanager/overridemanager.go b/pkg/util/overridemanager/overridemanager.go
index 7fb4e3d4511c..85067e190ad5 100644
--- a/pkg/util/overridemanager/overridemanager.go
+++ b/pkg/util/overridemanager/overridemanager.go
@@ -433,13 +433,13 @@ func applyFieldOverriders(rawObj *unstructured.Unstructured, FieldOverriders []p
 		if len(FieldOverriders[index].YAML) > 0 {
 			appliedRawData, err = applyRawYAMLPatch(dataBytes, parseYAMLPatchesByField(FieldOverriders[index].YAML))
 			if err != nil {
-				klog.Errorf("Error applying raw JSON patch: %v", err)
+				klog.Errorf("Error applying raw YAML patch: %v", err)
 				return err
 			}
 		} else if len(FieldOverriders[index].JSON) > 0 {
 			appliedRawData, err = applyRawJSONPatch(dataBytes, parseJSONPatchesByField(FieldOverriders[index].JSON))
 			if err != nil {
-				klog.Errorf("Error applying raw YAML patch: %v", err)
+				klog.Errorf("Error applying raw JSON patch: %v", err)
 				return err
 			}
 		}
diff --git a/pkg/util/validation/validation.go b/pkg/util/validation/validation.go
index 9e474486cc77..284d989ba0d7 100644
--- a/pkg/util/validation/validation.go
+++ b/pkg/util/validation/validation.go
@@ -435,6 +435,9 @@ func ValidateOverrideRules(overrideRules []policyv1alpha1.RuleWithCluster, fldPa
 			if len(fieldOverrider.YAML) > 0 && len(fieldOverrider.JSON) > 0 {
 				allErrs = append(allErrs, field.Invalid(fieldPath, fieldOverrider, "FieldOverrider has both YAML and JSON set. Only one is allowed"))
 			}
+			if len(fieldOverrider.YAML) == 0 && len(fieldOverrider.JSON) == 0 {
+				allErrs = append(allErrs, field.Required(fieldPath, "FieldOverrider must have either JSON or YAML set"))
+			}
 			// validates the field path.
 			if _, err := jsonpointer.New(fieldOverrider.FieldPath); err != nil {
 				allErrs = append(allErrs, field.Invalid(fieldPath.Child("fieldPath"), fieldOverrider.FieldPath, err.Error()))


Test patch:
diff --git a/pkg/util/validation/validation_test.go b/pkg/util/validation/validation_test.go
index fe2da92fe925..76b024b75674 100644
--- a/pkg/util/validation/validation_test.go
+++ b/pkg/util/validation/validation_test.go
@@ -234,6 +234,110 @@ func TestValidateOverrideSpec(t *testing.T) {
 			},
 			expectError: true,
 		},
+		{
+			name: "fieldOverrider with neither YAML nor JSON should be rejected",
+			overrideSpec: policyv1alpha1.OverrideSpec{
+				OverrideRules: []policyv1alpha1.RuleWithCluster{
+					{
+						TargetCluster: &policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{"cluster-name"},
+						},
+						Overriders: policyv1alpha1.Overriders{
+							FieldOverrider: []policyv1alpha1.FieldOverrider{
+								{
+									FieldPath: "/data/config",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectError: true,
+		},
+		{
+			name: "fieldOverrider with both YAML and JSON set should be rejected",
+			overrideSpec: policyv1alpha1.OverrideSpec{
+				OverrideRules: []policyv1alpha1.RuleWithCluster{
+					{
+						TargetCluster: &policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{"cluster-name"},
+						},
+						Overriders: policyv1alpha1.Overriders{
+							FieldOverrider: []policyv1alpha1.FieldOverrider{
+								{
+									FieldPath: "/data/config",
+									JSON: []policyv1alpha1.JSONPatchOperation{
+										{
+											SubPath:  "/key",
+											Operator: policyv1alpha1.OverriderOpRemove,
+										},
+									},
+									YAML: []policyv1alpha1.YAMLPatchOperation{
+										{
+											SubPath:  "/key",
+											Operator: policyv1alpha1.OverriderOpRemove,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectError: true,
+		},
+		{
+			name: "fieldOverrider with only YAML set should be allowed",
+			overrideSpec: policyv1alpha1.OverrideSpec{
+				OverrideRules: []policyv1alpha1.RuleWithCluster{
+					{
+						TargetCluster: &policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{"cluster-name"},
+						},
+						Overriders: policyv1alpha1.Overriders{
+							FieldOverrider: []policyv1alpha1.FieldOverrider{
+								{
+									FieldPath: "/data/config",
+									YAML: []policyv1alpha1.YAMLPatchOperation{
+										{
+											SubPath:  "/key",
+											Operator: policyv1alpha1.OverriderOpRemove,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectError: false,
+		},
+		{
+			name: "fieldOverrider with only JSON set should be allowed",
+			overrideSpec: policyv1alpha1.OverrideSpec{
+				OverrideRules: []policyv1alpha1.RuleWithCluster{
+					{
+						TargetCluster: &policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{"cluster-name"},
+						},
+						Overriders: policyv1alpha1.Overriders{
+							FieldOverrider: []policyv1alpha1.FieldOverrider{
+								{
+									FieldPath: "/data/config",
+									JSON: []policyv1alpha1.JSONPatchOperation{
+										{
+											SubPath:  "/key",
+											Operator: policyv1alpha1.OverriderOpRemove,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectError: false,
+		},
 	}
 
 	for _, test := range tests {