From 33fff655dbeefd02500c9d52237b10e70c50fdd2 Mon Sep 17 00:00:00 2001 From: jingfangliu Date: Thu, 11 Jul 2019 13:15:49 -0700 Subject: [PATCH] move strategic merge patch transformer to a builtin transformer --- k8sdeps/transformer/factory.go | 8 - k8sdeps/transformer/patch/transformer.go | 47 -- k8sdeps/transformer/patch/transformer_test.go | 579 ------------------ pkg/commands/build/build.go | 9 +- pkg/ifc/transformer/factory.go | 17 - pkg/kusttest/kusttestharness.go | 2 +- pkg/target/kusttarget.go | 19 +- pkg/target/kusttarget_configplugin.go | 23 + .../builtin/PatchStrategicMergeTransformer.go | 75 +++ .../PatchStrategicMergeTransformer.go | 76 +++ .../PatchStrategicMergeTransformer_test.go | 530 ++++++++++++++++ 11 files changed, 712 insertions(+), 673 deletions(-) delete mode 100644 k8sdeps/transformer/patch/transformer.go delete mode 100644 k8sdeps/transformer/patch/transformer_test.go delete mode 100644 pkg/ifc/transformer/factory.go create mode 100644 plugin/builtin/PatchStrategicMergeTransformer.go create mode 100644 plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go create mode 100644 plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer_test.go diff --git a/k8sdeps/transformer/factory.go b/k8sdeps/transformer/factory.go index 012913b32..f0cd05460 100644 --- a/k8sdeps/transformer/factory.go +++ b/k8sdeps/transformer/factory.go @@ -8,7 +8,6 @@ import ( "sigs.k8s.io/kustomize/v3/k8sdeps/transformer/patch" "sigs.k8s.io/kustomize/v3/pkg/resmap" "sigs.k8s.io/kustomize/v3/pkg/resource" - "sigs.k8s.io/kustomize/v3/pkg/transformers" ) // FactoryImpl makes patch transformer and name hash transformer @@ -24,10 +23,3 @@ func (p *FactoryImpl) MergePatches(patches []*resource.Resource, resmap.ResMap, error) { return patch.MergePatches(patches, rf) } - -// MakePatchTransformer makes a new patch transformer -func (p *FactoryImpl) MakePatchTransformer( - slice []*resource.Resource, - rf *resource.Factory) (transformers.Transformer, error) { - return patch.NewTransformer(slice, rf) -} diff --git a/k8sdeps/transformer/patch/transformer.go b/k8sdeps/transformer/patch/transformer.go deleted file mode 100644 index dd45720f7..000000000 --- a/k8sdeps/transformer/patch/transformer.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package patch - -import ( - "sigs.k8s.io/kustomize/v3/pkg/resmap" - "sigs.k8s.io/kustomize/v3/pkg/resource" - "sigs.k8s.io/kustomize/v3/pkg/transformers" -) - -// transformer applies strategic merge patches. -type transformer struct { - patches []*resource.Resource - rf *resource.Factory -} - -var _ transformers.Transformer = &transformer{} - -// NewTransformer constructs a strategic merge patch transformer. -func NewTransformer( - slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) { - if len(slice) == 0 { - return transformers.NewNoOpTransformer(), nil - } - return &transformer{patches: slice, rf: rf}, nil -} - -// Transform apply the patches on top of the base resources. -// nolint:ineffassign -func (tf *transformer) Transform(m resmap.ResMap) error { - patches, err := MergePatches(tf.patches, tf.rf) - if err != nil { - return err - } - for _, patch := range patches.Resources() { - target, err := m.GetById(patch.OrgId()) - if err != nil { - return err - } - err = target.Patch(patch.Kunstructured) - if err != nil { - return err - } - } - return nil -} diff --git a/k8sdeps/transformer/patch/transformer_test.go b/k8sdeps/transformer/patch/transformer_test.go deleted file mode 100644 index 4a9cec645..000000000 --- a/k8sdeps/transformer/patch/transformer_test.go +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package patch - -import ( - "reflect" - "strings" - "testing" - - "sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" - "sigs.k8s.io/kustomize/v3/pkg/resmaptest" - "sigs.k8s.io/kustomize/v3/pkg/resource" -) - -var rf = resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl()) - -func TestOverlayRun(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "old-label": "old-value", - }, - }, - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx", - }, - }, - }, - }, - }, - }).ResMap() - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "another-label": "foo", - }, - }, - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:latest", - "env": []interface{}{ - map[string]interface{}{ - "name": "SOMEENV", - "value": "BAR", - }, - }, - }, - }, - }, - }, - }, - }), - } - expected := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "old-label": "old-value", - "another-label": "foo", - }, - }, - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:latest", - "env": []interface{}{ - map[string]interface{}{ - "name": "SOMEENV", - "value": "BAR", - }, - }, - }, - }, - }, - }, - }, - }).ResMap() - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !reflect.DeepEqual(base, expected) { - err = expected.ErrorIfNotEqualLists(base) - t.Fatalf("actual doesn't match expected: %v", err) - } -} - -func TestMultiplePatches(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx", - }, - }, - }, - }, - }, - }).ResMap() - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:latest", - "env": []interface{}{ - map[string]interface{}{ - "name": "SOMEENV", - "value": "BAR", - }, - }, - }, - }, - }, - }, - }, - }), - rf.FromMap(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "env": []interface{}{ - map[string]interface{}{ - "name": "ANOTHERENV", - "value": "HELLO", - }, - }, - }, - map[string]interface{}{ - "name": "busybox", - "image": "busybox", - }, - }, - }, - }, - }, - }), - } - expected := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:latest", - "env": []interface{}{ - map[string]interface{}{ - "name": "ANOTHERENV", - "value": "HELLO", - }, - map[string]interface{}{ - "name": "SOMEENV", - "value": "BAR", - }, - }, - }, - map[string]interface{}{ - "name": "busybox", - "image": "busybox", - }, - }, - }, - }, - }, - }).ResMap() - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !reflect.DeepEqual(base, expected) { - err = expected.ErrorIfNotEqualLists(base) - t.Fatalf("actual doesn't match expected: %v", err) - } -} - -func TestMultiplePatchesWithConflict(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx", - }, - }, - }, - }, - }, - }).ResMap() - - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:latest", - "env": []interface{}{ - map[string]interface{}{ - "name": "SOMEENV", - "value": "BAR", - }, - }, - }, - }, - }, - }, - }, - }), - rf.FromMap(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:1.7.9", - }, - }, - }, - }, - }, - }), - } - - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err == nil { - t.Fatalf("did not get expected error") - } - if !strings.Contains(err.Error(), "conflict") { - t.Fatalf("expected error to contain %q but get %v", "conflict", err) - } -} - -func TestPatchesWithWrongNamespace(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - "namespace": "namespace1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx", - }, - }, - }, - }, - }, - }).ResMap() - - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - "namespace": "namespace2", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:1.7.9", - }, - }, - }, - }, - }, - }), - } - - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err == nil { - t.Fatalf("did not get expected error") - } - if !strings.Contains(err.Error(), "failed to find unique target for patch") { - t.Fatalf("expected error to contain %q but get %v", "failed to find target for patch", err) - } -} - -func TestNoSchemaOverlayRun(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "A": "X", - "B": "Y", - }, - }, - }).ResMap() - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "B": nil, - "C": "Z", - }, - }, - }), - } - expected := resmaptest_test.NewRmBuilder(t, rf). - Add( - map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "A": "X", - "C": "Z", - }, - }, - }).ResMap() - - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if err = expected.ErrorIfNotEqualLists(base); err != nil { - t.Fatalf("actual doesn't match expected: %v", err) - } -} - -func TestNoSchemaMultiplePatches(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "A": "X", - "B": "Y", - }, - }, - }).ResMap() - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "B": nil, - "C": "Z", - }, - }, - }), - rf.FromMap(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "C": "Z", - "D": "W", - }, - "baz": map[string]interface{}{ - "hello": "world", - }, - }, - }), - } - expected := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "A": "X", - "C": "Z", - "D": "W", - }, - "baz": map[string]interface{}{ - "hello": "world", - }, - }, - }).ResMap() - - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if err = expected.ErrorIfNotEqualLists(base); err != nil { - t.Fatalf("actual doesn't match expected: %v", err) - } -} - -func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) { - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "A": "X", - "B": "Y", - }, - }, - }).ResMap() - patch := []*resource.Resource{ - rf.FromMap(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "B": nil, - "C": "Z", - }, - }, - }), - rf.FromMap(map[string]interface{}{ - "apiVersion": "example.com/v1", - "kind": "Foo", - "metadata": map[string]interface{}{ - "name": "my-foo", - }, - "spec": map[string]interface{}{ - "bar": map[string]interface{}{ - "C": "NOT_Z", - }, - }, - }), - } - - lt, err := NewTransformer(patch, rf) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = lt.Transform(base) - if err == nil { - t.Fatalf("did not get expected error") - } - if !strings.Contains(err.Error(), "conflict") { - t.Fatalf("expected error to contain %q but get %v", "conflict", err) - } -} diff --git a/pkg/commands/build/build.go b/pkg/commands/build/build.go index 11df0a760..66938bcc5 100644 --- a/pkg/commands/build/build.go +++ b/pkg/commands/build/build.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/kustomize/v3/pkg/fs" "sigs.k8s.io/kustomize/v3/pkg/ifc" - "sigs.k8s.io/kustomize/v3/pkg/ifc/transformer" "sigs.k8s.io/kustomize/v3/pkg/loader" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig" "sigs.k8s.io/kustomize/v3/pkg/plugins" @@ -61,7 +60,7 @@ https://github.com/hashicorp/go-getter#url-format func NewCmdBuild( out io.Writer, fSys fs.FileSystem, v ifc.Validator, rf *resmap.Factory, - ptf transformer.Factory) *cobra.Command { + ptf resmap.PatchFactory) *cobra.Command { var o Options pluginConfig := plugins.DefaultPluginConfig() @@ -115,7 +114,7 @@ func (o *Options) Validate(args []string) (err error) { // RunBuild runs build command. func (o *Options) RunBuild( out io.Writer, v ifc.Validator, fSys fs.FileSystem, - rf *resmap.Factory, ptf transformer.Factory, + rf *resmap.Factory, ptf resmap.PatchFactory, pl *plugins.Loader) error { ldr, err := loader.NewLoader( o.loadRestrictor, v, o.kustomizationPath, fSys) @@ -136,7 +135,7 @@ func (o *Options) RunBuild( func (o *Options) RunBuildPrune( out io.Writer, v ifc.Validator, fSys fs.FileSystem, - rf *resmap.Factory, ptf transformer.Factory, + rf *resmap.Factory, ptf resmap.PatchFactory, pl *plugins.Loader) error { ldr, err := loader.NewLoader( o.loadRestrictor, v, o.kustomizationPath, fSys) @@ -180,7 +179,7 @@ func (o *Options) emitResources( func NewCmdBuildPrune( out io.Writer, v ifc.Validator, fSys fs.FileSystem, - rf *resmap.Factory, ptf transformer.Factory, + rf *resmap.Factory, ptf resmap.PatchFactory, pl *plugins.Loader) *cobra.Command { var o Options diff --git a/pkg/ifc/transformer/factory.go b/pkg/ifc/transformer/factory.go deleted file mode 100644 index 9d303e335..000000000 --- a/pkg/ifc/transformer/factory.go +++ /dev/null @@ -1,17 +0,0 @@ -/// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -// Package patch holds miscellaneous interfaces used by kustomize. -package transformer - -import ( - "sigs.k8s.io/kustomize/v3/pkg/resource" - "sigs.k8s.io/kustomize/v3/pkg/transformers" -) - -// Factory makes transformers that require k8sdeps. -type Factory interface { - MakePatchTransformer( - slice []*resource.Resource, - rf *resource.Factory) (transformers.Transformer, error) -} diff --git a/pkg/kusttest/kusttestharness.go b/pkg/kusttest/kusttestharness.go index 528e82a36..9c235e157 100644 --- a/pkg/kusttest/kusttestharness.go +++ b/pkg/kusttest/kusttestharness.go @@ -49,7 +49,7 @@ func NewKustTestHarnessFull( t *testing.T, path string, lr loader.LoadRestrictorFunc, pc *types.PluginConfig) *KustTestHarness { rf := resmap.NewFactory(resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl()), nil) + kunstruct.NewKunstructuredFactoryImpl()), transformer.NewFactoryImpl()) return &KustTestHarness{ t: t, rf: rf, diff --git a/pkg/target/kusttarget.go b/pkg/target/kusttarget.go index 42215cc92..247f2d0e5 100644 --- a/pkg/target/kusttarget.go +++ b/pkg/target/kusttarget.go @@ -14,7 +14,6 @@ import ( "github.com/pkg/errors" "sigs.k8s.io/kustomize/v3/pkg/accumulator" "sigs.k8s.io/kustomize/v3/pkg/ifc" - "sigs.k8s.io/kustomize/v3/pkg/ifc/transformer" "sigs.k8s.io/kustomize/v3/pkg/pgmconfig" "sigs.k8s.io/kustomize/v3/pkg/plugins" "sigs.k8s.io/kustomize/v3/pkg/resmap" @@ -30,7 +29,7 @@ type KustTarget struct { kustomization *types.Kustomization ldr ifc.Loader rFactory *resmap.Factory - tFactory transformer.Factory + tFactory resmap.PatchFactory pLdr *plugins.Loader } @@ -38,7 +37,7 @@ type KustTarget struct { func NewKustTarget( ldr ifc.Loader, rFactory *resmap.Factory, - tFactory transformer.Factory, + tFactory resmap.PatchFactory, pLdr *plugins.Loader) (*KustTarget, error) { content, err := loadKustFile(ldr) if err != nil { @@ -293,19 +292,7 @@ func (kt *KustTarget) configureExternalGenerators() ([]transformers.Generator, e } func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error { - patches, err := kt.rFactory.RF().SliceFromPatches( - kt.ldr, kt.kustomization.PatchesStrategicMerge) - if err != nil { - return errors.Wrapf( - err, "reading strategic merge patches %v", - kt.kustomization.PatchesStrategicMerge) - } var r []transformers.Transformer - t, err := kt.tFactory.MakePatchTransformer(patches, kt.rFactory.RF()) - if err != nil { - return err - } - r = append(r, t) tConfig := ra.GetTransformerConfig() lts, err := kt.configureBuiltinTransformers(tConfig) if err != nil { @@ -317,7 +304,7 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error { return err } r = append(r, lts...) - t = transformers.NewMultiTransformer(r) + t := transformers.NewMultiTransformer(r) return ra.Transform(t) } diff --git a/pkg/target/kusttarget_configplugin.go b/pkg/target/kusttarget_configplugin.go index f3a8d76cb..e616f4334 100644 --- a/pkg/target/kusttarget_configplugin.go +++ b/pkg/target/kusttarget_configplugin.go @@ -63,6 +63,7 @@ func (kt *KustTarget) configureBuiltinTransformers( // with tests: // - patch SMP configurators := []transformerConfigurator{ + kt.configureBuiltinPatchStrategicMergeTransformer, kt.configureBuiltinNamespaceTransformer, kt.configureBuiltinNameTransformer, kt.configureBuiltinLabelTransformer, @@ -165,6 +166,28 @@ func (kt *KustTarget) configureBuiltinPatchJson6902Transformer( return } +func (kt *KustTarget) configureBuiltinPatchStrategicMergeTransformer( + tConfig *config.TransformerConfig) ( + result []transformers.Transformer, err error) { + if len(kt.kustomization.PatchesStrategicMerge) == 0 { + result = append(result, transformers.NewNoOpTransformer()) + return + } + var c struct { + Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` + Patches string `json:"patches,omitempty" yaml:"patches,omitempty"` + } + c.Paths = kt.kustomization.PatchesStrategicMerge + c.Patches = "" // Not implemented for kustomization file yet + p := builtin.NewPatchStrategicMergeTransformerPlugin() + err = kt.configureBuiltinPlugin(p, c, "patchStrategicMerge") + if err != nil { + return nil, err + } + result = append(result, p) + return +} + func (kt *KustTarget) configureBuiltinLabelTransformer( tConfig *config.TransformerConfig) ( result []transformers.Transformer, err error) { diff --git a/plugin/builtin/PatchStrategicMergeTransformer.go b/plugin/builtin/PatchStrategicMergeTransformer.go new file mode 100644 index 000000000..4f0c34d98 --- /dev/null +++ b/plugin/builtin/PatchStrategicMergeTransformer.go @@ -0,0 +1,75 @@ +// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT. +package builtin + +import ( + "fmt" + "sigs.k8s.io/kustomize/v3/pkg/ifc" + "sigs.k8s.io/kustomize/v3/pkg/resmap" + "sigs.k8s.io/kustomize/v3/pkg/resource" + "sigs.k8s.io/kustomize/v3/pkg/types" + "sigs.k8s.io/yaml" +) + +type PatchStrategicMergeTransformerPlugin struct { + ldr ifc.Loader + rf *resmap.Factory + loadedPatches []*resource.Resource + Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` + Patches string `json:patches,omitempty" yaml:"patches,omitempty"` +} + +//noinspection GoUnusedGlobalVariable +func NewPatchStrategicMergeTransformerPlugin() *PatchStrategicMergeTransformerPlugin { + return &PatchStrategicMergeTransformerPlugin{} +} + +func (p *PatchStrategicMergeTransformerPlugin) Config( + ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { + p.ldr = ldr + p.rf = rf + err = yaml.Unmarshal(c, p) + if err != nil { + return err + } + if len(p.Paths) == 0 && p.Patches == "" { + return fmt.Errorf("empty file path and empty patch content") + } + if len(p.Paths) != 0 { + res, err := p.rf.RF().SliceFromPatches(ldr, p.Paths) + if err != nil { + return err + } + p.loadedPatches = res + } + if p.Patches != "" { + res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches)) + if err != nil { + return err + } + p.loadedPatches = append(p.loadedPatches, res...) + } + + if len(p.loadedPatches) == 0 { + return fmt.Errorf( + "patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches) + } + return err +} + +func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error { + patches, err := p.rf.MergePatches(p.loadedPatches) + if err != nil { + return err + } + for _, patch := range patches.Resources() { + target, err := m.GetById(patch.OrgId()) + if err != nil { + return err + } + err = target.Patch(patch.Kunstructured) + if err != nil { + return err + } + } + return nil +} diff --git a/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go b/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go new file mode 100644 index 000000000..d6c8d629d --- /dev/null +++ b/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go @@ -0,0 +1,76 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:generate go run sigs.k8s.io/kustomize/v3/cmd/pluginator +package main + +import ( + "fmt" + "sigs.k8s.io/kustomize/v3/pkg/ifc" + "sigs.k8s.io/kustomize/v3/pkg/resmap" + "sigs.k8s.io/kustomize/v3/pkg/resource" + "sigs.k8s.io/kustomize/v3/pkg/types" + "sigs.k8s.io/yaml" +) + +type plugin struct { + ldr ifc.Loader + rf *resmap.Factory + loadedPatches []*resource.Resource + Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"` + Patches string `json:patches,omitempty" yaml:"patches,omitempty"` +} + +//noinspection GoUnusedGlobalVariable +var KustomizePlugin plugin + +func (p *plugin) Config( + ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { + p.ldr = ldr + p.rf = rf + err = yaml.Unmarshal(c, p) + if err != nil { + return err + } + if len(p.Paths) == 0 && p.Patches == "" { + return fmt.Errorf("empty file path and empty patch content") + } + if len(p.Paths) != 0 { + res, err := p.rf.RF().SliceFromPatches(ldr, p.Paths) + if err != nil { + return err + } + p.loadedPatches = res + } + if p.Patches != "" { + res, err := p.rf.RF().SliceFromBytes([]byte(p.Patches)) + if err != nil { + return err + } + p.loadedPatches = append(p.loadedPatches, res...) + } + + if len(p.loadedPatches) == 0 { + return fmt.Errorf( + "patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches) + } + return err +} + +func (p *plugin) Transform(m resmap.ResMap) error { + patches, err := p.rf.MergePatches(p.loadedPatches) + if err != nil { + return err + } + for _, patch := range patches.Resources() { + target, err := m.GetById(patch.OrgId()) + if err != nil { + return err + } + err = target.Patch(patch.Kunstructured) + if err != nil { + return err + } + } + return nil +} diff --git a/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer_test.go b/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer_test.go new file mode 100644 index 000000000..b436c0a33 --- /dev/null +++ b/plugin/builtin/patchstrategicmergetransformer/PatchStrategicMergeTransformer_test.go @@ -0,0 +1,530 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "strings" + "testing" + + "sigs.k8s.io/kustomize/v3/pkg/kusttest" + "sigs.k8s.io/kustomize/v3/pkg/plugins" +) + +const ( + target = ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + replica: 2 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - name: nginx + image: nginx +` + targetWithNamespace = ` +apiVersion: apps/v1 +metadata: + name: myDeploy + namespace: namespace1 +kind: Deployment +spec: + replica: 2 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - name: nginx + image: nginx +` + targetNoschema = ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + A: X + B: Y +` +) + +func TestPatchStrategicMergeTransformerMissingFile(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch.yaml +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), + "cannot read file \"/app/patch.yaml\"") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestBadPatchStrategicMergeTransformer(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +patches: 'thisIsNotAPatch' +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), + "cannot unmarshal string into Go value of type map[string]interface {}") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestBothEmptyPatchStrategicMergeTransformer(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), "empty file path and empty patch content") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestPatchStrategicMergeTransformerFromFiles(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch.yaml", ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + replica: 3 +`) + + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch.yaml +`, target) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + replica: 3 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - image: nginx + name: nginx +`) +} + +func TestPatchStrategicMergeTransformerWithInline(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +patches: '{"apiVersion": "apps/v1", "metadata": {"name": "myDeploy"}, "kind": "Deployment", "spec": {"replica": 3}}' +`, target) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + replica: 3 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - image: nginx + name: nginx +`) +} + +func TestPatchStrategicMergeTransformerMultiplePatches(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch1.yaml", ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: nginx + image: nginx:latest + env: + - name: SOMEENV + value: BAR +`) + + th.WriteF("/app/patch2.yaml", ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: nginx + env: + - name: ANOTHERENV + value: HELLO + - name: busybox + image: busybox +`) + + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch1.yaml +- patch2.yaml +`, target) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + replica: 2 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - env: + - name: ANOTHERENV + value: HELLO + - name: SOMEENV + value: BAR + image: nginx:latest + name: nginx + - image: busybox + name: busybox +`) +} + +func TestStrategicMergeTransformerMultiplePatchesWithConflicts(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch1.yaml", ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: nginx + image: nginx:latest + env: + - name: SOMEENV + value: BAR +`) + + th.WriteF("/app/patch2.yaml", ` +apiVersion: apps/v1 +metadata: + name: myDeploy +kind: Deployment +spec: + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 + env: + - name: ANOTHERENV + value: HELLO + - name: busybox + image: busybox +`) + + err := th.ErrorFromLoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch1.yaml +- patch2.yaml +`, target) + + if err == nil { + t.Fatalf("did not get expected error") + } + if !strings.Contains(err.Error(), "conflict") { + t.Fatalf("expected error to contain %q but get %v", "conflict", err) + } +} + +func TestStrategicMergeTransformerWrongNamespace(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch.yaml", ` +apiVersion: apps/v1 +metadata: + name: myDeploy + namespace: namespace2 +kind: Deployment +spec: + template: + spec: + containers: + - name: nginx + image: nginx:latest + env: + - name: SOMEENV + value: BAR +`) + + err := th.ErrorFromLoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch.yaml +`, targetWithNamespace) + + if err == nil { + t.Fatalf("did not get expected error") + } + if !strings.Contains(err.Error(), "failed to find unique target for patch") { + t.Fatalf("expected error to contain %q but get %v", "failed to find target for patch", err) + } +} + +func TestStrategicMergeTransformerNoSchema(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch.yaml", ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + B: + C: Z +`) + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch.yaml +`, targetNoschema) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + A: X + C: Z +`) +} + +func TestStrategicMergeTransformerNoSchemaMultiPatches(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch1.yaml", ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + B: + C: Z +`) + th.WriteF("/app/patch2.yaml", ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + C: Z + D: W + baz: + hello: world +`) + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch1.yaml +- patch2.yaml +`, targetNoschema) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + A: X + C: Z + D: W + baz: + hello: world +`) +} + +func TestStrategicMergeTransformerNoSchemaMultiPatchesWithConflict(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchStrategicMergeTransformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/patch1.yaml", ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + C: Z +`) + th.WriteF("/app/patch2.yaml", ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: my-foo +spec: + bar: + C: NOT_Z + +`) + err := th.ErrorFromLoadAndRunTransformer(` +apiVersion: builtin +kind: PatchStrategicMergeTransformer +metadata: + name: notImportantHere +paths: +- patch1.yaml +- patch2.yaml +`, targetNoschema) + if !strings.Contains(err.Error(), "conflict") { + t.Fatalf("expected error to contain %q but get %v", "conflict", err) + } +}