From d5639f26d26a60baeee2eb55e9fb4d8720c0f5ac Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Tue, 27 Feb 2018 15:48:20 -0800 Subject: [PATCH] Add nameHashTransformer --- .../transformers/labelsandannotations_test.go | 6 +- pkg/kinflate/transformers/namehash.go | 103 ++++++++++++++++++ pkg/kinflate/transformers/namehash_test.go | 94 ++++++++++++++++ 3 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 pkg/kinflate/transformers/namehash.go create mode 100644 pkg/kinflate/transformers/namehash_test.go diff --git a/pkg/kinflate/transformers/labelsandannotations_test.go b/pkg/kinflate/transformers/labelsandannotations_test.go index 81579537e..2d81e43f9 100644 --- a/pkg/kinflate/transformers/labelsandannotations_test.go +++ b/pkg/kinflate/transformers/labelsandannotations_test.go @@ -25,13 +25,13 @@ import ( "k8s.io/kubectl/pkg/kinflate/types" ) -func makeConfigmap() *unstructured.Unstructured { +func makeConfigmap(name string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]interface{}{ - "name": "cm1", + "name": name, }, }, } @@ -92,7 +92,7 @@ func makeTestMap() types.KObject { { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm1", - }: makeConfigmap(), + }: makeConfigmap("cm1"), { GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, Name: "deploy1", diff --git a/pkg/kinflate/transformers/namehash.go b/pkg/kinflate/transformers/namehash.go new file mode 100644 index 000000000..9f344c9a4 --- /dev/null +++ b/pkg/kinflate/transformers/namehash.go @@ -0,0 +1,103 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package transformers + +import ( + "encoding/json" + "fmt" + + "k8s.io/api/core/v1" + "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/types" +) + +// nameHashTransformer contains the prefix and the path config for each field that +// the name prefix will be applied. +type nameHashTransformer struct{} + +var _ Transformer = &nameHashTransformer{} + +// NewNameHashTransformer construct a nameHashTransformer. +func NewNameHashTransformer() Transformer { + return &nameHashTransformer{} +} + +// Transform appends hash to configmaps and secrets. +func (o *nameHashTransformer) Transform(m types.KObject) error { + for gvkn, obj := range m { + switch { + case types.SelectByGVK(gvkn.GVK, &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}): + appendHashForConfigMap(obj) + + case types.SelectByGVK(gvkn.GVK, &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}): + appendHashForSecret(obj) + } + } + return nil +} + +func appendHashForConfigMap(obj *unstructured.Unstructured) error { + cm, err := unstructuredToConfigmap(obj) + if err != nil { + return err + } + h, err := hash.ConfigMapHash(cm) + if err != nil { + return err + } + nameWithHash := fmt.Sprintf("%s-%s", obj.GetName(), h) + obj.SetName(nameWithHash) + return nil +} + +// TODO: Remove this function after we support hash unstructured objects +func unstructuredToConfigmap(in *unstructured.Unstructured) (*v1.ConfigMap, error) { + marshaled, err := json.Marshal(in) + if err != nil { + return nil, err + } + var out v1.ConfigMap + err = json.Unmarshal(marshaled, &out) + return &out, err +} + +func appendHashForSecret(obj *unstructured.Unstructured) error { + secret, err := unstructuredToSecret(obj) + if err != nil { + return err + } + h, err := hash.SecretHash(secret) + if err != nil { + return err + } + nameWithHash := fmt.Sprintf("%s-%s", obj.GetName(), h) + obj.SetName(nameWithHash) + return nil +} + +// TODO: Remove this function after we support hash unstructured objects +func unstructuredToSecret(in *unstructured.Unstructured) (*v1.Secret, error) { + marshaled, err := json.Marshal(in) + if err != nil { + return nil, err + } + var out v1.Secret + err = json.Unmarshal(marshaled, &out) + return &out, err +} diff --git a/pkg/kinflate/transformers/namehash_test.go b/pkg/kinflate/transformers/namehash_test.go new file mode 100644 index 000000000..847bc02a2 --- /dev/null +++ b/pkg/kinflate/transformers/namehash_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package transformers + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubectl/pkg/kinflate/types" +) + +func makeSecret(name string) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": name, + }, + }, + } +} + +func makeHashTestMap() types.KObject { + return types.KObject{ + { + 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(), + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, + Name: "secret1", + }: makeSecret("secret1"), + } +} + +func makeExpectedHashTestMap() types.KObject { + return types.KObject{ + { + 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) + } +}