diff --git a/cmd/kubeadm/app/util/kustomize/BUILD b/cmd/kubeadm/app/util/kustomize/BUILD index 5156028d257..4d49d43e505 100644 --- a/cmd/kubeadm/app/util/kustomize/BUILD +++ b/cmd/kubeadm/app/util/kustomize/BUILD @@ -3,21 +3,25 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "json6902.go", "kustomize.go", - "unstructured.go", + "strategicmerge.go", ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/kustomize", visibility = ["//visibility:public"], deps = [ "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/constants:go_default_library", "//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library", "//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library", "//vendor/sigs.k8s.io/kustomize/pkg/loader:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/patch:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library", + "//vendor/sigs.k8s.io/yaml:go_default_library", ], ) @@ -39,10 +43,11 @@ go_test( name = "go_default_test", srcs = [ "kustomize_test.go", - "unstructured_test.go", + "strategicmerge_test.go", ], embed = [":go_default_library"], deps = [ + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/github.com/lithammer/dedent:go_default_library", ], diff --git a/cmd/kubeadm/app/util/kustomize/json6902.go b/cmd/kubeadm/app/util/kustomize/json6902.go new file mode 100644 index 00000000000..2fbfb94abd7 --- /dev/null +++ b/cmd/kubeadm/app/util/kustomize/json6902.go @@ -0,0 +1,64 @@ +/* +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 kustomize contains helpers for working with embedded kustomize commands +package kustomize + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/kustomize/pkg/ifc" + "sigs.k8s.io/kustomize/pkg/patch" +) + +// json6902 represents a json6902 patch +type json6902 struct { + // Target refers to a Kubernetes object that the json patch will be applied to + *patch.Target + + // Patch contain the json patch as a string + Patch string +} + +// json6902Slice is a slice of json6902 patches. +type json6902Slice []*json6902 + +// newJSON6902FromFile returns a json6902 patch from a file +func newJSON6902FromFile(f patch.Json6902, ldr ifc.Loader, file string) (*json6902, error) { + patch, err := ldr.Load(file) + if err != nil { + return nil, err + } + + return &json6902{ + Target: f.Target, + Patch: string(patch), + }, nil +} + +// filterByResource returns all the json6902 patches in the json6902Slice corresponding to a given resource +func (s *json6902Slice) filterByResource(r *unstructured.Unstructured) json6902Slice { + var result json6902Slice + for _, p := range *s { + if p.Group == r.GroupVersionKind().Group && + p.Version == r.GroupVersionKind().Version && + p.Kind == r.GroupVersionKind().Kind && + p.Namespace == r.GetNamespace() && + p.Name == r.GetName() { + result = append(result, p) + } + } + return result +} diff --git a/cmd/kubeadm/app/util/kustomize/kustomize.go b/cmd/kubeadm/app/util/kustomize/kustomize.go index d34499195cd..2c08ced5356 100644 --- a/cmd/kubeadm/app/util/kustomize/kustomize.go +++ b/cmd/kubeadm/app/util/kustomize/kustomize.go @@ -25,15 +25,25 @@ import ( "runtime" "sync" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + yamlutil "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/cli-runtime/pkg/kustomize" + "sigs.k8s.io/kustomize/pkg/constants" "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/ifc" "sigs.k8s.io/kustomize/pkg/loader" + "sigs.k8s.io/kustomize/pkg/patch" + "sigs.k8s.io/kustomize/pkg/types" + "sigs.k8s.io/yaml" ) // Manager define a manager that allow access to kustomize capabilities type Manager struct { - kustomizeDir string - us UnstructuredSlice + kustomizeDir string + kustomizationFile *types.Kustomization + strategicMergePatches strategicMergeSlice + json6902Patches json6902Slice } var ( @@ -42,6 +52,8 @@ var ( ) // GetManager return the KustomizeManager singleton instance +// NB. this is done at singleton instance level because kubeadm has a unique pool +// of patches that are applied to different content, at different time func GetManager(kustomizeDir string) (*Manager, error) { lock.Lock() defer lock.Unlock() @@ -52,12 +64,31 @@ func GetManager(kustomizeDir string) (*Manager, error) { kustomizeDir: kustomizeDir, } - // loads the UnstructuredSlice with all the patches into the Manager - // NB. this is done at singleton instance level because kubeadm has a unique pool - // of patches that are applied to different content, at different time - if err := km.getUnstructuredSlice(); err != nil { + // Create a loader that mimics the behavior of kubectl kustomize, including support for reading from + // a local folder or git repository like git@github.com:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash + // in order to do so you must use ldr.Root() instead of km.kustomizeDir and ldr.Load instead of other ways to read files + fSys := fs.MakeRealFS() + ldr, err := loader.NewLoader(km.kustomizeDir, fSys) + if err != nil { return nil, err } + defer ldr.Cleanup() + + // read the Kustomization file and all the patches it is + // referencing (either stategicMerge or json6902 patches) + if err := km.loadFromKustomizationFile(ldr); err != nil { + return nil, err + } + + // if a Kustomization file was not found, kubeadm creates + // one using all the patches in the folder; however in this + // case only stategicMerge patches are supported + if km.kustomizationFile == nil { + km.kustomizationFile = &types.Kustomization{} + if err := km.loadFromFolder(ldr); err != nil { + return nil, err + } + } instances[kustomizeDir] = km } @@ -65,78 +96,103 @@ func GetManager(kustomizeDir string) (*Manager, error) { return instances[kustomizeDir], nil } -// getUnstructuredSlice returns a UnstructuredSlice with all the patches. -func (km *Manager) getUnstructuredSlice() error { - // kubeadm does not require a kustomization.yaml file listing all the resources/patches, so it is necessary - // to rebuild the list of patches manually - // TODO: make this git friendly - currently this works only for patches in local folders - - files, err := ioutil.ReadDir(km.kustomizeDir) +// loadFromKustomizationFile reads a Kustomization file and all the patches it is +// referencing (either stategicMerge or json6902 patches) +func (km *Manager) loadFromKustomizationFile(ldr ifc.Loader) error { + // Kustomize support different KustomizationFileNames, so we try to read all + var content []byte + match := 0 + for _, kf := range constants.KustomizationFileNames { + c, err := ldr.Load(kf) + if err == nil { + match++ + content = c + } + } + + // if no kustomization file is found return + if match == 0 { + return nil + } + + // if more that one kustomization file is found, return error + if match > 1 { + return errors.Errorf("Found multiple kustomization files under: %s\n", ldr.Root()) + } + + // Decode the kustomization file + decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(content), 1024) + var k = &types.Kustomization{} + if err := decoder.Decode(k); err != nil { + return errors.Wrap(err, "Error decoding kustomization file") + } + km.kustomizationFile = k + + // gets all the strategic merge patches + for _, f := range km.kustomizationFile.PatchesStrategicMerge { + smp, err := newStrategicMergeSliceFromFile(ldr, string(f)) + if err != nil { + return err + } + km.strategicMergePatches = append(km.strategicMergePatches, smp...) + } + + // gets all the json6902 patches + for _, f := range km.kustomizationFile.PatchesJson6902 { + jp, err := newJSON6902FromFile(f, ldr, f.Path) + if err != nil { + return err + } + km.json6902Patches = append(km.json6902Patches, jp) + } + + return nil +} + +// loadFromFolder returns all the stategicMerge patches in a folder +func (km *Manager) loadFromFolder(ldr ifc.Loader) error { + files, err := ioutil.ReadDir(ldr.Root()) if err != nil { return err } - - var paths = []string{} - for _, file := range files { - if file.IsDir() { + for _, fileInfo := range files { + if fileInfo.IsDir() { continue } - paths = append(paths, file.Name()) - } - // Create a loader that mimics the behavior of kubectl kustomize, including support for reading from - // a local git repository like git@github.com:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash - fSys := fs.MakeRealFS() - ldr, err := loader.NewLoader(km.kustomizeDir, fSys) - if err != nil { - return err + smp, err := newStrategicMergeSliceFromFile(ldr, fileInfo.Name()) + if err != nil { + return err + } + km.strategicMergePatches = append(km.strategicMergePatches, smp...) } - defer ldr.Cleanup() - - // read all the kustomizations and build the UnstructuredSlice - us, err := NewUnstructuredSliceFromFiles(ldr, paths) - if err != nil { - return err - } - - km.us = us return nil } // Kustomize apply a set of patches to a resource. // Portions of the kustomize logic in this function are taken from the kubernetes-sigs/kind project -func (km *Manager) Kustomize(res []byte) ([]byte, error) { - // create a loader that mimics the behavior of kubectl kustomize - // and converts the resource into a UnstructuredSlice - // Nb. in kubeadm we are controlling resource generation, and so we - // we are expecting 1 object into each resource, eg. the static pod. - // Nevertheless, this code is ready for more than one object per resource - resList, err := NewUnstructuredSliceFromBytes(res) - if err != nil { +func (km *Manager) Kustomize(data []byte) ([]byte, error) { + // parse the resource to kustomize + decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(data), 1024) + var resource *unstructured.Unstructured + if err := decoder.Decode(&resource); err != nil { return nil, err } - // create a list of resource and corresponding patches - var resources, patches UnstructuredSlice - for _, r := range resList { - resources = append(resources, r) - - resourcePatches := km.us.FilterResource(r.GroupVersionKind(), r.GetNamespace(), r.GetName()) - - if len(resourcePatches) > 0 { - fmt.Printf("[kustomize] Applying %d patches to %s Resource=%s/%s\n", len(resourcePatches), r.GroupVersionKind(), r.GetNamespace(), r.GetName()) - patches = append(patches, resourcePatches...) - } - } + // get patches corresponding to this resource + strategicMerge := km.strategicMergePatches.filterByResource(resource) + json6902 := km.json6902Patches.filterByResource(resource) // if there are no patches, for the target resources, exit - if len(patches) == 0 { - return res, nil + if len(strategicMerge)+len(json6902) == 0 { + return data, nil } + fmt.Printf("[kustomize] Applying %d patches to %s Resource=%s/%s\n", len(strategicMerge)+len(json6902), resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName()) + // create an in memory fs to use for the kustomization memFS := fs.MakeFakeFS() - var kustomization bytes.Buffer fakeDir := "/" // for Windows we need this to be a drive because kustomize uses filepath.Abs() // which will add a drive letter if there is none. which drive letter is @@ -145,33 +201,44 @@ func (km *Manager) Kustomize(res []byte) ([]byte, error) { fakeDir = `C:\` } - // write resources and patches to the in memory fs, generate the kustomization.yaml - // that ties everything together - kustomization.WriteString("resources:\n") - for i, r := range resources { - b, err := r.MarshalJSON() + // writes the resource to a file in the temp file system + b, err := yaml.Marshal(resource) + if err != nil { + return nil, err + } + name := "resource.yaml" + _ = memFS.WriteFile(filepath.Join(fakeDir, name), b) + + km.kustomizationFile.Resources = []string{name} + + // writes strategic merge patches to files in the temp file system + km.kustomizationFile.PatchesStrategicMerge = []patch.StrategicMerge{} + for i, p := range strategicMerge { + b, err := yaml.Marshal(p) if err != nil { return nil, err } - - name := fmt.Sprintf("resource-%d.json", i) + name := fmt.Sprintf("patch-%d.yaml", i) _ = memFS.WriteFile(filepath.Join(fakeDir, name), b) - fmt.Fprintf(&kustomization, " - %s\n", name) + + km.kustomizationFile.PatchesStrategicMerge = append(km.kustomizationFile.PatchesStrategicMerge, patch.StrategicMerge(name)) } - kustomization.WriteString("patches:\n") - for i, p := range patches { - b, err := p.MarshalJSON() - if err != nil { - return nil, err - } + // writes json6902 patches to files in the temp file system + km.kustomizationFile.PatchesJson6902 = []patch.Json6902{} + for i, p := range json6902 { + name := fmt.Sprintf("patchjson-%d.yaml", i) + _ = memFS.WriteFile(filepath.Join(fakeDir, name), []byte(p.Patch)) - name := fmt.Sprintf("patch-%d.json", i) - _ = memFS.WriteFile(filepath.Join(fakeDir, name), b) - fmt.Fprintf(&kustomization, " - %s\n", name) + km.kustomizationFile.PatchesJson6902 = append(km.kustomizationFile.PatchesJson6902, patch.Json6902{Target: p.Target, Path: name}) } - memFS.WriteFile(filepath.Join(fakeDir, "kustomization.yaml"), kustomization.Bytes()) + // writes the kustomization file to the temp file system + kbytes, err := yaml.Marshal(km.kustomizationFile) + if err != nil { + return nil, err + } + memFS.WriteFile(filepath.Join(fakeDir, "kustomization.yaml"), kbytes) // Finally customize the target resource var out bytes.Buffer diff --git a/cmd/kubeadm/app/util/kustomize/kustomize_test.go b/cmd/kubeadm/app/util/kustomize/kustomize_test.go index 0175a68a4c7..41643916e7e 100644 --- a/cmd/kubeadm/app/util/kustomize/kustomize_test.go +++ b/cmd/kubeadm/app/util/kustomize/kustomize_test.go @@ -26,21 +26,14 @@ import ( "github.com/lithammer/dedent" ) -func TestKustomize(t *testing.T) { +func TestKustomizeWithoutKustomizationFile(t *testing.T) { tmpdir, err := ioutil.TempDir("", "") if err != nil { t.Fatal("Couldn't create tmpdir") } defer os.RemoveAll(tmpdir) - resourceString := dedent.Dedent(` - apiVersion: v1 - kind: Pod - metadata: - name: kube-apiserver - `) - - patch1String := dedent.Dedent(` + strategicMergePatch1 := dedent.Dedent(` apiVersion: v1 kind: Pod metadata: @@ -49,12 +42,12 @@ func TestKustomize(t *testing.T) { kustomize: patch for kube-apiserver `) - err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-1.yaml"), []byte(patch1String), 0644) + err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-1.yaml"), []byte(strategicMergePatch1), 0644) if err != nil { t.Fatalf("WriteFile returned unexpected error: %v", err) } - patch2String := dedent.Dedent(` + strategicMergePatch2 := dedent.Dedent(` apiVersion: v1 kind: Pod metadata: @@ -63,26 +56,148 @@ func TestKustomize(t *testing.T) { kustomize: patch for kube-scheduler `) - err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-2.yaml"), []byte(patch2String), 0644) + err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-2.yaml"), []byte(strategicMergePatch2), 0644) if err != nil { t.Fatalf("WriteFile returned unexpected error: %v", err) } km, err := GetManager(tmpdir) if err != nil { - t.Errorf("GetManager returned unexpected error: %v", err) + t.Fatalf("GetManager returned unexpected error: %v", err) } - kustomized, err := km.Kustomize([]byte(resourceString)) + resource := dedent.Dedent(` + apiVersion: v1 + kind: Pod + metadata: + name: kube-apiserver + `) + + kustomized, err := km.Kustomize([]byte(resource)) if err != nil { - t.Errorf("Kustomize returned unexpected error: %v", err) + t.Fatalf("Kustomize returned unexpected error: %v", err) } if !strings.Contains(string(kustomized), "kustomize: patch for kube-apiserver") { - t.Error("Kustomize did not apply patches corresponding to the resource") + t.Error("Kustomize did not apply strategicMergePatch") } if strings.Contains(string(kustomized), "kustomize: patch for kube-scheduler") { t.Error("Kustomize did apply patches not corresponding to the resource") } } + +func TestKustomizeWithKustomizationFile(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal("Couldn't create tmpdir") + } + defer os.RemoveAll(tmpdir) + + kustomizationFile := dedent.Dedent(` + patchesJson6902: + - target: + version: v1 + kind: Pod + name: kube-apiserver + path: patch-1.yaml + - target: + version: v1 + kind: Pod + name: kube-scheduler + path: patch-2.yaml + patchesStrategicMerge: + - patch-3.yaml + - patch-4.yaml + `) + + err = ioutil.WriteFile(filepath.Join(tmpdir, "kustomization.yaml"), []byte(kustomizationFile), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + jsonPatch1 := dedent.Dedent(` + - op: add + path: "/metadata/labels" + value: + kustomize1: patch for kube-apiserver + `) + + err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-1.yaml"), []byte(jsonPatch1), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + jsonPatch2 := dedent.Dedent(` + - op: add + path: "/metadata/labels" + value: + kustomize1: patch for kube-scheduler + `) + + err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-2.yaml"), []byte(jsonPatch2), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + strategicMergePatch1 := dedent.Dedent(` + apiVersion: v1 + kind: Pod + metadata: + name: kube-apiserver + annotations: + kustomize2: patch for kube-apiserver + `) + + err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-3.yaml"), []byte(strategicMergePatch1), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + strategicMergePatch2 := dedent.Dedent(` + apiVersion: v1 + kind: Pod + metadata: + name: kube-scheduler + annotations: + kustomize2: patch for kube-scheduler + `) + + err = ioutil.WriteFile(filepath.Join(tmpdir, "patch-4.yaml"), []byte(strategicMergePatch2), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + km, err := GetManager(tmpdir) + if err != nil { + t.Fatalf("GetManager returned unexpected error: %v", err) + } + + resource := dedent.Dedent(` + apiVersion: v1 + kind: Pod + metadata: + name: kube-apiserver + `) + + kustomized, err := km.Kustomize([]byte(resource)) + if err != nil { + t.Fatalf("Kustomize returned unexpected error: %v", err) + } + + if !strings.Contains(string(kustomized), "kustomize1: patch for kube-apiserver") { + t.Error("Kustomize did not apply json patches corresponding to the resource") + } + + if strings.Contains(string(kustomized), "kustomize1: patch for kube-scheduler") { + t.Error("Kustomize did apply json patches not corresponding to the resource") + } + + if !strings.Contains(string(kustomized), "kustomize2: patch for kube-apiserver") { + t.Error("Kustomize did not apply strategic merge patches corresponding to the resource") + } + + if strings.Contains(string(kustomized), "kustomize2: patch for kube-scheduler") { + t.Error("Kustomize did apply strategic merge patches not corresponding to the resource") + } +} diff --git a/cmd/kubeadm/app/util/kustomize/unstructured.go b/cmd/kubeadm/app/util/kustomize/strategicmerge.go similarity index 58% rename from cmd/kubeadm/app/util/kustomize/unstructured.go rename to cmd/kubeadm/app/util/kustomize/strategicmerge.go index 64bec38a3df..c220b502725 100644 --- a/cmd/kubeadm/app/util/kustomize/unstructured.go +++ b/cmd/kubeadm/app/util/kustomize/strategicmerge.go @@ -26,41 +26,33 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/kustomize/pkg/ifc" ) -// UnstructuredSlice is a slice of Unstructured objects. -// Unstructured objects are used to represent both resources and patches of any group/version/kind. -type UnstructuredSlice []*unstructured.Unstructured +// strategicMergeSlice is a slice of strategic merge patches. +// Unstructured objects are used to represent strategic merge patches of any group/version/kind. +type strategicMergeSlice []*unstructured.Unstructured -// NewUnstructuredSliceFromFiles returns a ResMap given a resource path slice. -// This func use a Loader to mimic the behavior of kubectl kustomize, and most specifically support for reading from -// a local git repository like git@github.com:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash -func NewUnstructuredSliceFromFiles(loader ifc.Loader, paths []string) (UnstructuredSlice, error) { - var result UnstructuredSlice - for _, path := range paths { - content, err := loader.Load(path) - if err != nil { - return nil, errors.Wrapf(err, "load from path %q failed", path) - } - res, err := NewUnstructuredSliceFromBytes(content) - if err != nil { - return nil, errors.Wrapf(err, "convert %q to Unstructured failed", path) - } - - result = append(result, res...) +// newStrategicMergeSliceFromFile returns a slice of strategic merge patches from a file +func newStrategicMergeSliceFromFile(loader ifc.Loader, path string) (strategicMergeSlice, error) { + content, err := loader.Load(path) + if err != nil { + return nil, errors.Wrapf(err, "load from path %q failed", path) } - return result, nil + res, err := newStrategicMergeSliceFromBytes(content) + if err != nil { + return nil, errors.Wrapf(err, "convert %q to Unstructured failed", path) + } + return res, nil } -// NewUnstructuredSliceFromBytes returns a slice of Unstructured. +// newStrategicMergeSliceFromBytes returns a strategic merge patches contained in a []byte. // This functions handles all the nuances of Kubernetes yaml (e.g. many yaml // documents in one file, List of objects) -func NewUnstructuredSliceFromBytes(in []byte) (UnstructuredSlice, error) { +func newStrategicMergeSliceFromBytes(in []byte) (strategicMergeSlice, error) { decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024) - var result UnstructuredSlice + var result strategicMergeSlice var err error // Parse all the yaml documents in the file for err == nil || isEmptyYamlError(err) { @@ -88,13 +80,13 @@ func NewUnstructuredSliceFromBytes(in []byte) (UnstructuredSlice, error) { return err } - // Get the UnstructuredSlice for the item - itemU, err := NewUnstructuredSliceFromBytes(itemJSON) + // Get the stategicMergeSlice for the item + itemU, err := newStrategicMergeSliceFromBytes(itemJSON) if err != nil { return err } - // append the UnstructuredSlice for the item to the UnstructuredSlice + // append the stategicMergeSlice for the item to the stategicMergeSlice result = append(result, itemU...) return nil @@ -105,7 +97,7 @@ func NewUnstructuredSliceFromBytes(in []byte) (UnstructuredSlice, error) { continue } - // append the object to the UnstructuredSlice + // append the object to the stategicMergeSlice result = append(result, &u) } } @@ -115,14 +107,14 @@ func NewUnstructuredSliceFromBytes(in []byte) (UnstructuredSlice, error) { return result, nil } -// FilterResource returns all the Unstructured items in the UnstructuredSlice corresponding to a given resource -func (rs *UnstructuredSlice) FilterResource(gvk schema.GroupVersionKind, namespace, name string) UnstructuredSlice { - var result UnstructuredSlice - for _, r := range *rs { - if r.GroupVersionKind() == gvk && - r.GetNamespace() == namespace && - r.GetName() == name { - result = append(result, r) +// filterByResource returns all the strategic merge patches in the strategicMergeSlice corresponding to a given resource +func (s *strategicMergeSlice) filterByResource(r *unstructured.Unstructured) strategicMergeSlice { + var result strategicMergeSlice + for _, p := range *s { + if p.GroupVersionKind() == r.GroupVersionKind() && + p.GetNamespace() == r.GetNamespace() && + p.GetName() == r.GetName() { + result = append(result, p) } } return result diff --git a/cmd/kubeadm/app/util/kustomize/unstructured_test.go b/cmd/kubeadm/app/util/kustomize/strategicmerge_test.go similarity index 57% rename from cmd/kubeadm/app/util/kustomize/unstructured_test.go rename to cmd/kubeadm/app/util/kustomize/strategicmerge_test.go index 6802f0a186f..afd13b50ab1 100644 --- a/cmd/kubeadm/app/util/kustomize/unstructured_test.go +++ b/cmd/kubeadm/app/util/kustomize/strategicmerge_test.go @@ -20,20 +20,21 @@ import ( "testing" "github.com/lithammer/dedent" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) -func TestNewUnstructuredSliceFromBytes(t *testing.T) { +func TestNewStategicMergeSliceFromBytes(t *testing.T) { var useCases = []struct { - name string - in string - expectedUnctructured int - expectedError bool + name string + in string + expectedPatches int + expectedError bool }{ { - name: "empty", - in: "", - expectedUnctructured: 0, + name: "empty", + in: "", + expectedPatches: 0, }, { name: "single patch", @@ -43,7 +44,7 @@ func TestNewUnstructuredSliceFromBytes(t *testing.T) { metadata: name: kube-apiserver `), - expectedUnctructured: 1, + expectedPatches: 1, }, { name: "two patches as separated yaml documents", @@ -58,7 +59,7 @@ func TestNewUnstructuredSliceFromBytes(t *testing.T) { metadata: name: kube-apiserver `), - expectedUnctructured: 2, + expectedPatches: 2, }, { name: "two patches as a k8s list", @@ -75,7 +76,7 @@ func TestNewUnstructuredSliceFromBytes(t *testing.T) { metadata: name: kube-apiserver `), - expectedUnctructured: 2, + expectedPatches: 2, }, { name: "nested k8s lists", @@ -95,7 +96,7 @@ func TestNewUnstructuredSliceFromBytes(t *testing.T) { metadata: name: kube-apiserver `), - expectedUnctructured: 2, + expectedPatches: 2, }, { name: "invalid yaml", @@ -125,18 +126,18 @@ func TestNewUnstructuredSliceFromBytes(t *testing.T) { } for _, rt := range useCases { t.Run(rt.name, func(t *testing.T) { - r, err := NewUnstructuredSliceFromBytes([]byte(rt.in)) + r, err := newStrategicMergeSliceFromBytes([]byte(rt.in)) if err != nil { if !rt.expectedError { - t.Errorf("NewUnstructuredSliceFromBytes returned unexpected error: %v", err) + t.Errorf("newStrategicMergeSliceFromBytes returned unexpected error: %v", err) } return } if err == nil && rt.expectedError { - t.Error("NewUnstructuredSliceFromBytes does not returned expected error") + t.Error("newStrategicMergeSliceFromBytes does not returned expected error") } - if len(r) != rt.expectedUnctructured { - t.Errorf("Expected %d Unstructured items in the slice, actual %d", rt.expectedUnctructured, len(r)) + if len(r) != rt.expectedPatches { + t.Errorf("Expected %d strategic merge patches in the slice, actual %d", rt.expectedPatches, len(r)) } }) } @@ -162,60 +163,65 @@ func TestFilterResource(t *testing.T) { name: kube-scheduler namespace: kube-system `) - u, err := NewUnstructuredSliceFromBytes([]byte(in)) + u, err := newStrategicMergeSliceFromBytes([]byte(in)) if err != nil { - t.Fatalf("NewUnstructuredSliceFromBytes returned unexpected error: %v", err) + t.Fatalf("newStategicMergeSliceFromBytes returned unexpected error: %v", err) } var useCases = []struct { - name string - rgvk schema.GroupVersionKind - rnamespace string - rname string - expectedUnctructured int + name string + rgvk schema.GroupVersionKind + rnamespace string + rname string + expectedPatches int }{ { - name: "match 1", - rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, - rnamespace: "kube-system", - rname: "kube-apiserver", - expectedUnctructured: 1, + name: "match 1", + rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, + rnamespace: "kube-system", + rname: "kube-apiserver", + expectedPatches: 1, }, { - name: "match 2", - rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, - rnamespace: "kube-system", - rname: "kube-scheduler", - expectedUnctructured: 2, + name: "match 2", + rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, + rnamespace: "kube-system", + rname: "kube-scheduler", + expectedPatches: 2, }, { - name: "match 0 (wrong gvk)", - rgvk: schema.GroupVersionKind{Group: "something", Version: "v1", Kind: "Pod"}, - rnamespace: "kube-system", - rname: "kube-scheduler", - expectedUnctructured: 0, + name: "match 0 (wrong gvk)", + rgvk: schema.GroupVersionKind{Group: "something", Version: "v1", Kind: "Pod"}, + rnamespace: "kube-system", + rname: "kube-scheduler", + expectedPatches: 0, }, { - name: "match 0 (wrong namespace)", - rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, - rnamespace: "kube-something", - rname: "kube-scheduler", - expectedUnctructured: 0, + name: "match 0 (wrong namespace)", + rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, + rnamespace: "kube-something", + rname: "kube-scheduler", + expectedPatches: 0, }, { - name: "match 0 (wrong namr)", - rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, - rnamespace: "kube-system", - rname: "kube-something", - expectedUnctructured: 0, + name: "match 0 (wrong namr)", + rgvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, + rnamespace: "kube-system", + rname: "kube-something", + expectedPatches: 0, }, } for _, rt := range useCases { t.Run(rt.name, func(t *testing.T) { - r := u.FilterResource(rt.rgvk, rt.rnamespace, rt.rname) + resource := &unstructured.Unstructured{} + resource.SetGroupVersionKind(rt.rgvk) + resource.SetNamespace(rt.rnamespace) + resource.SetName(rt.rname) - if len(r) != rt.expectedUnctructured { - t.Errorf("Expected %d Unstructured items in the slice, actual %d", rt.expectedUnctructured, len(r)) + r := u.filterByResource(resource) + + if len(r) != rt.expectedPatches { + t.Errorf("Expected %d strategic merge patches in the slice, actual %d", rt.expectedPatches, len(r)) } }) }