diff --git a/api/filters/patchstrategicmerge/patchstrategicmerge.go b/api/filters/patchstrategicmerge/patchstrategicmerge.go index 1a70d19aac..fd201eeb4a 100644 --- a/api/filters/patchstrategicmerge/patchstrategicmerge.go +++ b/api/filters/patchstrategicmerge/patchstrategicmerge.go @@ -4,13 +4,15 @@ package patchstrategicmerge import ( + "cmp" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml/merge2" ) type Filter struct { - Patch *yaml.RNode + Patch *yaml.RNode + MergeOptions *yaml.MergeOptions } var _ kio.Filter = Filter{} @@ -18,12 +20,13 @@ var _ kio.Filter = Filter{} // Filter does a strategic merge patch, which can delete nodes. func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { var result []*yaml.RNode + mergeOptions := *cmp.Or(pf.MergeOptions, &yaml.MergeOptions{ + ListIncreaseDirection: yaml.MergeOptionsListPrepend, + }) for i := range nodes { r, err := merge2.Merge( pf.Patch, nodes[i], - yaml.MergeOptions{ - ListIncreaseDirection: yaml.MergeOptionsListPrepend, - }, + mergeOptions, ) if err != nil { return nil, err diff --git a/api/filters/patchstrategicmerge/patchstrategicmerge_test.go b/api/filters/patchstrategicmerge/patchstrategicmerge_test.go index 0bcd951c0e..5c30de1f56 100644 --- a/api/filters/patchstrategicmerge/patchstrategicmerge_test.go +++ b/api/filters/patchstrategicmerge/patchstrategicmerge_test.go @@ -921,3 +921,150 @@ data: }) } } + +func TestFilterMergeOptionsListAppend(t *testing.T) { + testCases := map[string]struct { + input string + patch *yaml.RNode + expected string + }{ + "container patch": { + input: ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: foo1 + - name: foo2 + - name: foo3 +`, + patch: yaml.MustParse(` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: foo0 +`), + expected: ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: foo1 + - name: foo2 + - name: foo3 + - name: foo0 +`, + }, + "volumes patch": { + input: ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + volumes: + - name: foo1 + - name: foo2 + - name: foo3 +`, + patch: yaml.MustParse(` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + volumes: + - name: foo0 +`), + expected: ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + volumes: + - name: foo1 + - name: foo2 + - name: foo3 + - name: foo0 +`, + }, + "merge list - directive": { + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + template: + spec: + containers: + - name: test + image: test +`, + patch: yaml.MustParse(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + template: + spec: + containers: + - name: test2 + image: test2 + - $patch: merge +`), + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + template: + spec: + containers: + - name: test + image: test + - name: test2 + image: test2 +`, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + f := Filter{ + Patch: tc.patch, + MergeOptions: &yaml.MergeOptions{ + ListIncreaseDirection: yaml.MergeOptionsListAppend, + }, + } + if !assert.Equal(t, + strings.TrimSpace(tc.expected), + strings.TrimSpace( + filtertest.RunFilter(t, tc.input, f))) { + t.FailNow() + } + }) + } +} diff --git a/api/internal/builtins/PatchStrategicMergeTransformer.go b/api/internal/builtins/PatchStrategicMergeTransformer.go index d68f2425ea..822f4a1290 100644 --- a/api/internal/builtins/PatchStrategicMergeTransformer.go +++ b/api/internal/builtins/PatchStrategicMergeTransformer.go @@ -9,6 +9,7 @@ import ( "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/yaml" ) @@ -16,6 +17,7 @@ type PatchStrategicMergeTransformerPlugin struct { loadedPatches []*resource.Resource Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` Patches string `json:"patches,omitempty" yaml:"patches,omitempty"` + MergeOptions *kyaml.MergeOptions `json:"mergeOptions,omitempty" yaml:"mergeOptions,omitempty"` } func (p *PatchStrategicMergeTransformerPlugin) Config( @@ -45,6 +47,9 @@ func (p *PatchStrategicMergeTransformerPlugin) Config( return fmt.Errorf( "patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches) } + for i := range p.loadedPatches { + p.loadedPatches[i].MergeOptions = p.MergeOptions + } return nil } diff --git a/api/internal/builtins/PatchTransformer.go b/api/internal/builtins/PatchTransformer.go index 8e6eb41128..da80008e71 100644 --- a/api/internal/builtins/PatchTransformer.go +++ b/api/internal/builtins/PatchTransformer.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/kio/kioutil" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/yaml" ) @@ -23,11 +24,12 @@ type PatchTransformerPlugin struct { // patchText is pure patch text created by Path or Patch patchText string // patchSource is patch source message - patchSource string - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` - Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` - Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + patchSource string + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` + Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` + Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + MergeOptions *kyaml.MergeOptions `json:"MergeOptions,omitempty" yaml:"MergeOptions,omitempty"` } func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error { @@ -75,6 +77,7 @@ func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error if p.Options["allowKindChange"] { loadedPatch.AllowKindChange() } + loadedPatch.MergeOptions = p.MergeOptions } } else { p.jsonPatches = patchesJson diff --git a/api/internal/target/kusttarget.go b/api/internal/target/kusttarget.go index 5f1d1095a1..4c3ce9324d 100644 --- a/api/internal/target/kusttarget.go +++ b/api/internal/target/kusttarget.go @@ -24,6 +24,7 @@ import ( "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/openapi" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/yaml" ) @@ -36,6 +37,7 @@ type KustTarget struct { rFactory *resmap.Factory pLdr *loader.Loader origin *resource.Origin + mergeOptions *kyaml.MergeOptions } // NewKustTarget returns a new instance of KustTarget. @@ -43,12 +45,14 @@ func NewKustTarget( ldr ifc.Loader, validator ifc.Validator, rFactory *resmap.Factory, - pLdr *loader.Loader) *KustTarget { + pLdr *loader.Loader, + mOpts *kyaml.MergeOptions) *KustTarget { return &KustTarget{ - ldr: ldr, - validator: validator, - rFactory: rFactory, - pLdr: pLdr.LoaderWithWorkingDir(ldr.Root()), + ldr: ldr, + validator: validator, + rFactory: rFactory, + pLdr: pLdr.LoaderWithWorkingDir(ldr.Root()), + mergeOptions: mOpts, } } @@ -488,7 +492,7 @@ func (kt *KustTarget) accumulateComponents( func (kt *KustTarget) accumulateDirectory( ra *accumulator.ResAccumulator, ldr ifc.Loader, isComponent bool) (*accumulator.ResAccumulator, error) { defer ldr.Cleanup() - subKt := NewKustTarget(ldr, kt.validator, kt.rFactory, kt.pLdr) + subKt := NewKustTarget(ldr, kt.validator, kt.rFactory, kt.pLdr, kt.mergeOptions) err := subKt.Load() if err != nil { return nil, errors.WrapPrefixf( diff --git a/api/internal/target/kusttarget_configplugin.go b/api/internal/target/kusttarget_configplugin.go index 1ba028a36f..3dd91d2f16 100644 --- a/api/internal/target/kusttarget_configplugin.go +++ b/api/internal/target/kusttarget_configplugin.go @@ -232,9 +232,11 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func( return } var c struct { - Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` + Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` + MergeOptions *yaml.MergeOptions `json:"mergeOptions,omitempty" yaml:"mergeOptions,omitempty"` } c.Paths = kt.kustomization.PatchesStrategicMerge + c.MergeOptions = kt.mergeOptions p := f() err = kt.configureBuiltinPlugin(p, c, bpt) if err != nil { @@ -250,16 +252,18 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func( return } var c struct { - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` - Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` - Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` + Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` + Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + MergeOptions *yaml.MergeOptions `json:"mergeOptions,omitempty" yaml:"mergeOptions,omitempty"` } for _, pc := range kt.kustomization.Patches { c.Target = pc.Target c.Patch = pc.Patch c.Path = pc.Path c.Options = pc.Options + c.MergeOptions = kt.mergeOptions p := f() err = kt.configureBuiltinPlugin(p, c, bpt) if err != nil { diff --git a/api/internal/target/maker_test.go b/api/internal/target/maker_test.go index 95b1095a19..4940c457d4 100644 --- a/api/internal/target/maker_test.go +++ b/api/internal/target/maker_test.go @@ -4,6 +4,7 @@ package target_test import ( + "sigs.k8s.io/kustomize/kyaml/yaml" "testing" "sigs.k8s.io/kustomize/api/ifc" @@ -68,5 +69,8 @@ func makeKustTargetWithRfAndLoaderOverride( ldr, valtest_test.MakeFakeValidator(), rf, - pLdr.NewLoader(pc, rf, fSys)) + pLdr.NewLoader(pc, rf, fSys), + &yaml.MergeOptions{ + ListIncreaseDirection: yaml.MergeOptionsListPrepend, + }) } diff --git a/api/krusty/baseandoverlaymedium_test.go b/api/krusty/baseandoverlaymedium_test.go index cfa1afa48d..660d405034 100644 --- a/api/krusty/baseandoverlaymedium_test.go +++ b/api/krusty/baseandoverlaymedium_test.go @@ -7,6 +7,7 @@ import ( "testing" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" + "sigs.k8s.io/kustomize/kyaml/yaml" ) func writeMediumBase(th kusttest_test.Harness) { @@ -305,3 +306,199 @@ metadata: name: test-infra-app-config-49d6f5h7b5 `) } + +func TestMediumOverlayMergeOptionsListAppend(t *testing.T) { + th := kusttest_test.MakeHarness(t) + writeMediumBase(th) + th.WriteK("overlay", ` +namePrefix: test-infra- +commonLabels: + app: mungebot + org: kubernetes + repo: test-infra +commonAnnotations: + note: This is a test annotation +resources: +- ../base +patchesStrategicMerge: +- deployment/deployment.yaml +configMapGenerator: +- name: app-env + envs: + - configmap/db.env + - configmap/units.ini + - configmap/food.ini +- name: app-config + files: + - nonsense=configmap/dummy.txt +images: +- name: nginx + newTag: 1.8.0`) + + th.WriteF("overlay/configmap/db.env", ` +DB_USERNAME=admin +DB_PASSWORD=somepw +`) + th.WriteF("overlay/configmap/units.ini", ` +LENGTH=kilometer +ENERGY=electronvolt +`) + th.WriteF("overlay/configmap/food.ini", ` +FRUIT=banana +LEGUME=chickpea +`) + th.WriteF("overlay/configmap/dummy.txt", + `Lorem ipsum dolor sit amet, consectetur +adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. +`) + th.WriteF("overlay/deployment/deployment.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mungebot +spec: + replicas: 2 + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 + env: + - name: FOO + valueFrom: + configMapKeyRef: + name: app-env + key: somekey + - name: busybox + image: busybox + envFrom: + - configMapRef: + name: someConfigMap + - configMapRef: + name: app-env + volumeMounts: + - mountPath: /tmp/env + name: app-env + volumes: + - configMap: + name: app-env + name: app-env +`) + options := th.MakeDefaultOptions() + options.MergeOptions.ListIncreaseDirection = yaml.MergeOptionsListAppend + m := th.Run("overlay", options) + th.AssertActualEqualsExpected(m, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + baseAnno: This is a base annotation + note: This is a test annotation + labels: + app: mungebot + foo: bar + org: kubernetes + repo: test-infra + name: test-infra-baseprefix-mungebot +spec: + replicas: 2 + selector: + matchLabels: + app: mungebot + foo: bar + org: kubernetes + repo: test-infra + template: + metadata: + annotations: + baseAnno: This is a base annotation + note: This is a test annotation + labels: + app: mungebot + foo: bar + org: kubernetes + repo: test-infra + spec: + containers: + - env: + - name: foo + value: bar + - name: FOO + valueFrom: + configMapKeyRef: + key: somekey + name: test-infra-app-env-8h5mh7f7ch + image: nginx:1.8.0 + name: nginx + ports: + - containerPort: 80 + - envFrom: + - configMapRef: + name: someConfigMap + - configMapRef: + name: test-infra-app-env-8h5mh7f7ch + image: busybox + name: busybox + volumeMounts: + - mountPath: /tmp/env + name: app-env + volumes: + - configMap: + name: test-infra-app-env-8h5mh7f7ch + name: app-env +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + baseAnno: This is a base annotation + note: This is a test annotation + labels: + app: mungebot + foo: bar + org: kubernetes + repo: test-infra + name: test-infra-baseprefix-mungebot-service +spec: + ports: + - port: 7002 + selector: + app: mungebot + foo: bar + org: kubernetes + repo: test-infra +--- +apiVersion: v1 +data: + DB_PASSWORD: somepw + DB_USERNAME: admin + ENERGY: electronvolt + FRUIT: banana + LEGUME: chickpea + LENGTH: kilometer +kind: ConfigMap +metadata: + annotations: + note: This is a test annotation + labels: + app: mungebot + org: kubernetes + repo: test-infra + name: test-infra-app-env-8h5mh7f7ch +--- +apiVersion: v1 +data: + nonsense: "Lorem ipsum dolor sit amet, consectetur\nadipiscing elit, sed do eiusmod + tempor\nincididunt ut labore et dolore magna aliqua. \n" +kind: ConfigMap +metadata: + annotations: + note: This is a test annotation + labels: + app: mungebot + org: kubernetes + repo: test-infra + name: test-infra-app-config-49d6f5h7b5 +`) +} diff --git a/api/krusty/kustomizer.go b/api/krusty/kustomizer.go index 999cbf4537..0cdddad522 100644 --- a/api/krusty/kustomizer.go +++ b/api/krusty/kustomizer.go @@ -70,6 +70,7 @@ func (b *Kustomizer) Run( resmapFactory, // The plugin configs are always located on disk, regardless of the fSys passed in pLdr.NewLoader(b.options.PluginConfig, resmapFactory, filesys.MakeFsOnDisk()), + b.options.MergeOptions, ) err = kt.Load() if err != nil { diff --git a/api/krusty/multiplepatch_test.go b/api/krusty/multiplepatch_test.go index 860d53ad47..d5a89cf291 100644 --- a/api/krusty/multiplepatch_test.go +++ b/api/krusty/multiplepatch_test.go @@ -7,6 +7,7 @@ import ( "testing" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" + "sigs.k8s.io/kustomize/kyaml/yaml" ) func TestPatchesInOneFile(t *testing.T) { @@ -799,6 +800,181 @@ metadata: `) } +func TestSimpleMultiplePatchesMergeOptionsListAppend(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK("base", ` +namePrefix: b- +commonLabels: + team: foo +resources: +- deployment.yaml +- service.yaml +configMapGenerator: +- name: configmap-in-base + literals: + - foo=bar +`) + th.WriteF("base/deployment.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + template: + spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - name: nginx-persistent-storage + mountPath: /tmp/ps + - name: sidecar + image: sidecar:latest + volumes: + - configMap: + name: configmap-in-base + name: configmap-in-base +`) + th.WriteF("base/service.yaml", ` +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + ports: + - port: 80 +`) + th.WriteK("overlay", ` +namePrefix: a- +commonLabels: + env: staging +patchesStrategicMerge: +- deployment-patch1.yaml +- deployment-patch2.yaml +resources: +- ../base +configMapGenerator: +- name: configmap-in-overlay + literals: + - hello=world +`) + th.WriteF("overlay/deployment-patch1.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + template: + spec: + containers: + - name: nginx + image: nginx:latest + env: + - name: ENVKEY + value: ENVVALUE + volumes: + - name: nginx-persistent-storage + gcePersistentDisk: + pdName: nginx-persistent-storage + - configMap: + name: configmap-in-overlay + name: configmap-in-overlay +`) + th.WriteF("overlay/deployment-patch2.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + template: + spec: + containers: + - name: nginx + env: + - name: ANOTHERENV + value: FOO +`) + options := th.MakeDefaultOptions() + options.MergeOptions.ListIncreaseDirection = yaml.MergeOptionsListAppend + m := th.Run("overlay", options) + th.AssertActualEqualsExpected(m, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + env: staging + team: foo + name: a-b-nginx +spec: + selector: + matchLabels: + env: staging + team: foo + template: + metadata: + labels: + env: staging + team: foo + spec: + containers: + - env: + - name: ENVKEY + value: ENVVALUE + - name: ANOTHERENV + value: FOO + image: nginx:latest + name: nginx + volumeMounts: + - mountPath: /tmp/ps + name: nginx-persistent-storage + - image: sidecar:latest + name: sidecar + volumes: + - configMap: + name: a-b-configmap-in-base-798k5k7g9f + name: configmap-in-base + - gcePersistentDisk: + pdName: nginx-persistent-storage + name: nginx-persistent-storage + - configMap: + name: a-configmap-in-overlay-dc6fm46dhm + name: configmap-in-overlay +--- +apiVersion: v1 +kind: Service +metadata: + labels: + env: staging + team: foo + name: a-b-nginx +spec: + ports: + - port: 80 + selector: + env: staging + team: foo +--- +apiVersion: v1 +data: + foo: bar +kind: ConfigMap +metadata: + labels: + env: staging + team: foo + name: a-b-configmap-in-base-798k5k7g9f +--- +apiVersion: v1 +data: + hello: world +kind: ConfigMap +metadata: + labels: + env: staging + name: a-configmap-in-overlay-dc6fm46dhm +`) +} + func makeCommonFilesForMultiplePatchTests(th kusttest_test.Harness) { th.WriteK("base", ` namePrefix: team-foo- @@ -1009,6 +1185,146 @@ metadata: `) } +func TestMultiplePatchesNoConflictMergeOptionsListAppend(t *testing.T) { + th := kusttest_test.MakeHarness(t) + makeCommonFilesForMultiplePatchTests(th) + th.WriteF("overlay/staging/deployment-patch1.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + template: + spec: + containers: + - name: nginx + image: nginx:latest + env: + - name: ENVKEY + value: ENVVALUE + volumes: + - name: nginx-persistent-storage + gcePersistentDisk: + pdName: nginx-persistent-storage + - configMap: + name: configmap-in-overlay + name: configmap-in-overlay +`) + th.WriteF("overlay/staging/deployment-patch2.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + template: + spec: + containers: + - name: nginx + env: + - name: ANOTHERENV + value: FOO +`) + options := th.MakeDefaultOptions() + options.MergeOptions.ListIncreaseDirection = yaml.MergeOptionsListAppend + m := th.Run("overlay/staging", options) + th.AssertActualEqualsExpected(m, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + note: This is a test annotation + labels: + app: mynginx + env: staging + org: example.com + team: foo + name: staging-team-foo-nginx +spec: + selector: + matchLabels: + app: mynginx + env: staging + org: example.com + team: foo + template: + metadata: + annotations: + note: This is a test annotation + labels: + app: mynginx + env: staging + org: example.com + team: foo + spec: + containers: + - env: + - name: ENVKEY + value: ENVVALUE + - name: ANOTHERENV + value: FOO + image: nginx:latest + name: nginx + volumeMounts: + - mountPath: /tmp/ps + name: nginx-persistent-storage + - image: sidecar:latest + name: sidecar + volumes: + - configMap: + name: staging-team-foo-configmap-in-base-798k5k7g9f + name: configmap-in-base + - gcePersistentDisk: + pdName: nginx-persistent-storage + name: nginx-persistent-storage + - configMap: + name: staging-configmap-in-overlay-dc6fm46dhm + name: configmap-in-overlay +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + note: This is a test annotation + labels: + app: mynginx + env: staging + org: example.com + team: foo + name: staging-team-foo-nginx +spec: + ports: + - port: 80 + selector: + app: mynginx + env: staging + org: example.com + team: foo +--- +apiVersion: v1 +data: + foo: bar +kind: ConfigMap +metadata: + annotations: + note: This is a test annotation + labels: + app: mynginx + env: staging + org: example.com + team: foo + name: staging-team-foo-configmap-in-base-798k5k7g9f +--- +apiVersion: v1 +data: + hello: world +kind: ConfigMap +metadata: + labels: + env: staging + name: staging-configmap-in-overlay-dc6fm46dhm +`) +} + func TestNonCommutablePatches(t *testing.T) { th := kusttest_test.MakeHarness(t) makeCommonFilesForMultiplePatchTests(th) diff --git a/api/krusty/options.go b/api/krusty/options.go index 086242c8ed..c1063a09ba 100644 --- a/api/krusty/options.go +++ b/api/krusty/options.go @@ -6,6 +6,7 @@ package krusty import ( "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/yaml" ) type ReorderOption string @@ -34,6 +35,9 @@ type Options struct { // select the appropriate default. Reorder ReorderOption + // MergeOptions is a struct which contains the options for merge + MergeOptions *yaml.MergeOptions + // When true, a label // app.kubernetes.io/managed-by: kustomize- // is added to all the resources in the build out. @@ -52,8 +56,11 @@ func MakeDefaultOptions() *Options { return &Options{ Reorder: ReorderOptionNone, AddManagedbyLabel: false, - LoadRestrictions: types.LoadRestrictionsRootOnly, - PluginConfig: types.DisabledPluginConfig(), + MergeOptions: &yaml.MergeOptions{ + ListIncreaseDirection: yaml.MergeOptionsListPrepend, + }, + LoadRestrictions: types.LoadRestrictionsRootOnly, + PluginConfig: types.DisabledPluginConfig(), } } diff --git a/api/resource/resource.go b/api/resource/resource.go index 9884a672c5..6407ae2e50 100644 --- a/api/resource/resource.go +++ b/api/resource/resource.go @@ -23,7 +23,8 @@ import ( // paired with metadata used by kustomize. type Resource struct { kyaml.RNode - refVarNames []string + refVarNames []string + MergeOptions *kyaml.MergeOptions } var BuildAnnotations = []string{ @@ -147,7 +148,8 @@ type ResCtxMatcher func(ResCtx) bool // DeepCopy returns a new copy of resource func (r *Resource) DeepCopy() *Resource { rc := &Resource{ - RNode: *r.Copy(), + RNode: *r.Copy(), + MergeOptions: r.MergeOptions, } rc.copyKustomizeSpecificFields(r) return rc @@ -495,7 +497,8 @@ func (r *Resource) ApplySmPatch(patch *Resource) error { r.StorePreviousId() } if err := r.ApplyFilter(patchstrategicmerge.Filter{ - Patch: &patch.RNode, + Patch: &patch.RNode, + MergeOptions: patch.MergeOptions, }); err != nil { return err } diff --git a/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go b/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go index abc440c0f6..583134e999 100644 --- a/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go +++ b/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go @@ -10,6 +10,7 @@ import ( "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/yaml" ) @@ -17,6 +18,7 @@ type plugin struct { loadedPatches []*resource.Resource Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` Patches string `json:"patches,omitempty" yaml:"patches,omitempty"` + MergeOptions *kyaml.MergeOptions `json:"mergeOptions,omitempty" yaml:"mergeOptions,omitempty"` } var KustomizePlugin plugin //nolint:gochecknoglobals @@ -48,6 +50,9 @@ func (p *plugin) Config( return fmt.Errorf( "patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches) } + for i := range p.loadedPatches { + p.loadedPatches[i].MergeOptions = p.MergeOptions + } return nil } diff --git a/plugin/builtin/patchtransformer/PatchTransformer.go b/plugin/builtin/patchtransformer/PatchTransformer.go index 04f15b7ee0..853642d432 100644 --- a/plugin/builtin/patchtransformer/PatchTransformer.go +++ b/plugin/builtin/patchtransformer/PatchTransformer.go @@ -15,6 +15,7 @@ import ( "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/kio/kioutil" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/yaml" ) @@ -24,11 +25,12 @@ type plugin struct { // patchText is pure patch text created by Path or Patch patchText string // patchSource is patch source message - patchSource string - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` - Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` - Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + patchSource string + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` + Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` + Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + MergeOptions *kyaml.MergeOptions `json:"MergeOptions,omitempty" yaml:"MergeOptions,omitempty"` } var KustomizePlugin plugin //nolint:gochecknoglobals @@ -78,6 +80,7 @@ func (p *plugin) Config(h *resmap.PluginHelpers, c []byte) error { if p.Options["allowKindChange"] { loadedPatch.AllowKindChange() } + loadedPatch.MergeOptions = p.MergeOptions } } else { p.jsonPatches = patchesJson