From 40bd3ecc0d3afe65ef88bbfad0b70a27f9b28099 Mon Sep 17 00:00:00 2001 From: ssingla Date: Wed, 8 Jul 2026 16:32:14 +0530 Subject: [PATCH 1/3] :bug: Fix standalone pods migration by stripping ovn annotations on ocp --- openshift/openshift.go | 32 ++++++++++++++++++++++++++++++++ openshift/plugin.go | 7 ++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/openshift/openshift.go b/openshift/openshift.go index e93ddd9..4624054 100644 --- a/openshift/openshift.go +++ b/openshift/openshift.go @@ -71,6 +71,38 @@ func updateBuildConfigImageReference( return patch, nil } +// StripPodRuntimeAnnotations removes OCP/OVN runtime-injected annotations +// from standalone Pods. These annotations carry source-cluster-specific state +// (IP addresses, SCC assignment, SELinux levels) that is invalid on the target. +// Deployments/StatefulSets don't need this because they create fresh Pods. +func StripPodRuntimeAnnotations(u unstructured.Unstructured) (jsonpatch.Patch, error) { + annotations := u.GetAnnotations() + if len(annotations) == 0 { + return nil, nil + } + + runtimeAnnotations := []string{ + "k8s.ovn.org/pod-networks", + "k8s.v1.cni.cncf.io/network-status", + "k8s.v1.cni.cncf.io/networks-status", + } + + var patchOps []string + for _, ann := range runtimeAnnotations { + if _, exists := annotations[ann]; exists { + escaped := strings.ReplaceAll(ann, "/", "~1") + patchOps = append(patchOps, fmt.Sprintf(`{"op":"remove","path":"/metadata/annotations/%s"}`, escaped)) + } + } + + if len(patchOps) == 0 { + return nil, nil + } + + patchJSON := "[" + strings.Join(patchOps, ",") + "]" + return jsonpatch.DecodePatch([]byte(patchJSON)) +} + func UpdateDefaultPullSecrets(u unstructured.Unstructured, fields OpenshiftOptionalFields) (jsonpatch.Patch, error) { return updateSecretsForSlice(getPullSecrets(u), podReplaceImagePullSecret, podRemoveImagePullSecret, fields) } diff --git a/openshift/plugin.go b/openshift/plugin.go index 97a64f4..151e6a9 100644 --- a/openshift/plugin.go +++ b/openshift/plugin.go @@ -109,7 +109,7 @@ func (o *OpenShiftTransformPlugin) Run(request transform.PluginRequest) (transfo o.log().Info("found deployment config, processing") patch, err = UpdateDeploymentConfig(u, inputFields) case "Pod": - o.log().Info("found pod, processing update default pull secret") + o.log().Info("found pod, processing") pullSecretPatch, err := UpdateDefaultPullSecrets(u, inputFields) if err != nil { break @@ -118,7 +118,12 @@ func (o *OpenShiftTransformPlugin) Run(request transform.PluginRequest) (transfo if err != nil { break } + runtimePatch, err := StripPodRuntimeAnnotations(u) + if err != nil { + break + } patch = append(pullSecretPatch, securityContextPatch...) + patch = append(patch, runtimePatch...) case "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob", "ReplicaSet", "ReplicationController": o.log().Infof("found %s, stripping SCC-injected security context", u.GetKind()) patch, err = StripSecurityContext(u) From 11b39d8de37dfc38d809d6e623a98ab9248ee51e Mon Sep 17 00:00:00 2001 From: ssingla Date: Wed, 8 Jul 2026 16:59:05 +0530 Subject: [PATCH 2/3] fix coderabbit comment --- openshift/plugin.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openshift/plugin.go b/openshift/plugin.go index 151e6a9..6bd3560 100644 --- a/openshift/plugin.go +++ b/openshift/plugin.go @@ -110,15 +110,16 @@ func (o *OpenShiftTransformPlugin) Run(request transform.PluginRequest) (transfo patch, err = UpdateDeploymentConfig(u, inputFields) case "Pod": o.log().Info("found pod, processing") - pullSecretPatch, err := UpdateDefaultPullSecrets(u, inputFields) + var pullSecretPatch, securityContextPatch, runtimePatch jsonpatch.Patch + pullSecretPatch, err = UpdateDefaultPullSecrets(u, inputFields) if err != nil { break } - securityContextPatch, err := StripSecurityContext(u) + securityContextPatch, err = StripSecurityContext(u) if err != nil { break } - runtimePatch, err := StripPodRuntimeAnnotations(u) + runtimePatch, err = StripPodRuntimeAnnotations(u) if err != nil { break } From ff1b35dc1a5d985d01eadce77271a308a1817a0e Mon Sep 17 00:00:00 2001 From: ssingla Date: Wed, 8 Jul 2026 17:04:33 +0530 Subject: [PATCH 3/3] fix comments --- openshift/openshift.go | 18 +++++-- openshift/plugin_test.go | 112 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/openshift/openshift.go b/openshift/openshift.go index 4624054..a85cec1 100644 --- a/openshift/openshift.go +++ b/openshift/openshift.go @@ -87,20 +87,28 @@ func StripPodRuntimeAnnotations(u unstructured.Unstructured) (jsonpatch.Patch, e "k8s.v1.cni.cncf.io/networks-status", } - var patchOps []string + type patchOp struct { + Op string `json:"op"` + Path string `json:"path"` + } + + var ops []patchOp for _, ann := range runtimeAnnotations { if _, exists := annotations[ann]; exists { escaped := strings.ReplaceAll(ann, "/", "~1") - patchOps = append(patchOps, fmt.Sprintf(`{"op":"remove","path":"/metadata/annotations/%s"}`, escaped)) + ops = append(ops, patchOp{Op: "remove", Path: "/metadata/annotations/" + escaped}) } } - if len(patchOps) == 0 { + if len(ops) == 0 { return nil, nil } - patchJSON := "[" + strings.Join(patchOps, ",") + "]" - return jsonpatch.DecodePatch([]byte(patchJSON)) + patchJSON, err := json.Marshal(ops) + if err != nil { + return nil, fmt.Errorf("marshalling runtime annotation patch: %w", err) + } + return jsonpatch.DecodePatch(patchJSON) } func UpdateDefaultPullSecrets(u unstructured.Unstructured, fields OpenshiftOptionalFields) (jsonpatch.Patch, error) { diff --git a/openshift/plugin_test.go b/openshift/plugin_test.go index defc7be..368c185 100644 --- a/openshift/plugin_test.go +++ b/openshift/plugin_test.go @@ -1171,3 +1171,115 @@ func TestStripSecurityContext(t *testing.T) { }) } } + +func TestStripPodRuntimeAnnotations(t *testing.T) { + tests := []struct { + name string + annotations map[string]interface{} + expectPatchCount int + expectStripped []string + expectPreserved []string + }{ + { + name: "strips OVN annotations", + annotations: map[string]interface{}{ + "k8s.ovn.org/pod-networks": `{"default":{"ip_addresses":["10.129.2.65/23"]}}`, + "k8s.v1.cni.cncf.io/network-status": `[{"ips":["10.129.2.65"]}]`, + "k8s.v1.cni.cncf.io/networks-status": `[{"ips":["10.129.2.65"]}]`, + "app.kubernetes.io/name": "myapp", + }, + expectPatchCount: 3, + expectStripped: []string{"k8s.ovn.org/pod-networks", "k8s.v1.cni.cncf.io/network-status", "k8s.v1.cni.cncf.io/networks-status"}, + expectPreserved: []string{"app.kubernetes.io/name"}, + }, + { + name: "no OVN annotations present", + annotations: map[string]interface{}{ + "app.kubernetes.io/name": "myapp", + "openshift.io/scc": "restricted-v2", + }, + expectPatchCount: 0, + }, + { + name: "no annotations at all", + annotations: nil, + expectPatchCount: 0, + }, + { + name: "only some OVN annotations present", + annotations: map[string]interface{}{ + "k8s.ovn.org/pod-networks": `{"default":{}}`, + "app": "test", + }, + expectPatchCount: 1, + expectStripped: []string{"k8s.ovn.org/pod-networks"}, + expectPreserved: []string{"app"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + metadata := map[string]interface{}{ + "name": "test-pod", + "namespace": "test-ns", + } + if tt.annotations != nil { + metadata["annotations"] = tt.annotations + } + + u := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": metadata, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "test", + "image": "busybox", + }, + }, + }, + }, + } + + patch, err := StripPodRuntimeAnnotations(u) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tt.expectPatchCount == 0 { + if patch != nil && len(patch) > 0 { + t.Errorf("expected no patches, got %d", len(patch)) + } + return + } + + if len(patch) != tt.expectPatchCount { + t.Errorf("expected %d patches, got %d", tt.expectPatchCount, len(patch)) + } + + // Apply the patch and verify + original, _ := json.Marshal(u.Object) + patched, err := patch.Apply(original) + if err != nil { + t.Fatalf("failed to apply patch: %v", err) + } + + var result map[string]interface{} + json.Unmarshal(patched, &result) + resultAnnotations, _ := result["metadata"].(map[string]interface{})["annotations"].(map[string]interface{}) + + for _, key := range tt.expectStripped { + if _, exists := resultAnnotations[key]; exists { + t.Errorf("expected annotation %q to be stripped, but it's still present", key) + } + } + for _, key := range tt.expectPreserved { + if _, exists := resultAnnotations[key]; !exists { + t.Errorf("expected annotation %q to be preserved, but it's missing", key) + } + } + }) + } +}