Skip to content

Commit fa5b3b3

Browse files
authored
[K8s Plugin] Implement duplicateManifests (pipe-cd#5863)
* Add DeepCopyWithName to Manifest Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> * Add duplicateManifests to deployment package Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> --------- Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev>
1 parent 4c0e488 commit fa5b3b3

4 files changed

Lines changed: 151 additions & 0 deletions

File tree

pkg/app/pipedv1/plugin/kubernetes/deployment/misc.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,12 @@ func addVariantLabelsAndAnnotations(m []provider.Manifest, variantLabel, variant
118118
})
119119
}
120120
}
121+
122+
// duplicateManifests duplicates the given manifests and appends a name suffix to each manifest.
123+
func duplicateManifests(manifests []provider.Manifest, nameSuffix string) []provider.Manifest {
124+
copied := make([]provider.Manifest, len(manifests))
125+
for i, m := range manifests {
126+
copied[i] = m.DeepCopyWithName(makeSuffixedName(m.Name(), nameSuffix))
127+
}
128+
return copied
129+
}

pkg/app/pipedv1/plugin/kubernetes/deployment/misc_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,45 @@ metadata:
351351
})
352352
}
353353
}
354+
355+
func TestDuplicateManifests(t *testing.T) {
356+
yaml := `
357+
apiVersion: v1
358+
kind: ConfigMap
359+
metadata:
360+
name: test-config
361+
labels:
362+
foo: bar
363+
---
364+
apiVersion: v1
365+
kind: ConfigMap
366+
metadata:
367+
name: another-config
368+
labels:
369+
bar: baz
370+
`
371+
manifests := mustParseManifests(t, yaml)
372+
require.Len(t, manifests, 2)
373+
374+
nameSuffix := "canary"
375+
copied := duplicateManifests(manifests, nameSuffix)
376+
require.Len(t, copied, 2)
377+
378+
// Check that names are suffixed and originals are unchanged
379+
assert.Equal(t, "test-config", manifests[0].Name())
380+
assert.Equal(t, "another-config", manifests[1].Name())
381+
assert.Equal(t, "test-config-canary", copied[0].Name())
382+
assert.Equal(t, "another-config-canary", copied[1].Name())
383+
384+
// Mutate copied and ensure original is not affected
385+
copied[0].AddLabels(map[string]string{"foo": "changed"})
386+
387+
var origCfg, copiedCfg corev1.ConfigMap
388+
err := manifests[0].ConvertToStructuredObject(&origCfg)
389+
require.NoError(t, err)
390+
err = copied[0].ConvertToStructuredObject(&copiedCfg)
391+
require.NoError(t, err)
392+
393+
assert.Equal(t, "bar", origCfg.Labels["foo"], "original label should remain unchanged")
394+
assert.Equal(t, "changed", copiedCfg.Labels["foo"], "copied label should be updated")
395+
}

pkg/app/pipedv1/plugin/kubernetes/provider/manifest.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ func (m Manifest) DeepCopy() Manifest {
8080
return Manifest{body: m.body.DeepCopy()}
8181
}
8282

83+
// DeepCopyWithName returns a deep copy of the manifest with the given name.
84+
func (m Manifest) DeepCopyWithName(name string) Manifest {
85+
copied := m.DeepCopy()
86+
copied.body.SetName(name)
87+
return copied
88+
}
89+
8390
func (m Manifest) Key() ResourceKey {
8491
return makeResourceKey(m.body)
8592
}

pkg/app/pipedv1/plugin/kubernetes/provider/manifest_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,99 @@ metadata:
10261026
}
10271027
}
10281028

1029+
func TestManifest_DeepCopyWithName(t *testing.T) {
1030+
t.Parallel()
1031+
1032+
tests := []struct {
1033+
name string
1034+
yaml string
1035+
newName string
1036+
mutate func(orig, copy *Manifest)
1037+
checkOrig func(orig Manifest)
1038+
checkCopy func(copy Manifest)
1039+
}{
1040+
{
1041+
name: "deep copy with new name does not affect original",
1042+
yaml: `
1043+
apiVersion: v1
1044+
kind: ConfigMap
1045+
metadata:
1046+
name: original-name
1047+
labels:
1048+
foo: bar
1049+
`,
1050+
newName: "copied-name",
1051+
mutate: nil,
1052+
checkOrig: func(orig Manifest) {
1053+
assert.Equal(t, "original-name", orig.Name())
1054+
},
1055+
checkCopy: func(copy Manifest) {
1056+
assert.Equal(t, "copied-name", copy.Name())
1057+
},
1058+
},
1059+
{
1060+
name: "mutate copy does not affect original",
1061+
yaml: `
1062+
apiVersion: v1
1063+
kind: ConfigMap
1064+
metadata:
1065+
name: original-name
1066+
labels:
1067+
foo: bar
1068+
`,
1069+
newName: "copied-name",
1070+
mutate: func(orig, copy *Manifest) {
1071+
copy.AddLabels(map[string]string{"foo": "baz"})
1072+
},
1073+
checkOrig: func(orig Manifest) {
1074+
assert.Equal(t, "bar", orig.body.GetLabels()["foo"], "original label should remain unchanged")
1075+
},
1076+
checkCopy: func(copy Manifest) {
1077+
assert.Equal(t, "baz", copy.body.GetLabels()["foo"], "copy label should be updated")
1078+
},
1079+
},
1080+
{
1081+
name: "mutate original does not affect copy",
1082+
yaml: `
1083+
apiVersion: v1
1084+
kind: ConfigMap
1085+
metadata:
1086+
name: original-name
1087+
labels:
1088+
foo: bar
1089+
`,
1090+
newName: "copied-name",
1091+
mutate: func(orig, copy *Manifest) {
1092+
orig.AddLabels(map[string]string{"foo": "baz"})
1093+
},
1094+
checkOrig: func(orig Manifest) {
1095+
assert.Equal(t, "baz", orig.body.GetLabels()["foo"], "original label should be updated")
1096+
},
1097+
checkCopy: func(copy Manifest) {
1098+
assert.Equal(t, "bar", copy.body.GetLabels()["foo"], "copy label should remain unchanged")
1099+
},
1100+
},
1101+
}
1102+
1103+
for _, tt := range tests {
1104+
t.Run(tt.name, func(t *testing.T) {
1105+
t.Parallel()
1106+
1107+
manifests := mustParseManifests(t, tt.yaml)
1108+
require.Len(t, manifests, 1)
1109+
orig := manifests[0]
1110+
copy := orig.DeepCopyWithName(tt.newName)
1111+
1112+
if tt.mutate != nil {
1113+
tt.mutate(&orig, &copy)
1114+
}
1115+
1116+
tt.checkOrig(orig)
1117+
tt.checkCopy(copy)
1118+
})
1119+
}
1120+
}
1121+
10291122
func TestFromStructuredObject(t *testing.T) {
10301123
t.Parallel()
10311124

0 commit comments

Comments
 (0)