cleanup Resouce slice related code

This commit is contained in:
Mengqi Yu 2018-03-12 12:21:16 -07:00
parent 94f9347974
commit 2e06ca8bc0
30 changed files with 1364 additions and 824 deletions

View File

@ -17,8 +17,6 @@ limitations under the License.
package app package app
import ( import (
"fmt"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
@ -26,16 +24,15 @@ import (
interror "k8s.io/kubectl/pkg/kinflate/internal/error" interror "k8s.io/kubectl/pkg/kinflate/internal/error"
"k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/transformers" "k8s.io/kubectl/pkg/kinflate/transformers"
"k8s.io/kubectl/pkg/kinflate/types"
"k8s.io/kubectl/pkg/loader" "k8s.io/kubectl/pkg/loader"
) )
type Application interface { type Application interface {
// Resources computes and returns the resources for the app. // Resources computes and returns the resources for the app.
Resources() (types.ResourceCollection, error) Resources() (resource.ResourceCollection, error)
// RawResources computes and returns the raw resources from the manifest. // 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 // 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{} var _ Application = &applicationImpl{}
@ -63,27 +60,22 @@ func New(loader loader.Loader) (Application, error) {
} }
// Resources computes and returns the resources from the manifest. // 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{} errs := &interror.ManifestErrors{}
raw, err := a.RawResources() raw, err := a.RawResources()
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
resources := []*resource.Resource{}
cms, err := resource.NewFromConfigMaps(a.loader, a.manifest.Configmaps) cms, err := resource.NewFromConfigMaps(a.loader, a.manifest.Configmaps)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} else {
resources = append(resources, cms...)
} }
secrets, err := resource.NewFromSecretGenerators(a.loader.Root(), a.manifest.SecretGenerators) secrets, err := resource.NewFromSecretGenerators(a.loader.Root(), a.manifest.SecretGenerators)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} else {
resources = append(resources, secrets...)
} }
res, err := resourceCollectionFromResources(resources) res, err := resource.Merge(cms, secrets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,12 +86,7 @@ func (a *applicationImpl) Resources() (types.ResourceCollection, error) {
return nil, err return nil, err
} }
ps, err := resource.NewFromPaths(a.loader, a.manifest.Patches) patches, 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)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
@ -108,7 +95,7 @@ func (a *applicationImpl) Resources() (types.ResourceCollection, error) {
return nil, errs return nil, errs
} }
err = types.Merge(res, raw) allRes, err := resource.Merge(res, raw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,35 +104,30 @@ func (a *applicationImpl) Resources() (types.ResourceCollection, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = t.Transform(res) err = t.Transform(allRes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return res, nil return allRes, nil
} }
// RawResources computes and returns the raw resources from the manifest. // 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() subAppResources, errs := a.subAppResources()
allRes, err := resource.NewFromPaths(a.loader, a.manifest.Resources) resources, 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)
if err != nil { if err != nil {
errs.Append(err) errs.Append(err)
} }
if len(errs.Get()) > 0 { if len(errs.Get()) > 0 {
return nil, errs return nil, errs
} }
err = types.Merge(allResources, subAppResources) return resource.Merge(resources, subAppResources)
return allResources, err
} }
func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror.ManifestErrors) { func (a *applicationImpl) subAppResources() (resource.ResourceCollection, *interror.ManifestErrors) {
allResources := types.ResourceCollection{} sliceOfSubAppResources := []resource.ResourceCollection{}
errs := &interror.ManifestErrors{} errs := &interror.ManifestErrors{}
for _, pkgPath := range a.manifest.Packages { for _, pkgPath := range a.manifest.Packages {
subloader, err := a.loader.New(pkgPath) subloader, err := a.loader.New(pkgPath)
@ -164,7 +146,11 @@ func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror
errs.Append(err) errs.Append(err)
continue continue
} }
types.Merge(allResources, subAppResources) sliceOfSubAppResources = append(sliceOfSubAppResources, subAppResources)
}
allResources, err := resource.Merge(sliceOfSubAppResources...)
if err != nil {
errs.Append(err)
} }
return allResources, errs return allResources, errs
} }
@ -175,7 +161,7 @@ func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror
// 3) apply labels // 3) apply labels
// 4) apply annotations // 4) apply annotations
// 5) update name reference // 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{} ts := []transformers.Transformer{}
ot, err := transformers.NewOverlayTransformer(patches) ot, err := transformers.NewOverlayTransformer(patches)
@ -209,15 +195,3 @@ func (a *applicationImpl) getTransformer(patches types.ResourceCollection) (tran
ts = append(ts, nrt) ts = append(ts, nrt)
return transformers.NewMultiTransformer(ts), nil 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
}

View File

@ -25,6 +25,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
"k8s.io/kubectl/pkg/loader" "k8s.io/kubectl/pkg/loader"
"k8s.io/kubectl/pkg/loader/loadertest" "k8s.io/kubectl/pkg/loader/loadertest"
@ -74,11 +75,12 @@ metadata:
} }
func TestResources(t *testing.T) { func TestResources(t *testing.T) {
expected := types.ResourceCollection{ expected := resource.ResourceCollection{
types.GroupVersionKindName{ types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "dply1", Name: "dply1",
}: &unstructured.Unstructured{ }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@ -110,10 +112,12 @@ func TestResources(t *testing.T) {
}, },
}, },
}, },
},
types.GroupVersionKindName{ types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "literalConfigMap", Name: "literalConfigMap",
}: &unstructured.Unstructured{ }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@ -133,10 +137,12 @@ func TestResources(t *testing.T) {
}, },
}, },
}, },
},
types.GroupVersionKindName{ types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret", Name: "secret",
}: &unstructured.Unstructured{ }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@ -157,6 +163,7 @@ func TestResources(t *testing.T) {
}, },
}, },
}, },
},
} }
l := setupTest(t) l := setupTest(t)
app, err := New(l) app, err := New(l)
@ -175,11 +182,12 @@ func TestResources(t *testing.T) {
} }
func TestRawResources(t *testing.T) { func TestRawResources(t *testing.T) {
expected := types.ResourceCollection{ expected := resource.ResourceCollection{
types.GroupVersionKindName{ types.GroupVersionKindName{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "dply1", Name: "dply1",
}: &unstructured.Unstructured{ }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@ -188,6 +196,7 @@ func TestRawResources(t *testing.T) {
}, },
}, },
}, },
},
} }
l := setupTest(t) l := setupTest(t)
app, err := New(l) app, err := New(l)
@ -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) { if len(m1) != len(m2) {
keySet1 := []types.GroupVersionKindName{} keySet1 := []types.GroupVersionKindName{}
keySet2 := []types.GroupVersionKindName{} keySet2 := []types.GroupVersionKindName{}

View File

@ -26,12 +26,12 @@ import (
"time" "time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
cutil "k8s.io/kubectl/pkg/kinflate/configmapandsecret/util" cutil "k8s.io/kubectl/pkg/kinflate/configmapandsecret/util"
"k8s.io/kubectl/pkg/kinflate/hash" "k8s.io/kubectl/pkg/kinflate/hash"
"k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
) )
@ -123,26 +123,22 @@ func makeSecret(secret manifest.SecretGenerator, path string) (*corev1.Secret, e
return corev1secret, nil return corev1secret, nil
} }
func populateMap(m types.ResourceCollection, obj *unstructured.Unstructured, newName string) error { func populateMap(m resource.ResourceCollection, obj *unstructured.Unstructured, newName string) error {
accessor, err := meta.Accessor(obj) oldName := obj.GetName()
if err != nil { gvk := obj.GroupVersionKind()
return err
}
oldName := accessor.GetName()
gvk := obj.GetObjectKind().GroupVersionKind()
gvkn := types.GroupVersionKindName{GVK: gvk, Name: oldName} gvkn := types.GroupVersionKindName{GVK: gvk, Name: oldName}
if _, found := m[gvkn]; found { if _, found := m[gvkn]; found {
return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk) return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
} }
accessor.SetName(newName) obj.SetName(newName)
m[gvkn] = obj m[gvkn] = &resource.Resource{Data: obj}
return nil return nil
} }
// MakeConfigMapsResourceCollection returns a map of <GVK, oldName> -> unstructured object. // MakeConfigMapsResourceCollection returns a map of <GVK, oldName> -> unstructured object.
func MakeConfigMapsResourceCollection(maps []manifest.ConfigMap) (types.ResourceCollection, error) { func MakeConfigMapsResourceCollection(maps []manifest.ConfigMap) (resource.ResourceCollection, error) {
m := types.ResourceCollection{} m := resource.ResourceCollection{}
for _, cm := range maps { for _, cm := range maps {
unstructuredConfigMap, nameWithHash, err := MakeConfigmapAndGenerateName(cm) unstructuredConfigMap, nameWithHash, err := MakeConfigmapAndGenerateName(cm)
if err != nil { if err != nil {
@ -157,8 +153,8 @@ func MakeConfigMapsResourceCollection(maps []manifest.ConfigMap) (types.Resource
} }
// MakeSecretsResourceCollection returns a map of <GVK, oldName> -> unstructured object. // MakeSecretsResourceCollection returns a map of <GVK, oldName> -> unstructured object.
func MakeSecretsResourceCollection(secrets []manifest.SecretGenerator, path string) (types.ResourceCollection, error) { func MakeSecretsResourceCollection(secrets []manifest.SecretGenerator, path string) (resource.ResourceCollection, error) {
m := types.ResourceCollection{} m := resource.ResourceCollection{}
for _, secret := range secrets { for _, secret := range secrets {
unstructuredSecret, nameWithHash, err := MakeSecretAndGenerateName(secret, path) unstructuredSecret, nameWithHash, err := MakeSecretAndGenerateName(secret, path)
if err != nil { if err != nil {

View File

@ -17,37 +17,27 @@ limitations under the License.
package resource package resource
import ( import (
kutil "k8s.io/kubectl/pkg/kinflate/util"
"k8s.io/kubectl/pkg/loader" "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) content, err := loader.Load(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
objs, err := kutil.Decode(content) return decodeToResourceCollection(content)
if err != nil {
return nil, err
}
var res []*Resource
for _, obj := range objs {
res = append(res, &Resource{Data: obj})
}
return res, nil
} }
// NewFromPaths returns a slice of Resources given a resource path slice from manifest file. // NewFromPaths returns a slice of Resources given a resource path slice from manifest file.
func NewFromPaths(loader loader.Loader, paths []string) ([]*Resource, error) { func NewFromPaths(loader loader.Loader, paths []string) (ResourceCollection, error) {
allResources := []*Resource{} allResources := []ResourceCollection{}
for _, path := range paths { for _, path := range paths {
res, err := resourcesFromPath(loader, path) res, err := resourcesFromPath(loader, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
allResources = append(allResources, res...) allResources = append(allResources, res)
} }
return allResources, nil return Merge(allResources...)
} }

View File

@ -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 package resource
import ( import (
"fmt"
"reflect"
"testing" "testing"
"reflect"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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" "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) { func TestNewFromPaths(t *testing.T) {
resourceStr := `apiVersion: v1 resourceStr := `apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: dply1 name: dply1
--- ---
apiVersion: v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: dply2 name: dply2
@ -38,9 +44,35 @@ metadata:
if ferr := l.AddFile("/home/seans/project/deployment.yaml", []byte(resourceStr)); ferr != nil { if ferr := l.AddFile("/home/seans/project/deployment.yaml", []byte(resourceStr)); ferr != nil {
t.Fatalf("Error adding fake file: %v\n", ferr) t.Fatalf("Error adding fake file: %v\n", ferr)
} }
expected := []*Resource{ expected := ResourceCollection{
{Data: makeUnconstructed("dply1")}, {
{Data: makeUnconstructed("dply2")}, 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"}) 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)) t.Fatalf("%#v should contain 2 appResource, but got %d", resources, len(resources))
} }
for i, r := range resources { if err := compareMap(resources, expected); err != nil {
if !reflect.DeepEqual(r.Data, expected[i].Data) { t.Fatalf("actual doesn't match expected: %v", err)
t.Fatalf("expected %v, but got %v", expected[i].Data, r.Data)
}
} }
} }
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

@ -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. // 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{} allResources := []*Resource{}
for _, cm := range cmList { for _, cm := range cmList {
res, err := newFromConfigMap(loader, cm) res, err := newFromConfigMap(loader, cm)
@ -140,5 +140,5 @@ func NewFromConfigMaps(loader loader.Loader, cmList []manifest.ConfigMap) ([]*Re
} }
allResources = append(allResources, res) allResources = append(allResources, res)
} }
return allResources, nil return resourceCollectionFromResources(allResources)
} }

View File

@ -21,6 +21,7 @@ import (
"testing" "testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
"k8s.io/kubectl/pkg/kinflate/resource" "k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/loader/loadertest" "k8s.io/kubectl/pkg/loader/loadertest"
@ -32,7 +33,7 @@ func TestNewFromConfigMaps(t *testing.T) {
input []manifest.ConfigMap input []manifest.ConfigMap
filepath string filepath string
content string content string
expected []*resource.Resource expected resource.ResourceCollection
} }
l := loadertest.NewFakeLoader("/home/seans/project/") l := loadertest.NewFakeLoader("/home/seans/project/")
@ -49,8 +50,11 @@ func TestNewFromConfigMaps(t *testing.T) {
}, },
filepath: "/home/seans/project/app.env", filepath: "/home/seans/project/app.env",
content: "DB_USERNAME=admin\nDB_PASSWORD=somepw", content: "DB_USERNAME=admin\nDB_PASSWORD=somepw",
expected: []*resource.Resource{ expected: resource.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "envConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{ Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
@ -79,8 +83,11 @@ func TestNewFromConfigMaps(t *testing.T) {
}, },
filepath: "/home/seans/project/app-init.ini", filepath: "/home/seans/project/app-init.ini",
content: "FOO=bar\nBAR=baz\n", content: "FOO=bar\nBAR=baz\n",
expected: []*resource.Resource{ expected: resource.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "fileConfigMap",
}: &resource.Resource{
Data: &unstructured.Unstructured{ Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "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{ Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",

View File

@ -19,7 +19,6 @@ package resource
import ( import (
"encoding/json" "encoding/json"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
@ -37,14 +36,13 @@ func (r *Resource) GVKN() types.GroupVersionKindName {
if r.Data == nil { if r.Data == nil {
return emptyZVKN return emptyZVKN
} }
accessor, err := meta.Accessor(r.Data) gvk := r.Data.GroupVersionKind()
if err != nil { return types.GroupVersionKindName{GVK: gvk, Name: r.Data.GetName()}
return emptyZVKN
}
gvk := r.Data.GetObjectKind().GroupVersionKind()
return types.GroupVersionKindName{GVK: gvk, Name: accessor.GetName()}
} }
// ResourceCollection is a map from GroupVersionKindName to Resource
type ResourceCollection map[types.GroupVersionKindName]*Resource
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) { func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in) marshaled, err := json.Marshal(in)
if err != nil { if err != nil {

View File

@ -70,7 +70,7 @@ func createSecretKey(wd string, command string) ([]byte, error) {
// NewFromSecretGenerators takes a SecretGenerator slice and executes its command in directory p // NewFromSecretGenerators takes a SecretGenerator slice and executes its command in directory p
// then writes the output to a Resource slice and return it. // then writes the output to a Resource slice and return it.
func NewFromSecretGenerators(p string, secretList []manifest.SecretGenerator) ([]*Resource, error) { func NewFromSecretGenerators(p string, secretList []manifest.SecretGenerator) (ResourceCollection, error) {
allResources := []*Resource{} allResources := []*Resource{}
for _, secret := range secretList { for _, secret := range secretList {
res, err := newFromSecretGenerator(p, secret) res, err := newFromSecretGenerator(p, secret)
@ -79,5 +79,5 @@ func NewFromSecretGenerators(p string, secretList []manifest.SecretGenerator) ([
} }
allResources = append(allResources, res) allResources = append(allResources, res)
} }
return allResources, nil return resourceCollectionFromResources(allResources)
} }

View File

@ -23,6 +23,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1" manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
) )
@ -42,8 +43,12 @@ func TestNewFromSecretGenerators(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := []*Resource{ expected := ResourceCollection{
{Data: &unstructured.Unstructured{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret",
}: &Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@ -57,7 +62,8 @@ func TestNewFromSecretGenerators(t *testing.T) {
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")), "DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
}, },
}, },
}}, },
},
} }
if !reflect.DeepEqual(re, expected) { if !reflect.DeepEqual(re, expected) {

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
) )
@ -55,9 +56,9 @@ func NewMapTransformer(pc []PathConfig, m map[string]string) (Transformer, error
// Transform apply each <key, value> pair in the mapTransformer to the // Transform apply each <key, value> pair in the mapTransformer to the
// fields specified in mapTransformer. // fields specified in mapTransformer.
func (o *mapTransformer) Transform(m types.ResourceCollection) error { func (o *mapTransformer) Transform(m resource.ResourceCollection) error {
for gvkn := range m { for gvkn := range m {
obj := m[gvkn] obj := m[gvkn].Data
objMap := obj.UnstructuredContent() objMap := obj.UnstructuredContent()
for _, path := range o.pathConfigs { for _, path := range o.pathConfigs {
if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) { if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) {

View File

@ -22,23 +22,30 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/resource"
) )
func makeConfigmap(name string) *unstructured.Unstructured { func TestLabelsRun(t *testing.T) {
return &unstructured.Unstructured{ m := resource.ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": name, "name": "cm1",
}, },
}, },
} },
} },
{
func makeDeployment() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
return &unstructured.Unstructured{ Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@ -64,11 +71,13 @@ func makeDeployment() *unstructured.Unstructured {
}, },
}, },
}, },
} },
} },
{
func makeService() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
return &unstructured.Unstructured{ Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@ -84,28 +93,15 @@ func makeService() *unstructured.Unstructured {
}, },
}, },
}, },
},
},
} }
} expected := resource.ResourceCollection{
func makeTestMap() types.ResourceCollection {
return types.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1", Name: "cm1",
}: makeConfigmap("cm1"), }: &resource.Resource{
{ Data: &unstructured.Unstructured{
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{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@ -117,11 +113,13 @@ func makeLabeledConfigMap() *unstructured.Unstructured {
}, },
}, },
}, },
} },
} },
{
func makeLabeledDeployment() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
return &unstructured.Unstructured{ Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@ -159,11 +157,13 @@ func makeLabeledDeployment() *unstructured.Unstructured {
}, },
}, },
}, },
} },
} },
{
func makeLabeledService() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
return &unstructured.Unstructured{ Name: "svc1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
@ -187,28 +187,10 @@ func makeLabeledService() *unstructured.Unstructured {
}, },
}, },
}, },
},
},
} }
}
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"}) lt, err := NewDefaultingLabelsMapTransformer(map[string]string{"label-key1": "label-value1", "label-key2": "label-value2"})
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -217,7 +199,6 @@ func TestLabelsRun(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := makeLabeledMap()
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = compareMap(m, expected)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
@ -302,25 +283,163 @@ func makeAnnotatededService() *unstructured.Unstructured {
} }
} }
func makeAnnotatedMap() types.ResourceCollection { func TestAnnotationsRun(t *testing.T) {
return types.ResourceCollection{ m := resource.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1", 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"}, GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1", 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"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1", 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"}) at, err := NewDefaultingAnnotationsMapTransformer(map[string]string{"anno-key1": "anno-value1", "anno-key2": "anno-value2"})
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -329,7 +448,6 @@ func TestAnnotationsRun(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := makeAnnotatedMap()
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = compareMap(m, expected)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)

View File

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

View File

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

View File

@ -22,71 +22,183 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/resource"
) )
func makeSecret(name string) *unstructured.Unstructured { func TestNameHashTransformer(t *testing.T) {
return &unstructured.Unstructured{ 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{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": name, "name": "secret1",
},
},
}, },
}, },
} }
}
func makeHashTestMap() types.ResourceCollection { expected := resource.ResourceCollection{
return types.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1", 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"}, GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1", 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"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "Service"},
Name: "svc1", 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"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret1", 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 := NewNameHashTransformer()
tran.Transform(objs) tran.Transform(objs)
expected := makeExpectedHashTestMap()
if !reflect.DeepEqual(objs, expected) { if !reflect.DeepEqual(objs, expected) {
err := compareMap(objs, expected) err := compareMap(objs, expected)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
) )
@ -50,9 +51,9 @@ func NewNameReferenceTransformer(pc []referencePathConfig) (Transformer, error)
// associated with the key. e.g. if <k, v> is one of the key-value pair in the map, // associated with the key. e.g. if <k, v> is one of the key-value pair in the map,
// then the old name is k.Name and the new name is v.GetName() // then the old name is k.Name and the new name is v.GetName()
func (o *nameReferenceTransformer) Transform( func (o *nameReferenceTransformer) Transform(
m types.ResourceCollection) error { m resource.ResourceCollection) error {
for GVKn := range m { for GVKn := range m {
obj := m[GVKn] obj := m[GVKn].Data
objMap := obj.UnstructuredContent() objMap := obj.UnstructuredContent()
for _, referencePathConfig := range o.pathConfigs { for _, referencePathConfig := range o.pathConfigs {
for _, path := range referencePathConfig.pathConfigs { for _, path := range referencePathConfig.pathConfigs {
@ -88,7 +89,7 @@ func (err noMatchingGVKNError) Error() string {
func (o *nameReferenceTransformer) updateNameReference( func (o *nameReferenceTransformer) updateNameReference(
GVK schema.GroupVersionKind, GVK schema.GroupVersionKind,
m types.ResourceCollection, m resource.ResourceCollection,
) func(in interface{}) (interface{}, error) { ) func(in interface{}) (interface{}, error) {
return func(in interface{}) (interface{}, error) { return func(in interface{}) (interface{}, error) {
s, ok := in.(string) s, ok := in.(string)
@ -101,7 +102,7 @@ func (o *nameReferenceTransformer) updateNameReference(
continue continue
} }
if GVKn.Name == s { if GVKn.Name == s {
return obj.GetName(), nil return obj.Data.GetName(), nil
} }
} }
return in, nil return in, nil

View File

@ -22,11 +22,16 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/resource"
) )
func makeReferencedConfigMap() *unstructured.Unstructured { func TestNameReferenceRun(t *testing.T) {
return &unstructured.Unstructured{ m := resource.ResourceCollection{
{
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
@ -34,11 +39,13 @@ func makeReferencedConfigMap() *unstructured.Unstructured {
"name": "someprefix-cm1-somehash", "name": "someprefix-cm1-somehash",
}, },
}, },
} },
} },
{
func makeReferencedSecret() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
return &unstructured.Unstructured{ Name: "secret1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Secret", "kind": "Secret",
@ -46,11 +53,13 @@ func makeReferencedSecret() *unstructured.Unstructured {
"name": "someprefix-secret1-somehash", "name": "someprefix-secret1-somehash",
}, },
}, },
} },
} },
{
func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
return &unstructured.Unstructured{ Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"group": "apps", "group": "apps",
"apiVersion": "v1", "apiVersion": "v1",
@ -70,7 +79,7 @@ func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured
"name": "CM_FOO", "name": "CM_FOO",
"valueFrom": map[string]interface{}{ "valueFrom": map[string]interface{}{
"configMapKeyRef": map[string]interface{}{ "configMapKeyRef": map[string]interface{}{
"name": cmName, "name": "cm1",
"key": "somekey", "key": "somekey",
}, },
}, },
@ -79,7 +88,7 @@ func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured
"name": "SECRET_FOO", "name": "SECRET_FOO",
"valueFrom": map[string]interface{}{ "valueFrom": map[string]interface{}{
"secretKeyRef": map[string]interface{}{ "secretKeyRef": map[string]interface{}{
"name": secretName, "name": "secret1",
"key": "somekey", "key": "somekey",
}, },
}, },
@ -88,13 +97,13 @@ func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured
"envFrom": []interface{}{ "envFrom": []interface{}{
map[string]interface{}{ map[string]interface{}{
"configMapRef": map[string]interface{}{ "configMapRef": map[string]interface{}{
"name": cmName, "name": "cm1",
"key": "somekey", "key": "somekey",
}, },
}, },
map[string]interface{}{ map[string]interface{}{
"secretRef": map[string]interface{}{ "secretRef": map[string]interface{}{
"name": secretName, "name": "secret1",
"key": "somekey", "key": "somekey",
}, },
}, },
@ -103,10 +112,12 @@ func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured
}, },
"volumes": map[string]interface{}{ "volumes": map[string]interface{}{
"configMap": map[string]interface{}{ "configMap": map[string]interface{}{
"name": cmName, "name": "cm1",
}, },
"secret": map[string]interface{}{ "secret": map[string]interface{}{
"secretName": secretName, "secretName": "secret1",
},
},
}, },
}, },
}, },
@ -114,50 +125,112 @@ func makeNameRefDeployment(cmName, secretName string) *unstructured.Unstructured
}, },
}, },
} }
}
func makeNameReferenceTestMap() types.ResourceCollection { expected := resource.ResourceCollection{
return types.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1", 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"}, GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
Name: "secret1", 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"}, GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1", 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() nrt, err := NewDefaultingNameReferenceTransformer()
err = nrt.Transform(m) err = nrt.Transform(m)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := makeNameReferenceUpdatedTestMap()
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = compareMap(m, expected)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)

View File

@ -16,7 +16,7 @@ limitations under the License.
package transformers package transformers
import "k8s.io/kubectl/pkg/kinflate/types" import "k8s.io/kubectl/pkg/kinflate/resource"
// noOpTransformer contains a no-op transformer. // noOpTransformer contains a no-op transformer.
type noOpTransformer struct{} type noOpTransformer struct{}
@ -29,6 +29,6 @@ func NewNoOpTransformer() Transformer {
} }
// Transform does nothing. // Transform does nothing.
func (o *noOpTransformer) Transform(_ types.ResourceCollection) error { func (o *noOpTransformer) Transform(_ resource.ResourceCollection) error {
return nil return nil
} }

View File

@ -24,19 +24,19 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
) )
// overlayTransformer contains a map of overlay objects // overlayTransformer contains a map of overlay objects
type overlayTransformer struct { type overlayTransformer struct {
overlay types.ResourceCollection overlay resource.ResourceCollection
} }
var _ Transformer = &overlayTransformer{} var _ Transformer = &overlayTransformer{}
// NewOverlayTransformer constructs a overlayTransformer. // NewOverlayTransformer constructs a overlayTransformer.
func NewOverlayTransformer(overlay types.ResourceCollection) (Transformer, error) { func NewOverlayTransformer(overlay resource.ResourceCollection) (Transformer, error) {
if len(overlay) == 0 { if len(overlay) == 0 {
return NewNoOpTransformer(), nil return NewNoOpTransformer(), nil
} }
@ -44,7 +44,7 @@ func NewOverlayTransformer(overlay types.ResourceCollection) (Transformer, error
} }
// Transform apply the overlay on top of the base resources. // Transform apply the overlay on top of the base resources.
func (o *overlayTransformer) Transform(baseResourceMap types.ResourceCollection) error { func (o *overlayTransformer) Transform(baseResourceMap resource.ResourceCollection) error {
// Strategic merge the resources exist in both base and overlay. // Strategic merge the resources exist in both base and overlay.
for gvkn, overlay := range o.overlay { for gvkn, overlay := range o.overlay {
// Merge overlay with base resource. // Merge overlay with base resource.
@ -54,15 +54,15 @@ func (o *overlayTransformer) Transform(baseResourceMap types.ResourceCollection)
} }
merged := map[string]interface{}{} merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(gvkn.GVK) versionedObj, err := scheme.Scheme.New(gvkn.GVK)
baseName := base.GetName() baseName := base.Data.GetName()
switch { switch {
case runtime.IsNotRegisteredError(err): case runtime.IsNotRegisteredError(err):
// Use JSON merge patch to handle types w/o schema // Use JSON merge patch to handle types w/o schema
baseBytes, err := json.Marshal(base) baseBytes, err := json.Marshal(base.Data)
if err != nil { if err != nil {
return err return err
} }
patchBytes, err := json.Marshal(overlay) patchBytes, err := json.Marshal(overlay.Data)
if err != nil { if err != nil {
return err 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. // Store the name of the base object, because this name may have been munged.
// Apply this name to the StrategicMergePatched object. // Apply this name to the StrategicMergePatched object.
merged, err = strategicpatch.StrategicMergeMapPatch( merged, err = strategicpatch.StrategicMergeMapPatch(
base.UnstructuredContent(), base.Data.UnstructuredContent(),
overlay.UnstructuredContent(), overlay.Data.UnstructuredContent(),
versionedObj) versionedObj)
if err != nil { if err != nil {
return err return err
} }
} }
base.SetName(baseName) base.Data.SetName(baseName)
baseResourceMap[gvkn].Object = merged baseResourceMap[gvkn].Data.Object = merged
} }
return nil return nil
} }

View File

@ -22,11 +22,16 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/resource"
) )
func makeBaseDeployment() *unstructured.Unstructured { func TestOverlayRun(t *testing.T) {
return &unstructured.Unstructured{ base := resource.ResourceCollection{
{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@ -51,11 +56,15 @@ func makeBaseDeployment() *unstructured.Unstructured {
}, },
}, },
}, },
},
},
} }
} overlay := resource.ResourceCollection{
{
func makeOverlayDeployment() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
return &unstructured.Unstructured{ Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@ -86,11 +95,15 @@ func makeOverlayDeployment() *unstructured.Unstructured {
}, },
}, },
}, },
},
},
} }
} expected := resource.ResourceCollection{
{
func makeMergedDeployment() *unstructured.Unstructured { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
return &unstructured.Unstructured{ Name: "deploy1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
@ -122,21 +135,10 @@ func makeMergedDeployment() *unstructured.Unstructured {
}, },
}, },
}, },
},
},
} }
} lt, err := NewOverlayTransformer(overlay)
func makeTestResourceCollection(genDeployment func() *unstructured.Unstructured) types.ResourceCollection {
return types.ResourceCollection{
{
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Name: "deploy1",
}: genDeployment(),
}
}
func TestOverlayRun(t *testing.T) {
base := makeTestResourceCollection(makeBaseDeployment)
lt, err := NewOverlayTransformer(makeTestResourceCollection(makeOverlayDeployment))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -144,7 +146,6 @@ func TestOverlayRun(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := makeTestResourceCollection(makeMergedDeployment)
if !reflect.DeepEqual(base, expected) { if !reflect.DeepEqual(base, expected) {
err = compareMap(base, expected) err = compareMap(base, expected)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)
@ -152,11 +153,12 @@ func TestOverlayRun(t *testing.T) {
} }
func TestNoSchemaOverlayRun(t *testing.T) { func TestNoSchemaOverlayRun(t *testing.T) {
base := types.ResourceCollection{ base := resource.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"},
Name: "my-foo", Name: "my-foo",
}: { }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@ -171,12 +173,14 @@ func TestNoSchemaOverlayRun(t *testing.T) {
}, },
}, },
}, },
},
} }
Overlay := types.ResourceCollection{ overlay := resource.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"},
Name: "my-foo", Name: "my-foo",
}: { }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@ -191,12 +195,14 @@ func TestNoSchemaOverlayRun(t *testing.T) {
}, },
}, },
}, },
},
} }
expected := types.ResourceCollection{ expected := resource.ResourceCollection{
{ {
GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}, GVK: schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"},
Name: "my-foo", Name: "my-foo",
}: { }: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "example.com/v1", "apiVersion": "example.com/v1",
"kind": "Foo", "kind": "Foo",
@ -211,9 +217,10 @@ func TestNoSchemaOverlayRun(t *testing.T) {
}, },
}, },
}, },
},
} }
lt, err := NewOverlayTransformer(Overlay) lt, err := NewOverlayTransformer(overlay)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View File

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
) )
@ -56,9 +57,9 @@ func NewNamePrefixTransformer(pc []PathConfig, np string) (Transformer, error) {
} }
// Transform prepends the name prefix. // 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 { for gvkn := range m {
obj := m[gvkn] obj := m[gvkn].Data
objMap := obj.UnstructuredContent() objMap := obj.UnstructuredContent()
for _, path := range o.pathConfigs { for _, path := range o.pathConfigs {
if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) { if !types.SelectByGVK(gvkn.GVK, path.GroupVersionKind) {

View File

@ -19,10 +19,74 @@ package transformers
import ( import (
"reflect" "reflect"
"testing" "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) { 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-") npt, err := NewDefaultingNamePrefixTransformer("someprefix-")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -31,7 +95,6 @@ func TestPrefixNameRun(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
expected := makeConfigMaps("cm1", "cm2", "someprefix-cm1", "someprefix-cm2")
if !reflect.DeepEqual(m, expected) { if !reflect.DeepEqual(m, expected) {
err = compareMap(m, expected) err = compareMap(m, expected)
t.Fatalf("actual doesn't match expected: %v", err) t.Fatalf("actual doesn't match expected: %v", err)

View File

@ -16,10 +16,10 @@ limitations under the License.
package transformers package transformers
import "k8s.io/kubectl/pkg/kinflate/types" import "k8s.io/kubectl/pkg/kinflate/resource"
// Transformer can transform objects. // Transformer can transform objects.
type Transformer interface { type Transformer interface {
// Transform modifies objects in a map, e.g. add prefixes or additional labels. // Transform modifies objects in a map, e.g. add prefixes or additional labels.
Transform(m types.ResourceCollection) error Transform(m resource.ResourceCollection) error
} }

View File

@ -20,39 +20,11 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
) )
func makeConfigMap(name string) *unstructured.Unstructured { func compareMap(m1, m2 resource.ResourceCollection) error {
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 {
if len(m1) != len(m2) { if len(m1) != len(m2) {
keySet1 := []types.GroupVersionKindName{} keySet1 := []types.GroupVersionKindName{}
keySet2 := []types.GroupVersionKindName{} keySet2 := []types.GroupVersionKindName{}
@ -69,8 +41,8 @@ func compareMap(m1, m2 types.ResourceCollection) error {
if !found { if !found {
return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2) return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2)
} }
if !reflect.DeepEqual(obj1, obj2) { if !reflect.DeepEqual(obj1.Data, obj2.Data) {
return fmt.Errorf("%#v doesn't match %#v", obj1, obj2) return fmt.Errorf("%#v doesn't match %#v", obj1.Data, obj2.Data)
} }
} }
return nil return nil

View File

@ -17,7 +17,6 @@ limitations under the License.
package types package types
import ( import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
@ -28,6 +27,3 @@ type GroupVersionKindName struct {
// original name of the resource before transformation. // original name of the resource before transformation.
Name string Name string
} }
// ResourceCollection is a map from GroupVersionKindName to unstructured objects
type ResourceCollection map[GroupVersionKindName]*unstructured.Unstructured

View File

@ -17,10 +17,8 @@ limitations under the License.
package types package types
import ( import (
"fmt"
"strings" "strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
@ -59,15 +57,3 @@ func SelectByGVK(in schema.GroupVersionKind, selector *schema.GroupVersionKind)
} }
return true 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
}

View File

@ -18,82 +18,16 @@ package util
import ( import (
"bytes" "bytes"
"fmt"
"io"
"sort" "sort"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/kubectl/pkg/kinflate/resource"
"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/types" "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 `---`. // 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{} gvknList := []types.GroupVersionKindName{}
for gvkn := range in { for gvkn := range in {
gvknList = append(gvknList, gvkn) gvknList = append(gvknList, gvkn)
@ -104,7 +38,7 @@ func Encode(in types.ResourceCollection) ([]byte, error) {
var b []byte var b []byte
buf := bytes.NewBuffer(b) buf := bytes.NewBuffer(b)
for _, gvkn := range gvknList { for _, gvkn := range gvknList {
obj := in[gvkn] obj := in[gvkn].Data
out, err := yaml.Marshal(obj) out, err := yaml.Marshal(obj)
if err != nil { if err != nil {
return nil, err 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. // 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) dir, err := CreateDirectory(dirName)
if err != nil { if err != nil {
return &Directory{}, err return &Directory{}, err
@ -137,7 +71,7 @@ func WriteToDir(in types.ResourceCollection, dirName string, printer Printer) (*
return &Directory{}, err return &Directory{}, err
} }
defer f.Close() defer f.Close()
err = printer.Print(obj, f) err = printer.Print(obj.Data, f)
if err != nil { if err != nil {
return &Directory{}, err return &Directory{}, err
} }

View File

@ -23,10 +23,12 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/kinflate/resource"
"k8s.io/kubectl/pkg/kinflate/types" "k8s.io/kubectl/pkg/kinflate/types"
) )
var encoded = []byte(`apiVersion: v1 func TestEncode(t *testing.T) {
encoded := []byte(`apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: cm1 name: cm1
@ -36,48 +38,37 @@ kind: ConfigMap
metadata: metadata:
name: cm2 name: cm2
`) `)
input := resource.ResourceCollection{
func makeConfigMap(name string) *unstructured.Unstructured { {
return &unstructured.Unstructured{ GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
Name: "cm1",
}: &resource.Resource{
Data: &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": "v1", "apiVersion": "v1",
"kind": "ConfigMap", "kind": "ConfigMap",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"name": name, "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",
},
},
}, },
}, },
} }
} out, err := Encode(input)
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"))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) 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) { if len(m1) != len(m2) {
keySet1 := []types.GroupVersionKindName{} keySet1 := []types.GroupVersionKindName{}
keySet2 := []types.GroupVersionKindName{} keySet2 := []types.GroupVersionKindName{}