Merge pull request #393 from monopole/resourceTests

Add tests for patch/resource reading.
This commit is contained in:
k8s-ci-robot
2018-10-01 14:47:51 -07:00
committed by GitHub
4 changed files with 269 additions and 45 deletions

View File

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

View File

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

View File

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

View File

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