diff --git a/pkg/resmap/resmap.go b/pkg/resmap/resmap.go index c0af129e4..3e03e578a 100644 --- a/pkg/resmap/resmap.go +++ b/pkg/resmap/resmap.go @@ -20,20 +20,16 @@ package resmap import ( "bytes" "fmt" - "io" "reflect" "sort" - "strings" "github.com/ghodss/yaml" "github.com/golang/glog" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/kustomize/pkg/gvk" internal "sigs.k8s.io/kustomize/pkg/internal/error" "sigs.k8s.io/kustomize/pkg/loader" - "sigs.k8s.io/kustomize/pkg/patch" "sigs.k8s.io/kustomize/pkg/resource" ) @@ -154,24 +150,6 @@ func (m ResMap) FilterBy(inputId resource.ResId) ResMap { return result } -// NewResourceSliceFromPatches returns a slice of resources given a patch path slice from a kustomization file. -func NewResourceSliceFromPatches( - loader loader.Loader, paths []patch.StrategicMerge) ([]*resource.Resource, error) { - var result []*resource.Resource - for _, path := range paths { - content, err := loader.Load(string(path)) - if err != nil { - return nil, err - } - res, err := newResourceSliceFromBytes(content) - if err != nil { - return nil, internal.Handler(err, string(path)) - } - result = append(result, res...) - } - return result, nil -} - // NewResMapFromFiles returns a ResMap given a resource path slice. func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) { var result []ResMap @@ -191,7 +169,7 @@ func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) { // newResMapFromBytes decodes a list of objects in byte array format. func newResMapFromBytes(b []byte) (ResMap, error) { - resources, err := newResourceSliceFromBytes(b) + resources, err := resource.NewResourceSliceFromBytes(b) if err != nil { return nil, err } @@ -219,23 +197,6 @@ func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) return result, nil } -func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) { - decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024) - var result []*resource.Resource - var err error - for err == nil || isEmptyYamlError(err) { - var out unstructured.Unstructured - err = decoder.Decode(&out) - if err == nil { - result = append(result, resource.NewResourceFromUnstruct(out)) - } - } - if err != io.EOF { - return nil, err - } - return result, nil -} - // MergeWithoutOverride combines multiple ResMap instances, failing on key collision // and skipping nil maps. In case if all of the maps are nil, an empty ResMap is returned. func MergeWithoutOverride(maps ...ResMap) (ResMap, error) { @@ -303,7 +264,3 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) { } return result, nil } - -func isEmptyYamlError(err error) bool { - return strings.Contains(err.Error(), "is missing in 'null'") -} diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index c7273d67a..472078cd1 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -18,14 +18,20 @@ limitations under the License. package resource import ( + "bytes" "encoding/json" "fmt" + "io" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/kustomize/pkg/gvk" + internal "sigs.k8s.io/kustomize/pkg/internal/error" + "sigs.k8s.io/kustomize/pkg/loader" + "sigs.k8s.io/kustomize/pkg/patch" ) // Resource is an "Unstructured" (json/map form) Kubernetes API resource object @@ -59,6 +65,56 @@ func NewResourceFromUnstruct(u unstructured.Unstructured) *Resource { return &Resource{Unstructured: u, b: BehaviorUnspecified} } +// NewResourceSliceFromPatches returns a slice of resources given a patch path +// slice from a kustomization file. +func NewResourceSliceFromPatches( + ldr loader.Loader, paths []patch.StrategicMerge) ([]*Resource, error) { + var result []*Resource + for _, path := range paths { + content, err := ldr.Load(string(path)) + if err != nil { + return nil, err + } + res, err := NewResourceSliceFromBytes(content) + if err != nil { + return nil, internal.Handler(err, string(path)) + } + result = append(result, res...) + } + return result, nil +} + +// NewResourceSliceFromBytes unmarshalls bytes into a Resource slice. +func NewResourceSliceFromBytes(in []byte) ([]*Resource, error) { + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024) + var result []*Resource + var err error + for err == nil || isEmptyYamlError(err) { + var out unstructured.Unstructured + err = decoder.Decode(&out) + if err == nil { + result = append(result, NewResourceFromUnstruct(out)) + } + } + if err != io.EOF { + return nil, err + } + return result, nil +} + +func isEmptyYamlError(err error) bool { + return strings.Contains(err.Error(), "is missing in 'null'") +} + +// String returns resource as JSON. +func (r *Resource) String() string { + bs, err := r.MarshalJSON() + if err != nil { + return "<" + err.Error() + ">" + } + return r.b.String() + ":" + strings.TrimSpace(string(bs)) +} + // Behavior returns the behavior for the resource. func (r *Resource) Behavior() GenerationBehavior { return r.b diff --git a/pkg/resource/resource_test.go b/pkg/resource/resource_test.go index 04d6be554..6aac0f08b 100644 --- a/pkg/resource/resource_test.go +++ b/pkg/resource/resource_test.go @@ -17,9 +17,220 @@ limitations under the License. package resource import ( + "reflect" "testing" + + "sigs.k8s.io/kustomize/pkg/internal/loadertest" + "sigs.k8s.io/kustomize/pkg/patch" ) +var testConfigMap = NewResourceFromMap( + map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "winnie", + }, + }) + +const testConfigMapString = `unspecified:{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}` + +var testDeployment = NewResourceFromMap( + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "pooh", + }, + }) + +const testDeploymentString = `unspecified:{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"pooh"}}` + +func TestResourceString(t *testing.T) { + tests := []struct { + in *Resource + s string + }{ + { + in: testConfigMap, + s: testConfigMapString, + }, + { + in: testDeployment, + s: testDeploymentString, + }, + } + for _, test := range tests { + if test.in.String() != test.s { + t.Fatalf("Expected %s == %s", test.in.String(), test.s) + } + } +} + +func TestNewResourceSliceFromPatches(t *testing.T) { + patchGood1 := patch.StrategicMerge("/foo/patch1.yaml") + patch1 := ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pooh +` + patchGood2 := patch.StrategicMerge("/foo/patch2.yaml") + patch2 := ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: winnie +--- +# some comment +--- +--- +` + patchBad := patch.StrategicMerge("/foo/patch3.yaml") + patch3 := ` +WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOT: woot +` + l := loadertest.NewFakeLoader("/foo") + l.AddFile(string(patchGood1), []byte(patch1)) + l.AddFile(string(patchGood2), []byte(patch2)) + l.AddFile(string(patchBad), []byte(patch3)) + + tests := []struct { + name string + input []patch.StrategicMerge + expectedOut []*Resource + expectedErr bool + }{ + { + name: "happy", + input: []patch.StrategicMerge{patchGood1, patchGood2}, + expectedOut: []*Resource{testDeployment, testConfigMap}, + expectedErr: false, + }, + { + name: "badFileName", + input: []patch.StrategicMerge{patchGood1, "doesNotExist"}, + expectedOut: []*Resource{}, + expectedErr: true, + }, + { + name: "badData", + input: []patch.StrategicMerge{patchGood1, patchBad}, + expectedOut: []*Resource{}, + expectedErr: true, + }, + } + for _, test := range tests { + rs, err := NewResourceSliceFromPatches(l, test.input) + if test.expectedErr && err == nil { + t.Fatalf("%v: should return error", test.name) + } + if !test.expectedErr && err != nil { + t.Fatalf("%v: unexpected error: %s", test.name, err) + } + if len(rs) != len(test.expectedOut) { + t.Fatalf("%s: length mismatch %d != %d", + test.name, len(rs), len(test.expectedOut)) + } + for i := range rs { + if !reflect.DeepEqual(test.expectedOut[i], rs[i]) { + t.Fatalf("%s: Got: %v\nexpected:%v", + test.name, test.expectedOut[i], rs[i]) + } + } + } +} + +func TestNewResourceSliceFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + expectedOut []*Resource + expectedErr bool + }{ + { + name: "garbage", + input: []byte("garbageIn: garbageOut"), + expectedOut: []*Resource{}, + expectedErr: true, + }, + { + name: "noBytes", + input: []byte{}, + expectedOut: []*Resource{}, + expectedErr: false, + }, + { + name: "goodJson", + input: []byte(` +{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}} +`), + expectedOut: []*Resource{testConfigMap}, + expectedErr: false, + }, + { + name: "goodYaml1", + input: []byte(` +apiVersion: v1 +kind: ConfigMap +metadata: + name: winnie +`), + expectedOut: []*Resource{testConfigMap}, + expectedErr: false, + }, + { + name: "goodYaml2", + input: []byte(` +apiVersion: v1 +kind: ConfigMap +metadata: + name: winnie +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: winnie +`), + expectedOut: []*Resource{testConfigMap, testConfigMap}, + expectedErr: false, + }, + { + name: "garbageInOneOfTwoObjects", + input: []byte(` +apiVersion: v1 +kind: ConfigMap +metadata: + name: winnie +--- +WOOOOOOOOOOOOOOOOOOOOOOOOT: woot +`), + expectedOut: []*Resource{}, + expectedErr: true, + }, + } + + for _, test := range tests { + rs, err := NewResourceSliceFromBytes(test.input) + if test.expectedErr && err == nil { + t.Fatalf("%v: should return error", test.name) + } + if !test.expectedErr && err != nil { + t.Fatalf("%v: unexpected error: %s", test.name, err) + } + if len(rs) != len(test.expectedOut) { + t.Fatalf("%s: length mismatch %d != %d", + test.name, len(rs), len(test.expectedOut)) + } + for i := range rs { + if !reflect.DeepEqual(test.expectedOut[i], rs[i]) { + t.Fatalf("%s: Got: %v\nexpected:%v", + test.name, test.expectedOut[i], rs[i]) + } + } + } +} + func TestGetFieldValue(t *testing.T) { res := NewResourceFromMap(map[string]interface{}{ "Kind": "Service", diff --git a/pkg/target/kusttarget.go b/pkg/target/kusttarget.go index a6c60b53b..e2b331959 100644 --- a/pkg/target/kusttarget.go +++ b/pkg/target/kusttarget.go @@ -157,7 +157,7 @@ func (kt *KustTarget) loadCustomizedResMap() (resmap.ResMap, error) { kt.kustomization.PatchesStrategicMerge = patch.Append( kt.kustomization.PatchesStrategicMerge, kt.kustomization.Patches...) - patches, err := resmap.NewResourceSliceFromPatches( + patches, err := resource.NewResourceSliceFromPatches( kt.ldr, kt.kustomization.PatchesStrategicMerge) if err != nil { errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))