diff --git a/go.sum b/go.sum index 5b4e8a845..86f575653 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/kusttest/kusttestharness.go b/pkg/kusttest/kusttestharness.go index 7b2e49e36..d0405e077 100644 --- a/pkg/kusttest/kusttestharness.go +++ b/pkg/kusttest/kusttestharness.go @@ -124,7 +124,7 @@ func (th *KustTestHarness) LoadAndRunGenerator( func (th *KustTestHarness) LoadAndRunTransformer( config, input string) resmap.ResMap { - resMap, err := th.runTransformer(config, input) + resMap, err := th.RunTransformer(config, input) if err != nil { th.t.Fatalf("Err: %v", err) } @@ -133,11 +133,11 @@ func (th *KustTestHarness) LoadAndRunTransformer( func (th *KustTestHarness) ErrorFromLoadAndRunTransformer( config, input string) error { - _, err := th.runTransformer(config, input) + _, err := th.RunTransformer(config, input) return err } -func (th *KustTestHarness) runTransformer( +func (th *KustTestHarness) RunTransformer( config, input string) (resmap.ResMap, error) { transConfig, err := th.rf.RF().FromBytes([]byte(config)) if err != nil { @@ -149,7 +149,7 @@ func (th *KustTestHarness) runTransformer( } g, err := th.pl.LoadTransformer(th.ldr, transConfig) if err != nil { - th.t.Fatalf("Err: %v", err) + return nil, err } err = g.Transform(resMap) return resMap, err diff --git a/pkg/patch/transformer/factory.go b/pkg/patch/transformer/factory.go deleted file mode 100644 index 69aff144d..000000000 --- a/pkg/patch/transformer/factory.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package transformer - -import ( - "fmt" - - "sigs.k8s.io/kustomize/v3/pkg/gvk" - "sigs.k8s.io/kustomize/v3/pkg/ifc" - "sigs.k8s.io/kustomize/v3/pkg/resid" - "sigs.k8s.io/kustomize/v3/pkg/transformers" - "sigs.k8s.io/kustomize/v3/pkg/types" -) - -// PatchJson6902Factory makes PatchJson6902 transformers -type PatchJson6902Factory struct { - loader ifc.Loader -} - -// NewPatchJson6902Factory returns a new PatchJson6902Factory. -func NewPatchJson6902Factory(l ifc.Loader) PatchJson6902Factory { - return PatchJson6902Factory{loader: l} -} - -// MakePatchJson6902Transformer returns a transformer for applying PatchJson6902 patch -func (f PatchJson6902Factory) MakePatchJson6902Transformer(patches []types.PatchJson6902) (transformers.Transformer, error) { - var ts []transformers.Transformer - for _, p := range patches { - t, err := f.makeOnePatchJson6902Transformer(p) - if err != nil { - return nil, err - } - if t != nil { - ts = append(ts, t) - } - } - return transformers.NewMultiTransformerWithConflictCheck(ts), nil -} - -func (f PatchJson6902Factory) makeOnePatchJson6902Transformer(p types.PatchJson6902) (transformers.Transformer, error) { - if p.Target == nil { - return nil, fmt.Errorf("must specify the target field in patchesJson6902") - } - if p.Path == "" { - return nil, fmt.Errorf("must specify the path for a json patch file") - } - - targetId := resid.NewResIdWithNamespace( - gvk.Gvk{ - Group: p.Target.Group, - Version: p.Target.Version, - Kind: p.Target.Kind, - }, - p.Target.Name, - p.Target.Namespace, - ) - - rawOp, err := f.loader.Load(p.Path) - if err != nil { - return nil, err - } - - return newPatchJson6902JSONTransformer(targetId, rawOp) -} - -func isJsonFormat(data []byte) bool { - return data[0] == '[' -} diff --git a/pkg/patch/transformer/factory_test.go b/pkg/patch/transformer/factory_test.go deleted file mode 100644 index 256d7b125..000000000 --- a/pkg/patch/transformer/factory_test.go +++ /dev/null @@ -1,326 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package transformer - -import ( - "reflect" - "strings" - "testing" - - "gopkg.in/yaml.v2" - "sigs.k8s.io/kustomize/v3/internal/loadertest" - "sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" - "sigs.k8s.io/kustomize/v3/pkg/resmaptest" - "sigs.k8s.io/kustomize/v3/pkg/resource" - "sigs.k8s.io/kustomize/v3/pkg/types" -) - -var rf = resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl()) - -func TestNewPatchJson6902FactoryNoTarget(t *testing.T) { - p := types.PatchJson6902{} - _, err := NewPatchJson6902Factory(nil).makeOnePatchJson6902Transformer(p) - if err == nil { - t.Fatal("expected error") - } - if !strings.Contains(err.Error(), "must specify the target field in patchesJson6902") { - t.Fatalf("incorrect error returned: %v", err) - } -} - -func TestNewPatchJson6902FactoryConflict(t *testing.T) { - jsonPatch := []byte(` -target: - name: some-name - kind: Deployment -`) - p := types.PatchJson6902{} - err := yaml.Unmarshal(jsonPatch, &p) - if err != nil { - t.Fatalf("expected error %v", err) - } - f := NewPatchJson6902Factory(nil) - _, err = f.makeOnePatchJson6902Transformer(p) - if err == nil { - t.Fatal("expected error") - } - if !strings.Contains(err.Error(), "must specify the path for a json patch file") { - t.Fatalf("incorrect error returned %v", err) - } -} - -func TestNewPatchJson6902FactoryJSON(t *testing.T) { - ldr := loadertest.NewFakeLoader("/testpath") - operations := []byte(`[ - {"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"}, - {"op": "add", "path": "/spec/replica", "value": "3"}, - {"op": "add", "path": "/spec/template/spec/containers/0/command", "value": ["arg1", "arg2", "arg3"]} -]`) - err := ldr.AddFile("/testpath/patch.json", operations) - if err != nil { - t.Fatalf("Failed to setup fake ldr.") - } - - jsonPatch := []byte(` -target: - kind: Deployment - name: some-name -path: patch.json -`) - p := types.PatchJson6902{} - err = yaml.Unmarshal(jsonPatch, &p) - if err != nil { - t.Fatal("expected error") - } - - tr, err := NewPatchJson6902Factory(ldr).makeOnePatchJson6902Transformer(p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - if tr == nil { - t.Fatal("the returned transformer should not be nil") - } -} - -func TestNewPatchJson6902FactoryYAML(t *testing.T) { - ldr := loadertest.NewFakeLoader("/testpath") - operations := []byte(` -- op: replace - path: /spec/template/spec/containers/0/name - value: my-nginx -- op: add - path: /spec/replica - value: 3 -- op: add - path: /spec/template/spec/containers/0/command - value: ["arg1", "arg2", "arg3"] -`) - err := ldr.AddFile("/testpath/patch.yaml", operations) - if err != nil { - t.Fatalf("Failed to setup fake ldr.") - } - jsonPatch := []byte(` -target: - name: some-name - kind: Deployment -path: patch.yaml -`) - p := types.PatchJson6902{} - err = yaml.Unmarshal(jsonPatch, &p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - - tr, err := NewPatchJson6902Factory(ldr).makeOnePatchJson6902Transformer(p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - if tr == nil { - t.Fatal("the returned transformer should not be nil") - } -} - -func TestNewPatchJson6902FactoryMulti(t *testing.T) { - ldr := loadertest.NewFakeLoader("/testpath") - operations := []byte(`[ - {"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"}, - {"op": "add", "path": "/spec/replica", "value": "3"} -]`) - err := ldr.AddFile("/testpath/patch.json", operations) - if err != nil { - t.Fatalf("Failed to setup fake ldr.") - } - - operations = []byte(` -- op: add - path: /spec/template/spec/containers/0/command - value: ["arg1", "arg2", "arg3"] -`) - err = ldr.AddFile("/testpath/patch.yaml", operations) - if err != nil { - t.Fatalf("Failed to setup fake ldr.") - } - - jsonPatches := []byte(` -- target: - kind: foo - name: some-name - path: patch.json - -- target: - kind: foo - name: some-name - path: patch.yaml -`) - var p []types.PatchJson6902 - err = yaml.Unmarshal(jsonPatches, &p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - - f := NewPatchJson6902Factory(ldr) - tr, err := f.MakePatchJson6902Transformer(p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - if tr == nil { - t.Fatal("the returned transformer should not be nil") - } - - base := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "kind": "foo", - "metadata": map[string]interface{}{ - "name": "some-name", - }, - "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{}{ - "image": "nginx", - "name": "nginx", - }, - }, - }, - }, - }, - }).ResMap() - expected := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "kind": "foo", - "metadata": map[string]interface{}{ - "name": "some-name", - }, - "spec": map[string]interface{}{ - "replica": "3", - "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{}{ - "image": "nginx", - "name": "my-nginx", - "command": []interface{}{ - "arg1", - "arg2", - "arg3", - }, - }, - }, - }, - }, - }, - }).ResMap() - err = tr.Transform(base) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - if !reflect.DeepEqual(base, expected) { - err = expected.ErrorIfNotEqualSets(base) - t.Fatalf("actual doesn't match expected: %v", err) - } -} - -func TestNewPatchJson6902FactoryMultiConflict(t *testing.T) { - ldr := loadertest.NewFakeLoader("/testpath") - operations := []byte(`[ - {"op": "add", "path": "/spec/replica", "value": "3"} -]`) - err := ldr.AddFile("/testpath/patch.json", operations) - if err != nil { - t.Fatalf("Failed to setup fake ldr.") - } - operations = []byte(` -- op: add - path: /spec/replica - value: 4 -`) - err = ldr.AddFile("/testpath/patch.yaml", operations) - if err != nil { - t.Fatalf("Failed to setup fake ldr.") - } - - jsonPatches := []byte(` -- target: - kind: foo - name: somename - path: patch.json - -- target: - kind: foo - name: somename - path: patch.yaml -`) - var p []types.PatchJson6902 - err = yaml.Unmarshal(jsonPatches, &p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - - f := NewPatchJson6902Factory(ldr) - tr, err := f.MakePatchJson6902Transformer(p) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - if tr == nil { - t.Fatal("the returned transformer should not be nil") - } - - m := resmaptest_test.NewRmBuilder(t, rf). - Add(map[string]interface{}{ - "kind": "foo", - "metadata": map[string]interface{}{ - "name": "somename", - }, - "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{}{ - "image": "nginx", - "name": "nginx", - }, - }, - }, - }, - }, - }).ResMap() - - err = tr.Transform(m) - if err == nil { - t.Fatal("expected conflict") - } - if !strings.Contains(err.Error(), "found conflict between different patches") { - t.Fatalf("incorrect error happened %v", err) - } -} diff --git a/pkg/patch/transformer/patchjson6902json.go b/pkg/patch/transformer/patchjson6902json.go deleted file mode 100644 index 0f44e9393..000000000 --- a/pkg/patch/transformer/patchjson6902json.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package transformer - -import ( - "fmt" - - jsonpatch "github.com/evanphx/json-patch" - "github.com/pkg/errors" - "sigs.k8s.io/kustomize/v3/pkg/resid" - "sigs.k8s.io/kustomize/v3/pkg/resmap" - "sigs.k8s.io/kustomize/v3/pkg/transformers" - "sigs.k8s.io/yaml" -) - -// patchJson6902JSONTransformer applies patches. -type patchJson6902JSONTransformer struct { - target resid.ResId - patch jsonpatch.Patch - rawOp []byte -} - -var _ transformers.Transformer = &patchJson6902JSONTransformer{} - -// newPatchJson6902JSONTransformer constructs a PatchJson6902 transformer. -func newPatchJson6902JSONTransformer( - id resid.ResId, rawOp []byte) (transformers.Transformer, error) { - op := rawOp - var err error - - if len(op) == 0 { - return nil, fmt.Errorf("json patch file is empty %v", id) - } - - if !isJsonFormat(op) { - // if it isn't JSON, try to parse it as YAML - op, err = yaml.YAMLToJSON(rawOp) - if err != nil { - return nil, err - } - } - decodedPatch, err := jsonpatch.DecodePatch(op) - if err != nil { - return nil, err - } - if len(decodedPatch) == 0 { - return transformers.NewNoOpTransformer(), nil - } - return &patchJson6902JSONTransformer{target: id, patch: decodedPatch, rawOp: rawOp}, nil -} - -// Transform apply the json patches on top of the base resources. -func (t *patchJson6902JSONTransformer) Transform(m resmap.ResMap) error { - obj, err := m.GetById(t.target) - if err != nil { - return err - } - rawObj, err := obj.MarshalJSON() - if err != nil { - return err - } - modifiedObj, err := t.patch.Apply(rawObj) - if err != nil { - return errors.Wrapf(err, "failed to apply json patch '%s'", string(t.rawOp)) - } - err = obj.UnmarshalJSON(modifiedObj) - if err != nil { - return err - } - return nil -} diff --git a/pkg/patch/transformer/patchjson6902json_test.go b/pkg/patch/transformer/patchjson6902json_test.go deleted file mode 100644 index 132ca9c13..000000000 --- a/pkg/patch/transformer/patchjson6902json_test.go +++ /dev/null @@ -1,180 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package transformer - -import ( - "reflect" - "strings" - "testing" - - "sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct" - "sigs.k8s.io/kustomize/v3/pkg/gvk" - "sigs.k8s.io/kustomize/v3/pkg/resid" - "sigs.k8s.io/kustomize/v3/pkg/resmaptest" - "sigs.k8s.io/kustomize/v3/pkg/resource" -) - -var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"} - -func TestJsonPatchJSONTransformer_Transform(t *testing.T) { - rf := resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl()) - m := 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() - - operations := []byte(`[ - {"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"}, - {"op": "add", "path": "/spec/replica", "value": "3"}, - {"op": "add", "path": "/spec/template/spec/containers/0/command", "value": ["arg1", "arg2", "arg3"]} -]`) - - 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{}{ - "replica": "3", - "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{}{ - "image": "nginx", - "name": "my-nginx", - "command": []interface{}{ - "arg1", - "arg2", - "arg3", - }, - }, - }, - }, - }, - }, - }).ResMap() - - patchId := m.GetByIndex(0).OrgId() - - jpt, err := newPatchJson6902JSONTransformer(patchId, operations) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - err = jpt.Transform(m) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !reflect.DeepEqual(m, expected) { - err = expected.ErrorIfNotEqualSets(m) - t.Fatalf("actual doesn't match expected: %v", err) - } -} - -func TestJsonPatchJSONTransformer_UnHappyTransform(t *testing.T) { - rf := resource.NewFactory( - kunstruct.NewKunstructuredFactoryImpl()) - m := 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() - - operations := []byte(`[ - {"op": "add", "path": "/spec/template/spec/containers/0/command/", "value": ["arg1", "arg2", "arg3"]} -]`) - - jpt, err := newPatchJson6902JSONTransformer( - m.GetByIndex(0).OrgId(), operations) - if err != nil { - t.Fatalf("unexpected error : %v", err) - } - err = jpt.Transform(m) - if err == nil { - t.Fatalf("expected error didn't happen") - } - if !strings.HasPrefix( - err.Error(), "failed to apply json patch") || - !strings.Contains(err.Error(), string(operations)) { - t.Fatalf("expected error didn't happen, but got %v", err) - } -} - -func TestJsonPatchJSONTransformer_EmptyPatchFile(t *testing.T) { - id := resid.NewResId(deploy, "deploy1") - operations := []byte(``) - - _, err := newPatchJson6902JSONTransformer(id, operations) - - if err == nil { - t.Fatalf("expected an error") - } - - if err != nil { - if !strings.HasPrefix(err.Error(), "json patch file is empty") { - t.Fatalf("expected %s, but got %v", "json patch file is empty", err) - } - } -} diff --git a/pkg/target/kusttarget_configplugin.go b/pkg/target/kusttarget_configplugin.go index a0e98978c..429b75547 100644 --- a/pkg/target/kusttarget_configplugin.go +++ b/pkg/target/kusttarget_configplugin.go @@ -147,15 +147,21 @@ func (kt *KustTarget) configureBuiltinPatchJson6902Transformer( tConfig *config.TransformerConfig) ( result []transformers.Transformer, err error) { var c struct { - Patches []types.PatchJson6902 + Target types.PatchTarget `json:"target,omitempty" yaml:"target,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"` } - c.Patches = kt.kustomization.PatchesJson6902 - p := builtin.NewPatchJson6902TransformerPlugin() - err = kt.configureBuiltinPlugin(p, c, "patchJson6902") - if err != nil { - return nil, err + for _, args := range kt.kustomization.PatchesJson6902 { + c.Target = *args.Target + c.Path = args.Path + c.JsonOp = "" // Not implemented for kustomization file yet. + p := builtin.NewPatchJson6902TransformerPlugin() + err = kt.configureBuiltinPlugin(p, c, "patchJson6902") + if err != nil { + return nil, err + } + result = append(result, p) } - result = append(result, p) return } diff --git a/plugin/builtin/PatchJson6902Transformer.go b/plugin/builtin/PatchJson6902Transformer.go index fc8214020..40f162900 100644 --- a/plugin/builtin/PatchJson6902Transformer.go +++ b/plugin/builtin/PatchJson6902Transformer.go @@ -2,16 +2,23 @@ package builtin import ( + "fmt" + jsonpatch "github.com/evanphx/json-patch" + "github.com/pkg/errors" + "sigs.k8s.io/kustomize/v3/pkg/gvk" "sigs.k8s.io/kustomize/v3/pkg/ifc" - "sigs.k8s.io/kustomize/v3/pkg/patch/transformer" + "sigs.k8s.io/kustomize/v3/pkg/resid" "sigs.k8s.io/kustomize/v3/pkg/resmap" "sigs.k8s.io/kustomize/v3/pkg/types" "sigs.k8s.io/yaml" ) type PatchJson6902TransformerPlugin struct { - ldr ifc.Loader - Patches []types.PatchJson6902 `json:"patches,omitempty" yaml:"patches,omitempty"` + ldr ifc.Loader + decodedPatch jsonpatch.Patch + Target types.PatchTarget `json:"target,omitempty" yaml:"target,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"` } //noinspection GoUnusedGlobalVariable @@ -22,15 +29,71 @@ func NewPatchJson6902TransformerPlugin() *PatchJson6902TransformerPlugin { func (p *PatchJson6902TransformerPlugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.ldr = ldr - p.Patches = nil - return yaml.Unmarshal(c, p) -} - -func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error { - t, err := transformer.NewPatchJson6902Factory(p.ldr). - MakePatchJson6902Transformer(p.Patches) + err = yaml.Unmarshal(c, p) if err != nil { return err } - return t.Transform(m) + if p.Target.Name == "" { + return fmt.Errorf("must specify the target name") + } + if p.Path == "" && p.JsonOp == "" { + return fmt.Errorf("empty file path and empty jsonOp") + } + if p.Path != "" { + if p.JsonOp != "" { + return fmt.Errorf("must specify a file path or jsonOp, not both") + } + rawOp, err := p.ldr.Load(p.Path) + if err != nil { + return err + } + p.JsonOp = string(rawOp) + if p.JsonOp == "" { + return fmt.Errorf("patch file '%s' empty seems to be empty", p.Path) + } + } + if p.JsonOp[0] != '[' { + // if it doesn't seem to be JSON, imagine + // it is YAML, and convert to JSON. + op, err := yaml.YAMLToJSON([]byte(p.JsonOp)) + if err != nil { + return err + } + p.JsonOp = string(op) + } + p.decodedPatch, err = jsonpatch.DecodePatch([]byte(p.JsonOp)) + if err != nil { + return errors.Wrapf(err, "decoding %s", p.JsonOp) + } + if len(p.decodedPatch) == 0 { + return fmt.Errorf( + "patch appears to be empty; file=%s, JsonOp=%s", p.Path, p.JsonOp) + } + return err +} + +func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error { + id := resid.NewResIdWithNamespace( + gvk.Gvk{ + Group: p.Target.Group, + Version: p.Target.Version, + Kind: p.Target.Kind, + }, + p.Target.Name, + p.Target.Namespace, + ) + obj, err := m.GetById(id) + if err != nil { + return err + } + rawObj, err := obj.MarshalJSON() + if err != nil { + return err + } + modifiedObj, err := p.decodedPatch.Apply(rawObj) + if err != nil { + return errors.Wrapf( + err, "failed to apply json patch '%s'", p.JsonOp) + } + return obj.UnmarshalJSON(modifiedObj) } diff --git a/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer.go b/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer.go index b22562980..ba74db05a 100644 --- a/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer.go +++ b/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer.go @@ -5,8 +5,12 @@ package main import ( + "fmt" + jsonpatch "github.com/evanphx/json-patch" + "github.com/pkg/errors" + "sigs.k8s.io/kustomize/v3/pkg/gvk" "sigs.k8s.io/kustomize/v3/pkg/ifc" - "sigs.k8s.io/kustomize/v3/pkg/patch/transformer" + "sigs.k8s.io/kustomize/v3/pkg/resid" "sigs.k8s.io/kustomize/v3/pkg/resmap" "sigs.k8s.io/kustomize/v3/pkg/types" "sigs.k8s.io/yaml" @@ -14,7 +18,10 @@ import ( type plugin struct { ldr ifc.Loader - Patches []types.PatchJson6902 `json:"patches,omitempty" yaml:"patches,omitempty"` + decodedPatch jsonpatch.Patch + Target types.PatchTarget `json:"target,omitempty" yaml:"target,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"` } //noinspection GoUnusedGlobalVariable @@ -23,15 +30,71 @@ var KustomizePlugin plugin func (p *plugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.ldr = ldr - p.Patches = nil - return yaml.Unmarshal(c, p) -} - -func (p *plugin) Transform(m resmap.ResMap) error { - t, err := transformer.NewPatchJson6902Factory(p.ldr). - MakePatchJson6902Transformer(p.Patches) + err = yaml.Unmarshal(c, p) if err != nil { return err } - return t.Transform(m) + if p.Target.Name == "" { + return fmt.Errorf("must specify the target name") + } + if p.Path == "" && p.JsonOp == "" { + return fmt.Errorf("empty file path and empty jsonOp") + } + if p.Path != "" { + if p.JsonOp != "" { + return fmt.Errorf("must specify a file path or jsonOp, not both") + } + rawOp, err := p.ldr.Load(p.Path) + if err != nil { + return err + } + p.JsonOp = string(rawOp) + if p.JsonOp == "" { + return fmt.Errorf("patch file '%s' empty seems to be empty", p.Path) + } + } + if p.JsonOp[0] != '[' { + // if it doesn't seem to be JSON, imagine + // it is YAML, and convert to JSON. + op, err := yaml.YAMLToJSON([]byte(p.JsonOp)) + if err != nil { + return err + } + p.JsonOp = string(op) + } + p.decodedPatch, err = jsonpatch.DecodePatch([]byte(p.JsonOp)) + if err != nil { + return errors.Wrapf(err, "decoding %s", p.JsonOp) + } + if len(p.decodedPatch) == 0 { + return fmt.Errorf( + "patch appears to be empty; file=%s, JsonOp=%s", p.Path, p.JsonOp) + } + return err +} + +func (p *plugin) Transform(m resmap.ResMap) error { + id := resid.NewResIdWithNamespace( + gvk.Gvk{ + Group: p.Target.Group, + Version: p.Target.Version, + Kind: p.Target.Kind, + }, + p.Target.Name, + p.Target.Namespace, + ) + obj, err := m.GetById(id) + if err != nil { + return err + } + rawObj, err := obj.MarshalJSON() + if err != nil { + return err + } + modifiedObj, err := p.decodedPatch.Apply(rawObj) + if err != nil { + return errors.Wrapf( + err, "failed to apply json patch '%s'", p.JsonOp) + } + return obj.UnmarshalJSON(modifiedObj) } diff --git a/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer_test.go b/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer_test.go index 8ebdba540..6e820c13b 100644 --- a/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer_test.go +++ b/plugin/builtin/patchjson6902transformer/PatchJson6902Transformer_test.go @@ -4,13 +4,117 @@ package main_test import ( + "strings" "testing" "sigs.k8s.io/kustomize/v3/pkg/kusttest" "sigs.k8s.io/kustomize/v3/pkg/plugins" ) -func TestPatchJson6902Transformer(t *testing.T) { +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 +` + +func TestPatchJson6902TransformerMissingFile(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchJson6902Transformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: notImportantHere +target: + group: apps + version: v1 + kind: Deployment + name: myDeploy +path: jsonpatch.json +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), "cannot read file \"/app/jsonpatch.json\"") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestBadPatchJson6902Transformer(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchJson6902Transformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: notImportantHere +target: + group: apps + version: v1 + kind: Deployment + name: myDeploy +jsonOp: 'thisIsNotAPatch' +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), "cannot unmarshal string into Go value of type jsonpatch") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestBothEmptyJson6902Transformer(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchJson6902Transformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: notImportantHere +target: + group: apps + version: v1 + kind: Deployment + name: myDeploy +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), "empty file path and empty jsonOp") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestBothSpecifiedJson6902Transformer(t *testing.T) { tc := plugins.NewEnvForTest(t).Set() defer tc.Reset() @@ -20,7 +124,45 @@ func TestPatchJson6902Transformer(t *testing.T) { th := kusttest_test.NewKustTestPluginHarness(t, "/app") th.WriteF("/app/jsonpatch.json", `[ - {"op": "add", "path": "/spec/replica", "value": "3"} +{"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"}, +{"op": "add", "path": "/spec/replica", "value": "999"}, +{"op": "add", "path": "/spec/template/spec/containers/0/command", "value": ["arg1", "arg2", "arg3"]} +]`) + + _, err := th.RunTransformer(` +apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: notImportantHere +target: + group: apps + version: v1 + kind: Deployment + name: myDeploy +path: jsonpatch.json +jsonOp: '[{"op": "add", "path": "/spec/template/spec/dnsPolicy", "value": "ClusterFirst"}]' +`, target) + if err == nil { + t.Fatalf("expected error") + } + if !strings.Contains(err.Error(), "must specify a file path or jsonOp, not both") { + t.Fatalf("unexpected err: %v", err) + } +} + +func TestPatchJson6902TransformerFromJsonFile(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchJson6902Transformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/jsonpatch.json", `[ +{"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"}, +{"op": "add", "path": "/spec/replica", "value": "999"}, +{"op": "add", "path": "/spec/template/spec/containers/0/command", "value": ["arg1", "arg2", "arg3"]} ]`) rm := th.LoadAndRunTransformer(` @@ -28,21 +170,13 @@ apiVersion: builtin kind: PatchJson6902Transformer metadata: name: notImportantHere -patches: -- target: - group: apps - version: v1 - kind: Deployment - name: myDeploy - path: jsonpatch.json -`, ` -apiVersion: apps/v1 -metadata: +target: + group: apps + version: v1 + kind: Deployment name: myDeploy -kind: Deployment -spec: - replica: 1 -`) +path: jsonpatch.json +`, target) th.AssertActualEqualsExpected(rm, ` apiVersion: apps/v1 @@ -50,6 +184,109 @@ kind: Deployment metadata: name: myDeploy spec: - replica: "3" + replica: "999" + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - command: + - arg1 + - arg2 + - arg3 + image: nginx + name: my-nginx +`) +} + +func TestPatchJson6902TransformerFromYamlFile(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchJson6902Transformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + th.WriteF("/app/jsonpatch.json", ` +- op: add + path: /spec/template/spec/containers/0/command + value: ["arg1", "arg2", "arg3"] +`) + + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: notImportantHere +target: + group: apps + version: v1 + kind: Deployment + name: myDeploy +path: jsonpatch.json +`, target) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + replica: 2 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - command: + - arg1 + - arg2 + - arg3 + image: nginx + name: nginx +`) +} + +func TestPatchJson6902TransformerWithInline(t *testing.T) { + tc := plugins.NewEnvForTest(t).Set() + defer tc.Reset() + + tc.BuildGoPlugin( + "builtin", "", "PatchJson6902Transformer") + + th := kusttest_test.NewKustTestPluginHarness(t, "/app") + + rm := th.LoadAndRunTransformer(` +apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: notImportantHere +target: + group: apps + version: v1 + kind: Deployment + name: myDeploy +jsonOp: '[{"op": "add", "path": "/spec/template/spec/dnsPolicy", "value": "ClusterFirst"}]' +`, target) + + th.AssertActualEqualsExpected(rm, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myDeploy +spec: + replica: 2 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - image: nginx + name: nginx + dnsPolicy: ClusterFirst `) }