Merge pull request #81910 from fabriziopandini/kubeadm-Json6902-Patches

kubeadm: add support for Json6902 Patches
This commit is contained in:
Kubernetes Prow Robot 2019-08-28 03:09:54 -07:00 committed by GitHub
commit b98f622852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 430 additions and 181 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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