Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,46 @@ 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) {
Comment thread
aufi marked this conversation as resolved.
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",
}

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")
ops = append(ops, patchOp{Op: "remove", Path: "/metadata/annotations/" + escaped})
}
}

if len(ops) == 0 {
return nil, nil
}

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) {
return updateSecretsForSlice(getPullSecrets(u), podReplaceImagePullSecret, podRemoveImagePullSecret, fields)
}
Expand Down
12 changes: 9 additions & 3 deletions openshift/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,22 @@ 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")
pullSecretPatch, err := UpdateDefaultPullSecrets(u, inputFields)
o.log().Info("found pod, processing")
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)
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)
Expand Down
112 changes: 112 additions & 0 deletions openshift/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
})
}
}
Loading