Skip to content

Commit 6be0da6

Browse files
Merge pipeline (#2625)
* Merge pipeline * Updated tests * Update logic to adding merge key * Add ideal testcase * Suggested changes * Support exec
1 parent c737224 commit 6be0da6

File tree

4 files changed

+927
-51
lines changed

4 files changed

+927
-51
lines changed

internal/util/update/update_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3352,6 +3352,7 @@ func TestRun_remote_subpackages(t *testing.T) {
33523352
WithUpstreamRef("foo", "/", "v1.0", "resource-merge").
33533353
WithUpstreamLockRef("foo", "/", "v1.0", 0).
33543354
WithPipeline(
3355+
pkgbuilder.NewFunction("gcr.io/kpt-dev/bar:latest"),
33553356
pkgbuilder.NewFunction("gcr.io/kpt-dev/foo:latest"),
33563357
),
33573358
).
@@ -3405,6 +3406,7 @@ func TestRun_remote_subpackages(t *testing.T) {
34053406
WithUpstreamLockRef(testutil.Upstream, "/", masterBranch, 1).
34063407
WithPipeline(
34073408
pkgbuilder.NewFunction("gcr.io/kpt-dev/foo:v1"),
3409+
pkgbuilder.NewFunction("gcr.io/kpt-dev/zork:v1"),
34083410
pkgbuilder.NewFunction("gcr.io/kpt-dev/bar:v1"),
34093411
),
34103412
).
@@ -3457,6 +3459,7 @@ func TestRun_remote_subpackages(t *testing.T) {
34573459
pkgbuilder.NewKptfile().
34583460
WithPipeline(
34593461
pkgbuilder.NewFunction("gcr.io/kpt-dev/foo:v1"),
3462+
pkgbuilder.NewFunction("gcr.io/kpt-dev/bar:latest"),
34603463
),
34613464
).
34623465
WithResource(pkgbuilder.ConfigMapResource),
@@ -3491,7 +3494,6 @@ func TestRun_remote_subpackages(t *testing.T) {
34913494
WithUpstreamLockRef("foo", "/", masterBranch, 0).
34923495
WithPipeline(
34933496
pkgbuilder.NewFunction("gcr.io/kpt-dev/zork:v1"),
3494-
pkgbuilder.NewFunction("gcr.io/kpt-dev/foo:v1"),
34953497
),
34963498
).
34973499
WithResource(pkgbuilder.ConfigMapResource),
@@ -3511,6 +3513,7 @@ func TestRun_remote_subpackages(t *testing.T) {
35113513
WithUpstreamRef("foo", "/", masterBranch, "resource-merge").
35123514
WithUpstreamLockRef("foo", "/", masterBranch, 1).
35133515
WithPipeline(
3516+
pkgbuilder.NewFunction("gcr.io/kpt-dev/zork:v1"),
35143517
pkgbuilder.NewFunction("gcr.io/kpt-dev/foo:latest"),
35153518
pkgbuilder.NewFunction("gcr.io/kpt-dev/bar:latest"),
35163519
),

pkg/api/kptfile/v1/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ type Function struct {
284284
// `ConfigMap` is a convenient way to specify a function config of kind ConfigMap.
285285
ConfigMap map[string]string `yaml:"configMap,omitempty" json:"configMap,omitempty"`
286286

287+
// `Name` is used to uniquely identify the function declaration
288+
// this is primarily used for merging function declaration with upstream counterparts
289+
Name string `yaml:"name,omitempty" json:"name,omitempty"`
290+
287291
// `Selectors` are used to specify resources on which the function should be executed
288292
// if not specified, all resources are selected
289293
Selectors []Selector `yaml:"selectors,omitempty" json:"selectors,omitempty"`

pkg/kptfile/kptfileutil/util.go

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/GoogleContainerTools/kpt/internal/types"
2929
"github.com/GoogleContainerTools/kpt/internal/util/git"
3030
kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
31+
"sigs.k8s.io/kustomize/kyaml/sets"
3132
"sigs.k8s.io/kustomize/kyaml/yaml"
3233
"sigs.k8s.io/kustomize/kyaml/yaml/merge3"
3334
)
@@ -223,7 +224,15 @@ func UpdateUpstreamLockFromGit(path string, spec *git.RepoSpec) error {
223224
return nil
224225
}
225226

227+
// merge merges the Kptfiles from various sources and updates localKf with output
228+
// please refer to https://github.com/GoogleContainerTools/kpt/blob/main/docs/design-docs/03-pipeline-merge.md
229+
// for related design
226230
func merge(localKf, updatedKf, originalKf *kptfilev1.KptFile) error {
231+
shouldAddSyntheticMergeName := shouldAddFnKey(localKf, updatedKf, originalKf)
232+
if shouldAddSyntheticMergeName {
233+
addNameForMerge(localKf, updatedKf, originalKf)
234+
}
235+
227236
localBytes, err := yaml.Marshal(localKf)
228237
if err != nil {
229238
return err
@@ -239,7 +248,7 @@ func merge(localKf, updatedKf, originalKf *kptfilev1.KptFile) error {
239248
return err
240249
}
241250

242-
mergedBytes, err := merge3.MergeStrings(string(localBytes), string(originalBytes), string(updatedBytes), false)
251+
mergedBytes, err := merge3.MergeStrings(string(localBytes), string(originalBytes), string(updatedBytes), true)
243252
if err != nil {
244253
return err
245254
}
@@ -250,6 +259,10 @@ func merge(localKf, updatedKf, originalKf *kptfilev1.KptFile) error {
250259
return err
251260
}
252261

262+
if shouldAddSyntheticMergeName {
263+
removeFnKey(localKf, updatedKf, originalKf, &mergedKf)
264+
}
265+
253266
// Copy the merged content into the local Kptfile struct. We don't copy
254267
// name, namespace, Upstream or UpstreamLock, since we don't want those
255268
// merged.
@@ -261,6 +274,100 @@ func merge(localKf, updatedKf, originalKf *kptfilev1.KptFile) error {
261274
return nil
262275
}
263276

277+
// shouldAddFnKey returns true iff all the functions from all sources
278+
// doesn't have name field set and there are no duplicate function declarations,
279+
// it means the user is unaware of name field, and we use image name or exec field
280+
// value as mergeKey instead of name in such cases
281+
func shouldAddFnKey(kfs ...*kptfilev1.KptFile) bool {
282+
for _, kf := range kfs {
283+
if kf == nil || kf.Pipeline == nil {
284+
continue
285+
}
286+
if !shouldAddFnKeyUtil(kf.Pipeline.Mutators) || !shouldAddFnKeyUtil(kf.Pipeline.Validators) {
287+
return false
288+
}
289+
}
290+
return true
291+
}
292+
293+
// shouldAddFnKeyUtil returns true iff all the functions from input list
294+
// doesn't have name field set and there are no duplicate function declarations,
295+
// it means the user is unaware of name field, and we use image name or exec field
296+
// value as mergeKey instead of name in such cases
297+
func shouldAddFnKeyUtil(fns []kptfilev1.Function) bool {
298+
keySet := sets.String{}
299+
for _, fn := range fns {
300+
if fn.Name != "" {
301+
return false
302+
}
303+
var key string
304+
if fn.Exec != "" {
305+
key = fn.Exec
306+
} else {
307+
key = strings.Split(fn.Image, ":")[0]
308+
}
309+
if keySet.Has(key) {
310+
return false
311+
}
312+
keySet.Insert(key)
313+
}
314+
return true
315+
}
316+
317+
// addNameForMerge adds name field for all the functions if empty
318+
// name is primarily used as merge-key
319+
func addNameForMerge(kfs ...*kptfilev1.KptFile) {
320+
for _, kf := range kfs {
321+
if kf == nil || kf.Pipeline == nil {
322+
continue
323+
}
324+
for i, mutator := range kf.Pipeline.Mutators {
325+
kf.Pipeline.Mutators[i] = addName(mutator)
326+
}
327+
for i, validator := range kf.Pipeline.Validators {
328+
kf.Pipeline.Validators[i] = addName(validator)
329+
}
330+
}
331+
}
332+
333+
// addName adds name field to the input function if empty
334+
// name is nothing but image name in this case as we use it as fall back mergeKey
335+
func addName(fn kptfilev1.Function) kptfilev1.Function {
336+
if fn.Name != "" {
337+
return fn
338+
}
339+
var key string
340+
if fn.Exec != "" {
341+
key = fn.Exec
342+
} else {
343+
parts := strings.Split(fn.Image, ":")
344+
if len(parts) > 0 {
345+
key = parts[0]
346+
}
347+
}
348+
fn.Name = fmt.Sprintf("_kpt-merge_%s", key)
349+
return fn
350+
}
351+
352+
// removeFnKey remove the synthesized function name field before writing
353+
func removeFnKey(kfs ...*kptfilev1.KptFile) {
354+
for _, kf := range kfs {
355+
if kf == nil || kf.Pipeline == nil {
356+
continue
357+
}
358+
for i := range kf.Pipeline.Mutators {
359+
if strings.HasPrefix(kf.Pipeline.Mutators[i].Name, "_kpt-merge_") {
360+
kf.Pipeline.Mutators[i].Name = ""
361+
}
362+
}
363+
for i := range kf.Pipeline.Validators {
364+
if strings.HasPrefix(kf.Pipeline.Validators[i].Name, "_kpt-merge_") {
365+
kf.Pipeline.Validators[i].Name = ""
366+
}
367+
}
368+
}
369+
}
370+
264371
func updateUpstreamAndUpstreamLock(localKf, updatedKf *kptfilev1.KptFile) {
265372
if updatedKf.Upstream != nil {
266373
localKf.Upstream = updatedKf.Upstream

0 commit comments

Comments
 (0)