Introduce ResId and ResMap.

This commit is contained in:
jregan
2018-05-31 23:22:46 -07:00
committed by Jeffrey Regan
parent 9a5c0f5a25
commit ef71cb478f
39 changed files with 1063 additions and 1447 deletions

View File

@@ -25,6 +25,7 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/constants" "github.com/kubernetes-sigs/kustomize/pkg/constants"
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error" interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
"github.com/kubernetes-sigs/kustomize/pkg/loader" "github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/transformers" "github.com/kubernetes-sigs/kustomize/pkg/transformers"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
@@ -32,14 +33,14 @@ import (
type Application interface { type Application interface {
// Resources computes and returns the resources for the app. // Resources computes and returns the resources for the app.
Resources() (resource.ResourceCollection, error) Resources() (resmap.ResMap, error)
// SemiResources computes and returns the resources without name hash and name reference for the app // SemiResources computes and returns the resources without name hash and name reference for the app
SemiResources() (resource.ResourceCollection, error) SemiResources() (resmap.ResMap, error)
// RawResources computes and returns the raw resources from the kustomization file. // RawResources computes and returns the raw resources from the kustomization file.
// It contains resources from // It contains resources from
// 1) untransformed resources from current kustomization file // 1) untransformed resources from current kustomization file
// 2) transformed resources from sub packages // 2) transformed resources from sub packages
RawResources() (resource.ResourceCollection, error) RawResources() (resmap.ResMap, error)
} }
var _ Application = &applicationImpl{} var _ Application = &applicationImpl{}
@@ -68,7 +69,7 @@ func New(loader loader.Loader) (Application, error) {
// Resources computes and returns the resources from the kustomization file. // Resources computes and returns the resources from the kustomization file.
// The namehashing for configmap/secrets and resolving name reference is only done // The namehashing for configmap/secrets and resolving name reference is only done
// in the most top overlay once at the end of getting resources. // in the most top overlay once at the end of getting resources.
func (a *applicationImpl) Resources() (resource.ResourceCollection, error) { func (a *applicationImpl) Resources() (resmap.ResMap, error) {
res, err := a.SemiResources() res, err := a.SemiResources()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -85,32 +86,32 @@ func (a *applicationImpl) Resources() (resource.ResourceCollection, error) {
} }
// SemiResources computes and returns the resources without name hash and name reference for the app // SemiResources computes and returns the resources without name hash and name reference for the app
func (a *applicationImpl) SemiResources() (resource.ResourceCollection, error) { func (a *applicationImpl) SemiResources() (resmap.ResMap, error) {
errs := &interror.KustomizationErrors{} errs := &interror.KustomizationErrors{}
raw, err := a.rawResources() raw, err := a.rawResources()
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
cms, err := resource.NewFromConfigMaps(a.loader, a.kustomization.ConfigMapGenerator) cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
secrets, err := resource.NewFromSecretGenerators(a.loader.Root(), a.kustomization.SecretGenerator) secrets, err := resmap.NewResMapFromSecretArgs(a.loader.Root(), a.kustomization.SecretGenerator)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
res, err := resource.Merge(cms, secrets) res, err := resmap.Merge(cms, secrets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
allRes, err := resource.MergeWithOverride(raw, res) allRes, err := resmap.MergeWithOverride(raw, res)
if err != nil { if err != nil {
return nil, err return nil, err
} }
patches, err := resource.NewFromPatches(a.loader, a.kustomization.Patches) patches, err := resmap.NewResourceSliceFromPatches(a.loader, a.kustomization.Patches)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
@@ -134,7 +135,7 @@ func (a *applicationImpl) SemiResources() (resource.ResourceCollection, error) {
// RawResources computes and returns the raw resources from the kustomization file. // RawResources computes and returns the raw resources from the kustomization file.
// The namehashing for configmap/secrets and resolving name reference is only done // The namehashing for configmap/secrets and resolving name reference is only done
// in the most top overlay once at the end of getting resources. // in the most top overlay once at the end of getting resources.
func (a *applicationImpl) RawResources() (resource.ResourceCollection, error) { func (a *applicationImpl) RawResources() (resmap.ResMap, error) {
res, err := a.rawResources() res, err := a.rawResources()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -150,9 +151,9 @@ func (a *applicationImpl) RawResources() (resource.ResourceCollection, error) {
return res, nil return res, nil
} }
func (a *applicationImpl) rawResources() (resource.ResourceCollection, error) { func (a *applicationImpl) rawResources() (resmap.ResMap, error) {
subAppResources, errs := a.subAppResources() subAppResources, errs := a.subAppResources()
resources, err := resource.NewFromResources(a.loader, a.kustomization.Resources) resources, err := resmap.NewResMapFromFiles(a.loader, a.kustomization.Resources)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
@@ -161,11 +162,11 @@ func (a *applicationImpl) rawResources() (resource.ResourceCollection, error) {
return nil, errs return nil, errs
} }
return resource.Merge(resources, subAppResources) return resmap.Merge(resources, subAppResources)
} }
func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *interror.KustomizationErrors) { func (a *applicationImpl) subAppResources() (resmap.ResMap, *interror.KustomizationErrors) {
sliceOfSubAppResources := []resource.ResourceCollection{} sliceOfSubAppResources := []resmap.ResMap{}
errs := &interror.KustomizationErrors{} errs := &interror.KustomizationErrors{}
for _, pkgPath := range a.kustomization.Bases { for _, pkgPath := range a.kustomization.Bases {
subloader, err := a.loader.New(pkgPath) subloader, err := a.loader.New(pkgPath)
@@ -186,7 +187,7 @@ func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *inter
} }
sliceOfSubAppResources = append(sliceOfSubAppResources, subAppResources) sliceOfSubAppResources = append(sliceOfSubAppResources, subAppResources)
} }
allResources, err := resource.Merge(sliceOfSubAppResources...) allResources, err := resmap.Merge(sliceOfSubAppResources...)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }

View File

@@ -18,15 +18,14 @@ package app
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"reflect" "reflect"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants" "github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/loader" "github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/loader/loadertest" "github.com/kubernetes-sigs/kustomize/pkg/loader/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@@ -71,13 +70,14 @@ metadata:
return loader return loader
} }
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
func TestResources(t *testing.T) { func TestResources(t *testing.T) {
expected := resource.ResourceCollection{ expected := resmap.ResMap{
types.GroupVersionKindName{ resource.NewResId(deploy, "dply1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "dply1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -108,13 +108,9 @@ func TestResources(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(cmap, "literalConfigMap"): resource.NewBehaviorlessResource(
types.GroupVersionKindName{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "literalConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -133,13 +129,9 @@ func TestResources(t *testing.T) {
"DB_PASSWORD": "somepw", "DB_PASSWORD": "somepw",
}, },
}, },
}, }),
}, resource.NewResId(secret, "secret"): resource.NewBehaviorlessResource(
types.GroupVersionKindName{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -159,8 +151,7 @@ func TestResources(t *testing.T) {
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
}, },
}, },
}, }),
},
} }
l := setupTest(t) l := setupTest(t)
app, err := New(l) app, err := New(l)
@@ -173,18 +164,15 @@ func TestResources(t *testing.T) {
} }
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
err = compareMap(actual, expected) err = expected.ErrorIfNotEqual(actual)
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
} }
func TestRawResources(t *testing.T) { func TestRawResources(t *testing.T) {
expected := resource.ResourceCollection{ expected := resmap.ResMap{
types.GroupVersionKindName{ resource.NewResId(deploy, "dply1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "dply1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -192,8 +180,7 @@ func TestRawResources(t *testing.T) {
"name": "dply1", "name": "dply1",
}, },
}, },
}, }),
},
} }
l := setupTest(t) l := setupTest(t)
app, err := New(l) app, err := New(l)
@@ -205,31 +192,7 @@ func TestRawResources(t *testing.T) {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
if err := compareMap(actual, expected); err != nil { if err := expected.ErrorIfNotEqual(actual); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
} }
func compareMap(m1, m2 resource.ResourceCollection) error {
if len(m1) != len(m2) {
keySet1 := []types.GroupVersionKindName{}
keySet2 := []types.GroupVersionKindName{}
for GVKn := range m1 {
keySet1 = append(keySet1, GVKn)
}
for GVKn := range m1 {
keySet2 = append(keySet2, GVKn)
}
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
}
for GVKn, obj1 := range m1 {
obj2, found := m2[GVKn]
if !found {
return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2)
}
if !reflect.DeepEqual(obj1, obj2) {
return fmt.Errorf("%#v doesn't match %#v", obj1, obj2)
}
}
return nil
}

View File

@@ -27,7 +27,6 @@ import (
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util" cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
"github.com/kubernetes-sigs/kustomize/pkg/hash" "github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -122,51 +121,6 @@ func makeSecret(secret types.SecretArgs, path string) (*corev1.Secret, error) {
return corev1secret, nil return corev1secret, nil
} }
func populateMap(m resource.ResourceCollection, obj *unstructured.Unstructured, newName string) error {
oldName := obj.GetName()
gvk := obj.GroupVersionKind()
gvkn := types.GroupVersionKindName{GVK: gvk, Name: oldName}
if _, found := m[gvkn]; found {
return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
}
obj.SetName(newName)
m[gvkn] = &resource.Resource{Data: obj}
return nil
}
// MakeConfigMapsResourceCollection returns a map of <GVK, oldName> -> unstructured object.
func MakeConfigMapsResourceCollection(maps []types.ConfigMapArgs) (resource.ResourceCollection, error) {
m := resource.ResourceCollection{}
for _, cm := range maps {
unstructuredConfigMap, nameWithHash, err := MakeConfigmapAndGenerateName(cm)
if err != nil {
return nil, err
}
err = populateMap(m, unstructuredConfigMap, nameWithHash)
if err != nil {
return nil, err
}
}
return m, nil
}
// MakeSecretsResourceCollection returns a map of <GVK, oldName> -> unstructured object.
func MakeSecretsResourceCollection(secrets []types.SecretArgs, path string) (resource.ResourceCollection, error) {
m := resource.ResourceCollection{}
for _, secret := range secrets {
unstructuredSecret, nameWithHash, err := MakeSecretAndGenerateName(secret, path)
if err != nil {
return nil, err
}
err = populateMap(m, unstructuredSecret, nameWithHash)
if err != nil {
return nil, err
}
}
return m, nil
}
func createSecretKey(wd string, command string) ([]byte, error) { func createSecretKey(wd string, command string) ([]byte, error) {
fi, err := os.Stat(wd) fi, err := os.Stat(wd)
if err != nil || !fi.IsDir() { if err != nil || !fi.IsDir() {

View File

@@ -5,12 +5,11 @@ import (
"io" "io"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resmap"
) )
// RunDiff runs system diff program to compare two ResourceCollections. // RunDiff runs system diff program to compare two Maps.
func RunDiff(raw, transformed resource.ResourceCollection, func RunDiff(raw, transformed resmap.ResMap, out, errOut io.Writer) error {
out, errOut io.Writer) error {
transformedDir, err := writeYamlToNewDir(transformed, "transformed") transformedDir, err := writeYamlToNewDir(transformed, "transformed")
if err != nil { if err != nil {
return err return err
@@ -26,10 +25,10 @@ func RunDiff(raw, transformed resource.ResourceCollection,
return newProgram(out, errOut).run(noopDir.name(), transformedDir.name()) return newProgram(out, errOut).run(noopDir.name(), transformedDir.name())
} }
// writeYamlToNewDir writes each obj in ResourceCollection to a file in a new directory. // writeYamlToNewDir writes each obj in ResMap to a file in a new directory.
// The directory's name will begin with the given prefix. // The directory's name will begin with the given prefix.
// Each file is named with GroupVersionKindName. // Each file is named with GroupVersionKindName.
func writeYamlToNewDir(in resource.ResourceCollection, prefix string) (*directory, error) { func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
dir, err := newDirectory(prefix) dir, err := newDirectory(prefix)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -40,7 +39,7 @@ func writeYamlToNewDir(in resource.ResourceCollection, prefix string) (*director
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = print(obj.Data, f) err = print(obj.Unstruct(), f)
f.Close() f.Close()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resource package resmap
import ( import (
"fmt" "fmt"
@@ -22,22 +22,23 @@ import (
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util" cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
"github.com/kubernetes-sigs/kustomize/pkg/loader" "github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
) )
func newFromConfigMap(l loader.Loader, cm types.ConfigMapArgs) (*Resource, error) { func newResourceFromConfigMap(l loader.Loader, cm types.ConfigMapArgs) (*resource.Resource, error) {
corev1CM, err := makeConfigMap(l, cm) corev1CM, err := makeConfigMap(l, cm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := objectToUnstructured(corev1CM) data, err := newUnstructuredFromObject(corev1CM)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Resource{Data: data, Behavior: cm.Behavior}, nil return resource.NewResource(data, cm.Behavior), nil
} }
func makeConfigMap(l loader.Loader, cm types.ConfigMapArgs) (*corev1.ConfigMap, error) { func makeConfigMap(l loader.Loader, cm types.ConfigMapArgs) (*corev1.ConfigMap, error) {
@@ -130,15 +131,15 @@ func addKV(m map[string]string, kv kvPair) error {
return nil return nil
} }
// NewFromConfigMaps returns a Resource slice given a configmap metadata slice from kustomization file. // NewResMapFromConfigMapArgs returns a Resource slice given a configmap metadata slice from kustomization file.
func NewFromConfigMaps(loader loader.Loader, cmList []types.ConfigMapArgs) (ResourceCollection, error) { func NewResMapFromConfigMapArgs(loader loader.Loader, cmList []types.ConfigMapArgs) (ResMap, error) {
allResources := []*Resource{} allResources := []*resource.Resource{}
for _, cm := range cmList { for _, cm := range cmList {
res, err := newFromConfigMap(loader, cm) res, err := newResourceFromConfigMap(loader, cm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
allResources = append(allResources, res) allResources = append(allResources, res)
} }
return resourceCollectionFromResources(allResources) return newResMapFromResourceSlice(allResources)
} }

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resource_test package resmap
import ( import (
"reflect" "reflect"
@@ -27,13 +27,15 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
func TestNewFromConfigMaps(t *testing.T) { func TestNewFromConfigMaps(t *testing.T) {
type testCase struct { type testCase struct {
description string description string
input []types.ConfigMapArgs input []types.ConfigMapArgs
filepath string filepath string
content string content string
expected resource.ResourceCollection expected ResMap
} }
l := loadertest.NewFakeLoader("/home/seans/project/") l := loadertest.NewFakeLoader("/home/seans/project/")
@@ -50,12 +52,9 @@ func TestNewFromConfigMaps(t *testing.T) {
}, },
filepath: "/home/seans/project/app.env", filepath: "/home/seans/project/app.env",
content: "DB_USERNAME=admin\nDB_PASSWORD=somepw", content: "DB_USERNAME=admin\nDB_PASSWORD=somepw",
expected: resource.ResourceCollection{ expected: ResMap{
{ resource.NewResId(cmap, "envConfigMap"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "envConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -68,8 +67,7 @@ func TestNewFromConfigMaps(t *testing.T) {
"DB_PASSWORD": "somepw", "DB_PASSWORD": "somepw",
}, },
}, },
}, }),
},
}, },
}, },
{ {
@@ -83,12 +81,9 @@ func TestNewFromConfigMaps(t *testing.T) {
}, },
filepath: "/home/seans/project/app-init.ini", filepath: "/home/seans/project/app-init.ini",
content: "FOO=bar\nBAR=baz\n", content: "FOO=bar\nBAR=baz\n",
expected: resource.ResourceCollection{ expected: ResMap{
{ resource.NewResId(cmap, "fileConfigMap"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "fileConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -102,8 +97,7 @@ BAR=baz
`, `,
}, },
}, },
}, }),
},
}, },
}, },
{ {
@@ -116,12 +110,9 @@ BAR=baz
}, },
}, },
}, },
expected: resource.ResourceCollection{ expected: ResMap{
{ resource.NewResId(cmap, "literalConfigMap"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "literalConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -134,8 +125,7 @@ BAR=baz
"b": "y", "b": "y",
}, },
}, },
}, }),
},
}, },
}, },
// TODO: add testcase for data coming from multiple sources like // TODO: add testcase for data coming from multiple sources like
@@ -147,7 +137,7 @@ BAR=baz
if ferr := l.AddFile(tc.filepath, []byte(tc.content)); ferr != nil { if ferr := l.AddFile(tc.filepath, []byte(tc.content)); ferr != nil {
t.Fatalf("Error adding fake file: %v\n", ferr) t.Fatalf("Error adding fake file: %v\n", ferr)
} }
r, err := resource.NewFromConfigMaps(l, tc.input) r, err := NewResMapFromConfigMapArgs(l, tc.input)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View File

@@ -14,22 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package types package resmap
import ( import (
"sort" "sort"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
) )
// ByGVKN implements the sort interface. // IdSlice implements the sort interface.
type ByGVKN []GroupVersionKindName type IdSlice []resource.ResId
var _ sort.Interface = ByGVKN{} var _ sort.Interface = IdSlice{}
func (a ByGVKN) Len() int { return len(a) } func (a IdSlice) Len() int { return len(a) }
func (a ByGVKN) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a IdSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByGVKN) Less(i, j int) bool { func (a IdSlice) Less(i, j int) bool {
if a[i].GVK.String() != a[j].GVK.String() { if a[i].Gvk().String() != a[j].Gvk().String() {
return a[i].GVK.String() < a[j].GVK.String() return a[i].Gvk().String() < a[j].Gvk().String()
} }
return a[i].Name < a[j].Name return a[i].Name() < a[j].Name()
} }

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resource package resmap
import ( import (
"bufio" "bufio"

View File

@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resource package resmap
import ( import (
"reflect" "reflect"

247
pkg/resmap/resmap.go Normal file
View File

@@ -0,0 +1,247 @@
package resmap
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"sort"
"github.com/ghodss/yaml"
"github.com/golang/glog"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
)
// ResMap is a map from ResId to Resource
type ResMap map[resource.ResId]*resource.Resource
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
func (m ResMap) EncodeAsYaml() ([]byte, error) {
ids := []resource.ResId{}
for gvkn := range m {
ids = append(ids, gvkn)
}
sort.Sort(IdSlice(ids))
firstObj := true
var b []byte
buf := bytes.NewBuffer(b)
for _, id := range ids {
obj := m[id].Unstruct()
out, err := yaml.Marshal(obj)
if err != nil {
return nil, err
}
if firstObj {
firstObj = false
} else {
_, err = buf.WriteString("---\n")
if err != nil {
return nil, err
}
}
_, err = buf.Write(out)
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
func (m1 ResMap) ErrorIfNotEqual(m2 ResMap) error {
if len(m1) != len(m2) {
keySet1 := []resource.ResId{}
keySet2 := []resource.ResId{}
for id := range m1 {
keySet1 = append(keySet1, id)
}
for id := range m2 {
keySet2 = append(keySet2, id)
}
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
}
for id, obj1 := range m1 {
obj2, found := m2[id]
if !found {
return fmt.Errorf("%#v doesn't exist in %#v", id, m2)
}
if !reflect.DeepEqual(obj1.Unstruct(), obj2.Unstruct()) {
return fmt.Errorf("%#v doesn't match %#v", obj1.Unstruct(), obj2.Unstruct())
}
}
return nil
}
func (m ResMap) insert(newName string, obj *unstructured.Unstructured) error {
oldName := obj.GetName()
gvk := obj.GroupVersionKind()
id := resource.NewResId(gvk, oldName)
if _, found := m[id]; found {
return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
}
obj.SetName(newName)
m[id] = resource.NewBehaviorlessResource(obj)
return nil
}
// NewResourceSliceFromPatches returns a slice of Resources given a patch path slice from kustomization file.
func NewResourceSliceFromPatches(
loader loader.Loader, paths []string) ([]*resource.Resource, error) {
result := []*resource.Resource{}
for _, path := range paths {
content, err := loader.Load(path)
if err != nil {
return nil, err
}
res, err := newResourceSliceFromBytes(content)
if err != nil {
return nil, err
}
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) {
result := []ResMap{}
for _, path := range paths {
content, err := loader.Load(path)
if err != nil {
return nil, err
}
res, err := newResMapFromBytes(content)
if err != nil {
return nil, err
}
result = append(result, res)
}
return Merge(result...)
}
// newResMapFromBytes decodes a list of objects in byte array format.
func newResMapFromBytes(b []byte) (ResMap, error) {
resources, err := newResourceSliceFromBytes(b)
if err != nil {
return nil, err
}
result := ResMap{}
for _, res := range resources {
gvkn := res.Id()
if _, found := result[gvkn]; found {
return result, fmt.Errorf("GroupVersionKindName: %#v already exists b the map", gvkn)
}
result[gvkn] = res
}
return result, nil
}
func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) {
result := ResMap{}
for _, res := range resources {
gvkn := res.Id()
if _, found := result[gvkn]; found {
return nil, fmt.Errorf("duplicated %#v is not allowed", gvkn)
}
result[gvkn] = res
}
return result, nil
}
func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
result := []*resource.Resource{}
var err error
for {
var out unstructured.Unstructured
err = decoder.Decode(&out)
if err != nil {
break
}
result = append(result, resource.NewBehaviorlessResource(&out))
}
if err != io.EOF {
return nil, err
}
return result, nil
}
// Merge combines many maps to one.
func Merge(maps ...ResMap) (ResMap, error) {
result := ResMap{}
for _, m := range maps {
for gvkn, obj := range m {
if _, found := result[gvkn]; found {
return nil, fmt.Errorf("there is already an entry: %q", gvkn)
}
result[gvkn] = obj
}
}
return result, nil
}
const behaviorCreate = "create"
const behaviorReplace = "replace"
const behaviorMerge = "merge"
// MergeWithOverride merges the entries in the ResMap slice with Override.
// If there is already an entry with the same Id , different actions are performed
// according to value of behavior field:
// 'create': create a new one;
// 'replace': replace the data only; keep the labels and annotations
// 'merge': merge the data; keep the labels and annotations
func MergeWithOverride(maps ...ResMap) (ResMap, error) {
result := ResMap{}
for _, m := range maps {
for gvkn, resource := range m {
if _, found := result[gvkn]; found {
switch resource.Behavior() {
case "", behaviorCreate:
return nil, fmt.Errorf("Create an existing gvkn %#v is not allowed", gvkn)
case behaviorReplace:
glog.V(4).Infof("Replace object %v by %v", result[gvkn].Unstruct().Object, resource.Unstruct().Object)
resource.Replace(result[gvkn])
result[gvkn] = resource
case behaviorMerge:
glog.V(4).Infof("Merge object %v with %v", result[gvkn].Unstruct().Object, resource.Unstruct().Object)
resource.Merge(result[gvkn])
result[gvkn] = resource
glog.V(4).Infof("The merged object is %v", result[gvkn].Unstruct().Object)
default:
return nil, fmt.Errorf("The behavior of %#v must be one of merge and replace since it already exists in the base", gvkn)
}
} else {
switch resource.Behavior() {
case "", behaviorCreate:
result[gvkn] = resource
case behaviorMerge, behaviorReplace:
return nil, fmt.Errorf("No merge or replace is allowed for non existing gvkn %#v", gvkn)
default:
return nil, fmt.Errorf("The behavior of %#v must be create since it doesn't exist", gvkn)
}
}
}
}
return result, nil
}
func newUnstructuredFromObject(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
err = out.UnmarshalJSON(marshaled)
return &out, err
}

222
pkg/resmap/resmap_test.go Normal file
View File

@@ -0,0 +1,222 @@
/*
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 resmap
import (
"fmt"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/loader/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
var statefulset = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}
func TestEncodeAsYaml(t *testing.T) {
encoded := []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`)
input := ResMap{
resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
},
}),
resource.NewResId(cmap, "cm2"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm2",
},
},
}),
}
out, err := input.EncodeAsYaml()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(out, encoded) {
t.Fatalf("%s doesn't match expected %s", out, encoded)
}
}
func TestNewMapFromFiles(t *testing.T) {
resourceStr := `apiVersion: apps/v1
kind: Deployment
metadata:
name: dply1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dply2
`
l := loadertest.NewFakeLoader("/home/seans/project")
if ferr := l.AddFile("/home/seans/project/deployment.yaml", []byte(resourceStr)); ferr != nil {
t.Fatalf("Error adding fake file: %v\n", ferr)
}
expected := ResMap{resource.NewResId(deploy, "dply1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "dply1",
},
},
}),
resource.NewResId(deploy, "dply2"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "dply2",
},
},
}),
}
m, _ := NewResMapFromFiles(l, []string{"/home/seans/project/deployment.yaml"})
if len(m) != 2 {
t.Fatalf("%#v should contain 2 appResource, but got %d", m, len(m))
}
if err := expected.ErrorIfNotEqual(m); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestNewMapFromBytes(t *testing.T) {
encoded := []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`)
expected := ResMap{
resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
},
}),
resource.NewResId(cmap, "cm2"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm2",
},
},
}),
}
m, err := newResMapFromBytes(encoded)
fmt.Printf("%v\n", m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(m, expected) {
t.Fatalf("%#v doesn't match expected %#v", m, expected)
}
}
func TestMerge(t *testing.T) {
input1 := ResMap{
resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "foo-deploy1",
},
},
}),
}
input2 := ResMap{
resource.NewResId(statefulset, "stateful1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "StatefulSet",
"metadata": map[string]interface{}{
"name": "bar-stateful",
},
},
}),
}
input := []ResMap{input1, input2}
expected := ResMap{
resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "foo-deploy1",
},
},
}),
resource.NewResId(statefulset, "stateful1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "StatefulSet",
"metadata": map[string]interface{}{
"name": "bar-stateful",
},
},
}),
}
merged, err := Merge(input...)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(merged, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged, expected)
}
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resource package resmap
import ( import (
"context" "context"
@@ -23,11 +23,12 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
) )
func newFromSecretGenerator(p string, s types.SecretArgs) (*Resource, error) { func newFromSecretGenerator(p string, s types.SecretArgs) (*resource.Resource, error) {
corev1secret := &corev1.Secret{} corev1secret := &corev1.Secret{}
corev1secret.APIVersion = "v1" corev1secret.APIVersion = "v1"
corev1secret.Kind = "Secret" corev1secret.Kind = "Secret"
@@ -46,13 +47,13 @@ func newFromSecretGenerator(p string, s types.SecretArgs) (*Resource, error) {
corev1secret.Data[k] = out corev1secret.Data[k] = out
} }
obj, err := objectToUnstructured(corev1secret) obj, err := newUnstructuredFromObject(corev1secret)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Resource{Data: obj, Behavior: s.Behavior}, nil return resource.NewResource(obj, s.Behavior), nil
} }
func createSecretKey(wd string, command string) ([]byte, error) { func createSecretKey(wd string, command string) ([]byte, error) {
@@ -68,10 +69,10 @@ func createSecretKey(wd string, command string) ([]byte, error) {
return cmd.Output() return cmd.Output()
} }
// NewFromSecretGenerators takes a SecretGenerator slice and executes its command in directory p // NewResMapFromSecretArgs takes a SecretArgs slice and executes its command in directory p
// then writes the output to a Resource slice and return it. // then writes the output to a Resource slice and return it.
func NewFromSecretGenerators(p string, secretList []types.SecretArgs) (ResourceCollection, error) { func NewResMapFromSecretArgs(p string, secretList []types.SecretArgs) (ResMap, error) {
allResources := []*Resource{} allResources := []*resource.Resource{}
for _, secret := range secretList { for _, secret := range secretList {
res, err := newFromSecretGenerator(p, secret) res, err := newFromSecretGenerator(p, secret)
if err != nil { if err != nil {
@@ -79,5 +80,5 @@ func NewFromSecretGenerators(p string, secretList []types.SecretArgs) (ResourceC
} }
allResources = append(allResources, res) allResources = append(allResources, res)
} }
return resourceCollectionFromResources(allResources) return newResMapFromResourceSlice(allResources)
} }

View File

@@ -14,19 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resource package resmap
import ( import (
"encoding/base64" "encoding/base64"
"reflect" "reflect"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
func TestNewFromSecretGenerators(t *testing.T) { func TestNewFromSecretGenerators(t *testing.T) {
secrets := []types.SecretArgs{ secrets := []types.SecretArgs{
{ {
@@ -38,17 +41,14 @@ func TestNewFromSecretGenerators(t *testing.T) {
Type: "Opaque", Type: "Opaque",
}, },
} }
re, err := NewFromSecretGenerators(".", secrets) re, err := NewResMapFromSecretArgs(".", secrets)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := ResourceCollection{ expected := ResMap{
{ resource.NewResId(secret, "secret"): resource.NewResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, &unstructured.Unstructured{
Name: "secret",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -63,7 +63,7 @@ func TestNewFromSecretGenerators(t *testing.T) {
}, },
}, },
}, },
}, ""),
} }
if !reflect.DeepEqual(re, expected) { if !reflect.DeepEqual(re, expected) {

View File

@@ -1,57 +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 resource
import (
"github.com/kubernetes-sigs/kustomize/pkg/loader"
)
// NewFromResources returns a ResourceCollection given a resource path slice from kustomization file.
func NewFromResources(loader loader.Loader, paths []string) (ResourceCollection, error) {
allResources := []ResourceCollection{}
for _, path := range paths {
content, err := loader.Load(path)
if err != nil {
return nil, err
}
res, err := decodeToResourceCollection(content)
if err != nil {
return nil, err
}
allResources = append(allResources, res)
}
return Merge(allResources...)
}
// NewFromPatches returns a slice of Resources given a patch path slice from kustomization file.
func NewFromPatches(loader loader.Loader, paths []string) ([]*Resource, error) {
allResources := []*Resource{}
for _, path := range paths {
content, err := loader.Load(path)
if err != nil {
return nil, err
}
res, err := decode(content)
if err != nil {
return nil, err
}
allResources = append(allResources, res...)
}
return allResources, nil
}

View File

@@ -1,110 +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 resource
import (
"fmt"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/loader/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestNewFromPaths(t *testing.T) {
resourceStr := `apiVersion: apps/v1
kind: Deployment
metadata:
name: dply1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dply2
`
l := loadertest.NewFakeLoader("/home/seans/project")
if ferr := l.AddFile("/home/seans/project/deployment.yaml", []byte(resourceStr)); ferr != nil {
t.Fatalf("Error adding fake file: %v\n", ferr)
}
expected := ResourceCollection{
{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "dply1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "dply1",
},
},
},
},
{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "dply2",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "dply2",
},
},
},
},
}
resources, _ := NewFromResources(l, []string{"/home/seans/project/deployment.yaml"})
if len(resources) != 2 {
t.Fatalf("%#v should contain 2 appResource, but got %d", resources, len(resources))
}
if err := compareMap(resources, expected); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func compareMap(m1, m2 ResourceCollection) error {
if len(m1) != len(m2) {
keySet1 := []types.GroupVersionKindName{}
keySet2 := []types.GroupVersionKindName{}
for GVKn := range m1 {
keySet1 = append(keySet1, GVKn)
}
for GVKn := range m1 {
keySet2 = append(keySet2, GVKn)
}
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
}
for GVKn, obj1 := range m1 {
obj2, found := m2[GVKn]
if !found {
return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2)
}
if !reflect.DeepEqual(obj1.Data, obj2.Data) {
return fmt.Errorf("%#v doesn't match %#v", obj1.Data, obj2.Data)
}
}
return nil
}

View File

@@ -14,16 +14,37 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package types package resource
import ( import (
"strings"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
// GroupVersionKindName contains GroupVersionKind and original name of the resource. // ResId conflates GroupVersionKind with a textual name to uniquely identify a kubernetes resource (object).
type GroupVersionKindName struct { type ResId struct {
// GroupVersionKind of the resource. // GroupVersionKind of the resource.
GVK schema.GroupVersionKind gvk schema.GroupVersionKind
// original name of the resource before transformation. // original name of the resource before transformation.
Name string name string
}
func NewResId(g schema.GroupVersionKind, n string) ResId {
return ResId{gvk: g, name: n}
}
func (n ResId) String() string {
if n.gvk.Group == "" {
return strings.Join([]string{n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
}
return strings.Join([]string{n.gvk.Group, n.gvk.Version, n.gvk.Kind, n.name}, "_") + ".yaml"
}
func (n ResId) Gvk() schema.GroupVersionKind {
return n.gvk
}
func (n ResId) Name() string {
return n.name
} }

View File

@@ -17,35 +17,89 @@ limitations under the License.
package resource package resource
import ( import (
"bytes"
"encoding/json" "encoding/json"
"sort"
"github.com/ghodss/yaml"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
// Resource represents a Kubernetes Resource Object for ex. Deployment, Server // Resource is a Kubernetes Resource Object paired with a behavior.
// ConfigMap etc.
type Resource struct { type Resource struct {
Data *unstructured.Unstructured unstruct *unstructured.Unstructured
Behavior string behavior string
} }
// GVKN returns Group/Version/Kind/Name for the resource. // NewResource returns a new instance of Resource.
func (r *Resource) GVKN() types.GroupVersionKindName { func NewResource(u *unstructured.Unstructured, b string) *Resource {
var emptyZVKN types.GroupVersionKindName return &Resource{unstruct: u, behavior: b}
if r.Data == nil { }
return emptyZVKN
// NewBehaviorlessResource returns a new instance of Resource.
func NewBehaviorlessResource(u *unstructured.Unstructured) *Resource {
return &Resource{unstruct: u}
}
// Behavior returns the behavior for the resource.
func (r *Resource) Behavior() string {
return r.behavior
}
// Unstruct returns the unstructured object holding the resource.
func (r *Resource) Unstruct() *unstructured.Unstructured {
return r.unstruct
}
// SetUnstruct sets a new member.
func (r *Resource) SetUnstruct(u *unstructured.Unstructured) {
r.unstruct = u
}
// Id returns the ResId for the resource.
func (r *Resource) Id() ResId {
var empty ResId
if r.unstruct == nil {
return empty
} }
gvk := r.Data.GroupVersionKind() gvk := r.unstruct.GroupVersionKind()
return types.GroupVersionKindName{GVK: gvk, Name: r.Data.GetName()} return NewResId(gvk, r.unstruct.GetName())
} }
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) { func (r *Resource) Merge(other *Resource) {
r.Replace(other)
mergeConfigmap(r.unstruct.Object, other.unstruct.Object, r.unstruct.Object)
}
func (r *Resource) Replace(other *Resource) {
r.unstruct.SetLabels(mergeStringMaps(other.unstruct.GetLabels(), r.unstruct.GetLabels()))
r.unstruct.SetAnnotations(mergeStringMaps(other.unstruct.GetAnnotations(), r.unstruct.GetAnnotations()))
r.unstruct.SetName(other.unstruct.GetName())
}
// TODO: Add BinaryData once we sync to new k8s.io/api
func mergeConfigmap(mergedTo map[string]interface{}, maps ...map[string]interface{}) {
mergedMap := map[string]interface{}{}
for _, m := range maps {
datamap, ok := m["data"].(map[string]interface{})
if ok {
for key, value := range datamap {
mergedMap[key] = value
}
}
}
mergedTo["data"] = mergedMap
}
func mergeStringMaps(maps ...map[string]string) map[string]string {
result := map[string]string{}
for _, m := range maps {
for key, value := range m {
result[key] = value
}
}
return result
}
func newUnstructuredFromObject(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in) marshaled, err := json.Marshal(in)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -54,38 +108,3 @@ func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error)
err = out.UnmarshalJSON(marshaled) err = out.UnmarshalJSON(marshaled)
return &out, err return &out, err
} }
// ResourceCollection is a map from GroupVersionKindName to Resource
type ResourceCollection map[types.GroupVersionKindName]*Resource
// EncodeAsYaml encodes the map `in` and output the encoded objects separated by `---`.
func (in ResourceCollection) EncodeAsYaml() ([]byte, error) {
gvknList := []types.GroupVersionKindName{}
for gvkn := range in {
gvknList = append(gvknList, gvkn)
}
sort.Sort(types.ByGVKN(gvknList))
firstObj := true
var b []byte
buf := bytes.NewBuffer(b)
for _, gvkn := range gvknList {
obj := in[gvkn].Data
out, err := yaml.Marshal(obj)
if err != nil {
return nil, err
}
if !firstObj {
_, err = buf.WriteString("---\n")
if err != nil {
return nil, err
}
}
_, err = buf.Write(out)
if err != nil {
return nil, err
}
firstObj = false
}
return buf.Bytes(), nil
}

View File

@@ -1,75 +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 resource
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestEncode(t *testing.T) {
encoded := []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`)
input := ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
},
},
},
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm2",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm2",
},
},
},
},
}
out, err := input.EncodeAsYaml()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(out, encoded) {
t.Fatalf("%s doesn't match expected %s", out, encoded)
}
}

View File

@@ -1,174 +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 resource
import (
"bytes"
"fmt"
"io"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
)
// decode decodes a list of objects in byte array format
func decode(in []byte) ([]*Resource, error) {
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
resources := []*Resource{}
var err error
for {
var out unstructured.Unstructured
err = decoder.Decode(&out)
if err != nil {
break
}
resources = append(resources, &Resource{Data: &out})
}
if err != io.EOF {
return nil, err
}
return resources, nil
}
// decodeToResourceCollection decodes a list of objects in byte array format.
// it will return a ResourceCollection.
func decodeToResourceCollection(in []byte) (ResourceCollection, error) {
resources, err := decode(in)
if err != nil {
return nil, err
}
into := ResourceCollection{}
for _, res := range resources {
gvkn := res.GVKN()
if _, found := into[gvkn]; found {
return into, fmt.Errorf("GroupVersionKindName: %#v already exists in the map", gvkn)
}
into[gvkn] = res
}
return into, nil
}
func resourceCollectionFromResources(resources []*Resource) (ResourceCollection, error) {
out := ResourceCollection{}
for _, res := range resources {
gvkn := res.GVKN()
if _, found := out[gvkn]; found {
return nil, fmt.Errorf("duplicated %#v is not allowed", gvkn)
}
out[gvkn] = res
}
return out, nil
}
// Merge will merge all of the entries in the slice of ResourceCollection.
func Merge(rcs ...ResourceCollection) (ResourceCollection, error) {
all := ResourceCollection{}
for _, rc := range rcs {
for gvkn, obj := range rc {
if _, found := all[gvkn]; found {
return nil, fmt.Errorf("there is already an entry: %q", gvkn)
}
all[gvkn] = obj
}
}
return all, nil
}
const behaviorCreate = "create"
const behaviorReplace = "replace"
const behaviorMerge = "merge"
// MergeWithOverride merges the entries in the ResourceCollection slice with Override.
// If there is already an entry with the same GVKN , different actions are performed
// according to value of Behavior field:
// 'create': create a new one;
// 'replace': replace the data only; keep the labels and annotations
// 'merge': merge the data; keep the labels and annotations
func MergeWithOverride(rcs ...ResourceCollection) (ResourceCollection, error) {
all := ResourceCollection{}
for _, rc := range rcs {
for gvkn, obj := range rc {
if _, found := all[gvkn]; found {
switch obj.Behavior {
case "", behaviorCreate:
return nil, fmt.Errorf("Create an existing gvkn %#v is not allowed", gvkn)
case behaviorReplace:
glog.V(4).Infof("Replace object %v by %v", all[gvkn].Data.Object, obj.Data.Object)
obj.replace(all[gvkn])
all[gvkn] = obj
case behaviorMerge:
glog.V(4).Infof("Merge object %v with %v", all[gvkn].Data.Object, obj.Data.Object)
obj.merge(all[gvkn])
all[gvkn] = obj
glog.V(4).Infof("The merged object is %v", all[gvkn].Data.Object)
default:
return nil, fmt.Errorf("The behavior of %#v must be one of merge and replace since it already exists in the base", gvkn)
}
} else {
switch obj.Behavior {
case "", behaviorCreate:
all[gvkn] = obj
case behaviorMerge, behaviorReplace:
return nil, fmt.Errorf("No merge or replace is allowed for non existing gvkn %#v", gvkn)
default:
return nil, fmt.Errorf("The behavior of %#v must be create since it doesn't exist", gvkn)
}
}
}
}
return all, nil
}
func (r *Resource) replace(other *Resource) {
r.Data.SetLabels(mergeMap(other.Data.GetLabels(), r.Data.GetLabels()))
r.Data.SetAnnotations(mergeMap(other.Data.GetAnnotations(), r.Data.GetAnnotations()))
r.Data.SetName(other.Data.GetName())
}
func (r *Resource) merge(other *Resource) {
r.replace(other)
mergeConfigmap(r.Data.Object, other.Data.Object, r.Data.Object)
}
func mergeMap(maps ...map[string]string) map[string]string {
mergedMap := map[string]string{}
for _, m := range maps {
for key, value := range m {
mergedMap[key] = value
}
}
return mergedMap
}
// TODO: Add BinaryData once we sync to new k8s.io/api
func mergeConfigmap(mergedTo map[string]interface{}, maps ...map[string]interface{}) {
mergedMap := map[string]interface{}{}
for _, m := range maps {
datamap, ok := m["data"].(map[string]interface{})
if ok {
for key, value := range datamap {
mergedMap[key] = value
}
}
}
mergedTo["data"] = mergedMap
}

View File

@@ -1,151 +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 resource
import (
"fmt"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestDecodeToResourceCollection(t *testing.T) {
encoded := []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`)
expected := ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
},
},
},
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm2",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm2",
},
},
},
},
}
m, err := decodeToResourceCollection(encoded)
fmt.Printf("%v\n", m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(m, expected) {
t.Fatalf("%#v doesn't match expected %#v", m, expected)
}
}
func TestMerge(t *testing.T) {
input1 := ResourceCollection{
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "foo-deploy1",
},
},
},
},
}
input2 := ResourceCollection{
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"},
Name: "stateful1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "StatefulSet",
"metadata": map[string]interface{}{
"name": "bar-stateful",
},
},
},
},
}
input := []ResourceCollection{input1, input2}
expected := ResourceCollection{
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "foo-deploy1",
},
},
},
},
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"},
Name: "stateful1",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "StatefulSet",
"metadata": map[string]interface{}{
"name": "bar-stateful",
},
},
},
},
}
merged, err := Merge(input...)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(merged, expected) {
t.Fatalf("%#v doesn't equal expected %#v", merged, expected)
}
}

View File

@@ -20,7 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
) )
@@ -56,12 +56,12 @@ func NewMapTransformer(pc []PathConfig, m map[string]string) (Transformer, error
// Transform apply each <key, value> pair in the mapTransformer to the // Transform apply each <key, value> pair in the mapTransformer to the
// fields specified in mapTransformer. // fields specified in mapTransformer.
func (o *mapTransformer) Transform(m resource.ResourceCollection) error { func (o *mapTransformer) Transform(m resmap.ResMap) error {
for gvkn := range m { for id := range m {
obj := m[gvkn].Data obj := m[id].Unstruct()
objMap := obj.UnstructuredContent() objMap := obj.UnstructuredContent()
for _, path := range o.pathConfigs { for _, path := range o.pathConfigs {
if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) { if !types.SelectByGVK(id.Gvk(), path.GroupVersionKind) {
continue continue
} }
err := mutateField(objMap, path.Path, path.CreateIfNotPresent, o.addMap) err := mutateField(objMap, path.Path, path.CreateIfNotPresent, o.addMap)

View File

@@ -20,18 +20,23 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
var service = schema.GroupVersionKind{Version: "v1", Kind: "Service"}
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
var statefulset = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}
var foo = schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}
func TestLabelsRun(t *testing.T) { func TestLabelsRun(t *testing.T) {
m := resource.ResourceCollection{ m := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -39,13 +44,9 @@ func TestLabelsRun(t *testing.T) {
"name": "cm1", "name": "cm1",
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -71,13 +72,9 @@ func TestLabelsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(service, "svc1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@@ -93,15 +90,11 @@ func TestLabelsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -113,13 +106,9 @@ func TestLabelsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -157,13 +146,9 @@ func TestLabelsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(service, "svc1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@@ -187,8 +172,7 @@ func TestLabelsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
lt, err := NewDefaultingLabelsMapTransformer(map[string]string{"label-key1": "label-value1", "label-key2": "label-value2"}) lt, err := NewDefaultingLabelsMapTransformer(map[string]string{"label-key1": "label-value1", "label-key2": "label-value2"})
@@ -200,7 +184,7 @@ func TestLabelsRun(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = expected.ErrorIfNotEqual(m)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }
@@ -284,12 +268,9 @@ func makeAnnotatededService() *unstructured.Unstructured {
} }
func TestAnnotationsRun(t *testing.T) { func TestAnnotationsRun(t *testing.T) {
m := resource.ResourceCollection{ m := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -297,13 +278,9 @@ func TestAnnotationsRun(t *testing.T) {
"name": "cm1", "name": "cm1",
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -329,13 +306,9 @@ func TestAnnotationsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(service, "svc1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@@ -351,15 +324,11 @@ func TestAnnotationsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -371,13 +340,9 @@ func TestAnnotationsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -411,13 +376,9 @@ func TestAnnotationsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(service, "svc1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@@ -437,8 +398,7 @@ func TestAnnotationsRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
at, err := NewDefaultingAnnotationsMapTransformer(map[string]string{"anno-key1": "anno-value1", "anno-key2": "anno-value2"}) at, err := NewDefaultingAnnotationsMapTransformer(map[string]string{"anno-key1": "anno-value1", "anno-key2": "anno-value2"})
if err != nil { if err != nil {
@@ -449,7 +409,7 @@ func TestAnnotationsRun(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = expected.ErrorIfNotEqual(m)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }

View File

@@ -16,7 +16,7 @@ limitations under the License.
package transformers package transformers
import "github.com/kubernetes-sigs/kustomize/pkg/resource" import "github.com/kubernetes-sigs/kustomize/pkg/resmap"
// multiTransformer contains a list of transformers. // multiTransformer contains a list of transformers.
type multiTransformer struct { type multiTransformer struct {
@@ -34,7 +34,7 @@ func NewMultiTransformer(t []Transformer) Transformer {
} }
// Transform prepends the name prefix. // Transform prepends the name prefix.
func (o *multiTransformer) Transform(m resource.ResourceCollection) error { func (o *multiTransformer) Transform(m resmap.ResMap) error {
for _, t := range o.transformers { for _, t := range o.transformers {
err := t.Transform(m) err := t.Transform(m)
if err != nil { if err != nil {

View File

@@ -21,7 +21,7 @@ import (
"fmt" "fmt"
"github.com/kubernetes-sigs/kustomize/pkg/hash" "github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -40,14 +40,14 @@ func NewNameHashTransformer() Transformer {
} }
// Transform appends hash to configmaps and secrets. // Transform appends hash to configmaps and secrets.
func (o *nameHashTransformer) Transform(m resource.ResourceCollection) error { func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
for gvkn, obj := range m { for id, obj := range m {
switch { switch {
case types.SelectByGVK(gvkn.GVK, &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}): case types.SelectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}):
appendHashForConfigMap(obj.Data) appendHashForConfigMap(obj.Unstruct())
case types.SelectByGVK(gvkn.GVK, &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}): case types.SelectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}):
appendHashForSecret(obj.Data) appendHashForSecret(obj.Unstruct())
} }
} }
return nil return nil

View File

@@ -20,18 +20,15 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
) )
func TestNameHashTransformer(t *testing.T) { func TestNameHashTransformer(t *testing.T) {
objs := resource.ResourceCollection{ objs := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -39,13 +36,9 @@ func TestNameHashTransformer(t *testing.T) {
"name": "cm1", "name": "cm1",
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -71,13 +64,9 @@ func TestNameHashTransformer(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(service, "svc1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@@ -93,13 +82,9 @@ func TestNameHashTransformer(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(secret, "secret1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -107,16 +92,12 @@ func TestNameHashTransformer(t *testing.T) {
"name": "secret1", "name": "secret1",
}, },
}, },
}, }),
},
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -124,13 +105,9 @@ func TestNameHashTransformer(t *testing.T) {
"name": "cm1-m462kdfb68", "name": "cm1-m462kdfb68",
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -156,13 +133,9 @@ func TestNameHashTransformer(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(service, "svc1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@@ -178,13 +151,9 @@ func TestNameHashTransformer(t *testing.T) {
}, },
}, },
}, },
}, }),
}, resource.NewResId(secret, "secret1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -192,15 +161,14 @@ func TestNameHashTransformer(t *testing.T) {
"name": "secret1-7kc45hd5f7", "name": "secret1-7kc45hd5f7",
}, },
}, },
}, }),
},
} }
tran := NewNameHashTransformer() tran := NewNameHashTransformer()
tran.Transform(objs) tran.Transform(objs)
if !reflect.DeepEqual(objs, expected) { if !reflect.DeepEqual(objs, expected) {
err := compareMap(objs, expected) err := expected.ErrorIfNotEqual(objs)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }

View File

@@ -20,7 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
@@ -51,13 +51,13 @@ func NewNameReferenceTransformer(pc []referencePathConfig) (Transformer, error)
// associated with the key. e.g. if <k, v> is one of the key-value pair in the map, // associated with the key. e.g. if <k, v> is one of the key-value pair in the map,
// then the old name is k.Name and the new name is v.GetName() // then the old name is k.Name and the new name is v.GetName()
func (o *nameReferenceTransformer) Transform( func (o *nameReferenceTransformer) Transform(
m resource.ResourceCollection) error { m resmap.ResMap) error {
for GVKn := range m { for id := range m {
obj := m[GVKn].Data obj := m[id].Unstruct()
objMap := obj.UnstructuredContent() objMap := obj.UnstructuredContent()
for _, referencePathConfig := range o.pathConfigs { for _, referencePathConfig := range o.pathConfigs {
for _, path := range referencePathConfig.pathConfigs { for _, path := range referencePathConfig.pathConfigs {
if !types.SelectByGVK(GVKn.GVK, path.GroupVersionKind) { if !types.SelectByGVK(id.Gvk(), path.GroupVersionKind) {
continue continue
} }
err := mutateField(objMap, path.Path, path.CreateIfNotPresent, err := mutateField(objMap, path.Path, path.CreateIfNotPresent,
@@ -71,25 +71,9 @@ func (o *nameReferenceTransformer) Transform(
return nil return nil
} }
// noMatchingGVKNError indicates failing to find a gvkn.GroupVersionKindName.
type noMatchingGVKNError struct {
message string
}
// newNoMatchingGVKNError constructs an instance of noMatchingGVKNError with
// a given error message.
func newNoMatchingGVKNError(errMsg string) noMatchingGVKNError {
return noMatchingGVKNError{errMsg}
}
// Error returns the error in string format.
func (err noMatchingGVKNError) Error() string {
return err.message
}
func (o *nameReferenceTransformer) updateNameReference( func (o *nameReferenceTransformer) updateNameReference(
GVK schema.GroupVersionKind, GVK schema.GroupVersionKind,
m resource.ResourceCollection, m resmap.ResMap,
) func(in interface{}) (interface{}, error) { ) func(in interface{}) (interface{}, error) {
return func(in interface{}) (interface{}, error) { return func(in interface{}) (interface{}, error) {
s, ok := in.(string) s, ok := in.(string)
@@ -97,12 +81,12 @@ func (o *nameReferenceTransformer) updateNameReference(
return nil, fmt.Errorf("%#v is expectd to be %T", in, s) return nil, fmt.Errorf("%#v is expectd to be %T", in, s)
} }
for GVKn, obj := range m { for id, obj := range m {
if !types.SelectByGVK(GVKn.GVK, &GVK) { if !types.SelectByGVK(id.Gvk(), &GVK) {
continue continue
} }
if GVKn.Name == s { if id.Name() == s {
return obj.Data.GetName(), nil return obj.Unstruct().GetName(), nil
} }
} }
return in, nil return in, nil

View File

@@ -20,18 +20,15 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
) )
func TestNameReferenceRun(t *testing.T) { func TestNameReferenceRun(t *testing.T) {
m := resource.ResourceCollection{ m := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -39,13 +36,9 @@ func TestNameReferenceRun(t *testing.T) {
"name": "someprefix-cm1-somehash", "name": "someprefix-cm1-somehash",
}, },
}, },
}, }),
}, resource.NewResId(secret, "secret1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -53,13 +46,9 @@ func TestNameReferenceRun(t *testing.T) {
"name": "someprefix-secret1-somehash", "name": "someprefix-secret1-somehash",
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -122,16 +111,12 @@ func TestNameReferenceRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -139,13 +124,9 @@ func TestNameReferenceRun(t *testing.T) {
"name": "someprefix-cm1-somehash", "name": "someprefix-cm1-somehash",
}, },
}, },
}, }),
}, resource.NewResId(secret, "secret1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@@ -153,13 +134,9 @@ func TestNameReferenceRun(t *testing.T) {
"name": "someprefix-secret1-somehash", "name": "someprefix-secret1-somehash",
}, },
}, },
}, }),
}, resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@@ -222,8 +199,7 @@ func TestNameReferenceRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
nrt, err := NewDefaultingNameReferenceTransformer() nrt, err := NewDefaultingNameReferenceTransformer()
@@ -232,7 +208,7 @@ func TestNameReferenceRun(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = expected.ErrorIfNotEqual(m)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }

View File

@@ -16,7 +16,7 @@ limitations under the License.
package transformers package transformers
import "github.com/kubernetes-sigs/kustomize/pkg/resource" import "github.com/kubernetes-sigs/kustomize/pkg/resmap"
// noOpTransformer contains a no-op transformer. // noOpTransformer contains a no-op transformer.
type noOpTransformer struct{} type noOpTransformer struct{}
@@ -29,6 +29,6 @@ func NewNoOpTransformer() Transformer {
} }
// Transform does nothing. // Transform does nothing.
func (o *noOpTransformer) Transform(_ resource.ResourceCollection) error { func (o *noOpTransformer) Transform(_ resmap.ResMap) error {
return nil return nil
} }

View File

@@ -22,6 +22,7 @@ import (
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@@ -45,8 +46,8 @@ func NewOverlayTransformer(overlay []*resource.Resource) (Transformer, error) {
} }
// Transform apply the overlay on top of the base resources. // Transform apply the overlay on top of the base resources.
func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollection) error { func (o *overlayTransformer) Transform(baseResourceMap resmap.ResMap) error {
// Merge and then index the patches by GVKN. // Merge and then index the patches by Id.
overlays, err := o.mergePatches() overlays, err := o.mergePatches()
if err != nil { if err != nil {
return err return err
@@ -55,22 +56,22 @@ func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollecti
// Strategic merge the resources exist in both base and overlay. // Strategic merge the resources exist in both base and overlay.
for _, overlay := range overlays { for _, overlay := range overlays {
// Merge overlay with base resource. // Merge overlay with base resource.
gvkn := overlay.GVKN() id := overlay.Id()
base, found := baseResourceMap[gvkn] base, found := baseResourceMap[id]
if !found { if !found {
return fmt.Errorf("failed to find an object with %#v to apply the patch", gvkn.GVK) return fmt.Errorf("failed to find an object with %#v to apply the patch", id.Gvk())
} }
merged := map[string]interface{}{} merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(gvkn.GVK) versionedObj, err := scheme.Scheme.New(id.Gvk())
baseName := base.Data.GetName() baseName := base.Unstruct().GetName()
switch { switch {
case runtime.IsNotRegisteredError(err): case runtime.IsNotRegisteredError(err):
// Use JSON merge patch to handle types w/o schema // Use JSON merge patch to handle types w/o schema
baseBytes, err := json.Marshal(base.Data) baseBytes, err := json.Marshal(base.Unstruct())
if err != nil { if err != nil {
return err return err
} }
patchBytes, err := json.Marshal(overlay.Data) patchBytes, err := json.Marshal(overlay.Unstruct())
if err != nil { if err != nil {
return err return err
} }
@@ -94,33 +95,33 @@ func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollecti
return err return err
} }
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta( merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
base.Data.Object, base.Unstruct().Object,
overlay.Data.Object, overlay.Unstruct().Object,
lookupPatchMeta) lookupPatchMeta)
if err != nil { if err != nil {
return err return err
} }
} }
base.Data.SetName(baseName) base.Unstruct().SetName(baseName)
baseResourceMap[gvkn].Data.Object = merged baseResourceMap[id].Unstruct().Object = merged
} }
return nil return nil
} }
// mergePatches merge and index patches by GVKN. // mergePatches merge and index patches by Id.
// It errors out if there is conflict between patches. // It errors out if there is conflict between patches.
func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error) { func (o *overlayTransformer) mergePatches() (resmap.ResMap, error) {
rc := resource.ResourceCollection{} rc := resmap.ResMap{}
patches := resourcesToObjects(o.overlay) patches := resourcesToObjects(o.overlay)
for ix, patch := range o.overlay { for ix, patch := range o.overlay {
gvkn := patch.GVKN() id := patch.Id()
existing, found := rc[gvkn] existing, found := rc[id]
if !found { if !found {
rc[gvkn] = patch rc[id] = patch
continue continue
} }
versionedObj, err := scheme.Scheme.New(gvkn.GVK) versionedObj, err := scheme.Scheme.New(id.Gvk())
if err != nil && !runtime.IsNotRegisteredError(err) { if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err return nil, err
} }
@@ -134,7 +135,7 @@ func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error)
} }
} }
conflict, err := cd.hasConflict(existing.Data, patch.Data) conflict, err := cd.hasConflict(existing.Unstruct(), patch.Unstruct())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -143,13 +144,13 @@ func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return nil, fmt.Errorf("there is conflict between %#v and %#v", conflictingPatch.Object, patch.Data.Object) return nil, fmt.Errorf("there is conflict between %#v and %#v", conflictingPatch.Object, patch.Unstruct().Object)
} else { } else {
merged, err := cd.mergePatches(existing.Data, patch.Data) merged, err := cd.mergePatches(existing.Unstruct(), patch.Unstruct())
if err != nil { if err != nil {
return nil, err return nil, err
} }
existing.Data = merged existing.SetUnstruct(merged)
} }
} }
return rc, nil return rc, nil
@@ -158,7 +159,7 @@ func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error)
func resourcesToObjects(rs []*resource.Resource) []*unstructured.Unstructured { func resourcesToObjects(rs []*resource.Resource) []*unstructured.Unstructured {
objectList := make([]*unstructured.Unstructured, len(rs)) objectList := make([]*unstructured.Unstructured, len(rs))
for i := range rs { for i := range rs {
objectList[i] = rs[i].Data objectList[i] = rs[i].Unstruct()
} }
return objectList return objectList
} }

View File

@@ -21,18 +21,15 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
) )
func TestOverlayRun(t *testing.T) { func TestOverlayRun(t *testing.T) {
base := resource.ResourceCollection{ base := resmap.ResMap{
{ resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -57,35 +54,32 @@ func TestOverlayRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
overlay := []*resource.Resource{ overlay := []*resource.Resource{
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "apps/v1",
"apiVersion": "apps/v1", "kind": "Deployment",
"kind": "Deployment", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "deploy1",
"name": "deploy1", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "template": map[string]interface{}{
"template": map[string]interface{}{ "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "labels": map[string]interface{}{
"labels": map[string]interface{}{ "another-label": "foo",
"another-label": "foo",
},
}, },
"spec": map[string]interface{}{ },
"containers": []interface{}{ "spec": map[string]interface{}{
map[string]interface{}{ "containers": []interface{}{
"name": "nginx", map[string]interface{}{
"image": "nginx:latest", "name": "nginx",
"env": []interface{}{ "image": "nginx:latest",
map[string]interface{}{ "env": []interface{}{
"name": "SOMEENV", map[string]interface{}{
"value": "BAR", "name": "SOMEENV",
}, "value": "BAR",
}, },
}, },
}, },
@@ -95,13 +89,11 @@ func TestOverlayRun(t *testing.T) {
}, },
}, },
}, },
),
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -133,8 +125,7 @@ func TestOverlayRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
lt, err := NewOverlayTransformer(overlay) lt, err := NewOverlayTransformer(overlay)
if err != nil { if err != nil {
@@ -145,18 +136,15 @@ func TestOverlayRun(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(base, expected) { if !reflect.DeepEqual(base, expected) {
err = compareMap(base, expected) err = expected.ErrorIfNotEqual(base)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }
func TestMultiplePatches(t *testing.T) { func TestMultiplePatches(t *testing.T) {
base := resource.ResourceCollection{ base := resmap.ResMap{
{ resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -176,30 +164,27 @@ func TestMultiplePatches(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
overlay := []*resource.Resource{ overlay := []*resource.Resource{
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "apps/v1",
"apiVersion": "apps/v1", "kind": "Deployment",
"kind": "Deployment", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "deploy1",
"name": "deploy1", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "template": map[string]interface{}{
"template": map[string]interface{}{ "spec": map[string]interface{}{
"spec": map[string]interface{}{ "containers": []interface{}{
"containers": []interface{}{ map[string]interface{}{
map[string]interface{}{ "name": "nginx",
"name": "nginx", "image": "nginx:latest",
"image": "nginx:latest", "env": []interface{}{
"env": []interface{}{ map[string]interface{}{
map[string]interface{}{ "name": "SOMEENV",
"name": "SOMEENV", "value": "BAR",
"value": "BAR",
},
}, },
}, },
}, },
@@ -209,31 +194,30 @@ func TestMultiplePatches(t *testing.T) {
}, },
}, },
}, },
{ ),
Data: &unstructured.Unstructured{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": "deploy1", "name": "deploy1",
}, },
"spec": map[string]interface{}{ "spec": map[string]interface{}{
"template": map[string]interface{}{ "template": map[string]interface{}{
"spec": map[string]interface{}{ "spec": map[string]interface{}{
"containers": []interface{}{ "containers": []interface{}{
map[string]interface{}{ map[string]interface{}{
"name": "nginx", "name": "nginx",
"env": []interface{}{ "env": []interface{}{
map[string]interface{}{ map[string]interface{}{
"name": "ANOTHERENV", "name": "ANOTHERENV",
"value": "HELLO", "value": "HELLO",
},
}, },
}, },
map[string]interface{}{ },
"name": "busybox", map[string]interface{}{
"image": "busybox", "name": "busybox",
}, "image": "busybox",
}, },
}, },
}, },
@@ -241,13 +225,11 @@ func TestMultiplePatches(t *testing.T) {
}, },
}, },
}, },
),
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -281,8 +263,7 @@ func TestMultiplePatches(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
lt, err := NewOverlayTransformer(overlay) lt, err := NewOverlayTransformer(overlay)
if err != nil { if err != nil {
@@ -293,18 +274,15 @@ func TestMultiplePatches(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(base, expected) { if !reflect.DeepEqual(base, expected) {
err = compareMap(base, expected) err = expected.ErrorIfNotEqual(base)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }
func TestMultiplePatchesWithConflict(t *testing.T) { func TestMultiplePatchesWithConflict(t *testing.T) {
base := resource.ResourceCollection{ base := resmap.ResMap{
{ resource.NewResId(deploy, "deploy1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, &unstructured.Unstructured{
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@@ -324,30 +302,27 @@ func TestMultiplePatchesWithConflict(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
overlay := []*resource.Resource{ overlay := []*resource.Resource{
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "apps/v1",
"apiVersion": "apps/v1", "kind": "Deployment",
"kind": "Deployment", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "deploy1",
"name": "deploy1", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "template": map[string]interface{}{
"template": map[string]interface{}{ "spec": map[string]interface{}{
"spec": map[string]interface{}{ "containers": []interface{}{
"containers": []interface{}{ map[string]interface{}{
map[string]interface{}{ "name": "nginx",
"name": "nginx", "image": "nginx:latest",
"image": "nginx:latest", "env": []interface{}{
"env": []interface{}{ map[string]interface{}{
map[string]interface{}{ "name": "SOMEENV",
"name": "SOMEENV", "value": "BAR",
"value": "BAR",
},
}, },
}, },
}, },
@@ -357,22 +332,21 @@ func TestMultiplePatchesWithConflict(t *testing.T) {
}, },
}, },
}, },
{ ),
Data: &unstructured.Unstructured{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": "deploy1", "name": "deploy1",
}, },
"spec": map[string]interface{}{ "spec": map[string]interface{}{
"template": map[string]interface{}{ "template": map[string]interface{}{
"spec": map[string]interface{}{ "spec": map[string]interface{}{
"containers": []interface{}{ "containers": []interface{}{
map[string]interface{}{ map[string]interface{}{
"name": "nginx", "name": "nginx",
"image": "nginx:1.7.9", "image": "nginx:1.7.9",
},
}, },
}, },
}, },
@@ -380,6 +354,7 @@ func TestMultiplePatchesWithConflict(t *testing.T) {
}, },
}, },
}, },
),
} }
lt, err := NewOverlayTransformer(overlay) lt, err := NewOverlayTransformer(overlay)
@@ -396,12 +371,9 @@ func TestMultiplePatchesWithConflict(t *testing.T) {
} }
func TestNoSchemaOverlayRun(t *testing.T) { func TestNoSchemaOverlayRun(t *testing.T) {
base := resource.ResourceCollection{ base := resmap.ResMap{
{ resource.NewResId(foo, "my-foo"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, &unstructured.Unstructured{
Name: "my-foo",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@@ -415,34 +387,29 @@ func TestNoSchemaOverlayRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
overlay := []*resource.Resource{ overlay := []*resource.Resource{
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "example.com/v1",
"apiVersion": "example.com/v1", "kind": "Foo",
"kind": "Foo", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "my-foo",
"name": "my-foo", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "bar": map[string]interface{}{
"bar": map[string]interface{}{ "B": nil,
"B": nil, "C": "Z",
"C": "Z",
},
}, },
}, },
}, },
}, },
),
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(foo, "my-foo"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, &unstructured.Unstructured{
Name: "my-foo",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@@ -456,8 +423,7 @@ func TestNoSchemaOverlayRun(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
lt, err := NewOverlayTransformer(overlay) lt, err := NewOverlayTransformer(overlay)
@@ -468,18 +434,15 @@ func TestNoSchemaOverlayRun(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if err = compareMap(base, expected); err != nil { if err = expected.ErrorIfNotEqual(base); err != nil {
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }
func TestNoSchemaMultiplePatches(t *testing.T) { func TestNoSchemaMultiplePatches(t *testing.T) {
base := resource.ResourceCollection{ base := resmap.ResMap{
{ resource.NewResId(foo, "my-foo"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, &unstructured.Unstructured{
Name: "my-foo",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@@ -493,54 +456,48 @@ func TestNoSchemaMultiplePatches(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
overlay := []*resource.Resource{ overlay := []*resource.Resource{
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "example.com/v1",
"apiVersion": "example.com/v1", "kind": "Foo",
"kind": "Foo", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "my-foo",
"name": "my-foo", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "bar": map[string]interface{}{
"bar": map[string]interface{}{ "B": nil,
"B": nil, "C": "Z",
"C": "Z",
},
}, },
}, },
}, },
}, },
{ ),
Data: &unstructured.Unstructured{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": "my-foo", "name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"C": "Z",
"D": "W",
}, },
"spec": map[string]interface{}{ "baz": map[string]interface{}{
"bar": map[string]interface{}{ "hello": "world",
"C": "Z",
"D": "W",
},
"baz": map[string]interface{}{
"hello": "world",
},
}, },
}, },
}, },
}, },
),
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(foo, "my-foo"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, &unstructured.Unstructured{
Name: "my-foo",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@@ -558,8 +515,7 @@ func TestNoSchemaMultiplePatches(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
lt, err := NewOverlayTransformer(overlay) lt, err := NewOverlayTransformer(overlay)
@@ -570,18 +526,15 @@ func TestNoSchemaMultiplePatches(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if err = compareMap(base, expected); err != nil { if err = expected.ErrorIfNotEqual(base); err != nil {
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }
func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) { func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) {
base := resource.ResourceCollection{ base := resmap.ResMap{
{ resource.NewResId(foo, "my-foo"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, &unstructured.Unstructured{
Name: "my-foo",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@@ -595,43 +548,38 @@ func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) {
}, },
}, },
}, },
}, }),
},
} }
overlay := []*resource.Resource{ overlay := []*resource.Resource{
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "example.com/v1",
"apiVersion": "example.com/v1", "kind": "Foo",
"kind": "Foo", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "my-foo",
"name": "my-foo", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "bar": map[string]interface{}{
"bar": map[string]interface{}{ "B": nil,
"B": nil, "C": "Z",
"C": "Z",
},
}, },
}, },
}, },
}, }),
{ resource.NewBehaviorlessResource(&unstructured.Unstructured{
Data: &unstructured.Unstructured{ Object: map[string]interface{}{
Object: map[string]interface{}{ "apiVersion": "example.com/v1",
"apiVersion": "example.com/v1", "kind": "Foo",
"kind": "Foo", "metadata": map[string]interface{}{
"metadata": map[string]interface{}{ "name": "my-foo",
"name": "my-foo", },
}, "spec": map[string]interface{}{
"spec": map[string]interface{}{ "bar": map[string]interface{}{
"bar": map[string]interface{}{ "C": "NOT_Z",
"C": "NOT_Z",
},
}, },
}, },
}, },
}, }),
} }
lt, err := NewOverlayTransformer(overlay) lt, err := NewOverlayTransformer(overlay)

View File

@@ -20,14 +20,14 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
// PathConfig contains the configuration of a field, including the GVK it ties to, // PathConfig contains the configuration of a field, including the gvk it ties to,
// path to the field, etc. // path to the field, etc.
type PathConfig struct { type PathConfig struct {
// If true, it will create the path if it is not found. // If true, it will create the path if it is not found.
CreateIfNotPresent bool CreateIfNotPresent bool
// The GVK that this path tied to. // The gvk that this path tied to.
// If unset, it applied to any GVK // If unset, it applied to any gvk
// If some fields are set, it applies to all matching GVK. // If some fields are set, it applies to all matching gvk.
GroupVersionKind *schema.GroupVersionKind GroupVersionKind *schema.GroupVersionKind
// Path to the field that will be munged. // Path to the field that will be munged.
Path []string Path []string
@@ -48,8 +48,8 @@ type PathConfig struct {
// } // }
type referencePathConfig struct { type referencePathConfig struct {
// referencedGVK is the GroupVersionKind that is referenced by // referencedGVK is the GroupVersionKind that is referenced by
// the PathConfig's GVK in the path of PathConfig.Path. // the PathConfig's gvk in the path of PathConfig.Path.
referencedGVK schema.GroupVersionKind referencedGVK schema.GroupVersionKind
// PathConfig is the GVK that is referencing the referencedGVK object's name. // PathConfig is the gvk that is referencing the referencedGVK object's name.
pathConfigs []PathConfig pathConfigs []PathConfig
} }

View File

@@ -20,7 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types" "github.com/kubernetes-sigs/kustomize/pkg/types"
) )
@@ -57,12 +57,12 @@ func NewNamePrefixTransformer(pc []PathConfig, np string) (Transformer, error) {
} }
// Transform prepends the name prefix. // Transform prepends the name prefix.
func (o *namePrefixTransformer) Transform(m resource.ResourceCollection) error { func (o *namePrefixTransformer) Transform(m resmap.ResMap) error {
for gvkn := range m { for id := range m {
obj := m[gvkn].Data obj := m[id].Unstruct()
objMap := obj.UnstructuredContent() objMap := obj.UnstructuredContent()
for _, path := range o.pathConfigs { for _, path := range o.pathConfigs {
if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) { if !types.SelectByGVK(id.Gvk(), path.GroupVersionKind) {
continue continue
} }
err := mutateField(objMap, path.Path, path.CreateIfNotPresent, o.addPrefix) err := mutateField(objMap, path.Path, path.CreateIfNotPresent, o.addPrefix)

View File

@@ -20,18 +20,15 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource" "github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
) )
func TestPrefixNameRun(t *testing.T) { func TestPrefixNameRun(t *testing.T) {
m := resource.ResourceCollection{ m := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -39,13 +36,9 @@ func TestPrefixNameRun(t *testing.T) {
"name": "cm1", "name": "cm1",
}, },
}, },
}, }),
}, resource.NewResId(cmap, "cm2"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm2",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -53,15 +46,11 @@ func TestPrefixNameRun(t *testing.T) {
"name": "cm2", "name": "cm2",
}, },
}, },
}, }),
},
} }
expected := resource.ResourceCollection{ expected := resmap.ResMap{
{ resource.NewResId(cmap, "cm1"): resource.NewBehaviorlessResource(
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, &unstructured.Unstructured{
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -69,13 +58,9 @@ func TestPrefixNameRun(t *testing.T) {
"name": "someprefix-cm1", "name": "someprefix-cm1",
}, },
}, },
}, }),
}, resource.NewResId(cmap, "cm2"): resource.NewBehaviorlessResource(
{ &unstructured.Unstructured{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm2",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@@ -83,8 +68,7 @@ func TestPrefixNameRun(t *testing.T) {
"name": "someprefix-cm2", "name": "someprefix-cm2",
}, },
}, },
}, }),
},
} }
npt, err := NewDefaultingNamePrefixTransformer("someprefix-") npt, err := NewDefaultingNamePrefixTransformer("someprefix-")
@@ -96,7 +80,7 @@ func TestPrefixNameRun(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = expected.ErrorIfNotEqual(m)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
} }
} }

View File

@@ -16,10 +16,10 @@ limitations under the License.
package transformers package transformers
import "github.com/kubernetes-sigs/kustomize/pkg/resource" import "github.com/kubernetes-sigs/kustomize/pkg/resmap"
// Transformer can transform objects. // Transformer can transform objects.
type Transformer interface { type Transformer interface {
// Transform modifies objects in a map, e.g. add prefixes or additional labels. // Transform modifies objects in a map, e.g. add prefixes or additional labels.
Transform(m resource.ResourceCollection) error Transform(m resmap.ResMap) error
} }

View File

@@ -1,49 +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 transformers
import (
"fmt"
"reflect"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
func compareMap(m1, m2 resource.ResourceCollection) error {
if len(m1) != len(m2) {
keySet1 := []types.GroupVersionKindName{}
keySet2 := []types.GroupVersionKindName{}
for GVKn := range m1 {
keySet1 = append(keySet1, GVKn)
}
for GVKn := range m1 {
keySet2 = append(keySet2, GVKn)
}
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
}
for GVKn, obj1 := range m1 {
obj2, found := m2[GVKn]
if !found {
return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2)
}
if !reflect.DeepEqual(obj1.Data, obj2.Data) {
return fmt.Errorf("%#v doesn't match %#v", obj1.Data, obj2.Data)
}
}
return nil
}

View File

@@ -67,7 +67,7 @@ type ConfigMapArgs struct {
// hash(content of configmap). // hash(content of configmap).
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Behavior of configmap, must be one of create, merge and replace // behavior of configmap, must be one of create, merge and replace
// 'create': create a new one; // 'create': create a new one;
// 'replace': replace the existing one; // 'replace': replace the existing one;
// 'merge': merge the existing one. // 'merge': merge the existing one.
@@ -84,7 +84,7 @@ type SecretArgs struct {
// hash(content of secret). // hash(content of secret).
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Behavior of secretGenerator, must be one of create, merge and replace // behavior of secretGenerator, must be one of create, merge and replace
// 'create': create a new one; // 'create': create a new one;
// 'replace': replace the existing one; // 'replace': replace the existing one;
// 'merge': merge the existing one. // 'merge': merge the existing one.

View File

@@ -17,25 +17,16 @@ limitations under the License.
package types package types
import ( import (
"strings"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
func (gvkn GroupVersionKindName) String() string {
if gvkn.GVK.Group == "" {
return strings.Join([]string{gvkn.GVK.Version, gvkn.GVK.Kind, gvkn.Name}, "_") + ".yaml"
}
return strings.Join([]string{gvkn.GVK.Group, gvkn.GVK.Version, gvkn.GVK.Kind, gvkn.Name}, "_") + ".yaml"
}
// SelectByGVK returns true if `selector` selects `in`; otherwise, false. // SelectByGVK returns true if `selector` selects `in`; otherwise, false.
// If `selector` and `in` are the same, return true. // If `selector` and `in` are the same, return true.
// If `selector` is nil, it is considered as a wildcard and always return true. // If `selector` is nil, it is considered as a wildcard and always return true.
// e.g. selector <Group: "", Version: "", Kind: "Deployemt"> CAN select // e.g. selector <Group: "", Version: "", Kind: "Deployment"> CAN select
// <Group: "extensions", Version: "v1beta1", Kind: "Deployemt">. // <Group: "extensions", Version: "v1beta1", Kind: "Deployment">.
// selector <Group: "apps", Version: "", Kind: "Deployemt"> CANNOT select // selector <Group: "apps", Version: "", Kind: "Deployment"> CANNOT select
// <Group: "extensions", Version: "v1beta1", Kind: "Deployemt">. // <Group: "extensions", Version: "v1beta1", Kind: "Deployment">.
func SelectByGVK(in schema.GroupVersionKind, selector *schema.GroupVersionKind) bool { func SelectByGVK(in schema.GroupVersionKind, selector *schema.GroupVersionKind) bool {
if selector == nil { if selector == nil {
return true return true

View File

@@ -22,7 +22,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
func TestFilterByGVK(t *testing.T) { func TestSelectByGVK(t *testing.T) {
type testCase struct { type testCase struct {
description string description string
in schema.GroupVersionKind in schema.GroupVersionKind
@@ -37,7 +37,7 @@ func TestFilterByGVK(t *testing.T) {
expected: true, expected: true,
}, },
{ {
description: "GVK matches", description: "gvk matches",
in: schema.GroupVersionKind{ in: schema.GroupVersionKind{
Group: "group1", Group: "group1",
Version: "version1", Version: "version1",