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"
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
"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/transformers"
"github.com/kubernetes-sigs/kustomize/pkg/types"
@@ -32,14 +33,14 @@ import (
type Application interface {
// 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() (resource.ResourceCollection, error)
SemiResources() (resmap.ResMap, error)
// RawResources computes and returns the raw resources from the kustomization file.
// It contains resources from
// 1) untransformed resources from current kustomization file
// 2) transformed resources from sub packages
RawResources() (resource.ResourceCollection, error)
RawResources() (resmap.ResMap, error)
}
var _ Application = &applicationImpl{}
@@ -68,7 +69,7 @@ func New(loader loader.Loader) (Application, error) {
// Resources computes and returns the resources from the kustomization file.
// The namehashing for configmap/secrets and resolving name reference is only done
// 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()
if err != nil {
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
func (a *applicationImpl) SemiResources() (resource.ResourceCollection, error) {
func (a *applicationImpl) SemiResources() (resmap.ResMap, error) {
errs := &interror.KustomizationErrors{}
raw, err := a.rawResources()
if err != nil {
errs.Append(err)
}
cms, err := resource.NewFromConfigMaps(a.loader, a.kustomization.ConfigMapGenerator)
cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
if err != nil {
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 {
errs.Append(err)
}
res, err := resource.Merge(cms, secrets)
res, err := resmap.Merge(cms, secrets)
if err != nil {
return nil, err
}
allRes, err := resource.MergeWithOverride(raw, res)
allRes, err := resmap.MergeWithOverride(raw, res)
if err != nil {
return nil, err
}
patches, err := resource.NewFromPatches(a.loader, a.kustomization.Patches)
patches, err := resmap.NewResourceSliceFromPatches(a.loader, a.kustomization.Patches)
if err != nil {
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.
// The namehashing for configmap/secrets and resolving name reference is only done
// 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()
if err != nil {
return nil, err
@@ -150,9 +151,9 @@ func (a *applicationImpl) RawResources() (resource.ResourceCollection, error) {
return res, nil
}
func (a *applicationImpl) rawResources() (resource.ResourceCollection, error) {
func (a *applicationImpl) rawResources() (resmap.ResMap, error) {
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 {
errs.Append(err)
}
@@ -161,11 +162,11 @@ func (a *applicationImpl) rawResources() (resource.ResourceCollection, error) {
return nil, errs
}
return resource.Merge(resources, subAppResources)
return resmap.Merge(resources, subAppResources)
}
func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *interror.KustomizationErrors) {
sliceOfSubAppResources := []resource.ResourceCollection{}
func (a *applicationImpl) subAppResources() (resmap.ResMap, *interror.KustomizationErrors) {
sliceOfSubAppResources := []resmap.ResMap{}
errs := &interror.KustomizationErrors{}
for _, pkgPath := range a.kustomization.Bases {
subloader, err := a.loader.New(pkgPath)
@@ -186,7 +187,7 @@ func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *inter
}
sliceOfSubAppResources = append(sliceOfSubAppResources, subAppResources)
}
allResources, err := resource.Merge(sliceOfSubAppResources...)
allResources, err := resmap.Merge(sliceOfSubAppResources...)
if err != nil {
errs.Append(err)
}

View File

@@ -18,15 +18,14 @@ package app
import (
"encoding/base64"
"fmt"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"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/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -71,13 +70,14 @@ metadata:
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) {
expected := resource.ResourceCollection{
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "dply1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
@@ -108,13 +108,9 @@ func TestResources(t *testing.T) {
},
},
},
},
},
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "literalConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
}),
resource.NewResId(cmap, "literalConfigMap"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -133,13 +129,9 @@ func TestResources(t *testing.T) {
"DB_PASSWORD": "somepw",
},
},
},
},
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret",
}: &resource.Resource{
Data: &unstructured.Unstructured{
}),
resource.NewResId(secret, "secret"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
@@ -159,8 +151,7 @@ func TestResources(t *testing.T) {
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
},
},
},
}),
}
l := setupTest(t)
app, err := New(l)
@@ -173,18 +164,15 @@ func TestResources(t *testing.T) {
}
if !reflect.DeepEqual(actual, expected) {
err = compareMap(actual, expected)
err = expected.ErrorIfNotEqual(actual)
t.Fatalf("unexpected error: %v", err)
}
}
func TestRawResources(t *testing.T) {
expected := resource.ResourceCollection{
types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "dply1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
expected := resmap.ResMap{
resource.NewResId(deploy, "dply1"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
@@ -192,8 +180,7 @@ func TestRawResources(t *testing.T) {
"name": "dply1",
},
},
},
},
}),
}
l := setupTest(t)
app, err := New(l)
@@ -205,31 +192,7 @@ func TestRawResources(t *testing.T) {
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)
}
}
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"
"github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
"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
}
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) {
fi, err := os.Stat(wd)
if err != nil || !fi.IsDir() {

View File

@@ -5,12 +5,11 @@ import (
"io"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
)
// RunDiff runs system diff program to compare two ResourceCollections.
func RunDiff(raw, transformed resource.ResourceCollection,
out, errOut io.Writer) error {
// RunDiff runs system diff program to compare two Maps.
func RunDiff(raw, transformed resmap.ResMap, out, errOut io.Writer) error {
transformedDir, err := writeYamlToNewDir(transformed, "transformed")
if err != nil {
return err
@@ -26,10 +25,10 @@ func RunDiff(raw, transformed resource.ResourceCollection,
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.
// 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)
if err != nil {
return nil, err
@@ -40,7 +39,7 @@ func writeYamlToNewDir(in resource.ResourceCollection, prefix string) (*director
if err != nil {
return nil, err
}
err = print(obj.Data, f)
err = print(obj.Unstruct(), f)
f.Close()
if err != nil {
return nil, err

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resource
package resmap
import (
"fmt"
@@ -22,22 +22,23 @@ import (
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
"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)
if err != nil {
return nil, err
}
data, err := objectToUnstructured(corev1CM)
data, err := newUnstructuredFromObject(corev1CM)
if err != nil {
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) {
@@ -130,15 +131,15 @@ func addKV(m map[string]string, kv kvPair) error {
return nil
}
// NewFromConfigMaps returns a Resource slice given a configmap metadata slice from kustomization file.
func NewFromConfigMaps(loader loader.Loader, cmList []types.ConfigMapArgs) (ResourceCollection, error) {
allResources := []*Resource{}
// NewResMapFromConfigMapArgs returns a Resource slice given a configmap metadata slice from kustomization file.
func NewResMapFromConfigMapArgs(loader loader.Loader, cmList []types.ConfigMapArgs) (ResMap, error) {
allResources := []*resource.Resource{}
for _, cm := range cmList {
res, err := newFromConfigMap(loader, cm)
res, err := newResourceFromConfigMap(loader, cm)
if err != nil {
return nil, err
}
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.
*/
package resource_test
package resmap
import (
"reflect"
@@ -27,13 +27,15 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
var cmap = schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}
func TestNewFromConfigMaps(t *testing.T) {
type testCase struct {
description string
input []types.ConfigMapArgs
filepath string
content string
expected resource.ResourceCollection
expected ResMap
}
l := loadertest.NewFakeLoader("/home/seans/project/")
@@ -50,12 +52,9 @@ func TestNewFromConfigMaps(t *testing.T) {
},
filepath: "/home/seans/project/app.env",
content: "DB_USERNAME=admin\nDB_PASSWORD=somepw",
expected: resource.ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "envConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
expected: ResMap{
resource.NewResId(cmap, "envConfigMap"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -68,8 +67,7 @@ func TestNewFromConfigMaps(t *testing.T) {
"DB_PASSWORD": "somepw",
},
},
},
},
}),
},
},
{
@@ -83,12 +81,9 @@ func TestNewFromConfigMaps(t *testing.T) {
},
filepath: "/home/seans/project/app-init.ini",
content: "FOO=bar\nBAR=baz\n",
expected: resource.ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "fileConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
expected: ResMap{
resource.NewResId(cmap, "fileConfigMap"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -102,8 +97,7 @@ BAR=baz
`,
},
},
},
},
}),
},
},
{
@@ -116,12 +110,9 @@ BAR=baz
},
},
},
expected: resource.ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "literalConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{
expected: ResMap{
resource.NewResId(cmap, "literalConfigMap"): resource.NewBehaviorlessResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
@@ -134,8 +125,7 @@ BAR=baz
"b": "y",
},
},
},
},
}),
},
},
// 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 {
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 {
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.
*/
package types
package resmap
import (
"sort"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
)
// ByGVKN implements the sort interface.
type ByGVKN []GroupVersionKindName
// IdSlice implements the sort interface.
type IdSlice []resource.ResId
var _ sort.Interface = ByGVKN{}
var _ sort.Interface = IdSlice{}
func (a ByGVKN) Len() int { return len(a) }
func (a ByGVKN) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByGVKN) Less(i, j int) bool {
if a[i].GVK.String() != a[j].GVK.String() {
return a[i].GVK.String() < a[j].GVK.String()
func (a IdSlice) Len() int { return len(a) }
func (a IdSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a IdSlice) Less(i, j int) bool {
if 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.
*/
package resource
package resmap
import (
"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
limitations under the License.
*/
package resource
package resmap
import (
"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.
*/
package resource
package resmap
import (
"context"
@@ -23,11 +23,12 @@ import (
"path/filepath"
"time"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
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.APIVersion = "v1"
corev1secret.Kind = "Secret"
@@ -46,13 +47,13 @@ func newFromSecretGenerator(p string, s types.SecretArgs) (*Resource, error) {
corev1secret.Data[k] = out
}
obj, err := objectToUnstructured(corev1secret)
obj, err := newUnstructuredFromObject(corev1secret)
if err != nil {
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) {
@@ -68,10 +69,10 @@ func createSecretKey(wd string, command string) ([]byte, error) {
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.
func NewFromSecretGenerators(p string, secretList []types.SecretArgs) (ResourceCollection, error) {
allResources := []*Resource{}
func NewResMapFromSecretArgs(p string, secretList []types.SecretArgs) (ResMap, error) {
allResources := []*resource.Resource{}
for _, secret := range secretList {
res, err := newFromSecretGenerator(p, secret)
if err != nil {
@@ -79,5 +80,5 @@ func NewFromSecretGenerators(p string, secretList []types.SecretArgs) (ResourceC
}
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.
*/
package resource
package resmap
import (
"encoding/base64"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
func TestNewFromSecretGenerators(t *testing.T) {
secrets := []types.SecretArgs{
{
@@ -38,17 +41,14 @@ func TestNewFromSecretGenerators(t *testing.T) {
Type: "Opaque",
},
}
re, err := NewFromSecretGenerators(".", secrets)
re, err := NewResMapFromSecretArgs(".", secrets)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret",
}: &Resource{
Data: &unstructured.Unstructured{
expected := ResMap{
resource.NewResId(secret, "secret"): resource.NewResource(
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
@@ -63,7 +63,7 @@ func TestNewFromSecretGenerators(t *testing.T) {
},
},
},
},
""),
}
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.
*/
package types
package resource
import (
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupVersionKindName contains GroupVersionKind and original name of the resource.
type GroupVersionKindName struct {
// ResId conflates GroupVersionKind with a textual name to uniquely identify a kubernetes resource (object).
type ResId struct {
// GroupVersionKind of the resource.
GVK schema.GroupVersionKind
gvk schema.GroupVersionKind
// 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
import (
"bytes"
"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/runtime"
)
// Resource represents a Kubernetes Resource Object for ex. Deployment, Server
// ConfigMap etc.
// Resource is a Kubernetes Resource Object paired with a behavior.
type Resource struct {
Data *unstructured.Unstructured
Behavior string
unstruct *unstructured.Unstructured
behavior string
}
// GVKN returns Group/Version/Kind/Name for the resource.
func (r *Resource) GVKN() types.GroupVersionKindName {
var emptyZVKN types.GroupVersionKindName
if r.Data == nil {
return emptyZVKN
// NewResource returns a new instance of Resource.
func NewResource(u *unstructured.Unstructured, b string) *Resource {
return &Resource{unstruct: u, behavior: b}
}
// 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()
return types.GroupVersionKindName{GVK: gvk, Name: r.Data.GetName()}
gvk := r.unstruct.GroupVersionKind()
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)
if err != nil {
return nil, err
@@ -54,38 +108,3 @@ func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error)
err = out.UnmarshalJSON(marshaled)
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"
"fmt"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"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
// fields specified in mapTransformer.
func (o *mapTransformer) Transform(m resource.ResourceCollection) error {
for gvkn := range m {
obj := m[gvkn].Data
func (o *mapTransformer) Transform(m resmap.ResMap) error {
for id := range m {
obj := m[id].Unstruct()
objMap := obj.UnstructuredContent()
for _, path := range o.pathConfigs {
if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) {
if !types.SelectByGVK(id.Gvk(), path.GroupVersionKind) {
continue
}
err := mutateField(objMap, path.Path, path.CreateIfNotPresent, o.addMap)

View File

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

View File

@@ -16,7 +16,7 @@ limitations under the License.
package transformers
import "github.com/kubernetes-sigs/kustomize/pkg/resource"
import "github.com/kubernetes-sigs/kustomize/pkg/resmap"
// multiTransformer contains a list of transformers.
type multiTransformer struct {
@@ -34,7 +34,7 @@ func NewMultiTransformer(t []Transformer) Transformer {
}
// 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 {
err := t.Transform(m)
if err != nil {

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ import (
"errors"
"fmt"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"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,
// then the old name is k.Name and the new name is v.GetName()
func (o *nameReferenceTransformer) Transform(
m resource.ResourceCollection) error {
for GVKn := range m {
obj := m[GVKn].Data
m resmap.ResMap) error {
for id := range m {
obj := m[id].Unstruct()
objMap := obj.UnstructuredContent()
for _, referencePathConfig := range o.pathConfigs {
for _, path := range referencePathConfig.pathConfigs {
if !types.SelectByGVK(GVKn.GVK, path.GroupVersionKind) {
if !types.SelectByGVK(id.Gvk(), path.GroupVersionKind) {
continue
}
err := mutateField(objMap, path.Path, path.CreateIfNotPresent,
@@ -71,25 +71,9 @@ func (o *nameReferenceTransformer) Transform(
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(
GVK schema.GroupVersionKind,
m resource.ResourceCollection,
m resmap.ResMap,
) func(in interface{}) (interface{}, error) {
return func(in interface{}) (interface{}, error) {
s, ok := in.(string)
@@ -97,12 +81,12 @@ func (o *nameReferenceTransformer) updateNameReference(
return nil, fmt.Errorf("%#v is expectd to be %T", in, s)
}
for GVKn, obj := range m {
if !types.SelectByGVK(GVKn.GVK, &GVK) {
for id, obj := range m {
if !types.SelectByGVK(id.Gvk(), &GVK) {
continue
}
if GVKn.Name == s {
return obj.Data.GetName(), nil
if id.Name() == s {
return obj.Unstruct().GetName(), nil
}
}
return in, nil

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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.
func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollection) error {
// Merge and then index the patches by GVKN.
func (o *overlayTransformer) Transform(baseResourceMap resmap.ResMap) error {
// Merge and then index the patches by Id.
overlays, err := o.mergePatches()
if err != nil {
return err
@@ -55,22 +56,22 @@ func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollecti
// Strategic merge the resources exist in both base and overlay.
for _, overlay := range overlays {
// Merge overlay with base resource.
gvkn := overlay.GVKN()
base, found := baseResourceMap[gvkn]
id := overlay.Id()
base, found := baseResourceMap[id]
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{}{}
versionedObj, err := scheme.Scheme.New(gvkn.GVK)
baseName := base.Data.GetName()
versionedObj, err := scheme.Scheme.New(id.Gvk())
baseName := base.Unstruct().GetName()
switch {
case runtime.IsNotRegisteredError(err):
// 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 {
return err
}
patchBytes, err := json.Marshal(overlay.Data)
patchBytes, err := json.Marshal(overlay.Unstruct())
if err != nil {
return err
}
@@ -94,33 +95,33 @@ func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollecti
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
base.Data.Object,
overlay.Data.Object,
base.Unstruct().Object,
overlay.Unstruct().Object,
lookupPatchMeta)
if err != nil {
return err
}
}
base.Data.SetName(baseName)
baseResourceMap[gvkn].Data.Object = merged
base.Unstruct().SetName(baseName)
baseResourceMap[id].Unstruct().Object = merged
}
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.
func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error) {
rc := resource.ResourceCollection{}
func (o *overlayTransformer) mergePatches() (resmap.ResMap, error) {
rc := resmap.ResMap{}
patches := resourcesToObjects(o.overlay)
for ix, patch := range o.overlay {
gvkn := patch.GVKN()
existing, found := rc[gvkn]
id := patch.Id()
existing, found := rc[id]
if !found {
rc[gvkn] = patch
rc[id] = patch
continue
}
versionedObj, err := scheme.Scheme.New(gvkn.GVK)
versionedObj, err := scheme.Scheme.New(id.Gvk())
if err != nil && !runtime.IsNotRegisteredError(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 {
return nil, err
}
@@ -143,13 +144,13 @@ func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error)
if err != nil {
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 {
merged, err := cd.mergePatches(existing.Data, patch.Data)
merged, err := cd.mergePatches(existing.Unstruct(), patch.Unstruct())
if err != nil {
return nil, err
}
existing.Data = merged
existing.SetUnstruct(merged)
}
}
return rc, nil
@@ -158,7 +159,7 @@ func (o *overlayTransformer) mergePatches() (resource.ResourceCollection, error)
func resourcesToObjects(rs []*resource.Resource) []*unstructured.Unstructured {
objectList := make([]*unstructured.Unstructured, len(rs))
for i := range rs {
objectList[i] = rs[i].Data
objectList[i] = rs[i].Unstruct()
}
return objectList
}

View File

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

View File

@@ -20,14 +20,14 @@ import (
"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.
type PathConfig struct {
// If true, it will create the path if it is not found.
CreateIfNotPresent bool
// The GVK that this path tied to.
// If unset, it applied to any GVK
// If some fields are set, it applies to all matching GVK.
// The gvk that this path tied to.
// If unset, it applied to any gvk
// If some fields are set, it applies to all matching gvk.
GroupVersionKind *schema.GroupVersionKind
// Path to the field that will be munged.
Path []string
@@ -48,8 +48,8 @@ type PathConfig struct {
// }
type referencePathConfig struct {
// 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
// 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
}

View File

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

View File

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

View File

@@ -16,10 +16,10 @@ limitations under the License.
package transformers
import "github.com/kubernetes-sigs/kustomize/pkg/resource"
import "github.com/kubernetes-sigs/kustomize/pkg/resmap"
// Transformer can transform objects.
type Transformer interface {
// 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).
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;
// 'replace': replace the existing one;
// 'merge': merge the existing one.
@@ -84,7 +84,7 @@ type SecretArgs struct {
// hash(content of secret).
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;
// 'replace': replace the existing one;
// 'merge': merge the existing one.

View File

@@ -17,25 +17,16 @@ limitations under the License.
package types
import (
"strings"
"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.
// If `selector` and `in` are the same, 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
// <Group: "extensions", Version: "v1beta1", Kind: "Deployemt">.
// selector <Group: "apps", Version: "", Kind: "Deployemt"> CANNOT select
// <Group: "extensions", Version: "v1beta1", Kind: "Deployemt">.
// e.g. selector <Group: "", Version: "", Kind: "Deployment"> CAN select
// <Group: "extensions", Version: "v1beta1", Kind: "Deployment">.
// selector <Group: "apps", Version: "", Kind: "Deployment"> CANNOT select
// <Group: "extensions", Version: "v1beta1", Kind: "Deployment">.
func SelectByGVK(in schema.GroupVersionKind, selector *schema.GroupVersionKind) bool {
if selector == nil {
return true

View File

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