copy kustomize/k8sdeps into cli-runtime

pull/564/head
Jingfang Liu 2019-02-11 14:16:43 -08:00
parent 23a9b0c239
commit 74a64a9f3d
34 changed files with 3542 additions and 24 deletions

View File

@ -644,11 +644,11 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/common",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/client-go/discovery",
@ -700,91 +700,91 @@
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/commands/build",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/constants",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/expansion",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/factory",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/fs",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/git",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/gvk",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/ifc",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/ifc/transformer",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/image",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/internal/error",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/loader",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/patch",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/patch/transformer",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/resid",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/resmap",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/resource",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/target",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/kustomize/pkg/types",
"Rev": "0184d5b69700607a1b9280f303152e2959bc19e7"
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",

View File

@ -0,0 +1,38 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["builder.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize",
importpath = "k8s.io/cli-runtime/pkg/kustomize",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/commands/build:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["builder_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library"],
)

View File

@ -0,0 +1,39 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"factory.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/factory:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret:all-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:all-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv:all-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer:all-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,53 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"configmapfactory.go",
"kv.go",
"secretfactory.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"configmapfactory_test.go",
"kv_test.go",
"secretfactory_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/loader:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,126 @@
/*
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 configmapandsecret generates configmaps and secrets per generator rules.
package configmapandsecret
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/types"
)
// ConfigMapFactory makes ConfigMaps.
type ConfigMapFactory struct {
ldr ifc.Loader
}
// NewConfigMapFactory returns a new ConfigMapFactory.
func NewConfigMapFactory(l ifc.Loader) *ConfigMapFactory {
return &ConfigMapFactory{ldr: l}
}
func (f *ConfigMapFactory) makeFreshConfigMap(
args *types.ConfigMapArgs) *corev1.ConfigMap {
cm := &corev1.ConfigMap{}
cm.APIVersion = "v1"
cm.Kind = "ConfigMap"
cm.Name = args.Name
cm.Namespace = args.Namespace
cm.Data = map[string]string{}
return cm
}
// MakeConfigMap returns a new ConfigMap, or nil and an error.
func (f *ConfigMapFactory) MakeConfigMap(
args *types.ConfigMapArgs, options *types.GeneratorOptions) (*corev1.ConfigMap, error) {
var all []kv.Pair
var err error
cm := f.makeFreshConfigMap(args)
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvSource))
}
all = append(all, pairs...)
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"literal sources %v", args.LiteralSources))
}
all = append(all, pairs...)
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"file sources: %v", args.FileSources))
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToConfigMap(cm, kv.Key, kv.Value)
if err != nil {
return nil, err
}
}
if options != nil {
cm.SetLabels(options.Labels)
cm.SetAnnotations(options.Annotations)
}
return cm, nil
}
// addKvToConfigMap adds the given key and data to the given config map.
// Error if key invalid, or already exists.
func addKvToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
keyExistsErrorMsg := "cannot add key %s, another key by that name already exists: %v"
// If the configmap data contains byte sequences that are all in the UTF-8
// range, we will write it to .Data
if utf8.Valid([]byte(data)) {
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}
// otherwise, it's BinaryData
if configMap.BinaryData == nil {
configMap.BinaryData = map[string][]byte{}
}
if _, entryExists := configMap.BinaryData[keyName]; entryExists {
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.BinaryData)
}
configMap.BinaryData[keyName] = []byte(data)
return nil
}

View File

@ -0,0 +1,154 @@
/*
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 configmapandsecret
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/loader"
"sigs.k8s.io/kustomize/pkg/types"
)
func makeEnvConfigMap(name string) *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string]string{
"DB_USERNAME": "admin",
"DB_PASSWORD": "somepw",
},
}
}
func makeFileConfigMap(name string) *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string]string{
"app-init.ini": `FOO=bar
BAR=baz
`,
},
BinaryData: map[string][]byte{
"app.bin": {0xff, 0xfd},
},
}
}
func makeLiteralConfigMap(name string) *corev1.ConfigMap {
cm := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string]string{
"a": "x",
"b": "y",
"c": "Hello World",
"d": "true",
},
}
cm.SetLabels(map[string]string{"foo": "bar"})
return cm
}
func TestConstructConfigMap(t *testing.T) {
type testCase struct {
description string
input types.ConfigMapArgs
options *types.GeneratorOptions
expected *corev1.ConfigMap
}
testCases := []testCase{
{
description: "construct config map from env",
input: types.ConfigMapArgs{
GeneratorArgs: types.GeneratorArgs{
Name: "envConfigMap",
DataSources: types.DataSources{
EnvSource: "configmap/app.env",
},
},
},
options: nil,
expected: makeEnvConfigMap("envConfigMap"),
},
{
description: "construct config map from file",
input: types.ConfigMapArgs{
GeneratorArgs: types.GeneratorArgs{
Name: "fileConfigMap",
DataSources: types.DataSources{
FileSources: []string{"configmap/app-init.ini", "configmap/app.bin"},
},
},
},
options: nil,
expected: makeFileConfigMap("fileConfigMap"),
},
{
description: "construct config map from literal",
input: types.ConfigMapArgs{
GeneratorArgs: types.GeneratorArgs{
Name: "literalConfigMap",
DataSources: types.DataSources{
LiteralSources: []string{"a=x", "b=y", "c=\"Hello World\"", "d='true'"},
},
},
},
options: &types.GeneratorOptions{
Labels: map[string]string{
"foo": "bar",
},
},
expected: makeLiteralConfigMap("literalConfigMap"),
},
}
fSys := fs.MakeFakeFS()
fSys.WriteFile("/configmap/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n"))
fSys.WriteFile("/configmap/app-init.ini", []byte("FOO=bar\nBAR=baz\n"))
fSys.WriteFile("/configmap/app.bin", []byte{0xff, 0xfd})
f := NewConfigMapFactory(loader.NewFileLoaderAtRoot(fSys))
for _, tc := range testCases {
cm, err := f.MakeConfigMap(&tc.input, tc.options)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(*cm, *tc.expected) {
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
}
}
}

View File

@ -0,0 +1,107 @@
/*
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 configmapandsecret
import (
"fmt"
"path"
"strings"
"github.com/pkg/errors"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/ifc"
)
func keyValuesFromLiteralSources(sources []string) ([]kv.Pair, error) {
var kvs []kv.Pair
for _, s := range sources {
k, v, err := parseLiteralSource(s)
if err != nil {
return nil, err
}
kvs = append(kvs, kv.Pair{Key: k, Value: v})
}
return kvs, nil
}
func keyValuesFromFileSources(ldr ifc.Loader, sources []string) ([]kv.Pair, error) {
var kvs []kv.Pair
for _, s := range sources {
k, fPath, err := parseFileSource(s)
if err != nil {
return nil, err
}
content, err := ldr.Load(fPath)
if err != nil {
return nil, err
}
kvs = append(kvs, kv.Pair{Key: k, Value: string(content)})
}
return kvs, nil
}
func keyValuesFromEnvFile(l ifc.Loader, path string) ([]kv.Pair, error) {
if path == "" {
return nil, nil
}
content, err := l.Load(path)
if err != nil {
return nil, err
}
return kv.KeyValuesFromLines(content)
}
// parseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func parseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("key names or file paths cannot contain '='")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// parseLiteralSource parses the source key=val pair into its component pieces.
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
// it returns an error in the case of empty keys, values, or a missing equals sign.
func parseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], strings.Trim(items[1], "\"'"), nil
}

View File

@ -0,0 +1,57 @@
/*
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 configmapandsecret
import (
"reflect"
"testing"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/loader"
)
func TestKeyValuesFromFileSources(t *testing.T) {
tests := []struct {
description string
sources []string
expected []kv.Pair
}{
{
description: "create kvs from file sources",
sources: []string{"files/app-init.ini"},
expected: []kv.Pair{
{
Key: "app-init.ini",
Value: "FOO=bar",
},
},
},
}
fSys := fs.MakeFakeFS()
fSys.WriteFile("/files/app-init.ini", []byte("FOO=bar"))
for _, tc := range tests {
kvs, err := keyValuesFromFileSources(loader.NewFileLoaderAtRoot(fSys), tc.sources)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(kvs, tc.expected) {
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, kvs, tc.expected)
}
}
}

View File

@ -0,0 +1,106 @@
/*
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 configmapandsecret
import (
"fmt"
"strings"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/types"
)
// SecretFactory makes Secrets.
type SecretFactory struct {
ldr ifc.Loader
}
// NewSecretFactory returns a new SecretFactory.
func NewSecretFactory(ldr ifc.Loader) *SecretFactory {
return &SecretFactory{ldr: ldr}
}
func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret {
s := &corev1.Secret{}
s.APIVersion = "v1"
s.Kind = "Secret"
s.Name = args.Name
s.Namespace = args.Namespace
s.Type = corev1.SecretType(args.Type)
if s.Type == "" {
s.Type = corev1.SecretTypeOpaque
}
s.Data = map[string][]byte{}
return s
}
// MakeSecret returns a new secret.
func (f *SecretFactory) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (*corev1.Secret, error) {
var all []kv.Pair
var err error
s := f.makeFreshSecret(args)
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvSource))
}
all = append(all, pairs...)
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"literal sources %v", args.LiteralSources))
}
all = append(all, pairs...)
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"file sources: %v", args.FileSources))
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToSecret(s, kv.Key, kv.Value)
if err != nil {
return nil, err
}
}
if options != nil {
s.SetLabels(options.Labels)
s.SetAnnotations(options.Annotations)
}
return s, nil
}
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
// Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
}
secret.Data[keyName] = []byte(data)
return nil
}

View File

@ -0,0 +1,151 @@
/*
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 configmapandsecret
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/loader"
"sigs.k8s.io/kustomize/pkg/types"
)
func makeEnvSecret(name string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"DB_PASSWORD": []byte("somepw"),
"DB_USERNAME": []byte("admin"),
},
Type: "Opaque",
}
}
func makeFileSecret(name string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"app-init.ini": []byte(`FOO=bar
BAR=baz
`),
},
Type: "Opaque",
}
}
func makeLiteralSecret(name string) *corev1.Secret {
s := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"a": []byte("x"),
"b": []byte("y"),
},
Type: "Opaque",
}
s.SetLabels(map[string]string{"foo": "bar"})
return s
}
func TestConstructSecret(t *testing.T) {
type testCase struct {
description string
input types.SecretArgs
options *types.GeneratorOptions
expected *corev1.Secret
}
testCases := []testCase{
{
description: "construct secret from env",
input: types.SecretArgs{
GeneratorArgs: types.GeneratorArgs{
Name: "envSecret",
DataSources: types.DataSources{
EnvSource: "secret/app.env",
},
},
},
options: nil,
expected: makeEnvSecret("envSecret"),
},
{
description: "construct secret from file",
input: types.SecretArgs{
GeneratorArgs: types.GeneratorArgs{
Name: "fileSecret",
DataSources: types.DataSources{
FileSources: []string{"secret/app-init.ini"},
},
},
},
options: nil,
expected: makeFileSecret("fileSecret"),
},
{
description: "construct secret from literal",
input: types.SecretArgs{
GeneratorArgs: types.GeneratorArgs{
Name: "literalSecret",
DataSources: types.DataSources{
LiteralSources: []string{"a=x", "b=y"},
},
},
},
options: &types.GeneratorOptions{
Labels: map[string]string{
"foo": "bar",
},
},
expected: makeLiteralSecret("literalSecret"),
},
}
fSys := fs.MakeFakeFS()
fSys.WriteFile("/secret/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n"))
fSys.WriteFile("/secret/app-init.ini", []byte("FOO=bar\nBAR=baz\n"))
f := NewSecretFactory(loader.NewFileLoaderAtRoot(fSys))
for _, tc := range testCases {
cm, err := f.MakeSecret(&tc.input, tc.options)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(*cm, *tc.expected) {
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
}
}
}

View File

@ -0,0 +1,76 @@
/*
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.
*/
// It's possible that kustomize's features will be vendored into
// the kubernetes/kubernetes repo and made available to kubectl
// commands, while at the same time the kustomize program will
// continue to exist as an independent CLI. Vendoring snapshots
// would be taken just before a kubectl release.
//
// This creates a problem in that freestanding-kustomize depends on
// (for example):
//
// https://github.com/kubernetes/apimachinery/
// tree/master/pkg/util/yaml
//
// It vendors that package into
// sigs.k8s.io/kustomize/vendor/k8s.io/apimachinery/
//
// Whereas kubectl-kustomize would have to depend on the "staging"
// version of this code, located at
//
// https://github.com/kubernetes/kubernetes/
// blob/master/staging/src/k8s.io/apimachinery/pkg/util/yaml
//
// which is "vendored" via symlinks:
// k8s.io/kubernetes/vendor/k8s.io/apimachinery
// is a symlink to
// ../../staging/src/k8s.io/apimachinery
//
// The staging version is the canonical, under-development
// version of the code that kubectl depends on, whereas the packages
// at kubernetes/apimachinery are periodic snapshots of staging made
// for outside tools to depend on.
//
// apimachinery isn't the only package that poses this problem, just
// using it as a specific example.
//
// The kubectl binary cannot vendor in kustomize code that in
// turn vendors in the non-staging packages.
//
// One way to fix some of this would be to copy code - a hard fork.
// This has all the problems associated with a hard forking.
//
// Another way would be to break the kustomize repo into three:
//
// (1) kustomize - repo with the main() function,
// vendoring (2) and (3).
//
// (2) kustomize-libs - packages used by (1) with no
// apimachinery dependence.
//
// (3) kustomize-k8sdeps - A thin code layer that depends
// on (vendors) apimachinery to provide thin implementations
// to interfaces used in (2).
//
// The kubectl repo would then vendor from (2) only, and have
// a local implementation of (3). With that in mind, it's clear
// that (3) doesn't have to be a repo; the kustomize version of
// the thin layer can live directly in (1).
//
// This package is the code in (3), meant for kustomize.
package k8sdeps

View File

@ -0,0 +1,34 @@
/*
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 k8sdeps provides kustomize factory with k8s dependencies
package k8sdeps
import (
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator"
"sigs.k8s.io/kustomize/pkg/factory"
)
// NewFactory creates an instance of KustFactory using k8sdeps factories
func NewFactory() *factory.KustFactory {
return factory.NewKustFactory(
kunstruct.NewKunstructuredFactoryImpl(),
validator.NewKustValidator(),
transformer.NewFactoryImpl(),
)
}

View File

@ -0,0 +1,47 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"factory.go",
"helper.go",
"kunstruct.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//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/util/yaml:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"factory_test.go",
"kunstruct_test.go",
],
embed = [":go_default_library"],
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,118 @@
/*
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 kunstruct
import (
"bytes"
"fmt"
"io"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/types"
)
// KunstructuredFactoryImpl hides construction using apimachinery types.
type KunstructuredFactoryImpl struct {
cmFactory *configmapandsecret.ConfigMapFactory
secretFactory *configmapandsecret.SecretFactory
}
var _ ifc.KunstructuredFactory = &KunstructuredFactoryImpl{}
// NewKunstructuredFactoryImpl returns a factory.
func NewKunstructuredFactoryImpl() ifc.KunstructuredFactory {
return &KunstructuredFactoryImpl{}
}
// SliceFromBytes returns a slice of Kunstructured.
func (kf *KunstructuredFactoryImpl) SliceFromBytes(
in []byte) ([]ifc.Kunstructured, error) {
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
var result []ifc.Kunstructured
var err error
for err == nil || isEmptyYamlError(err) {
var out unstructured.Unstructured
err = decoder.Decode(&out)
if err == nil {
if len(out.Object) == 0 {
continue
}
err = kf.validate(out)
if err != nil {
return nil, err
}
result = append(result, &UnstructAdapter{Unstructured: out})
}
}
if err != io.EOF {
return nil, err
}
return result, nil
}
func isEmptyYamlError(err error) bool {
return strings.Contains(err.Error(), "is missing in 'null'")
}
// FromMap returns an instance of Kunstructured.
func (kf *KunstructuredFactoryImpl) FromMap(
m map[string]interface{}) ifc.Kunstructured {
return &UnstructAdapter{Unstructured: unstructured.Unstructured{Object: m}}
}
// MakeConfigMap returns an instance of Kunstructured for ConfigMap
func (kf *KunstructuredFactoryImpl) MakeConfigMap(args *types.ConfigMapArgs, options *types.GeneratorOptions) (ifc.Kunstructured, error) {
cm, err := kf.cmFactory.MakeConfigMap(args, options)
if err != nil {
return nil, err
}
return NewKunstructuredFromObject(cm)
}
// MakeSecret returns an instance of Kunstructured for Secret
func (kf *KunstructuredFactoryImpl) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (ifc.Kunstructured, error) {
sec, err := kf.secretFactory.MakeSecret(args, options)
if err != nil {
return nil, err
}
return NewKunstructuredFromObject(sec)
}
// Set sets loader
func (kf *KunstructuredFactoryImpl) Set(ldr ifc.Loader) {
kf.cmFactory = configmapandsecret.NewConfigMapFactory(ldr)
kf.secretFactory = configmapandsecret.NewSecretFactory(ldr)
}
// validate validates that u has kind and name
// except for kind `List`, which doesn't require a name
func (kf *KunstructuredFactoryImpl) validate(u unstructured.Unstructured) error {
kind := u.GetKind()
if kind == "" {
return fmt.Errorf("missing kind in object %v", u)
} else if kind == "List" {
return nil
}
if u.GetName() == "" {
return fmt.Errorf("missing metadata.name in object %v", u)
}
return nil
}

View File

@ -0,0 +1,175 @@
/*
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 kunstruct
import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/pkg/ifc"
)
func TestSliceFromBytes(t *testing.T) {
factory := NewKunstructuredFactoryImpl()
testConfigMap := factory.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
})
testList := factory.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "List",
"items": []interface{}{
testConfigMap.Map(),
testConfigMap.Map(),
},
})
tests := []struct {
name string
input []byte
expectedOut []ifc.Kunstructured
expectedErr bool
}{
{
name: "garbage",
input: []byte("garbageIn: garbageOut"),
expectedOut: []ifc.Kunstructured{},
expectedErr: true,
},
{
name: "noBytes",
input: []byte{},
expectedOut: []ifc.Kunstructured{},
expectedErr: false,
},
{
name: "goodJson",
input: []byte(`
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
`),
expectedOut: []ifc.Kunstructured{testConfigMap},
expectedErr: false,
},
{
name: "goodYaml1",
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
expectedOut: []ifc.Kunstructured{testConfigMap},
expectedErr: false,
},
{
name: "goodYaml2",
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
expectedOut: []ifc.Kunstructured{testConfigMap, testConfigMap},
expectedErr: false,
},
{
name: "garbageInOneOfTwoObjects",
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
`),
expectedOut: []ifc.Kunstructured{},
expectedErr: true,
},
{
name: "emptyObjects",
input: []byte(`
---
#a comment
---
`),
expectedOut: []ifc.Kunstructured{},
expectedErr: false,
},
{
name: "Missing .metadata.name in object",
input: []byte(`
apiVersion: v1
kind: Namespace
metadata:
annotations:
foo: bar
`),
expectedOut: nil,
expectedErr: true,
},
{
name: "List",
input: []byte(`
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
expectedOut: []ifc.Kunstructured{testList},
expectedErr: false,
},
}
for _, test := range tests {
rs, err := factory.SliceFromBytes(test.input)
if test.expectedErr && err == nil {
t.Fatalf("%v: should return error", test.name)
}
if !test.expectedErr && err != nil {
t.Fatalf("%v: unexpected error: %s", test.name, err)
}
if len(rs) != len(test.expectedOut) {
t.Fatalf("%s: length mismatch %d != %d",
test.name, len(rs), len(test.expectedOut))
}
for i := range rs {
if !reflect.DeepEqual(test.expectedOut[i], rs[i]) {
t.Fatalf("%s: Got: %v\nexpected:%v",
test.name, test.expectedOut[i], rs[i])
}
}
}
}

View File

@ -0,0 +1,71 @@
/*
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 kunstruct provides unstructured from api machinery and factory for creating unstructured
package kunstruct
import (
"fmt"
"strings"
)
func parseFields(path string) ([]string, error) {
if !strings.Contains(path, "[") {
return strings.Split(path, "."), nil
}
var fields []string
start := 0
insideParentheses := false
for i := range path {
switch path[i] {
case '.':
if !insideParentheses {
fields = append(fields, path[start:i])
start = i + 1
}
case '[':
if !insideParentheses {
if i == start {
start = i + 1
} else {
fields = append(fields, path[start:i])
start = i + 1
}
insideParentheses = true
} else {
return nil, fmt.Errorf("nested parentheses are not allowed: %s", path)
}
case ']':
if insideParentheses {
fields = append(fields, path[start:i])
start = i + 1
insideParentheses = false
} else {
return nil, fmt.Errorf("invalid field path %s", path)
}
}
}
if start < len(path)-1 {
fields = append(fields, path[start:])
}
for i, f := range fields {
if strings.HasPrefix(f, "\"") || strings.HasPrefix(f, "'") {
fields[i] = strings.Trim(f, "\"'")
}
}
return fields, nil
}

View File

@ -0,0 +1,92 @@
/*
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 kunstruct provides unstructured from api machinery and factory for creating unstructured
package kunstruct
import (
"encoding/json"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
)
var _ ifc.Kunstructured = &UnstructAdapter{}
// UnstructAdapter wraps unstructured.Unstructured from
// https://github.com/kubernetes/apimachinery/blob/master/
// pkg/apis/meta/v1/unstructured/unstructured.go
// to isolate dependence on apimachinery.
type UnstructAdapter struct {
unstructured.Unstructured
}
// NewKunstructuredFromObject returns a new instance of Kunstructured.
func NewKunstructuredFromObject(obj runtime.Object) (ifc.Kunstructured, error) {
// Convert obj to a byte stream, then convert that to JSON (Unstructured).
marshaled, err := json.Marshal(obj)
if err != nil {
return &UnstructAdapter{}, err
}
var u unstructured.Unstructured
err = u.UnmarshalJSON(marshaled)
// creationTimestamp always 'null', remove it
u.SetCreationTimestamp(metav1.Time{})
return &UnstructAdapter{Unstructured: u}, err
}
// GetGvk returns the Gvk name of the object.
func (fs *UnstructAdapter) GetGvk() gvk.Gvk {
x := fs.GroupVersionKind()
return gvk.Gvk{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}
// Copy provides a copy behind an interface.
func (fs *UnstructAdapter) Copy() ifc.Kunstructured {
return &UnstructAdapter{*fs.DeepCopy()}
}
// Map returns the unstructured content map.
func (fs *UnstructAdapter) Map() map[string]interface{} {
return fs.Object
}
// SetMap overrides the unstructured content map.
func (fs *UnstructAdapter) SetMap(m map[string]interface{}) {
fs.Object = m
}
// GetFieldValue returns value at the given fieldpath.
func (fs *UnstructAdapter) GetFieldValue(path string) (string, error) {
fields, err := parseFields(path)
if err != nil {
return "", err
}
s, found, err := unstructured.NestedString(
fs.UnstructuredContent(), fields...)
if found || err != nil {
return s, err
}
return "", fmt.Errorf("no field named '%s'", path)
}

View File

@ -0,0 +1,148 @@
/*
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 kunstruct
import (
"testing"
)
func TestGetFieldValue(t *testing.T) {
factory := NewKunstructuredFactoryImpl()
kunstructured := factory.FromMap(map[string]interface{}{
"Kind": "Service",
"metadata": map[string]interface{}{
"labels": map[string]string{
"app": "application-name",
},
"name": "service-name",
},
"spec": map[string]interface{}{
"ports": map[string]interface{}{
"port": "80",
},
},
"this": map[string]interface{}{
"is": map[string]interface{}{
"aNumber": 1000,
"aNilValue": nil,
"anEmptyMap": map[string]interface{}{},
"unrecognizable": testing.InternalExample{
Name: "fooBar",
},
},
},
})
tests := []struct {
name string
pathToField string
expectedValue string
errorExpected bool
errorMsg string
}{
{
name: "oneField",
pathToField: "Kind",
expectedValue: "Service",
errorExpected: false,
},
{
name: "twoFields",
pathToField: "metadata.name",
expectedValue: "service-name",
errorExpected: false,
},
{
name: "threeFields",
pathToField: "spec.ports.port",
expectedValue: "80",
errorExpected: false,
},
{
name: "empty",
pathToField: "",
errorExpected: true,
errorMsg: "no field named ''",
},
{
name: "emptyDotEmpty",
pathToField: ".",
errorExpected: true,
errorMsg: "no field named '.'",
},
{
name: "twoFieldsOneMissing",
pathToField: "metadata.banana",
errorExpected: true,
errorMsg: "no field named 'metadata.banana'",
},
{
name: "deeperMissingField",
pathToField: "this.is.aDeep.field.that.does.not.exist",
errorExpected: true,
errorMsg: "no field named 'this.is.aDeep.field.that.does.not.exist'",
},
{
name: "emptyMap",
pathToField: "this.is.anEmptyMap",
errorExpected: true,
errorMsg: ".this.is.anEmptyMap accessor error: map[] is of the type map[string]interface {}, expected string",
},
{
name: "numberAsValue",
pathToField: "this.is.aNumber",
errorExpected: true,
errorMsg: ".this.is.aNumber accessor error: 1000 is of the type int, expected string",
},
{
name: "nilAsValue",
pathToField: "this.is.aNilValue",
errorExpected: true,
errorMsg: ".this.is.aNilValue accessor error: <nil> is of the type <nil>, expected string",
},
{
name: "unrecognizable",
pathToField: "this.is.unrecognizable.Name",
errorExpected: true,
errorMsg: ".this.is.unrecognizable.Name accessor error: {fooBar <nil> false} is of the type testing.InternalExample, expected map[string]interface{}",
},
}
for _, test := range tests {
s, err := kunstructured.GetFieldValue(test.pathToField)
if test.errorExpected {
if err == nil {
t.Fatalf("%q; path %q - should return error, but no error returned",
test.name, test.pathToField)
}
if test.errorMsg != err.Error() {
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
test.name, test.pathToField, test.errorMsg, err.Error())
}
continue
}
if err != nil {
t.Fatalf("%q; path %q - unexpected error %v",
test.name, test.pathToField, err)
}
if test.expectedValue != s {
t.Fatalf("%q; Got: %s expected: %s",
test.name, s, test.expectedValue)
}
}
}

View File

@ -0,0 +1,30 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["kv.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv",
visibility = ["//visibility:public"],
deps = ["//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["kv_test.go"],
embed = [":go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,102 @@
/*
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 kv
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
"k8s.io/apimachinery/pkg/util/validation"
)
// Pair represents a <key, value> pair.
type Pair struct {
Key string
Value string
}
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// KeyValuesFromLines parses given content in to a list of key-value pairs.
func KeyValuesFromLines(content []byte) ([]Pair, error) {
var kvs []Pair
scanner := bufio.NewScanner(bytes.NewReader(content))
currentLine := 0
for scanner.Scan() {
// Process the current line, retrieving a key/value pair if
// possible.
scannedBytes := scanner.Bytes()
kv, err := KeyValuesFromLine(scannedBytes, currentLine)
if err != nil {
return nil, err
}
currentLine++
if len(kv.Key) == 0 {
// no key means line was empty or a comment
continue
}
kvs = append(kvs, kv)
}
return kvs, nil
}
// KeyValuesFromLine returns a kv with blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary.
func KeyValuesFromLine(line []byte, currentLine int) (Pair, error) {
kv := Pair{}
if !utf8.Valid(line) {
return kv, fmt.Errorf("line %d has invalid utf8 bytes : %v", line, string(line))
}
// We trim UTF8 BOM from the first line of the file but no others
if currentLine == 0 {
line = bytes.TrimPrefix(line, utf8bom)
}
// trim the line from all leading whitespace first
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// If the line is empty or a comment, we return a blank key/value pair.
if len(line) == 0 || line[0] == '#' {
return kv, nil
}
data := strings.SplitN(string(line), "=", 2)
key := data[0]
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
return kv, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
if len(data) == 2 {
kv.Value = data[1]
} else {
// No value (no `=` in the line) is a signal to obtain the value
// from the environment.
kv.Value = os.Getenv(key)
}
kv.Key = key
return kv, nil
}

View File

@ -0,0 +1,68 @@
/*
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 kv
import (
"reflect"
"testing"
)
func TestKeyValuesFromLines(t *testing.T) {
tests := []struct {
desc string
content string
expectedPairs []Pair
expectedErr bool
}{
{
desc: "valid kv content parse",
content: `
k1=v1
k2=v2
`,
expectedPairs: []Pair{
{Key: "k1", Value: "v1"},
{Key: "k2", Value: "v2"},
},
expectedErr: false,
},
{
desc: "content with comments",
content: `
k1=v1
#k2=v2
`,
expectedPairs: []Pair{
{Key: "k1", Value: "v1"},
},
expectedErr: false,
},
// TODO: add negative testcases
}
for _, test := range tests {
pairs, err := KeyValuesFromLines([]byte(test.content))
if test.expectedErr && err == nil {
t.Fatalf("%s should not return error", test.desc)
}
if !reflect.DeepEqual(pairs, test.expectedPairs) {
t.Errorf("%s should succeed, got:%v exptected:%v", test.desc, pairs, test.expectedPairs)
}
}
}

View File

@ -0,0 +1,33 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["factory.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash:all-srcs",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,43 @@
/*
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 transformer provides transformer factory
package transformer
import (
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
)
// FactoryImpl makes patch transformer and name hash transformer
type FactoryImpl struct{}
// NewFactoryImpl makes a new factoryImpl instance
func NewFactoryImpl() *FactoryImpl {
return &FactoryImpl{}
}
// MakePatchTransformer makes a new patch transformer
func (p *FactoryImpl) MakePatchTransformer(slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
return patch.NewPatchTransformer(slice, rf)
}
// MakeHashTransformer makes a new name hash transformer
func (p *FactoryImpl) MakeHashTransformer() transformers.Transformer {
return hash.NewNameHashTransformer()
}

View File

@ -0,0 +1,50 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"hash.go",
"namehash.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"hash_test.go",
"namehash_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,168 @@
/*
Copyright 2017 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 hash
import (
"crypto/sha256"
"encoding/json"
"fmt"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// KustHash compute hash for unstructured objects
type KustHash struct{}
// NewKustHash returns a KustHash object
func NewKustHash() *KustHash {
return &KustHash{}
}
// Hash returns a hash of either a ConfigMap or a Secret
func (h *KustHash) Hash(m map[string]interface{}) (string, error) {
u := unstructured.Unstructured{
Object: m,
}
kind := u.GetKind()
switch kind {
case "ConfigMap":
cm, err := unstructuredToConfigmap(u)
if err != nil {
return "", err
}
return ConfigMapHash(cm)
case "Secret":
sec, err := unstructuredToSecret(u)
if err != nil {
return "", err
}
return SecretHash(sec)
default:
return "", fmt.Errorf("type %s is supported for hashing in %v", kind, m)
}
}
// ConfigMapHash returns a hash of the ConfigMap.
// The Data, Kind, and Name are taken into account.
func ConfigMapHash(cm *v1.ConfigMap) (string, error) {
encoded, err := encodeConfigMap(cm)
if err != nil {
return "", err
}
h, err := encodeHash(hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// SecretHash returns a hash of the Secret.
// The Data, Kind, Name, and Type are taken into account.
func SecretHash(sec *v1.Secret) (string, error) {
encoded, err := encodeSecret(sec)
if err != nil {
return "", err
}
h, err := encodeHash(hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// encodeConfigMap encodes a ConfigMap.
// Data, Kind, and Name are taken into account.
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}
if len(cm.BinaryData) > 0 {
m["binaryData"] = cm.BinaryData
}
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
// encodeSecret encodes a Secret.
// Data, Kind, Name, and Type are taken into account.
func encodeSecret(sec *v1.Secret) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data})
if err != nil {
return "", err
}
return string(data), nil
}
// encodeHash extracts the first 40 bits of the hash from the hex string
// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex
// characters to consonants to prevent bad words from being formed (the theory
// is that no vowels makes it really hard to make bad words). Since the string
// is hex, the only vowels it can contain are 'a' and 'e'.
// We picked some arbitrary consonants to map to from the same character set as GenerateName.
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
// If the hex string contains fewer than ten characters, returns an error.
func encodeHash(hex string) (string, error) {
if len(hex) < 10 {
return "", fmt.Errorf("the hex string must contain at least 10 characters")
}
enc := []rune(hex[:10])
for i := range enc {
switch enc[i] {
case '0':
enc[i] = 'g'
case '1':
enc[i] = 'h'
case '3':
enc[i] = 'k'
case 'a':
enc[i] = 'm'
case 'e':
enc[i] = 't'
}
}
return string(enc), nil
}
// hash hashes `data` with sha256 and returns the hex string
func hash(data string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
}
func unstructuredToConfigmap(u unstructured.Unstructured) (*v1.ConfigMap, error) {
marshaled, err := json.Marshal(u.Object)
if err != nil {
return nil, err
}
var out v1.ConfigMap
err = json.Unmarshal(marshaled, &out)
return &out, err
}
func unstructuredToSecret(u unstructured.Unstructured) (*v1.Secret, error) {
marshaled, err := json.Marshal(u.Object)
if err != nil {
return nil, err
}
var out v1.Secret
err = json.Unmarshal(marshaled, &out)
return &out, err
}

View File

@ -0,0 +1,208 @@
/*
Copyright 2017 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 hash
import (
"reflect"
"strings"
"testing"
"k8s.io/api/core/v1"
"sigs.k8s.io/kustomize/pkg/gvk"
)
var service = gvk.Gvk{Version: "v1", Kind: "Service"}
var secret = gvk.Gvk{Version: "v1", Kind: "Secret"}
var cmap = gvk.Gvk{Version: "v1", Kind: "ConfigMap"}
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
func TestConfigMapHash(t *testing.T) {
cases := []struct {
desc string
cm *v1.ConfigMap
hash string
err string
}{
// empty map
{"empty data", &v1.ConfigMap{Data: map[string]string{}, BinaryData: map[string][]byte{}}, "42745tchd9", ""},
// one key
{"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""},
// three keys (tests sorting order)
{"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""},
// empty binary data map
{"empty binary data", &v1.ConfigMap{BinaryData: map[string][]byte{}}, "dk855m5d49", ""},
// one key with binary data
{"one key with binary data", &v1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, "mk79584b8c", ""},
// three keys with binary data (tests sorting order)
{"three keys with binary data", &v1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "t458mc6db2", ""},
// two keys, one with string and another with binary data
{"two keys with one each", &v1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, "698h7c7t9m", ""},
}
for _, c := range cases {
h, err := ConfigMapHash(c.cm)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestSecretHash(t *testing.T) {
cases := []struct {
desc string
secret *v1.Secret
hash string
err string
}{
// empty map
{"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""},
// one key
{"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""},
// three keys (tests sorting order)
{"three keys", &v1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""},
}
for _, c := range cases {
h, err := SecretHash(c.secret)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if c.hash != h {
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
}
}
}
func TestEncodeConfigMap(t *testing.T) {
cases := []struct {
desc string
cm *v1.ConfigMap
expect string
err string
}{
// empty map
{"empty data", &v1.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""},
// one key
{"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
// three keys (tests sorting order)
{"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}},
`{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""},
// empty binary map
{"empty data", &v1.ConfigMap{BinaryData: map[string][]byte{}}, `{"data":null,"kind":"ConfigMap","name":""}`, ""},
// one key with binary data
{"one key", &v1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}},
`{"binaryData":{"one":""},"data":null,"kind":"ConfigMap","name":""}`, ""},
// three keys with binary data (tests sorting order)
{"three keys", &v1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}},
`{"binaryData":{"one":"","three":"Mw==","two":"Mg=="},"data":null,"kind":"ConfigMap","name":""}`, ""},
// two keys, one string and one binary values
{"two keys with one each", &v1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}},
`{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
}
for _, c := range cases {
s, err := encodeConfigMap(c.cm)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if s != c.expect {
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cm)
}
}
}
func TestEncodeSecret(t *testing.T) {
cases := []struct {
desc string
secret *v1.Secret
expect string
err string
}{
// empty map
{"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""},
// one key
{"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
{"three keys", &v1.Secret{
Type: "my-type",
Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")},
},
`{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""},
}
for _, c := range cases {
s, err := encodeSecret(c.secret)
if SkipRest(t, c.desc, err, c.err) {
continue
}
if s != c.expect {
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secret)
}
}
}
func TestHash(t *testing.T) {
// hash the empty string to be sure that sha256 is being used
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
sum := hash("")
if expect != sum {
t.Errorf("expected hash %q but got %q", expect, sum)
}
}
// warn devs who change types that they might have to update a hash function
// not perfect, as it only checks the number of top-level fields
func TestTypeStability(t *testing.T) {
errfmt := `case %q, expected %d fields but got %d
Depending on the field(s) you added, you may need to modify the hash function for this type.
To guide you: the hash function targets fields that comprise the contents of objects,
not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
`
cases := []struct {
typeName string
obj interface{}
expect int
}{
{"ConfigMap", v1.ConfigMap{}, 4},
{"Secret", v1.Secret{}, 5},
}
for _, c := range cases {
val := reflect.ValueOf(c.obj)
if num := val.NumField(); c.expect != num {
t.Errorf(errfmt, c.typeName, c.expect, num)
}
}
}
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
// and logs the appropriate error on the test object.
// The return value indicates whether we should skip the rest of the test case due to the error result.
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
if err != nil {
if len(contains) == 0 {
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
} else if !strings.Contains(err.Error(), contains) {
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
}
return true
} else if len(contains) > 0 {
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
return true
}
return false
}

View File

@ -0,0 +1,47 @@
/*
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 hash
import (
"fmt"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/transformers"
)
type nameHashTransformer struct{}
var _ transformers.Transformer = &nameHashTransformer{}
// NewNameHashTransformer construct a nameHashTransformer.
func NewNameHashTransformer() transformers.Transformer {
return &nameHashTransformer{}
}
// Transform appends hash to generated resources.
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
for _, res := range m {
if res.NeedHashSuffix() {
h, err := NewKustHash().Hash(res.Map())
if err != nil {
return err
}
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
}
}
return nil
}

View File

@ -0,0 +1,162 @@
/*
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 hash
import (
"reflect"
"testing"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/types"
)
func TestNameHashTransformer(t *testing.T) {
rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
objs := resmap.ResMap{
resid.NewResId(cmap, "cm1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
}),
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"group": "apps",
"apiVersion": "v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"old-label": "old-value",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:1.7.9",
},
},
},
},
},
}),
resid.NewResId(service, "svc1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Service",
"metadata": map[string]interface{}{
"name": "svc1",
},
"spec": map[string]interface{}{
"ports": []interface{}{
map[string]interface{}{
"name": "port1",
"port": "12345",
},
},
},
}),
resid.NewResId(secret, "secret1"): rf.FromMapAndOption(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": "secret1",
},
}, &types.GeneratorArgs{Behavior: "create"}, &types.GeneratorOptions{DisableNameSuffixHash: false}),
}
expected := resmap.ResMap{
resid.NewResId(cmap, "cm1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
}),
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"group": "apps",
"apiVersion": "v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"old-label": "old-value",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:1.7.9",
},
},
},
},
},
}),
resid.NewResId(service, "svc1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Service",
"metadata": map[string]interface{}{
"name": "svc1",
},
"spec": map[string]interface{}{
"ports": []interface{}{
map[string]interface{}{
"name": "port1",
"port": "12345",
},
},
},
}),
resid.NewResId(secret, "secret1"): rf.FromMapAndOption(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": "secret1-7kc45hd5f7",
},
}, &types.GeneratorArgs{Behavior: "create"}, &types.GeneratorOptions{DisableNameSuffixHash: false}),
}
tran := NewNameHashTransformer()
tran.Transform(objs)
if !reflect.DeepEqual(objs, expected) {
err := expected.ErrorIfNotEqual(objs)
t.Fatalf("actual doesn't match expected: %v", err)
}
}

View File

@ -0,0 +1,51 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"patch.go",
"patchconflictdetector.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch",
visibility = ["//visibility:public"],
deps = [
"//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/mergepatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["patch_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,174 @@
/*
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 patch
import (
"encoding/json"
"fmt"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
)
// patchTransformer applies patches.
type patchTransformer struct {
patches []*resource.Resource
rf *resource.Factory
}
var _ transformers.Transformer = &patchTransformer{}
// NewPatchTransformer constructs a patchTransformer.
func NewPatchTransformer(
slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
if len(slice) == 0 {
return transformers.NewNoOpTransformer(), nil
}
return &patchTransformer{patches: slice, rf: rf}, nil
}
// Transform apply the patches on top of the base resources.
func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
// Merge and then index the patches by Id.
patches, err := pt.mergePatches()
if err != nil {
return err
}
// Strategic merge the resources exist in both base and patches.
for _, patch := range patches {
// Merge patches with base resource.
id := patch.Id()
matchedIds := baseResourceMap.FindByGVKN(id)
if len(matchedIds) == 0 {
return fmt.Errorf("failed to find an object with %s to apply the patch", id.GvknString())
}
if len(matchedIds) > 1 {
return fmt.Errorf("found multiple objects %#v targeted by patch %#v (ambiguous)", matchedIds, id)
}
id = matchedIds[0]
base := baseResourceMap[id]
merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
baseName := base.GetName()
switch {
case runtime.IsNotRegisteredError(err):
// Use JSON merge patch to handle types w/o schema
baseBytes, err := json.Marshal(base.Map())
if err != nil {
return err
}
patchBytes, err := json.Marshal(patch.Map())
if err != nil {
return err
}
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
if err != nil {
return err
}
err = json.Unmarshal(mergedBytes, &merged)
if err != nil {
return err
}
case err != nil:
return err
default:
// Use Strategic-Merge-Patch to handle types w/ schema
// TODO: Change this to use the new Merge package.
// Store the name of the base object, because this name may have been munged.
// Apply this name to the patched object.
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
if err != nil {
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
base.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
base.SetName(baseName)
baseResourceMap[id].SetMap(merged)
}
return nil
}
// mergePatches merge and index patches by Id.
// It errors out if there is conflict between patches.
func (pt *patchTransformer) mergePatches() (resmap.ResMap, error) {
rc := resmap.ResMap{}
for ix, patch := range pt.patches {
id := patch.Id()
existing, found := rc[id]
if !found {
rc[id] = patch
continue
}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err
}
var cd conflictDetector
if err != nil {
cd = newJMPConflictDetector(pt.rf)
} else {
cd, err = newSMPConflictDetector(versionedObj, pt.rf)
if err != nil {
return nil, err
}
}
conflict, err := cd.hasConflict(existing, patch)
if err != nil {
return nil, err
}
if conflict {
conflictingPatch, err := cd.findConflict(ix, pt.patches)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(
"conflict between %#v and %#v",
conflictingPatch.Map(), patch.Map())
}
merged, err := cd.mergePatches(existing, patch)
if err != nil {
return nil, err
}
rc[id] = merged
}
return rc, nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@ -0,0 +1,563 @@
/*
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 patch
import (
"reflect"
"strings"
"testing"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
)
var rf = resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
var foo = gvk.Gvk{Group: "example.com", Version: "v1", Kind: "Foo"}
func TestOverlayRun(t *testing.T) {
base := resmap.ResMap{
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"old-label": "old-value",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx",
},
},
},
},
},
}),
}
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"another-label": "foo",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
},
),
}
expected := resmap.ResMap{
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"old-label": "old-value",
"another-label": "foo",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
}),
}
lt, err := NewPatchTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(base, expected) {
err = expected.ErrorIfNotEqual(base)
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestMultiplePatches(t *testing.T) {
base := resmap.ResMap{
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "apps/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",
},
},
},
},
},
}),
}
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/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:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
},
),
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/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",
"env": []interface{}{
map[string]interface{}{
"name": "ANOTHERENV",
"value": "HELLO",
},
},
},
map[string]interface{}{
"name": "busybox",
"image": "busybox",
},
},
},
},
},
},
),
}
expected := resmap.ResMap{
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "apps/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:latest",
"env": []interface{}{
map[string]interface{}{
"name": "ANOTHERENV",
"value": "HELLO",
},
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
map[string]interface{}{
"name": "busybox",
"image": "busybox",
},
},
},
},
},
}),
}
lt, err := NewPatchTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(base, expected) {
err = expected.ErrorIfNotEqual(base)
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestMultiplePatchesWithConflict(t *testing.T) {
base := resmap.ResMap{
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "apps/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",
},
},
},
},
},
}),
}
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/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:latest",
"env": []interface{}{
map[string]interface{}{
"name": "SOMEENV",
"value": "BAR",
},
},
},
},
},
},
},
},
),
rf.FromMap(map[string]interface{}{
"apiVersion": "apps/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",
},
},
},
},
},
},
),
}
lt, err := NewPatchTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
}
}
func TestNoSchemaOverlayRun(t *testing.T) {
base := resmap.ResMap{
resid.NewResId(foo, "my-foo"): rf.FromMap(
map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"B": "Y",
},
},
}),
}
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"B": nil,
"C": "Z",
},
},
},
),
}
expected := resmap.ResMap{
resid.NewResId(foo, "my-foo"): rf.FromMap(
map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"C": "Z",
},
},
}),
}
lt, err := NewPatchTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = expected.ErrorIfNotEqual(base); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestNoSchemaMultiplePatches(t *testing.T) {
base := resmap.ResMap{
resid.NewResId(foo, "my-foo"): rf.FromMap(
map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"B": "Y",
},
},
}),
}
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"B": nil,
"C": "Z",
},
},
},
),
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"C": "Z",
"D": "W",
},
"baz": map[string]interface{}{
"hello": "world",
},
},
},
),
}
expected := resmap.ResMap{
resid.NewResId(foo, "my-foo"): rf.FromMap(
map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"C": "Z",
"D": "W",
},
"baz": map[string]interface{}{
"hello": "world",
},
},
}),
}
lt, err := NewPatchTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = expected.ErrorIfNotEqual(base); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) {
base := resmap.ResMap{
resid.NewResId(foo, "my-foo"): rf.FromMap(
map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"A": "X",
"B": "Y",
},
},
}),
}
patch := []*resource.Resource{
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"B": nil,
"C": "Z",
},
},
}),
rf.FromMap(map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
},
"spec": map[string]interface{}{
"bar": map[string]interface{}{
"C": "NOT_Z",
},
},
}),
}
lt, err := NewPatchTransformer(patch, rf)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = lt.Transform(base)
if err == nil {
t.Fatalf("did not get expected error")
}
if !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
}
}

View File

@ -0,0 +1,137 @@
/*
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 patch
import (
"encoding/json"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/mergepatch"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"sigs.k8s.io/kustomize/pkg/resource"
)
type conflictDetector interface {
hasConflict(patch1, patch2 *resource.Resource) (bool, error)
findConflict(conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error)
mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error)
}
type jsonMergePatch struct {
rf *resource.Factory
}
var _ conflictDetector = &jsonMergePatch{}
func newJMPConflictDetector(rf *resource.Factory) conflictDetector {
return &jsonMergePatch{rf: rf}
}
func (jmp *jsonMergePatch) hasConflict(
patch1, patch2 *resource.Resource) (bool, error) {
return mergepatch.HasConflicts(patch1.Map(), patch2.Map())
}
func (jmp *jsonMergePatch) findConflict(
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
for i, patch := range patches {
if i == conflictingPatchIdx {
continue
}
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
continue
}
conflict, err := mergepatch.HasConflicts(
patch.Map(),
patches[conflictingPatchIdx].Map())
if err != nil {
return nil, err
}
if conflict {
return patch, nil
}
}
return nil, nil
}
func (jmp *jsonMergePatch) mergePatches(
patch1, patch2 *resource.Resource) (*resource.Resource, error) {
baseBytes, err := json.Marshal(patch1.Map())
if err != nil {
return nil, err
}
patchBytes, err := json.Marshal(patch2.Map())
if err != nil {
return nil, err
}
mergedBytes, err := jsonpatch.MergeMergePatches(baseBytes, patchBytes)
if err != nil {
return nil, err
}
mergedMap := make(map[string]interface{})
err = json.Unmarshal(mergedBytes, &mergedMap)
return jmp.rf.FromMap(mergedMap), err
}
type strategicMergePatch struct {
lookupPatchMeta strategicpatch.LookupPatchMeta
rf *resource.Factory
}
var _ conflictDetector = &strategicMergePatch{}
func newSMPConflictDetector(
versionedObj runtime.Object,
rf *resource.Factory) (conflictDetector, error) {
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
return &strategicMergePatch{lookupPatchMeta: lookupPatchMeta, rf: rf}, err
}
func (smp *strategicMergePatch) hasConflict(p1, p2 *resource.Resource) (bool, error) {
return strategicpatch.MergingMapsHaveConflicts(
p1.Map(), p2.Map(), smp.lookupPatchMeta)
}
func (smp *strategicMergePatch) findConflict(
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
for i, patch := range patches {
if i == conflictingPatchIdx {
continue
}
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
continue
}
conflict, err := strategicpatch.MergingMapsHaveConflicts(
patch.Map(),
patches[conflictingPatchIdx].Map(),
smp.lookupPatchMeta)
if err != nil {
return nil, err
}
if conflict {
return patch, nil
}
}
return nil, nil
}
func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error) {
mergeJSONMap, err := strategicpatch.MergeStrategicMergeMapPatchUsingLookupPatchMeta(
smp.lookupPatchMeta, patch1.Map(), patch2.Map())
return smp.rf.FromMap(mergeJSONMap), err
}

View File

@ -0,0 +1,29 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["validators.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator",
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,61 @@
/*
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 validator provides functions to validate labels, annotations, namespace using apimachinery
package validator
import (
"errors"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// KustValidator validates Labels and annotations by apimachinery
type KustValidator struct{}
// NewKustValidator returns a KustValidator object
func NewKustValidator() *KustValidator {
return &KustValidator{}
}
// MakeAnnotationValidator returns a MapValidatorFunc using apimachinery.
func (v *KustValidator) MakeAnnotationValidator() func(map[string]string) error {
return func(x map[string]string) error {
errs := apivalidation.ValidateAnnotations(x, field.NewPath("field"))
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
return nil
}
}
// MakeLabelValidator returns a MapValidatorFunc using apimachinery.
func (v *KustValidator) MakeLabelValidator() func(map[string]string) error {
return func(x map[string]string) error {
errs := v1validation.ValidateLabels(x, field.NewPath("field"))
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
return nil
}
}
// ValidateNamespace validates a string is a valid namespace using apimachinery.
func (v *KustValidator) ValidateNamespace(s string) []string {
return validation.IsDNS1123Label(s)
}