From fc542071238c50d4072f08ccc90f330325ddc404 Mon Sep 17 00:00:00 2001 From: ymqytw Date: Mon, 22 Jan 2018 15:48:21 -0800 Subject: [PATCH] add util to update name reference --- pkg/kinflate/util/namereference.go | 122 ++++++++ pkg/kinflate/util/namereference_test.go | 164 +++++++++++ pkg/kinflate/util/namereferenceconfig.go | 342 +++++++++++++++++++++++ pkg/kinflate/util/pathconfig.go | 21 ++ 4 files changed, 649 insertions(+) create mode 100644 pkg/kinflate/util/namereference.go create mode 100644 pkg/kinflate/util/namereference_test.go create mode 100644 pkg/kinflate/util/namereferenceconfig.go diff --git a/pkg/kinflate/util/namereference.go b/pkg/kinflate/util/namereference.go new file mode 100644 index 000000000..6febdc797 --- /dev/null +++ b/pkg/kinflate/util/namereference.go @@ -0,0 +1,122 @@ +/* +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 util + +import ( + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// NameReferenceTransformer contains the referencing info between 2 GroupVersionKinds +type NameReferenceTransformer struct { + pathConfigs []referencePathConfig +} + +var _ Transformer = &NameReferenceTransformer{} + +// NewDefaultingNameReferenceTransformer constructs a NameReferenceTransformer +// with defaultNameReferencepathConfigs. +func NewDefaultingNameReferenceTransformer() (Transformer, error) { + return NewNameReferenceTransformer(defaultNameReferencePathConfigs) +} + +// NewNameReferenceTransformer construct a NameReferenceTransformer. +func NewNameReferenceTransformer(pc []referencePathConfig) (Transformer, error) { + if pc == nil { + return nil, errors.New("pathConfigs is not expected to be nil") + } + return &NameReferenceTransformer{pathConfigs: pc}, nil +} + +// Transform does the fields update according to pathConfigs. +// The old name is in the key in the map and the new name is in the object +// associated with the key. e.g. if is one of the key-value pair in the map, +// then the old name is k.Name and the new name is v.GetName() +func (o *NameReferenceTransformer) Transform( + m map[GroupVersionKindName]*unstructured.Unstructured) error { + for GVKn := range m { + obj := m[GVKn] + objMap := obj.UnstructuredContent() + for _, referencePathConfig := range o.pathConfigs { + for _, path := range referencePathConfig.pathConfigs { + if !SelectByGVK(GVKn.GVK, path.GroupVersionKind) { + continue + } + err := mutateField(objMap, path.Path, path.CreateIfNotPresent, + o.updateNameReference(referencePathConfig.referencedGVK, m)) + // Ignore the error when we can't find the GVKN that is being + // referenced, because the missing GVKN may be not included in + // this manifest and will be created later. + if IsNoMatchingGVKNError(err) { + continue + } + if err != nil { + return err + } + } + } + } + return nil +} + +// NoMatchingGVKNError indicates failing to find a GroupVersionKindName. +type NoMatchingGVKNError struct { + message string +} + +// NewNoMatchingGVKNError constructs an instance of NoMatchingGVKNError with +// a given error message. +func NewNoMatchingGVKNError(errMsg string) NoMatchingGVKNError { + return NoMatchingGVKNError{errMsg} +} + +// IsNoMatchingGVKNError checks if the error is NoMatchingGVKNError type. +func IsNoMatchingGVKNError(err error) bool { + _, ok := err.(NoMatchingGVKNError) + return ok +} + +// Error returns the error in string format. +func (err NoMatchingGVKNError) Error() string { + return err.message +} + +func (o *NameReferenceTransformer) updateNameReference( + GVK schema.GroupVersionKind, + m map[GroupVersionKindName]*unstructured.Unstructured, +) func(in interface{}) (interface{}, error) { + return func(in interface{}) (interface{}, error) { + s, ok := in.(string) + if !ok { + return nil, fmt.Errorf("%#v is expectd to be %T", in, s) + } + + for GVKn, obj := range m { + if !SelectByGVK(GVKn.GVK, &GVK) { + continue + } + if GVKn.Name == s { + return obj.GetName(), nil + } + } + return nil, NewNoMatchingGVKNError( + fmt.Sprintf("no matching for GroupVersionKind %v and Name %v", GVK, s)) + } +} diff --git a/pkg/kinflate/util/namereference_test.go b/pkg/kinflate/util/namereference_test.go new file mode 100644 index 000000000..c26244c28 --- /dev/null +++ b/pkg/kinflate/util/namereference_test.go @@ -0,0 +1,164 @@ +/* +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 util + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func makeReferencedConfigMap() *unstructured.Unstructured { + return &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", + }, + }, + } +} + +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{}{ + "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": cmName, + "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": cmName, + "key": "somekey", + }, + }, + map[string]interface{}{ + "secretRef": map[string]interface{}{ + "name": secretName, + "key": "somekey", + }, + }, + }, + }, + }, + "volumes": map[string]interface{}{ + "configMap": map[string]interface{}{ + "name": cmName, + }, + "secret": map[string]interface{}{ + "name": secretName, + }, + }, + }, + }, + }, + }, + } +} + +func makeNameReferenceTestMap() map[GroupVersionKindName]*unstructured.Unstructured { + return map[GroupVersionKindName]*unstructured.Unstructured{ + { + 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("cm1", "secret1"), + } +} + +func makeNameReferenceUpdatedTestMap() map[GroupVersionKindName]*unstructured.Unstructured { + return map[GroupVersionKindName]*unstructured.Unstructured{ + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "cm1", + }: makeReferencedConfigMap(), + { + GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"}, + Name: "secret1", + }: makeReferencedSecret(), + { + GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, + Name: "deploy1", + }: makeNameRefDeployment("someprefix-cm1-somehash", "someprefix-secret1-somehash"), + } +} + +func TestNameReferenceRun(t *testing.T) { + m := makeNameReferenceTestMap() + nrt, err := NewDefaultingNameReferenceTransformer() + err = nrt.Transform(m) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expected := makeNameReferenceUpdatedTestMap() + if !reflect.DeepEqual(m, expected) { + err = compareMap(m, expected) + t.Fatalf("actual doesn't match expected: %v", err) + } +} diff --git a/pkg/kinflate/util/namereferenceconfig.go b/pkg/kinflate/util/namereferenceconfig.go new file mode 100644 index 000000000..c0b4b2b1e --- /dev/null +++ b/pkg/kinflate/util/namereferenceconfig.go @@ -0,0 +1,342 @@ +/* +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 util + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// defaultNameReferencePathConfigs is the default configuration for updating +// the fields reference the name of other resources. +var defaultNameReferencePathConfigs = []referencePathConfig{ + { + referencedGVK: schema.GroupVersionKind{ + Version: "v1", + Kind: "ConfigMap", + }, + pathConfigs: []PathConfig{ + { + GroupVersionKind: &schema.GroupVersionKind{ + Version: "v1", + Kind: "Pod", + }, + Path: []string{"spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Version: "v1", + Kind: "Pod", + }, + Path: []string{"spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Version: "v1", + Kind: "Pod", + }, + Path: []string{"spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Deployment", + }, + Path: []string{"spec", "template", "spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Deployment", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Deployment", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "ReplicaSet", + }, + Path: []string{"spec", "template", "spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "ReplicaSet", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "ReplicaSet", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "DaemonSet", + }, + Path: []string{"spec", "template", "spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "DaemonSet", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "DaemonSet", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "StatefulSet", + }, + Path: []string{"spec", "template", "spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "StatefulSet", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "StatefulSet", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Job", + }, + Path: []string{"spec", "template", "spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Job", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Job", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "CronJob", + }, + Path: []string{"spec", "jobTemplate", "spec", "template", "spec", "volumes", "configMap", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "CronJob", + }, + Path: []string{"spec", "jobTemplate", "spec", "template", "spec", "containers", "env", "valueFrom", "configMapKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "CronJob", + }, + Path: []string{"spec", "jobTemplate", "spec", "template", "spec", "containers", "envFrom", "configMapRef", "name"}, + CreateIfNotPresent: false, + }, + }, + }, + { + referencedGVK: schema.GroupVersionKind{ + Version: "v1", + Kind: "Secret", + }, + pathConfigs: []PathConfig{ + { + GroupVersionKind: &schema.GroupVersionKind{ + Version: "v1", + Kind: "Pod", + }, + Path: []string{"spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Version: "v1", + Kind: "Pod", + }, + Path: []string{"spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Version: "v1", + Kind: "Pod", + }, + Path: []string{"spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Deployment", + }, + Path: []string{"spec", "template", "spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Deployment", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Deployment", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "ReplicaSet", + }, + Path: []string{"spec", "template", "spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "ReplicaSet", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "ReplicaSet", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "DaemonSet", + }, + Path: []string{"spec", "template", "spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "DaemonSet", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "DaemonSet", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "StatefulSet", + }, + Path: []string{"spec", "template", "spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "StatefulSet", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "StatefulSet", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Job", + }, + Path: []string{"spec", "template", "spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Job", + }, + Path: []string{"spec", "template", "spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "Job", + }, + Path: []string{"spec", "template", "spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "CronJob", + }, + Path: []string{"spec", "jobTemplate", "spec", "template", "spec", "volumes", "secret", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "CronJob", + }, + Path: []string{"spec", "jobTemplate", "spec", "template", "spec", "containers", "env", "valueFrom", "secretKeyRef", "name"}, + CreateIfNotPresent: false, + }, + { + GroupVersionKind: &schema.GroupVersionKind{ + Kind: "CronJob", + }, + Path: []string{"spec", "jobTemplate", "spec", "template", "spec", "containers", "envFrom", "secretRef", "name"}, + CreateIfNotPresent: false, + }, + }, + }, +} diff --git a/pkg/kinflate/util/pathconfig.go b/pkg/kinflate/util/pathconfig.go index c75f49a65..d9b02f6af 100644 --- a/pkg/kinflate/util/pathconfig.go +++ b/pkg/kinflate/util/pathconfig.go @@ -32,3 +32,24 @@ type PathConfig struct { // Path to the field that will be munged. Path []string } + +// referencePathConfig contains the configuration of a field that references +// the name of another resource whose GroupVersionKind is specified in referencedGVK. +// e.g. pod.spec.template.volumes.configMap.name references the name of a configmap +// Its corresponding referencePathConfig will look like: +// +// referencePathConfig{ +// referencedGVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, +// pathConfigs: []PathConfig{ +// { +// GroupVersionKind: &schema.GroupVersionKind{Version: "v1", Kind: "Pod"}, +// Path: []string{"spec", "volumes", "configMap", "name"}, +// }, +// } +type referencePathConfig struct { + // referencedGVK is the GroupVersionKind that is referenced by + // the PathConfig's GVK in the path of PathConfig.Path. + referencedGVK schema.GroupVersionKind + // PathConfig is the GVK that is referencing the referencedGVK object's name. + pathConfigs []PathConfig +}