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

View File

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

View File

@ -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 <name: %q, GroupVersionKind: %v> 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 <GVK, oldName> -> 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 <GVK, oldName> -> 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 {

View File

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

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

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

View File

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

View File

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

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

View File

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

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"
"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 <key, value> 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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <k, v> is one of the key-value pair in the map,
// then the old name is k.Name and the new name is v.GetName()
func (o *nameReferenceTransformer) Transform(
m 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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