Push json transform code down to plugin.

This commit is contained in:
jregan
2019-06-30 17:30:52 -07:00
parent c2b0b6f781
commit 5de0673db1
10 changed files with 419 additions and 722 deletions

View File

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

View File

@@ -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] == '['
}

View File

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

View File

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

View File

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

View File

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