move strategic merge patch transformer to a builtin transformer

This commit is contained in:
jingfangliu
2019-07-11 13:15:49 -07:00
parent 31ab347da2
commit 33fff655db
11 changed files with 712 additions and 673 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}