From 3e85c4589b5743662375f424979006271c442eb9 Mon Sep 17 00:00:00 2001 From: Yujun Zhang Date: Sat, 2 Mar 2019 19:50:27 +0800 Subject: [PATCH 1/5] Load default config for image transformer --- pkg/target/kusttarget.go | 2 +- .../config/defaultconfig/defaultconfig.go | 2 ++ .../config/defaultconfig/image.go | 25 +++++++++++++++++++ pkg/transformers/config/transformerconfig.go | 6 +++++ pkg/transformers/image.go | 12 +++++---- 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 pkg/transformers/config/defaultconfig/image.go diff --git a/pkg/target/kusttarget.go b/pkg/target/kusttarget.go index 5bbd2198c..d916df6fb 100644 --- a/pkg/target/kusttarget.go +++ b/pkg/target/kusttarget.go @@ -312,7 +312,7 @@ func (kt *KustTarget) newTransformer( return nil, err } r = append(r, t) - t, err = transformers.NewImageTransformer(kt.kustomization.Images) + t, err = transformers.NewImageTransformer(kt.kustomization.Images, tConfig.Images) if err != nil { return nil, err } diff --git a/pkg/transformers/config/defaultconfig/defaultconfig.go b/pkg/transformers/config/defaultconfig/defaultconfig.go index d96639a8a..10fe504ac 100644 --- a/pkg/transformers/config/defaultconfig/defaultconfig.go +++ b/pkg/transformers/config/defaultconfig/defaultconfig.go @@ -31,6 +31,7 @@ func GetDefaultFieldSpecs() []byte { []byte(namespaceFieldSpecs), []byte(varReferenceFieldSpecs), []byte(nameReferenceFieldSpecs), + []byte(imageFieldSpecs), } return bytes.Join(configData, []byte("\n")) } @@ -45,5 +46,6 @@ func GetDefaultFieldSpecsAsMap() map[string]string { result["namespace"] = namespaceFieldSpecs result["varreference"] = varReferenceFieldSpecs result["namereference"] = nameReferenceFieldSpecs + result["image"] = imageFieldSpecs return result } diff --git a/pkg/transformers/config/defaultconfig/image.go b/pkg/transformers/config/defaultconfig/image.go new file mode 100644 index 000000000..d571c46aa --- /dev/null +++ b/pkg/transformers/config/defaultconfig/image.go @@ -0,0 +1,25 @@ +/* +Copyright 2019 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 defaultconfig + +const ( + imageFieldSpecs = ` +image: +- path: containers +- path: initContainers +` +) diff --git a/pkg/transformers/config/transformerconfig.go b/pkg/transformers/config/transformerconfig.go index 556f0b814..7ba048524 100644 --- a/pkg/transformers/config/transformerconfig.go +++ b/pkg/transformers/config/transformerconfig.go @@ -34,6 +34,7 @@ type TransformerConfig struct { CommonAnnotations fsSlice `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"` NameReference nbrSlice `json:"nameReference,omitempty" yaml:"nameReference,omitempty"` VarReference fsSlice `json:"varReference,omitempty" yaml:"varReference,omitempty"` + Images fsSlice `json:"image,omitempty" yaml:"image,omitempty"` } // MakeEmptyConfig returns an empty TransformerConfig object @@ -59,6 +60,7 @@ func (t *TransformerConfig) sortFields() { sort.Sort(t.CommonAnnotations) sort.Sort(t.NameReference) sort.Sort(t.VarReference) + sort.Sort(t.Images) } // AddPrefixFieldSpec adds a FieldSpec to NamePrefix @@ -129,6 +131,10 @@ func (t *TransformerConfig) Merge(input *TransformerConfig) ( if err != nil { return nil, err } + merged.Images, err = t.Images.mergeAll(input.Images) + if err != nil { + return nil, err + } merged.sortFields() return merged, nil } diff --git a/pkg/transformers/image.go b/pkg/transformers/image.go index 2e0797694..5ca3b86a1 100644 --- a/pkg/transformers/image.go +++ b/pkg/transformers/image.go @@ -23,18 +23,20 @@ import ( "sigs.k8s.io/kustomize/pkg/image" "sigs.k8s.io/kustomize/pkg/resmap" + "sigs.k8s.io/kustomize/pkg/transformers/config" ) // imageTransformer replace image names and tags type imageTransformer struct { - images []image.Image + images []image.Image + fieldSpecs []config.FieldSpec } var _ Transformer = &imageTransformer{} // NewImageTransformer constructs an imageTransformer. -func NewImageTransformer(slice []image.Image) (Transformer, error) { - return &imageTransformer{slice}, nil +func NewImageTransformer(slice []image.Image, fs []config.FieldSpec) (Transformer, error) { + return &imageTransformer{slice, fs}, nil } // Transform finds the matching images and replaces name, tag and/or digest @@ -58,9 +60,9 @@ func (pt *imageTransformer) Transform(resources resmap.ResMap) error { finds matched ones and update the image name and tag name */ func (pt *imageTransformer) findAndReplaceImage(obj map[string]interface{}) error { - paths := []string{"containers", "initContainers"} found := false - for _, path := range paths { + for _, fs := range pt.fieldSpecs { + path := fs.Path _, found = obj[path] if found { err := pt.updateContainers(obj, path) From f311ba8d4f0cc6b5c307ef4cd5255e41c735eda9 Mon Sep 17 00:00:00 2001 From: Yujun Zhang Date: Sat, 9 Mar 2019 12:04:18 +0800 Subject: [PATCH 2/5] Support custom config for image transformer --- .../config/defaultconfig/defaultconfig.go | 4 +- .../defaultconfig/{image.go => images.go} | 14 ++-- pkg/transformers/config/transformerconfig.go | 2 +- pkg/transformers/image.go | 67 ++++--------------- 4 files changed, 26 insertions(+), 61 deletions(-) rename pkg/transformers/config/defaultconfig/{image.go => images.go} (73%) diff --git a/pkg/transformers/config/defaultconfig/defaultconfig.go b/pkg/transformers/config/defaultconfig/defaultconfig.go index 10fe504ac..d34d9a934 100644 --- a/pkg/transformers/config/defaultconfig/defaultconfig.go +++ b/pkg/transformers/config/defaultconfig/defaultconfig.go @@ -31,7 +31,7 @@ func GetDefaultFieldSpecs() []byte { []byte(namespaceFieldSpecs), []byte(varReferenceFieldSpecs), []byte(nameReferenceFieldSpecs), - []byte(imageFieldSpecs), + []byte(imagesFieldSpecs), } return bytes.Join(configData, []byte("\n")) } @@ -46,6 +46,6 @@ func GetDefaultFieldSpecsAsMap() map[string]string { result["namespace"] = namespaceFieldSpecs result["varreference"] = varReferenceFieldSpecs result["namereference"] = nameReferenceFieldSpecs - result["image"] = imageFieldSpecs + result["images"] = imagesFieldSpecs return result } diff --git a/pkg/transformers/config/defaultconfig/image.go b/pkg/transformers/config/defaultconfig/images.go similarity index 73% rename from pkg/transformers/config/defaultconfig/image.go rename to pkg/transformers/config/defaultconfig/images.go index d571c46aa..f67521183 100644 --- a/pkg/transformers/config/defaultconfig/image.go +++ b/pkg/transformers/config/defaultconfig/images.go @@ -17,9 +17,15 @@ limitations under the License. package defaultconfig const ( - imageFieldSpecs = ` -image: -- path: containers -- path: initContainers + imagesFieldSpecs = ` +images: +- kind: Pod + path: spec/initContainers +- kind: Pod + path: spec/containers +- kind: Deployment + path: spec/template/spec/initContainers +- kind: Deployment + path: spec/template/spec/containers ` ) diff --git a/pkg/transformers/config/transformerconfig.go b/pkg/transformers/config/transformerconfig.go index 7ba048524..a00c6da66 100644 --- a/pkg/transformers/config/transformerconfig.go +++ b/pkg/transformers/config/transformerconfig.go @@ -34,7 +34,7 @@ type TransformerConfig struct { CommonAnnotations fsSlice `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"` NameReference nbrSlice `json:"nameReference,omitempty" yaml:"nameReference,omitempty"` VarReference fsSlice `json:"varReference,omitempty" yaml:"varReference,omitempty"` - Images fsSlice `json:"image,omitempty" yaml:"image,omitempty"` + Images fsSlice `json:"images,omitempty" yaml:"images,omitempty"` } // MakeEmptyConfig returns an empty TransformerConfig object diff --git a/pkg/transformers/image.go b/pkg/transformers/image.go index 5ca3b86a1..c60f0eeb8 100644 --- a/pkg/transformers/image.go +++ b/pkg/transformers/image.go @@ -40,47 +40,30 @@ func NewImageTransformer(slice []image.Image, fs []config.FieldSpec) (Transforme } // Transform finds the matching images and replaces name, tag and/or digest -func (pt *imageTransformer) Transform(resources resmap.ResMap) error { +func (pt *imageTransformer) Transform(m resmap.ResMap) error { if len(pt.images) == 0 { return nil } - for _, res := range resources { - err := pt.findAndReplaceImage(res.Map()) - if err != nil { - return err - } - } - return nil -} - -/* - findAndReplaceImage replaces the image name and tags inside one object - It searches the object for container session - then loops though all images inside containers session, - finds matched ones and update the image name and tag name -*/ -func (pt *imageTransformer) findAndReplaceImage(obj map[string]interface{}) error { - found := false - for _, fs := range pt.fieldSpecs { - path := fs.Path - _, found = obj[path] - if found { - err := pt.updateContainers(obj, path) + for id := range m { + objMap := m[id].Map() + for _, path := range pt.fieldSpecs { + if !id.Gvk().IsSelected(&path.Gvk) { + continue + } + err := mutateField(objMap, path.PathSlice(), false, pt.updateContainers) if err != nil { return err } + } } - if !found { - return pt.findContainers(obj) - } return nil } -func (pt *imageTransformer) updateContainers(obj map[string]interface{}, path string) error { - containers, ok := obj[path].([]interface{}) +func (pt *imageTransformer) updateContainers(in interface{}) (interface{}, error) { + containers, ok := in.([]interface{}) if !ok { - return fmt.Errorf("containers path is not of type []interface{} but %T", obj[path]) + return nil, fmt.Errorf("containers path is not of type []interface{} but %T", in) } for i := range containers { container := containers[i].(map[string]interface{}) @@ -108,31 +91,7 @@ func (pt *imageTransformer) updateContainers(obj map[string]interface{}, path st break } } - return nil -} - -func (pt *imageTransformer) findContainers(obj map[string]interface{}) error { - for key := range obj { - switch typedV := obj[key].(type) { - case map[string]interface{}: - err := pt.findAndReplaceImage(typedV) - if err != nil { - return err - } - case []interface{}: - for i := range typedV { - item := typedV[i] - typedItem, ok := item.(map[string]interface{}) - if ok { - err := pt.findAndReplaceImage(typedItem) - if err != nil { - return err - } - } - } - } - } - return nil + return containers, nil } func isImageMatched(s, t string) bool { From abf538d80d85da1f1739f0b42f59139210d8c10b Mon Sep 17 00:00:00 2001 From: Yujun Zhang Date: Tue, 12 Mar 2019 20:11:56 +0800 Subject: [PATCH 3/5] Keep backward compatibility for image transformer --- pkg/transformers/image.go | 54 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/pkg/transformers/image.go b/pkg/transformers/image.go index c60f0eeb8..ba8dbab50 100644 --- a/pkg/transformers/image.go +++ b/pkg/transformers/image.go @@ -54,8 +54,36 @@ func (pt *imageTransformer) Transform(m resmap.ResMap) error { if err != nil { return err } - } + // Keep for backward compatibility + err := pt.findAndReplaceImage(objMap) + if err != nil { + return err + } + } + return nil +} + +/* + findAndReplaceImage replaces the image name and tags inside one object + It searches the object for container session + then loops though all images inside containers session, + finds matched ones and update the image name and tag name +*/ +func (pt *imageTransformer) findAndReplaceImage(obj map[string]interface{}) error { + paths := []string{"containers", "initContainers"} + found := false + for _, path := range paths { + containers, found := obj[path] + if found { + _, err := pt.updateContainers(containers) + if err != nil { + return err + } + } + } + if !found { + return pt.findContainers(obj) } return nil } @@ -94,6 +122,30 @@ func (pt *imageTransformer) updateContainers(in interface{}) (interface{}, error return containers, nil } +func (pt *imageTransformer) findContainers(obj map[string]interface{}) error { + for key := range obj { + switch typedV := obj[key].(type) { + case map[string]interface{}: + err := pt.findAndReplaceImage(typedV) + if err != nil { + return err + } + case []interface{}: + for i := range typedV { + item := typedV[i] + typedItem, ok := item.(map[string]interface{}) + if ok { + err := pt.findAndReplaceImage(typedItem) + if err != nil { + return err + } + } + } + } + } + return nil +} + func isImageMatched(s, t string) bool { // Tag values are limited to [a-zA-Z0-9_.-]. pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$") From 7130e3ff1d656d0a616fffa3c67adf828bec3bbb Mon Sep 17 00:00:00 2001 From: Yujun Zhang Date: Sun, 17 Mar 2019 08:51:14 +0800 Subject: [PATCH 4/5] Leave defautconfig empty for images `containers` and `initContainers` of *ANY* kind in *ANY* path are builtin supported in code --- pkg/transformers/config/defaultconfig/images.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pkg/transformers/config/defaultconfig/images.go b/pkg/transformers/config/defaultconfig/images.go index f67521183..7612b616c 100644 --- a/pkg/transformers/config/defaultconfig/images.go +++ b/pkg/transformers/config/defaultconfig/images.go @@ -17,15 +17,7 @@ limitations under the License. package defaultconfig const ( - imagesFieldSpecs = ` -images: -- kind: Pod - path: spec/initContainers -- kind: Pod - path: spec/containers -- kind: Deployment - path: spec/template/spec/initContainers -- kind: Deployment - path: spec/template/spec/containers -` + // imageFieldSpecs is left empty since `containers` and `initContainers` + // of *ANY* kind in *ANY* path are builtin supported in code + imagesFieldSpecs = `` ) From e6c1b1410897a13d39983044c31e30db6c4f1e1a Mon Sep 17 00:00:00 2001 From: Yujun Zhang Date: Sun, 17 Mar 2019 16:54:50 +0800 Subject: [PATCH 5/5] Add test for transformers/image custom config --- pkg/target/transformersimage_test.go | 115 +++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/pkg/target/transformersimage_test.go b/pkg/target/transformersimage_test.go index 7d09bdf49..3f12e0cb7 100644 --- a/pkg/target/transformersimage_test.go +++ b/pkg/target/transformersimage_test.go @@ -168,3 +168,118 @@ spec3: name: my-cool-app `) } + +func makeTransfomersImageCustomBase(th *KustTestHarness) { + th.writeK("/app/base", ` +resources: +- custom.yaml +configurations: +- config/custom.yaml +images: +- name: nginx + newTag: v2 +- name: my-nginx + newTag: previous +- name: myprivaterepohostname:1234/my/image + newTag: v1.0.1 +- name: foobar + digest: sha256:24a0c4b4 +- name: alpine + newName: myprivaterepohostname:1234/my/cool-alpine +- name: gcr.io:8080/my-project/my-cool-app + newName: my-cool-app +- name: postgres + newName: my-postgres + newTag: v3 +- name: docker + newName: my-docker + digest: sha256:25a0d4b4 +`) + th.writeF("/app/base/custom.yaml", ` +kind: customKind +metadata: + name: custom +spec: + template: + spec: + myContainers: + - name: ngnix1 + image: nginx +spec2: + template: + spec: + myContainers: + - name: nginx3 + image: nginx:v1 + - name: nginx4 + image: my-nginx:latest +spec3: + template: + spec: + myInitContainers: + - name: postgresdb + image: postgres:alpine-9 + - name: init-docker + image: docker:17-git + - name: myImage + image: myprivaterepohostname:1234/my/image:latest + - name: myImage2 + image: myprivaterepohostname:1234/my/image + - name: my-app + image: my-app-image:v1 + - name: my-cool-app + image: gcr.io:8080/my-project/my-cool-app:latest +`) + th.writeF("/app/base/config/custom.yaml", ` +images: +- kind: Custom + path: spec/template/spec/myContainers +- kind: Custom + path: spec2/template/spec/myContainers +- kind: Custom + path: spec3/template/spec/myInitContainers +`) +} +func TestTransfomersImageCustomConfig(t *testing.T) { + th := NewKustTestHarness(t, "/app/base") + makeTransfomersImageCustomBase(th) + m, err := th.makeKustTarget().MakeCustomizedResMap() + if err != nil { + t.Fatalf("Err: %v", err) + } + th.assertActualEqualsExpected(m, ` +kind: customKind +metadata: + name: custom +spec: + template: + spec: + myContainers: + - image: nginx + name: ngnix1 +spec2: + template: + spec: + myContainers: + - image: nginx:v1 + name: nginx3 + - image: my-nginx:latest + name: nginx4 +spec3: + template: + spec: + myInitContainers: + - image: postgres:alpine-9 + name: postgresdb + - image: docker:17-git + name: init-docker + - image: myprivaterepohostname:1234/my/image:latest + name: myImage + - image: myprivaterepohostname:1234/my/image + name: myImage2 + - image: my-app-image:v1 + name: my-app + - image: gcr.io:8080/my-project/my-cool-app:latest + name: my-cool-app +`) +}