diff --git a/pkg/kinflate/app/application.go b/pkg/kinflate/app/application.go index 2a21c44a1..ef4b8fa0f 100644 --- a/pkg/kinflate/app/application.go +++ b/pkg/kinflate/app/application.go @@ -17,8 +17,6 @@ limitations under the License. package app import ( - "fmt" - "github.com/ghodss/yaml" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" @@ -26,16 +24,15 @@ import ( interror "k8s.io/kubectl/pkg/kinflate/internal/error" "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/transformers" - "k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/loader" ) type Application interface { // Resources computes and returns the resources for the app. - Resources() (types.ResourceCollection, error) + Resources() (resource.ResourceCollection, error) // RawResources computes and returns the raw resources from the manifest. // It contains resources from 1) untransformed resources from current manifest 2) transformed resources from sub packages - RawResources() (types.ResourceCollection, error) + RawResources() (resource.ResourceCollection, error) } var _ Application = &applicationImpl{} @@ -63,27 +60,22 @@ func New(loader loader.Loader) (Application, error) { } // Resources computes and returns the resources from the manifest. -func (a *applicationImpl) Resources() (types.ResourceCollection, error) { +func (a *applicationImpl) Resources() (resource.ResourceCollection, error) { errs := &interror.ManifestErrors{} raw, err := a.RawResources() if err != nil { errs.Append(err) } - resources := []*resource.Resource{} cms, err := resource.NewFromConfigMaps(a.loader, a.manifest.Configmaps) if err != nil { errs.Append(err) - } else { - resources = append(resources, cms...) } secrets, err := resource.NewFromSecretGenerators(a.loader.Root(), a.manifest.SecretGenerators) if err != nil { errs.Append(err) - } else { - resources = append(resources, secrets...) } - res, err := resourceCollectionFromResources(resources) + res, err := resource.Merge(cms, secrets) if err != nil { return nil, err } @@ -94,12 +86,7 @@ func (a *applicationImpl) Resources() (types.ResourceCollection, error) { return nil, err } - ps, err := resource.NewFromPaths(a.loader, a.manifest.Patches) - if err != nil { - errs.Append(err) - } - // TODO: remove this func after migrating to ResourceCollection - patches, err := resourceCollectionFromResources(ps) + patches, err := resource.NewFromPaths(a.loader, a.manifest.Patches) if err != nil { errs.Append(err) } @@ -108,7 +95,7 @@ func (a *applicationImpl) Resources() (types.ResourceCollection, error) { return nil, errs } - err = types.Merge(res, raw) + allRes, err := resource.Merge(res, raw) if err != nil { return nil, err } @@ -117,35 +104,30 @@ func (a *applicationImpl) Resources() (types.ResourceCollection, error) { if err != nil { return nil, err } - err = t.Transform(res) + err = t.Transform(allRes) if err != nil { return nil, err } - return res, nil + return allRes, nil } // RawResources computes and returns the raw resources from the manifest. -func (a *applicationImpl) RawResources() (types.ResourceCollection, error) { +func (a *applicationImpl) RawResources() (resource.ResourceCollection, error) { subAppResources, errs := a.subAppResources() - allRes, err := resource.NewFromPaths(a.loader, a.manifest.Resources) - if err != nil { - errs.Append(err) - } - // TODO: remove this func after migrating to ResourceCollection - allResources, err := resourceCollectionFromResources(allRes) + resources, err := resource.NewFromPaths(a.loader, a.manifest.Resources) if err != nil { errs.Append(err) } + if len(errs.Get()) > 0 { return nil, errs } - err = types.Merge(allResources, subAppResources) - return allResources, err + return resource.Merge(resources, subAppResources) } -func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror.ManifestErrors) { - allResources := types.ResourceCollection{} +func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *interror.ManifestErrors) { + sliceOfSubAppResources := []resource.ResourceCollection{} errs := &interror.ManifestErrors{} for _, pkgPath := range a.manifest.Packages { subloader, err := a.loader.New(pkgPath) @@ -164,7 +146,11 @@ func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror errs.Append(err) continue } - types.Merge(allResources, subAppResources) + sliceOfSubAppResources = append(sliceOfSubAppResources, subAppResources) + } + allResources, err := resource.Merge(sliceOfSubAppResources...) + if err != nil { + errs.Append(err) } return allResources, errs } @@ -175,7 +161,7 @@ func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror // 3) apply labels // 4) apply annotations // 5) update name reference -func (a *applicationImpl) getTransformer(patches types.ResourceCollection) (transformers.Transformer, error) { +func (a *applicationImpl) getTransformer(patches resource.ResourceCollection) (transformers.Transformer, error) { ts := []transformers.Transformer{} ot, err := transformers.NewOverlayTransformer(patches) @@ -209,15 +195,3 @@ func (a *applicationImpl) getTransformer(patches types.ResourceCollection) (tran ts = append(ts, nrt) return transformers.NewMultiTransformer(ts), nil } - -func resourceCollectionFromResources(res []*resource.Resource) (types.ResourceCollection, error) { - out := types.ResourceCollection{} - for _, res := range res { - gvkn := res.GVKN() - if _, found := out[gvkn]; found { - return nil, fmt.Errorf("duplicated %#v is not allowed", gvkn) - } - out[gvkn] = res.Data - } - return out, nil -} diff --git a/pkg/kinflate/app/application_test.go b/pkg/kinflate/app/application_test.go index c7f4330f3..aad297246 100644 --- a/pkg/kinflate/app/application_test.go +++ b/pkg/kinflate/app/application_test.go @@ -25,6 +25,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/loader" "k8s.io/kubectl/pkg/loader/loadertest" @@ -74,38 +75,40 @@ metadata: } func TestResources(t *testing.T) { - expected := types.ResourceCollection{ + expected := resource.ResourceCollection{ types.GroupVersionKindName{ GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "dply1", - }: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "foo-dply1", - "labels": map[string]interface{}{ - "app": "nginx", - }, - "annotations": map[string]interface{}{ - "note": "This is a test annotation", - }, - }, - "spec": map[string]interface{}{ - "selector": map[string]interface{}{ - "matchLabels": map[string]interface{}{ + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "foo-dply1", + "labels": map[string]interface{}{ "app": "nginx", }, + "annotations": map[string]interface{}{ + "note": "This is a test annotation", + }, }, - "template": map[string]interface{}{ - "metadata": map[string]interface{}{ - "annotations": map[string]interface{}{ - "note": "This is a test annotation", - }, - "labels": map[string]interface{}{ + "spec": map[string]interface{}{ + "selector": map[string]interface{}{ + "matchLabels": map[string]interface{}{ "app": "nginx", }, }, + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "note": "This is a test annotation", + }, + "labels": map[string]interface{}{ + "app": "nginx", + }, + }, + }, }, }, }, @@ -113,47 +116,51 @@ func TestResources(t *testing.T) { types.GroupVersionKindName{ GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "literalConfigMap", - }: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{ - "name": "foo-literalConfigMap-h25f8c59t4", - "labels": map[string]interface{}{ - "app": "nginx", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "foo-literalConfigMap-h25f8c59t4", + "labels": map[string]interface{}{ + "app": "nginx", + }, + "annotations": map[string]interface{}{ + "note": "This is a test annotation", + }, + "creationTimestamp": nil, }, - "annotations": map[string]interface{}{ - "note": "This is a test annotation", + "data": map[string]interface{}{ + "DB_USERNAME": "admin", + "DB_PASSWORD": "somepw", }, - "creationTimestamp": nil, - }, - "data": map[string]interface{}{ - "DB_USERNAME": "admin", - "DB_PASSWORD": "somepw", }, }, }, types.GroupVersionKindName{ GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, Name: "secret", - }: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": "foo-secret-2c9kh7fh8t", - "labels": map[string]interface{}{ - "app": "nginx", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "foo-secret-2c9kh7fh8t", + "labels": map[string]interface{}{ + "app": "nginx", + }, + "annotations": map[string]interface{}{ + "note": "This is a test annotation", + }, + "creationTimestamp": nil, }, - "annotations": map[string]interface{}{ - "note": "This is a test annotation", + "type": string(corev1.SecretTypeOpaque), + "data": map[string]interface{}{ + "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")), + "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), }, - "creationTimestamp": nil, - }, - "type": string(corev1.SecretTypeOpaque), - "data": map[string]interface{}{ - "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")), - "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), }, }, }, @@ -175,16 +182,18 @@ func TestResources(t *testing.T) { } func TestRawResources(t *testing.T) { - expected := types.ResourceCollection{ + expected := resource.ResourceCollection{ types.GroupVersionKindName{ GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "dply1", - }: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "dply1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "dply1", + }, }, }, }, @@ -204,7 +213,7 @@ func TestRawResources(t *testing.T) { } } -func compareMap(m1, m2 types.ResourceCollection) error { +func compareMap(m1, m2 resource.ResourceCollection) error { if len(m1) != len(m2) { keySet1 := []types.GroupVersionKindName{} keySet2 := []types.GroupVersionKindName{} diff --git a/pkg/kinflate/configmapandsecret/configmap_secret.go b/pkg/kinflate/configmapandsecret/configmap_secret.go index d67203ade..031d07829 100644 --- a/pkg/kinflate/configmapandsecret/configmap_secret.go +++ b/pkg/kinflate/configmapandsecret/configmap_secret.go @@ -26,12 +26,12 @@ import ( "time" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" cutil "k8s.io/kubectl/pkg/kinflate/configmapandsecret/util" "k8s.io/kubectl/pkg/kinflate/hash" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) @@ -123,26 +123,22 @@ func makeSecret(secret manifest.SecretGenerator, path string) (*corev1.Secret, e return corev1secret, nil } -func populateMap(m types.ResourceCollection, obj *unstructured.Unstructured, newName string) error { - accessor, err := meta.Accessor(obj) - if err != nil { - return err - } - oldName := accessor.GetName() - gvk := obj.GetObjectKind().GroupVersionKind() +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 already exists in the map", oldName, gvk) } - accessor.SetName(newName) - m[gvkn] = obj + obj.SetName(newName) + m[gvkn] = &resource.Resource{Data: obj} return nil } // MakeConfigMapsResourceCollection returns a map of -> unstructured object. -func MakeConfigMapsResourceCollection(maps []manifest.ConfigMap) (types.ResourceCollection, error) { - m := types.ResourceCollection{} +func MakeConfigMapsResourceCollection(maps []manifest.ConfigMap) (resource.ResourceCollection, error) { + m := resource.ResourceCollection{} for _, cm := range maps { unstructuredConfigMap, nameWithHash, err := MakeConfigmapAndGenerateName(cm) if err != nil { @@ -157,8 +153,8 @@ func MakeConfigMapsResourceCollection(maps []manifest.ConfigMap) (types.Resource } // MakeSecretsResourceCollection returns a map of -> unstructured object. -func MakeSecretsResourceCollection(secrets []manifest.SecretGenerator, path string) (types.ResourceCollection, error) { - m := types.ResourceCollection{} +func MakeSecretsResourceCollection(secrets []manifest.SecretGenerator, path string) (resource.ResourceCollection, error) { + m := resource.ResourceCollection{} for _, secret := range secrets { unstructuredSecret, nameWithHash, err := MakeSecretAndGenerateName(secret, path) if err != nil { diff --git a/pkg/kinflate/resource/appresource.go b/pkg/kinflate/resource/appresource.go index 497e2dd3e..43b5d72f7 100644 --- a/pkg/kinflate/resource/appresource.go +++ b/pkg/kinflate/resource/appresource.go @@ -17,37 +17,27 @@ limitations under the License. package resource import ( - kutil "k8s.io/kubectl/pkg/kinflate/util" "k8s.io/kubectl/pkg/loader" ) -func resourcesFromPath(loader loader.Loader, path string) ([]*Resource, error) { +func resourcesFromPath(loader loader.Loader, path string) (ResourceCollection, error) { content, err := loader.Load(path) if err != nil { return nil, err } - objs, err := kutil.Decode(content) - if err != nil { - return nil, err - } - - var res []*Resource - for _, obj := range objs { - res = append(res, &Resource{Data: obj}) - } - return res, nil + return decodeToResourceCollection(content) } // NewFromPaths returns a slice of Resources given a resource path slice from manifest file. -func NewFromPaths(loader loader.Loader, paths []string) ([]*Resource, error) { - allResources := []*Resource{} +func NewFromPaths(loader loader.Loader, paths []string) (ResourceCollection, error) { + allResources := []ResourceCollection{} for _, path := range paths { res, err := resourcesFromPath(loader, path) if err != nil { return nil, err } - allResources = append(allResources, res...) + allResources = append(allResources, res) } - return allResources, nil + return Merge(allResources...) } diff --git a/pkg/kinflate/resource/appresource_test.go b/pkg/kinflate/resource/appresource_test.go index 2fd3e0751..081bfad80 100644 --- a/pkg/kinflate/resource/appresource_test.go +++ b/pkg/kinflate/resource/appresource_test.go @@ -1,34 +1,40 @@ +/* +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" - "reflect" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/loader/loadertest" ) -func makeUnconstructed(name string) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": name, - }, - }, - } -} - func TestNewFromPaths(t *testing.T) { - resourceStr := `apiVersion: v1 + resourceStr := `apiVersion: apps/v1 kind: Deployment metadata: name: dply1 --- -apiVersion: v1 +apiVersion: apps/v1 kind: Deployment metadata: name: dply2 @@ -38,9 +44,35 @@ metadata: if ferr := l.AddFile("/home/seans/project/deployment.yaml", []byte(resourceStr)); ferr != nil { t.Fatalf("Error adding fake file: %v\n", ferr) } - expected := []*Resource{ - {Data: makeUnconstructed("dply1")}, - {Data: makeUnconstructed("dply2")}, + 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, _ := NewFromPaths(l, []string{"/home/seans/project/deployment.yaml"}) @@ -48,9 +80,31 @@ metadata: t.Fatalf("%#v should contain 2 appResource, but got %d", resources, len(resources)) } - for i, r := range resources { - if !reflect.DeepEqual(r.Data, expected[i].Data) { - t.Fatalf("expected %v, but got %v", expected[i].Data, r.Data) - } + 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 +} diff --git a/pkg/kinflate/resource/configmap.go b/pkg/kinflate/resource/configmap.go index ac24d360f..5a497236d 100644 --- a/pkg/kinflate/resource/configmap.go +++ b/pkg/kinflate/resource/configmap.go @@ -131,7 +131,7 @@ func addKV(m map[string]string, kv kvPair) error { } // NewFromConfigMaps returns a Resource slice given a configmap metadata slice from manifest file. -func NewFromConfigMaps(loader loader.Loader, cmList []manifest.ConfigMap) ([]*Resource, error) { +func NewFromConfigMaps(loader loader.Loader, cmList []manifest.ConfigMap) (ResourceCollection, error) { allResources := []*Resource{} for _, cm := range cmList { res, err := newFromConfigMap(loader, cm) @@ -140,5 +140,5 @@ func NewFromConfigMaps(loader loader.Loader, cmList []manifest.ConfigMap) ([]*Re } allResources = append(allResources, res) } - return allResources, nil + return resourceCollectionFromResources(allResources) } diff --git a/pkg/kinflate/resource/configmap_test.go b/pkg/kinflate/resource/configmap_test.go index 77523498e..bdd98712d 100644 --- a/pkg/kinflate/resource/configmap_test.go +++ b/pkg/kinflate/resource/configmap_test.go @@ -21,6 +21,7 @@ import ( "testing" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/loader/loadertest" @@ -32,7 +33,7 @@ func TestNewFromConfigMaps(t *testing.T) { input []manifest.ConfigMap filepath string content string - expected []*resource.Resource + expected resource.ResourceCollection } l := loadertest.NewFakeLoader("/home/seans/project/") @@ -49,8 +50,11 @@ func TestNewFromConfigMaps(t *testing.T) { }, filepath: "/home/seans/project/app.env", content: "DB_USERNAME=admin\nDB_PASSWORD=somepw", - expected: []*resource.Resource{ + expected: resource.ResourceCollection{ { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "envConfigMap", + }: &resource.Resource{ Data: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -79,8 +83,11 @@ func TestNewFromConfigMaps(t *testing.T) { }, filepath: "/home/seans/project/app-init.ini", content: "FOO=bar\nBAR=baz\n", - expected: []*resource.Resource{ + expected: resource.ResourceCollection{ { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "fileConfigMap", + }: &resource.Resource{ Data: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -109,8 +116,11 @@ BAR=baz }, }, }, - expected: []*resource.Resource{ + expected: resource.ResourceCollection{ { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "literalConfigMap", + }: &resource.Resource{ Data: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", diff --git a/pkg/kinflate/resource/resource.go b/pkg/kinflate/resource/resource.go index 13fc195f3..facd1899b 100644 --- a/pkg/kinflate/resource/resource.go +++ b/pkg/kinflate/resource/resource.go @@ -19,7 +19,6 @@ package resource import ( "encoding/json" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubectl/pkg/kinflate/types" @@ -37,14 +36,13 @@ func (r *Resource) GVKN() types.GroupVersionKindName { if r.Data == nil { return emptyZVKN } - accessor, err := meta.Accessor(r.Data) - if err != nil { - return emptyZVKN - } - gvk := r.Data.GetObjectKind().GroupVersionKind() - return types.GroupVersionKindName{GVK: gvk, Name: accessor.GetName()} + gvk := r.Data.GroupVersionKind() + return types.GroupVersionKindName{GVK: gvk, Name: r.Data.GetName()} } +// ResourceCollection is a map from GroupVersionKindName to Resource +type ResourceCollection map[types.GroupVersionKindName]*Resource + func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) { marshaled, err := json.Marshal(in) if err != nil { diff --git a/pkg/kinflate/resource/secret.go b/pkg/kinflate/resource/secret.go index 409168c4b..fb5187a69 100644 --- a/pkg/kinflate/resource/secret.go +++ b/pkg/kinflate/resource/secret.go @@ -70,7 +70,7 @@ func createSecretKey(wd string, command string) ([]byte, error) { // NewFromSecretGenerators takes a SecretGenerator slice and executes its command in directory p // then writes the output to a Resource slice and return it. -func NewFromSecretGenerators(p string, secretList []manifest.SecretGenerator) ([]*Resource, error) { +func NewFromSecretGenerators(p string, secretList []manifest.SecretGenerator) (ResourceCollection, error) { allResources := []*Resource{} for _, secret := range secretList { res, err := newFromSecretGenerator(p, secret) @@ -79,5 +79,5 @@ func NewFromSecretGenerators(p string, secretList []manifest.SecretGenerator) ([ } allResources = append(allResources, res) } - return allResources, nil + return resourceCollectionFromResources(allResources) } diff --git a/pkg/kinflate/resource/secret_test.go b/pkg/kinflate/resource/secret_test.go index 65c3ee3d1..e1f9aeaaa 100644 --- a/pkg/kinflate/resource/secret_test.go +++ b/pkg/kinflate/resource/secret_test.go @@ -23,6 +23,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" ) @@ -42,22 +43,27 @@ func TestNewFromSecretGenerators(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expected := []*Resource{ - {Data: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": "secret", - "creationTimestamp": nil, - }, - "type": string(corev1.SecretTypeOpaque), - "data": map[string]interface{}{ - "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")), - "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), + expected := ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, + Name: "secret", + }: &Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "secret", + "creationTimestamp": nil, + }, + "type": string(corev1.SecretTypeOpaque), + "data": map[string]interface{}{ + "DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")), + "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), + }, }, }, - }}, + }, } if !reflect.DeepEqual(re, expected) { diff --git a/pkg/kinflate/resource/util.go b/pkg/kinflate/resource/util.go new file mode 100644 index 000000000..35632cec8 --- /dev/null +++ b/pkg/kinflate/resource/util.go @@ -0,0 +1,96 @@ +/* +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" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/kubectl/pkg/kinflate/types" +) + +// decode decodes a list of objects in byte array format +func decode(in []byte) ([]*unstructured.Unstructured, error) { + decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024) + objs := []*unstructured.Unstructured{} + + var err error + for { + var out unstructured.Unstructured + err = decoder.Decode(&out) + if err != nil { + break + } + objs = append(objs, &out) + } + if err != io.EOF { + return nil, err + } + return objs, nil +} + +// decodeToResourceCollection decodes a list of objects in byte array format. +// it will return a ResourceCollection. +func decodeToResourceCollection(in []byte) (ResourceCollection, error) { + objs, err := decode(in) + if err != nil { + return nil, err + } + + into := ResourceCollection{} + for i, obj := range objs { + gvkn := types.GroupVersionKindName{ + GVK: obj.GroupVersionKind(), + Name: obj.GetName(), + } + if _, found := into[gvkn]; found { + return into, fmt.Errorf("GroupVersionKindName: %#v already exists in the map", gvkn) + } + into[gvkn] = &Resource{Data: objs[i]} + } + 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 +} diff --git a/pkg/kinflate/resource/util_test.go b/pkg/kinflate/resource/util_test.go new file mode 100644 index 000000000..ba16cddf1 --- /dev/null +++ b/pkg/kinflate/resource/util_test.go @@ -0,0 +1,151 @@ +/* +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" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/types" +) + +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) + } +} diff --git a/pkg/kinflate/transformers/labelsandannotations.go b/pkg/kinflate/transformers/labelsandannotations.go index ed15750e0..bd508947f 100644 --- a/pkg/kinflate/transformers/labelsandannotations.go +++ b/pkg/kinflate/transformers/labelsandannotations.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) @@ -55,9 +56,9 @@ func NewMapTransformer(pc []PathConfig, m map[string]string) (Transformer, error // Transform apply each pair in the mapTransformer to the // fields specified in mapTransformer. -func (o *mapTransformer) Transform(m types.ResourceCollection) error { +func (o *mapTransformer) Transform(m resource.ResourceCollection) error { for gvkn := range m { - obj := m[gvkn] + obj := m[gvkn].Data objMap := obj.UnstructuredContent() for _, path := range o.pathConfigs { if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) { diff --git a/pkg/kinflate/transformers/labelsandannotations_test.go b/pkg/kinflate/transformers/labelsandannotations_test.go index 3b9e2fb8e..78f360f38 100644 --- a/pkg/kinflate/transformers/labelsandannotations_test.go +++ b/pkg/kinflate/transformers/labelsandannotations_test.go @@ -22,42 +22,73 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubectl/pkg/kinflate/types" + "k8s.io/kubectl/pkg/kinflate/resource" ) -func makeConfigmap(name string) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{ - "name": name, +func TestLabelsRun(t *testing.T) { + m := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm1", + }, + }, }, }, - } -} - -func makeDeployment() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "group": "apps", - "apiVersion": "v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "old-label": "old-value", - }, + "name": "deploy1", }, "spec": map[string]interface{}{ - "containers": []interface{}{ + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "old-label": "old-value", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:1.7.9", + }, + }, + }, + }, + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + Name: "svc1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "svc1", + }, + "spec": map[string]interface{}{ + "ports": []interface{}{ map[string]interface{}{ - "name": "nginx", - "image": "nginx:1.7.9", + "name": "port1", + "port": "12345", }, }, }, @@ -65,150 +96,101 @@ func makeDeployment() *unstructured.Unstructured { }, }, } -} - -func makeService() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Service", - "metadata": map[string]interface{}{ - "name": "svc1", - }, - "spec": map[string]interface{}{ - "ports": []interface{}{ - map[string]interface{}{ - "name": "port1", - "port": "12345", - }, - }, - }, - }, - } -} - -func makeTestMap() types.ResourceCollection { - return types.ResourceCollection{ + expected := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm1", - }: makeConfigmap("cm1"), - { - GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - Name: "deploy1", - }: makeDeployment(), - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, - Name: "svc1", - }: makeService(), - } -} - -func makeLabeledConfigMap() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{ - "name": "cm1", - "labels": map[string]interface{}{ - "label-key1": "label-value1", - "label-key2": "label-value2", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm1", + "labels": map[string]interface{}{ + "label-key1": "label-value1", + "label-key2": "label-value2", + }, + }, }, }, }, - } -} - -func makeLabeledDeployment() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "group": "apps", - "apiVersion": "v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - "labels": map[string]interface{}{ - "label-key1": "label-value1", - "label-key2": "label-value2", - }, - }, - "spec": map[string]interface{}{ - "selector": map[string]interface{}{ - "matchLabels": map[string]interface{}{ - "label-key1": "label-value1", - "label-key2": "label-value2", - }, - }, - "template": map[string]interface{}{ + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", "metadata": map[string]interface{}{ + "name": "deploy1", "labels": map[string]interface{}{ - "old-label": "old-value", "label-key1": "label-value1", "label-key2": "label-value2", }, }, "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:1.7.9", + "selector": map[string]interface{}{ + "matchLabels": map[string]interface{}{ + "label-key1": "label-value1", + "label-key2": "label-value2", + }, + }, + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "old-label": "old-value", + "label-key1": "label-value1", + "label-key2": "label-value2", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:1.7.9", + }, + }, }, }, }, }, }, }, - } -} - -func makeLabeledService() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Service", - "metadata": map[string]interface{}{ - "name": "svc1", - "labels": map[string]interface{}{ - "label-key1": "label-value1", - "label-key2": "label-value2", - }, - }, - "spec": map[string]interface{}{ - "ports": []interface{}{ - map[string]interface{}{ - "name": "port1", - "port": "12345", + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + Name: "svc1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "svc1", + "labels": map[string]interface{}{ + "label-key1": "label-value1", + "label-key2": "label-value2", + }, + }, + "spec": map[string]interface{}{ + "ports": []interface{}{ + map[string]interface{}{ + "name": "port1", + "port": "12345", + }, + }, + "selector": map[string]interface{}{ + "label-key1": "label-value1", + "label-key2": "label-value2", + }, }, - }, - "selector": map[string]interface{}{ - "label-key1": "label-value1", - "label-key2": "label-value2", }, }, }, } -} -func makeLabeledMap() types.ResourceCollection { - return types.ResourceCollection{ - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: "cm1", - }: makeLabeledConfigMap(), - { - GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - Name: "deploy1", - }: makeLabeledDeployment(), - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, - Name: "svc1", - }: makeLabeledService(), - } -} - -func TestLabelsRun(t *testing.T) { - m := makeTestMap() lt, err := NewDefaultingLabelsMapTransformer(map[string]string{"label-key1": "label-value1", "label-key2": "label-value2"}) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -217,7 +199,6 @@ func TestLabelsRun(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expected := makeLabeledMap() if !reflect.DeepEqual(m, expected) { err = compareMap(m, expected) t.Fatalf("actual doesn't match expected: %v", err) @@ -302,25 +283,163 @@ func makeAnnotatededService() *unstructured.Unstructured { } } -func makeAnnotatedMap() types.ResourceCollection { - return types.ResourceCollection{ +func TestAnnotationsRun(t *testing.T) { + m := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm1", - }: makeAnnotatededConfigMap(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm1", + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "deploy1", - }: makeAnnotatededDeployment(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "deploy1", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "old-label": "old-value", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:1.7.9", + }, + }, + }, + }, + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, Name: "svc1", - }: makeAnnotatededService(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "svc1", + }, + "spec": map[string]interface{}{ + "ports": []interface{}{ + map[string]interface{}{ + "name": "port1", + "port": "12345", + }, + }, + }, + }, + }, + }, + } + expected := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm1", + "annotations": map[string]interface{}{ + "anno-key1": "anno-value1", + "anno-key2": "anno-value2", + }, + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "deploy1", + "annotations": map[string]interface{}{ + "anno-key1": "anno-value1", + "anno-key2": "anno-value2", + }, + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "anno-key1": "anno-value1", + "anno-key2": "anno-value2", + }, + "labels": map[string]interface{}{ + "old-label": "old-value", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:1.7.9", + }, + }, + }, + }, + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + Name: "svc1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "svc1", + "annotations": map[string]interface{}{ + "anno-key1": "anno-value1", + "anno-key2": "anno-value2", + }, + }, + "spec": map[string]interface{}{ + "ports": []interface{}{ + map[string]interface{}{ + "name": "port1", + "port": "12345", + }, + }, + }, + }, + }, + }, } -} - -func TestAnnotationsRun(t *testing.T) { - m := makeTestMap() at, err := NewDefaultingAnnotationsMapTransformer(map[string]string{"anno-key1": "anno-value1", "anno-key2": "anno-value2"}) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -329,7 +448,6 @@ func TestAnnotationsRun(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expected := makeAnnotatedMap() if !reflect.DeepEqual(m, expected) { err = compareMap(m, expected) t.Fatalf("actual doesn't match expected: %v", err) diff --git a/pkg/kinflate/transformers/multitransformer.go b/pkg/kinflate/transformers/multitransformer.go index a5ce6e6d7..3ac003f9b 100644 --- a/pkg/kinflate/transformers/multitransformer.go +++ b/pkg/kinflate/transformers/multitransformer.go @@ -16,7 +16,7 @@ limitations under the License. package transformers -import "k8s.io/kubectl/pkg/kinflate/types" +import "k8s.io/kubectl/pkg/kinflate/resource" // 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 types.ResourceCollection) error { +func (o *multiTransformer) Transform(m resource.ResourceCollection) error { for _, t := range o.transformers { err := t.Transform(m) if err != nil { diff --git a/pkg/kinflate/transformers/namehash.go b/pkg/kinflate/transformers/namehash.go index 961d47def..399083492 100644 --- a/pkg/kinflate/transformers/namehash.go +++ b/pkg/kinflate/transformers/namehash.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubectl/pkg/kinflate/hash" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) @@ -39,14 +40,14 @@ func NewNameHashTransformer() Transformer { } // Transform appends hash to configmaps and secrets. -func (o *nameHashTransformer) Transform(m types.ResourceCollection) error { +func (o *nameHashTransformer) Transform(m resource.ResourceCollection) error { for gvkn, obj := range m { switch { case types.SelectByGVK(gvkn.GVK, &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}): - appendHashForConfigMap(obj) + appendHashForConfigMap(obj.Data) case types.SelectByGVK(gvkn.GVK, &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}): - appendHashForSecret(obj) + appendHashForSecret(obj.Data) } } return nil diff --git a/pkg/kinflate/transformers/namehash_test.go b/pkg/kinflate/transformers/namehash_test.go index e40efcede..54fd5846f 100644 --- a/pkg/kinflate/transformers/namehash_test.go +++ b/pkg/kinflate/transformers/namehash_test.go @@ -22,71 +22,183 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubectl/pkg/kinflate/types" + "k8s.io/kubectl/pkg/kinflate/resource" ) -func makeSecret(name string) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": name, +func TestNameHashTransformer(t *testing.T) { + objs := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm1", + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "deploy1", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "old-label": "old-value", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:1.7.9", + }, + }, + }, + }, + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + Name: "svc1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "svc1", + }, + "spec": map[string]interface{}{ + "ports": []interface{}{ + map[string]interface{}{ + "name": "port1", + "port": "12345", + }, + }, + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, + Name: "secret1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "secret1", + }, + }, }, }, } -} -func makeHashTestMap() types.ResourceCollection { - return types.ResourceCollection{ + expected := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm1", - }: makeConfigmap("cm1"), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm1-m462kdfb68", + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "deploy1", - }: makeDeployment(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "deploy1", + }, + "spec": map[string]interface{}{ + "template": map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "old-label": "old-value", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:1.7.9", + }, + }, + }, + }, + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, Name: "svc1", - }: makeService(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "svc1", + }, + "spec": map[string]interface{}{ + "ports": []interface{}{ + map[string]interface{}{ + "name": "port1", + "port": "12345", + }, + }, + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, Name: "secret1", - }: makeSecret("secret1"), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "secret1-7kc45hd5f7", + }, + }, + }, + }, } -} - -func makeExpectedHashTestMap() types.ResourceCollection { - return types.ResourceCollection{ - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: "cm1", - }: makeConfigmap("cm1-m462kdfb68"), - { - GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - Name: "deploy1", - }: makeDeployment(), - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"}, - Name: "svc1", - }: makeService(), - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, - Name: "secret1", - }: makeSecret("secret1-7kc45hd5f7"), - } -} - -func TestNameHashTransformer(t *testing.T) { - objs := makeHashTestMap() tran := NewNameHashTransformer() tran.Transform(objs) - expected := makeExpectedHashTestMap() - if !reflect.DeepEqual(objs, expected) { err := compareMap(objs, expected) t.Fatalf("actual doesn't match expected: %v", err) diff --git a/pkg/kinflate/transformers/namereference.go b/pkg/kinflate/transformers/namereference.go index b7cbe47a2..e9bcf48b5 100644 --- a/pkg/kinflate/transformers/namereference.go +++ b/pkg/kinflate/transformers/namereference.go @@ -21,6 +21,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) @@ -50,9 +51,9 @@ func NewNameReferenceTransformer(pc []referencePathConfig) (Transformer, error) // associated with the key. e.g. if 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 types.ResourceCollection) error { + m resource.ResourceCollection) error { for GVKn := range m { - obj := m[GVKn] + obj := m[GVKn].Data objMap := obj.UnstructuredContent() for _, referencePathConfig := range o.pathConfigs { for _, path := range referencePathConfig.pathConfigs { @@ -88,7 +89,7 @@ func (err noMatchingGVKNError) Error() string { func (o *nameReferenceTransformer) updateNameReference( GVK schema.GroupVersionKind, - m types.ResourceCollection, + m resource.ResourceCollection, ) func(in interface{}) (interface{}, error) { return func(in interface{}) (interface{}, error) { s, ok := in.(string) @@ -101,7 +102,7 @@ func (o *nameReferenceTransformer) updateNameReference( continue } if GVKn.Name == s { - return obj.GetName(), nil + return obj.Data.GetName(), nil } } return in, nil diff --git a/pkg/kinflate/transformers/namereference_test.go b/pkg/kinflate/transformers/namereference_test.go index 03cf61572..2ad45ae1b 100644 --- a/pkg/kinflate/transformers/namereference_test.go +++ b/pkg/kinflate/transformers/namereference_test.go @@ -22,142 +22,215 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubectl/pkg/kinflate/types" + "k8s.io/kubectl/pkg/kinflate/resource" ) -func makeReferencedConfigMap() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{ - "name": "someprefix-cm1-somehash", +func TestNameReferenceRun(t *testing.T) { + m := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "someprefix-cm1-somehash", + }, + }, }, }, - } -} - -func makeReferencedSecret() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": "someprefix-secret1-somehash", + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, + Name: "secret1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "someprefix-secret1-somehash", + }, + }, }, }, - } -} - -func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "group": "apps", - "apiVersion": "v1", - "kind": "Deployment", - "metadata": map[string]interface{}{ - "name": "deploy1", - }, - "spec": map[string]interface{}{ - "template": map[string]interface{}{ + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "deploy1", + }, "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx:1.7.9", - "env": []interface{}{ + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{ map[string]interface{}{ - "name": "CM_FOO", - "valueFrom": map[string]interface{}{ - "configMapKeyRef": map[string]interface{}{ - "name": cmName, - "key": "somekey", + "name": "nginx", + "image": "nginx:1.7.9", + "env": []interface{}{ + map[string]interface{}{ + "name": "CM_FOO", + "valueFrom": map[string]interface{}{ + "configMapKeyRef": map[string]interface{}{ + "name": "cm1", + "key": "somekey", + }, + }, + }, + map[string]interface{}{ + "name": "SECRET_FOO", + "valueFrom": map[string]interface{}{ + "secretKeyRef": map[string]interface{}{ + "name": "secret1", + "key": "somekey", + }, + }, }, }, - }, - map[string]interface{}{ - "name": "SECRET_FOO", - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": secretName, - "key": "somekey", + "envFrom": []interface{}{ + map[string]interface{}{ + "configMapRef": map[string]interface{}{ + "name": "cm1", + "key": "somekey", + }, + }, + map[string]interface{}{ + "secretRef": map[string]interface{}{ + "name": "secret1", + "key": "somekey", + }, }, }, }, }, - "envFrom": []interface{}{ - map[string]interface{}{ - "configMapRef": map[string]interface{}{ - "name": cmName, - "key": "somekey", - }, + "volumes": map[string]interface{}{ + "configMap": map[string]interface{}{ + "name": "cm1", }, - map[string]interface{}{ - "secretRef": map[string]interface{}{ - "name": secretName, - "key": "somekey", - }, + "secret": map[string]interface{}{ + "secretName": "secret1", }, }, }, }, - "volumes": map[string]interface{}{ - "configMap": map[string]interface{}{ - "name": cmName, - }, - "secret": map[string]interface{}{ - "secretName": secretName, - }, - }, }, }, }, }, } -} -func makeNameReferenceTestMap() types.ResourceCollection { - return types.ResourceCollection{ + expected := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm1", - }: makeReferencedConfigMap(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "someprefix-cm1-somehash", + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, Name: "secret1", - }: makeReferencedSecret(), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "someprefix-secret1-somehash", + }, + }, + }, + }, { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "deploy1", - }: makeNameRefDeployment("cm1", "secret1"), + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "group": "apps", + "apiVersion": "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", + "env": []interface{}{ + map[string]interface{}{ + "name": "CM_FOO", + "valueFrom": map[string]interface{}{ + "configMapKeyRef": map[string]interface{}{ + "name": "someprefix-cm1-somehash", + "key": "somekey", + }, + }, + }, + map[string]interface{}{ + "name": "SECRET_FOO", + "valueFrom": map[string]interface{}{ + "secretKeyRef": map[string]interface{}{ + "name": "someprefix-secret1-somehash", + "key": "somekey", + }, + }, + }, + }, + "envFrom": []interface{}{ + map[string]interface{}{ + "configMapRef": map[string]interface{}{ + "name": "someprefix-cm1-somehash", + "key": "somekey", + }, + }, + map[string]interface{}{ + "secretRef": map[string]interface{}{ + "name": "someprefix-secret1-somehash", + "key": "somekey", + }, + }, + }, + }, + }, + "volumes": map[string]interface{}{ + "configMap": map[string]interface{}{ + "name": "someprefix-cm1-somehash", + }, + "secret": map[string]interface{}{ + "secretName": "someprefix-secret1-somehash", + }, + }, + }, + }, + }, + }, + }, + }, } -} -func makeNameReferenceUpdatedTestMap() types.ResourceCollection { - return types.ResourceCollection{ - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: "cm1", - }: makeReferencedConfigMap(), - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, - Name: "secret1", - }: makeReferencedSecret(), - { - GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - Name: "deploy1", - }: makeNameRefDeployment("someprefix-cm1-somehash", "someprefix-secret1-somehash"), - } -} - -func TestNameReferenceRun(t *testing.T) { - m := makeNameReferenceTestMap() nrt, err := NewDefaultingNameReferenceTransformer() err = nrt.Transform(m) if err != nil { t.Fatalf("unexpected error: %v", err) } - expected := makeNameReferenceUpdatedTestMap() if !reflect.DeepEqual(m, expected) { err = compareMap(m, expected) t.Fatalf("actual doesn't match expected: %v", err) diff --git a/pkg/kinflate/transformers/nooptransformer.go b/pkg/kinflate/transformers/nooptransformer.go index 1d4536f58..315f31b01 100644 --- a/pkg/kinflate/transformers/nooptransformer.go +++ b/pkg/kinflate/transformers/nooptransformer.go @@ -16,7 +16,7 @@ limitations under the License. package transformers -import "k8s.io/kubectl/pkg/kinflate/types" +import "k8s.io/kubectl/pkg/kinflate/resource" // noOpTransformer contains a no-op transformer. type noOpTransformer struct{} @@ -29,6 +29,6 @@ func NewNoOpTransformer() Transformer { } // Transform does nothing. -func (o *noOpTransformer) Transform(_ types.ResourceCollection) error { +func (o *noOpTransformer) Transform(_ resource.ResourceCollection) error { return nil } diff --git a/pkg/kinflate/transformers/overlay.go b/pkg/kinflate/transformers/overlay.go index 660be9873..d54c1d9c1 100644 --- a/pkg/kinflate/transformers/overlay.go +++ b/pkg/kinflate/transformers/overlay.go @@ -24,19 +24,19 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/kubectl/pkg/kinflate/types" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/scheme" ) // overlayTransformer contains a map of overlay objects type overlayTransformer struct { - overlay types.ResourceCollection + overlay resource.ResourceCollection } var _ Transformer = &overlayTransformer{} // NewOverlayTransformer constructs a overlayTransformer. -func NewOverlayTransformer(overlay types.ResourceCollection) (Transformer, error) { +func NewOverlayTransformer(overlay resource.ResourceCollection) (Transformer, error) { if len(overlay) == 0 { return NewNoOpTransformer(), nil } @@ -44,7 +44,7 @@ func NewOverlayTransformer(overlay types.ResourceCollection) (Transformer, error } // Transform apply the overlay on top of the base resources. -func (o *overlayTransformer) Transform(baseResourceMap types.ResourceCollection) error { +func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollection) error { // Strategic merge the resources exist in both base and overlay. for gvkn, overlay := range o.overlay { // Merge overlay with base resource. @@ -54,15 +54,15 @@ func (o *overlayTransformer) Transform(baseResourceMap types.ResourceCollection) } merged := map[string]interface{}{} versionedObj, err := scheme.Scheme.New(gvkn.GVK) - baseName := base.GetName() + baseName := base.Data.GetName() switch { case runtime.IsNotRegisteredError(err): // Use JSON merge patch to handle types w/o schema - baseBytes, err := json.Marshal(base) + baseBytes, err := json.Marshal(base.Data) if err != nil { return err } - patchBytes, err := json.Marshal(overlay) + patchBytes, err := json.Marshal(overlay.Data) if err != nil { return err } @@ -82,15 +82,15 @@ func (o *overlayTransformer) Transform(baseResourceMap types.ResourceCollection) // Store the name of the base object, because this name may have been munged. // Apply this name to the StrategicMergePatched object. merged, err = strategicpatch.StrategicMergeMapPatch( - base.UnstructuredContent(), - overlay.UnstructuredContent(), + base.Data.UnstructuredContent(), + overlay.Data.UnstructuredContent(), versionedObj) if err != nil { return err } } - base.SetName(baseName) - baseResourceMap[gvkn].Object = merged + base.Data.SetName(baseName) + baseResourceMap[gvkn].Data.Object = merged } return nil } diff --git a/pkg/kinflate/transformers/overlay_test.go b/pkg/kinflate/transformers/overlay_test.go index c3dbeabd4..6bdd7f173 100644 --- a/pkg/kinflate/transformers/overlay_test.go +++ b/pkg/kinflate/transformers/overlay_test.go @@ -22,121 +22,123 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubectl/pkg/kinflate/types" + "k8s.io/kubectl/pkg/kinflate/resource" ) -func makeBaseDeployment() *unstructured.Unstructured { - return &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{}{ - "old-label": "old-value", - }, - }, - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "name": "nginx", - "image": "nginx", - }, - }, - }, - }, - }, - }, - } -} - -func makeOverlayDeployment() *unstructured.Unstructured { - return &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", - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func makeMergedDeployment() *unstructured.Unstructured { - return &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{}{ - "old-label": "old-value", - "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", - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func makeTestResourceCollection(genDeployment func() *unstructured.Unstructured) types.ResourceCollection { - return types.ResourceCollection{ +func TestOverlayRun(t *testing.T) { + base := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "deploy1", - }: genDeployment(), + }: &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{}{ + "old-label": "old-value", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx", + }, + }, + }, + }, + }, + }, + }, + }, } -} - -func TestOverlayRun(t *testing.T) { - base := makeTestResourceCollection(makeBaseDeployment) - lt, err := NewOverlayTransformer(makeTestResourceCollection(makeOverlayDeployment)) + overlay := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &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", + }, + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:latest", + "env": []interface{}{ + map[string]interface{}{ + "name": "SOMEENV", + "value": "BAR", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + expected := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: &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{}{ + "old-label": "old-value", + "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", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + lt, err := NewOverlayTransformer(overlay) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -144,7 +146,6 @@ func TestOverlayRun(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expected := makeTestResourceCollection(makeMergedDeployment) if !reflect.DeepEqual(base, expected) { err = compareMap(base, expected) t.Fatalf("actual doesn't match expected: %v", err) @@ -152,68 +153,74 @@ func TestOverlayRun(t *testing.T) { } func TestNoSchemaOverlayRun(t *testing.T) { - base := types.ResourceCollection{ + base := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, Name: "my-foo", - }: { - 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{}{ - "A": "X", - "B": "Y", + }: &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{}{ + "A": "X", + "B": "Y", + }, }, }, }, }, } - Overlay := types.ResourceCollection{ + overlay := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, Name: "my-foo", - }: { - 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.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", + }, }, }, }, }, } - expected := types.ResourceCollection{ + expected := resource.ResourceCollection{ { GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, Name: "my-foo", - }: { - 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{}{ - "A": "X", - "C": "Z", + }: &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{}{ + "A": "X", + "C": "Z", + }, }, }, }, }, } - lt, err := NewOverlayTransformer(Overlay) + lt, err := NewOverlayTransformer(overlay) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/kinflate/transformers/prefixname.go b/pkg/kinflate/transformers/prefixname.go index 6aa7cb6ae..b67158349 100644 --- a/pkg/kinflate/transformers/prefixname.go +++ b/pkg/kinflate/transformers/prefixname.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) @@ -56,9 +57,9 @@ func NewNamePrefixTransformer(pc []PathConfig, np string) (Transformer, error) { } // Transform prepends the name prefix. -func (o *namePrefixTransformer) Transform(m types.ResourceCollection) error { +func (o *namePrefixTransformer) Transform(m resource.ResourceCollection) error { for gvkn := range m { - obj := m[gvkn] + obj := m[gvkn].Data objMap := obj.UnstructuredContent() for _, path := range o.pathConfigs { if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) { diff --git a/pkg/kinflate/transformers/prefixname_test.go b/pkg/kinflate/transformers/prefixname_test.go index 0e765039d..ba0c991fe 100644 --- a/pkg/kinflate/transformers/prefixname_test.go +++ b/pkg/kinflate/transformers/prefixname_test.go @@ -19,10 +19,74 @@ package transformers import ( "reflect" "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/resource" ) func TestPrefixNameRun(t *testing.T) { - m := makeConfigMaps("cm1", "cm2", "cm1", "cm2") + m := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.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.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm2", + }, + }, + }, + }, + } + expected := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "someprefix-cm1", + }, + }, + }, + }, + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm2", + }: &resource.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "someprefix-cm2", + }, + }, + }, + }, + } + npt, err := NewDefaultingNamePrefixTransformer("someprefix-") if err != nil { t.Fatalf("unexpected error: %v", err) @@ -31,7 +95,6 @@ func TestPrefixNameRun(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expected := makeConfigMaps("cm1", "cm2", "someprefix-cm1", "someprefix-cm2") if !reflect.DeepEqual(m, expected) { err = compareMap(m, expected) t.Fatalf("actual doesn't match expected: %v", err) diff --git a/pkg/kinflate/transformers/transformer.go b/pkg/kinflate/transformers/transformer.go index a57204722..3234c7406 100644 --- a/pkg/kinflate/transformers/transformer.go +++ b/pkg/kinflate/transformers/transformer.go @@ -16,10 +16,10 @@ limitations under the License. package transformers -import "k8s.io/kubectl/pkg/kinflate/types" +import "k8s.io/kubectl/pkg/kinflate/resource" // Transformer can transform objects. type Transformer interface { // Transform modifies objects in a map, e.g. add prefixes or additional labels. - Transform(m types.ResourceCollection) error + Transform(m resource.ResourceCollection) error } diff --git a/pkg/kinflate/transformers/util_test.go b/pkg/kinflate/transformers/util_test.go index 6c971beca..8b250717d 100644 --- a/pkg/kinflate/transformers/util_test.go +++ b/pkg/kinflate/transformers/util_test.go @@ -20,39 +20,11 @@ import ( "fmt" "reflect" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) -func makeConfigMap(name string) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{ - "name": name, - }, - }, - } -} - -func makeConfigMaps(name1InGVKN, name2InGVKN, name1InObj, name2InObj string) types.ResourceCollection { - cm1 := makeConfigMap(name1InObj) - cm2 := makeConfigMap(name2InObj) - return types.ResourceCollection{ - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: name1InGVKN, - }: cm1, - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: name2InGVKN, - }: cm2, - } -} - -func compareMap(m1, m2 types.ResourceCollection) error { +func compareMap(m1, m2 resource.ResourceCollection) error { if len(m1) != len(m2) { keySet1 := []types.GroupVersionKindName{} keySet2 := []types.GroupVersionKindName{} @@ -69,8 +41,8 @@ func compareMap(m1, m2 types.ResourceCollection) error { 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) + if !reflect.DeepEqual(obj1.Data, obj2.Data) { + return fmt.Errorf("%#v doesn't match %#v", obj1.Data, obj2.Data) } } return nil diff --git a/pkg/kinflate/types/types.go b/pkg/kinflate/types/types.go index d53424f3f..230a22969 100644 --- a/pkg/kinflate/types/types.go +++ b/pkg/kinflate/types/types.go @@ -17,7 +17,6 @@ limitations under the License. package types import ( - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -28,6 +27,3 @@ type GroupVersionKindName struct { // original name of the resource before transformation. Name string } - -// ResourceCollection is a map from GroupVersionKindName to unstructured objects -type ResourceCollection map[GroupVersionKindName]*unstructured.Unstructured diff --git a/pkg/kinflate/types/util.go b/pkg/kinflate/types/util.go index c5f445bbf..6a156aadb 100644 --- a/pkg/kinflate/types/util.go +++ b/pkg/kinflate/types/util.go @@ -17,10 +17,8 @@ limitations under the License. package types import ( - "fmt" "strings" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -59,15 +57,3 @@ func SelectByGVK(in schema.GroupVersionKind, selector *schema.GroupVersionKind) } return true } - -// Merge will merge all the entries in m2 to m1. -func Merge(m1, m2 map[GroupVersionKindName]*unstructured.Unstructured, -) error { - for gvkn, obj := range m2 { - if _, found := m1[gvkn]; found { - return fmt.Errorf("there is already an entry: %q", gvkn) - } - m1[gvkn] = obj - } - return nil -} diff --git a/pkg/kinflate/util/util.go b/pkg/kinflate/util/util.go index 2064b49f2..9f38b47e5 100644 --- a/pkg/kinflate/util/util.go +++ b/pkg/kinflate/util/util.go @@ -18,82 +18,16 @@ package util import ( "bytes" - "fmt" - "io" "sort" "github.com/ghodss/yaml" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) -// Decode decodes a list of objects in byte array format -func Decode(in []byte) ([]*unstructured.Unstructured, error) { - decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024) - objs := []*unstructured.Unstructured{} - - var err error - for { - var out unstructured.Unstructured - err = decoder.Decode(&out) - if err != nil { - break - } - objs = append(objs, &out) - } - if err != io.EOF { - return nil, err - } - return objs, nil -} - -// DecodeToResourceCollection decodes a list of objects in byte array format. -// Decoded object will be inserted in `into` if it's not nil. Otherwise, it will -// construct a new map and return it. -func DecodeToResourceCollection(in []byte, into types.ResourceCollection) (types.ResourceCollection, error) { - objs, err := Decode(in) - if err != nil { - return nil, err - } - - if into == nil { - into = types.ResourceCollection{} - } - for i := range objs { - metaAccessor, err := meta.Accessor(objs[i]) - if err != nil { - return nil, err - } - name := metaAccessor.GetName() - typeAccessor, err := meta.TypeAccessor(objs[i]) - if err != nil { - return nil, err - } - apiVersion := typeAccessor.GetAPIVersion() - kind := typeAccessor.GetKind() - gv, err := schema.ParseGroupVersion(apiVersion) - if err != nil { - return nil, err - } - gvk := gv.WithKind(kind) - gvkn := types.GroupVersionKindName{ - GVK: gvk, - Name: name, - } - if _, found := into[gvkn]; found { - return into, fmt.Errorf("GroupVersionKindName: %#v already exists in the map", gvkn) - } - into[gvkn] = objs[i] - } - return into, nil -} - // Encode encodes the map `in` and output the encoded objects separated by `---`. -func Encode(in types.ResourceCollection) ([]byte, error) { +func Encode(in resource.ResourceCollection) ([]byte, error) { gvknList := []types.GroupVersionKindName{} for gvkn := range in { gvknList = append(gvknList, gvkn) @@ -104,7 +38,7 @@ func Encode(in types.ResourceCollection) ([]byte, error) { var b []byte buf := bytes.NewBuffer(b) for _, gvkn := range gvknList { - obj := in[gvkn] + obj := in[gvkn].Data out, err := yaml.Marshal(obj) if err != nil { return nil, err @@ -125,7 +59,7 @@ func Encode(in types.ResourceCollection) ([]byte, error) { } // WriteToDir write each object in ResourceCollection to a file named with GroupVersionKindName. -func WriteToDir(in types.ResourceCollection, dirName string, printer Printer) (*Directory, error) { +func WriteToDir(in resource.ResourceCollection, dirName string, printer Printer) (*Directory, error) { dir, err := CreateDirectory(dirName) if err != nil { return &Directory{}, err @@ -137,7 +71,7 @@ func WriteToDir(in types.ResourceCollection, dirName string, printer Printer) (* return &Directory{}, err } defer f.Close() - err = printer.Print(obj, f) + err = printer.Print(obj.Data, f) if err != nil { return &Directory{}, err } diff --git a/pkg/kinflate/util/util_test.go b/pkg/kinflate/util/util_test.go index 20c76d105..d378be28d 100644 --- a/pkg/kinflate/util/util_test.go +++ b/pkg/kinflate/util/util_test.go @@ -23,10 +23,12 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/types" ) -var encoded = []byte(`apiVersion: v1 +func TestEncode(t *testing.T) { + encoded := []byte(`apiVersion: v1 kind: ConfigMap metadata: name: cm1 @@ -36,48 +38,37 @@ kind: ConfigMap metadata: name: cm2 `) - -func makeConfigMap(name string) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{ - "name": name, + input := resource.ResourceCollection{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: &resource.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.Resource{ + Data: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "cm2", + }, + }, }, }, } -} - -func makeConfigMaps(name1InGVKN, name2InGVKN, name1InObj, name2InObj string) types.ResourceCollection { - cm1 := makeConfigMap(name1InObj) - cm2 := makeConfigMap(name2InObj) - return types.ResourceCollection{ - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: name1InGVKN, - }: cm1, - { - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: name2InGVKN, - }: cm2, - } -} - -func TestDecodeToResourceCollection(t *testing.T) { - expected := makeConfigMaps("cm1", "cm2", "cm1", "cm2") - m, err := DecodeToResourceCollection(encoded, nil) - 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 TestEncode(t *testing.T) { - out, err := Encode(makeConfigMaps("cm1", "cm2", "cm1", "cm2")) + out, err := Encode(input) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -86,7 +77,7 @@ func TestEncode(t *testing.T) { } } -func compareMap(m1, m2 types.ResourceCollection) error { +func compareMap(m1, m2 resource.ResourceCollection) error { if len(m1) != len(m2) { keySet1 := []types.GroupVersionKindName{} keySet2 := []types.GroupVersionKindName{}