Merge branch 'master' into transformer-no-create-arrays

This commit is contained in:
Tom Wieczorek
2019-05-28 11:27:04 +02:00
1136 changed files with 20658 additions and 457722 deletions

View File

@@ -1,42 +0,0 @@
/*
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 config
// Annotation is to mark a field as annotations.
// "x-kubernetes-annotation": ""
const Annotation = "x-kubernetes-annotation"
// LabelSelector is to mark a field as LabelSelector
// "x-kubernetes-label-selector": ""
const LabelSelector = "x-kubernetes-label-selector"
// Identity is to mark a field as Identity
// "x-kubernetes-identity": ""
const Identity = "x-kubernetes-identity"
// Version marks the type version of an object ref field
// "x-kubernetes-object-ref-api-version": <apiVersion name>
const Version = "x-kubernetes-object-ref-api-version"
// Kind marks the type name of an object ref field
// "x-kubernetes-object-ref-kind": <kind name>
const Kind = "x-kubernetes-object-ref-kind"
// NameKey marks the field key that refers to an object of an object ref field
// "x-kubernetes-object-ref-name-key": "name"
// default is "name"
const NameKey = "x-kubernetes-object-ref-name-key"

View File

@@ -130,6 +130,11 @@ commonLabels:
group: batch
kind: CronJob
- path: spec/jobTemplate/metadata/labels
create: true
group: batch
kind: CronJob
- path: spec/jobTemplate/spec/template/metadata/labels
create: true
group: batch

View File

@@ -31,6 +31,7 @@ func GetDefaultFieldSpecs() []byte {
[]byte(namespaceFieldSpecs),
[]byte(varReferenceFieldSpecs),
[]byte(nameReferenceFieldSpecs),
[]byte(imagesFieldSpecs),
}
return bytes.Join(configData, []byte("\n"))
}
@@ -45,5 +46,6 @@ func GetDefaultFieldSpecsAsMap() map[string]string {
result["namespace"] = namespaceFieldSpecs
result["varreference"] = varReferenceFieldSpecs
result["namereference"] = nameReferenceFieldSpecs
result["images"] = imagesFieldSpecs
return result
}

View File

@@ -0,0 +1,23 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package defaultconfig
const (
// imageFieldSpecs is left empty since `containers` and `initContainers`
// of *ANY* kind in *ANY* path are builtin supported in code
imagesFieldSpecs = ``
)

View File

@@ -108,13 +108,15 @@ nameReference:
kind: Job
- path: spec/jobTemplate/spec/template/spec/volumes/configMap/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/configMap/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/env/valueFrom/configMapKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/env/valueFrom/configMapKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/envFrom/configMapRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/envFrom/configmapRef/name
- path: spec/jobTemplate/spec/template/spec/initContainers/envFrom/configMapRef/name
kind: CronJob
- kind: Secret
@@ -150,6 +152,8 @@ nameReference:
kind: Deployment
- path: spec/template/spec/imagePullSecrets/name
kind: Deployment
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: Deployment
- path: spec/template/spec/volumes/secret/secretName
kind: ReplicaSet
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
@@ -186,6 +190,8 @@ nameReference:
kind: StatefulSet
- path: spec/template/spec/imagePullSecrets/name
kind: StatefulSet
- path: spec/template/spec/volumes/projected/sources/secret/name
kind: StatefulSet
- path: spec/template/spec/volumes/secret/secretName
kind: Job
- path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name
@@ -200,6 +206,8 @@ nameReference:
kind: Job
- path: spec/jobTemplate/spec/template/spec/volumes/secret/secretName
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/volumes/projected/sources/secret/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/env/valueFrom/secretKeyRef/name
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/env/valueFrom/secretKeyRef/name
@@ -241,6 +249,9 @@ nameReference:
kind: Ingress
- path: spec/backend/serviceName
kind: Ingress
- path: spec/service/name
kind: APIService
group: apiregistration.k8s.io
- kind: Role
group: rbac.authorization.k8s.io
@@ -279,7 +290,7 @@ nameReference:
- path: spec/jobTemplate/spec/template/spec/serviceAccountName
kind: CronJob
- path: spec/template/spec/serviceAccountName
kind: job
kind: Job
- path: spec/template/spec/serviceAccountName
kind: DaemonSet
@@ -306,5 +317,14 @@ nameReference:
fieldSpecs:
- path: spec/volumeName
kind: PersistentVolumeClaim
- kind: StorageClass
version: v1
group: storage.k8s.io
fieldSpecs:
- path: spec/storageClassName
kind: PersistentVolume
- path: spec/storageClassName
kind: PersistentVolumeClaim
`
)

View File

@@ -19,73 +19,183 @@ package defaultconfig
const (
varReferenceFieldSpecs = `
varReference:
- path: spec/template/spec/initContainers/command
kind: StatefulSet
- path: spec/template/spec/containers/command
kind: StatefulSet
- path: spec/template/spec/initContainers/command
kind: Deployment
- path: spec/template/spec/containers/command
kind: Deployment
- path: spec/template/spec/containers/command
kind: Job
- path: spec/jobTemplate/spec/template/spec/containers/args
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/containers/command
kind: CronJob
- path: spec/template/spec/initContainers/args
kind: StatefulSet
- path: spec/template/spec/containers/args
kind: StatefulSet
- path: spec/template/spec/initContainers/args
kind: Deployment
- path: spec/template/spec/containers/args
kind: Deployment
- path: spec/template/spec/containers/args
kind: Job
- path: spec/jobTemplate/spec/template/spec/containers/args
kind: CronJob
- path: spec/template/spec/initContainers/env/value
kind: StatefulSet
- path: spec/template/spec/containers/env/value
kind: StatefulSet
- path: spec/template/spec/initContainers/env/value
kind: Deployment
- path: spec/template/spec/containers/env/value
kind: Deployment
- path: spec/template/spec/containers/env/value
kind: Job
- path: spec/jobTemplate/spec/template/spec/containers/env/value
kind: CronJob
- path: spec/containers/command
kind: Pod
- path: spec/jobTemplate/spec/template/spec/containers/volumeMounts/mountPath
kind: CronJob
- path: spec/containers/args
kind: Pod
- path: spec/jobTemplate/spec/template/spec/initContainers/args
kind: CronJob
- path: spec/containers/env/value
kind: Pod
- path: spec/jobTemplate/spec/template/spec/initContainers/command
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/env/value
kind: CronJob
- path: spec/jobTemplate/spec/template/spec/initContainers/volumeMounts/mountPath
kind: CronJob
- path: spec/template/spec/containers/args
kind: DaemonSet
- path: spec/template/spec/containers/command
kind: DaemonSet
- path: spec/template/spec/containers/env/value
kind: DaemonSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: DaemonSet
- path: spec/template/spec/initContainers/args
kind: DaemonSet
- path: spec/template/spec/initContainers/command
kind: DaemonSet
- path: spec/template/spec/initContainers/env/value
kind: DaemonSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: DaemonSet
- path: spec/template/spec/containers/args
kind: Deployment
- path: spec/template/spec/containers/command
kind: Deployment
- path: spec/template/spec/containers/env/value
kind: Deployment
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: Deployment
- path: spec/template/spec/initContainers/args
kind: Deployment
- path: spec/template/spec/initContainers/command
kind: Deployment
- path: spec/template/spec/initContainers/env/value
kind: Deployment
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: Deployment
- path: spec/rules/host
kind: Ingress
- path: spec/tls/hosts
kind: Ingress
- path: spec/tls/secretName
kind: Ingress
- path: spec/template/spec/containers/args
kind: Job
- path: spec/template/spec/containers/command
kind: Job
- path: spec/template/spec/containers/env/value
kind: Job
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: Job
- path: spec/template/spec/initContainers/args
kind: Job
- path: spec/template/spec/initContainers/command
kind: Job
- path: spec/template/spec/initContainers/env/value
kind: Job
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: Job
- path: spec/containers/args
kind: Pod
- path: spec/containers/command
kind: Pod
- path: spec/containers/env/value
kind: Pod
- path: spec/containers/volumeMounts/mountPath
kind: Pod
- path: spec/initContainers/args
kind: Pod
- path: spec/initContainers/command
kind: Pod
- path: spec/initContainers/env/value
kind: Pod
- path: spec/initContainers/volumeMounts/mountPath
kind: Pod
- path: spec/template/spec/containers/args
kind: ReplicaSet
- path: spec/template/spec/containers/command
kind: ReplicaSet
- path: spec/template/spec/containers/env/value
kind: ReplicaSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: ReplicaSet
- path: spec/template/spec/initContainers/args
kind: ReplicaSet
- path: spec/template/spec/initContainers/command
kind: ReplicaSet
- path: spec/template/spec/initContainers/env/value
kind: ReplicaSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: ReplicaSet
- path: spec/template/spec/containers/args
kind: StatefulSet
- path: spec/template/spec/containers/command
kind: StatefulSet
- path: spec/template/spec/containers/env/value
kind: StatefulSet
- path: spec/template/spec/containers/volumeMounts/mountPath
kind: StatefulSet
- path: spec/template/spec/initContainers/args
kind: StatefulSet
- path: spec/template/spec/initContainers/command
kind: StatefulSet
- path: spec/template/spec/initContainers/env/value
kind: StatefulSet
- path: spec/template/spec/initContainers/volumeMounts/mountPath
kind: StatefulSet
- path: metadata/labels
`
)

View File

@@ -19,9 +19,8 @@ package config
import (
"log"
"github.com/ghodss/yaml"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig"
"sigs.k8s.io/yaml"
)
// Factory makes instances of TransformerConfig.
@@ -29,6 +28,21 @@ type Factory struct {
ldr ifc.Loader
}
// MakeTransformerConfig returns a merger of custom config,
// if any, with default config.
func MakeTransformerConfig(
ldr ifc.Loader, paths []string) (*TransformerConfig, error) {
t1 := MakeDefaultConfig()
if len(paths) == 0 {
return t1, nil
}
t2, err := NewFactory(ldr).FromFiles(paths)
if err != nil {
return nil, err
}
return t1.Merge(t2)
}
func NewFactory(l ifc.Loader) *Factory {
return &Factory{ldr: l}
}
@@ -53,7 +67,10 @@ func (tf *Factory) FromFiles(
if err != nil {
return nil, err
}
result = result.Merge(t)
result, err = result.Merge(t)
if err != nil {
return nil, err
}
}
return result, nil
}
@@ -68,19 +85,3 @@ func makeTransformerConfigFromBytes(data []byte) (*TransformerConfig, error) {
t.sortFields()
return &t, nil
}
// EmptyConfig returns an empty TransformerConfig object
func (tf *Factory) EmptyConfig() *TransformerConfig {
return &TransformerConfig{}
}
// DefaultConfig returns a default TransformerConfig.
// This should never fail, hence the Fatal panic.
func (tf *Factory) DefaultConfig() *TransformerConfig {
c, err := makeTransformerConfigFromBytes(
defaultconfig.GetDefaultFieldSpecs())
if err != nil {
log.Fatalf("Unable to make default transformconfig: %v", err)
}
return c
}

View File

@@ -1,45 +1,33 @@
/*
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.
*/
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package config
import (
"reflect"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/loader"
"testing"
"sigs.k8s.io/kustomize/internal/loadertest"
"sigs.k8s.io/kustomize/pkg/gvk"
)
func TestMakeDefaultTransformerConfig(t *testing.T) {
func TestMakeDefaultConfig(t *testing.T) {
// Confirm default can be made without fatal error inside call.
l, _, _ := makeFakeLoaderAndOutput()
_ = NewFactory(l).DefaultConfig()
_ = MakeDefaultConfig()
}
func makeFakeLoaderAndOutput() (ifc.Loader, *TransformerConfig, *TransformerConfig) {
transformerConfig := `
func TestFromFiles(t *testing.T) {
ldr := loadertest.NewFakeLoader("/app")
ldr.AddFile("/app/config.yaml", []byte(`
namePrefix:
- path: nameprefix/path
kind: SomeKind
`
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile("/transformerconfig/test/config.yaml", []byte(transformerConfig))
ldr := loader.NewFileLoaderAtRoot(fakeFS)
`))
tcfg, err := NewFactory(ldr).FromFiles([]string{"/app/config.yaml"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := &TransformerConfig{
NamePrefix: []FieldSpec{
{
@@ -48,16 +36,6 @@ namePrefix:
},
},
}
return ldr, expected, NewFactory(ldr).DefaultConfig()
}
func TestMakeTransformerConfigFromFiles(t *testing.T) {
ldr, expected, _ := makeFakeLoaderAndOutput()
tcfg, err := NewFactory(ldr).FromFiles([]string{"transformerconfig/test/config.yaml"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(tcfg, expected) {
t.Fatalf("expected %v\n but go6t %v\n", expected, tcfg)
}

View File

@@ -20,140 +20,182 @@ import (
"encoding/json"
"strings"
"github.com/ghodss/yaml"
"github.com/go-openapi/spec"
"github.com/pkg/errors"
"k8s.io/kube-openapi/pkg/common"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/yaml"
)
// LoadCRDs parse CRD schemas from paths into a TransformerConfig
func (tf *Factory) LoadCRDs(paths []string) (*TransformerConfig, error) {
tc := tf.EmptyConfig()
type myProperties map[string]spec.Schema
type nameToApiMap map[string]common.OpenAPIDefinition
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
func LoadConfigFromCRDs(
ldr ifc.Loader, paths []string) (*TransformerConfig, error) {
tc := MakeEmptyConfig()
for _, path := range paths {
otherTc, err := tf.loadCRD(path)
content, err := ldr.Load(path)
if err != nil {
return nil, err
}
m, err := makeNameToApiMap(content)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse open API definition from '%s'", path)
}
otherTc, err := makeConfigFromApiMap(m)
if err != nil {
return nil, err
}
tc, err = tc.Merge(otherTc)
if err != nil {
return nil, err
}
tc = tc.Merge(otherTc)
}
return tc, nil
}
func (tf *Factory) loadCRD(path string) (*TransformerConfig, error) {
result := tf.EmptyConfig()
content, err := tf.loader().Load(path)
if err != nil {
return result, err
}
var types map[string]common.OpenAPIDefinition
func makeNameToApiMap(content []byte) (result nameToApiMap, err error) {
if content[0] == '{' {
err = json.Unmarshal(content, &types)
err = json.Unmarshal(content, &result)
} else {
err = yaml.Unmarshal(content, &types)
}
if err != nil {
return nil, err
err = yaml.Unmarshal(content, &result)
}
return
}
crds := getCRDs(types)
for crd, k := range crds {
tc := tf.EmptyConfig()
err = loadCrdIntoConfig(
types, crd, crd, k, []string{}, tc)
func makeConfigFromApiMap(m nameToApiMap) (*TransformerConfig, error) {
result := MakeEmptyConfig()
for name, api := range m {
if !looksLikeAk8sType(api.Schema.SchemaProps.Properties) {
continue
}
tc := MakeEmptyConfig()
err := loadCrdIntoConfig(
tc, makeGvkFromTypeName(name), m, name, []string{})
if err != nil {
return result, err
}
result, err = result.Merge(tc)
if err != nil {
return result, err
}
result = result.Merge(tc)
}
return result, nil
}
// getCRDs get all CRD types
func getCRDs(types map[string]common.OpenAPIDefinition) map[string]gvk.Gvk {
crds := map[string]gvk.Gvk{}
for typename, t := range types {
properties := t.Schema.SchemaProps.Properties
_, foundKind := properties["kind"]
_, foundAPIVersion := properties["apiVersion"]
_, foundMetadata := properties["metadata"]
if foundKind && foundAPIVersion && foundMetadata {
// TODO: Get Group and Version for CRD from the openAPI definition once
// "x-kubernetes-group-version-kind" is available in CRD
kind := strings.Split(typename, ".")[len(strings.Split(typename, "."))-1]
crds[typename] = gvk.Gvk{Kind: kind}
}
}
return crds
// TODO: Get Group and Version for CRD from the
// openAPI definition once
// "x-kubernetes-group-version-kind" is available in CRD
func makeGvkFromTypeName(n string) gvk.Gvk {
names := strings.Split(n, ".")
kind := names[len(names)-1]
return gvk.Gvk{Kind: kind}
}
func looksLikeAk8sType(properties myProperties) bool {
_, ok := properties["kind"]
if !ok {
return false
}
_, ok = properties["apiVersion"]
if !ok {
return false
}
_, ok = properties["metadata"]
if !ok {
return false
}
return true
}
const (
// "x-kubernetes-annotation": ""
xAnnotation = "x-kubernetes-annotation"
// "x-kubernetes-label-selector": ""
xLabelSelector = "x-kubernetes-label-selector"
// "x-kubernetes-identity": ""
xIdentity = "x-kubernetes-identity"
// "x-kubernetes-object-ref-api-version": <apiVersion name>
xVersion = "x-kubernetes-object-ref-api-version"
// "x-kubernetes-object-ref-kind": <kind name>
xKind = "x-kubernetes-object-ref-kind"
// "x-kubernetes-object-ref-name-key": "name"
// default is "name"
xNameKey = "x-kubernetes-object-ref-name-key"
)
// loadCrdIntoConfig loads a CRD spec into a TransformerConfig
func loadCrdIntoConfig(
types map[string]common.OpenAPIDefinition,
atype string, crd string, in gvk.Gvk,
path []string, config *TransformerConfig) error {
if _, ok := types[crd]; !ok {
theConfig *TransformerConfig, theGvk gvk.Gvk, theMap nameToApiMap,
typeName string, path []string) (err error) {
api, ok := theMap[typeName]
if !ok {
return nil
}
for propname, property := range types[atype].Schema.SchemaProps.Properties {
_, annotate := property.Extensions.GetString(Annotation)
for propName, property := range api.Schema.SchemaProps.Properties {
_, annotate := property.Extensions.GetString(xAnnotation)
if annotate {
config.AddAnnotationFieldSpec(
FieldSpec{
CreateIfNotPresent: false,
Gvk: in,
Path: strings.Join(append(path, propname), "/"),
},
)
err = theConfig.AddAnnotationFieldSpec(
makeFs(theGvk, append(path, propName)))
if err != nil {
return
}
}
_, label := property.Extensions.GetString(LabelSelector)
_, label := property.Extensions.GetString(xLabelSelector)
if label {
config.AddLabelFieldSpec(
FieldSpec{
CreateIfNotPresent: false,
Gvk: in,
Path: strings.Join(append(path, propname), "/"),
},
)
err = theConfig.AddLabelFieldSpec(
makeFs(theGvk, append(path, propName)))
if err != nil {
return
}
}
_, identity := property.Extensions.GetString(Identity)
_, identity := property.Extensions.GetString(xIdentity)
if identity {
config.AddPrefixFieldSpec(
FieldSpec{
CreateIfNotPresent: false,
Gvk: in,
Path: strings.Join(append(path, propname), "/"),
},
)
err = theConfig.AddPrefixFieldSpec(
makeFs(theGvk, append(path, propName)))
if err != nil {
return
}
}
version, ok := property.Extensions.GetString(Version)
version, ok := property.Extensions.GetString(xVersion)
if ok {
kind, ok := property.Extensions.GetString(Kind)
kind, ok := property.Extensions.GetString(xKind)
if ok {
nameKey, ok := property.Extensions.GetString(NameKey)
nameKey, ok := property.Extensions.GetString(xNameKey)
if !ok {
nameKey = "name"
}
config.AddNamereferenceFieldSpec(NameBackReferences{
Gvk: gvk.Gvk{Kind: kind, Version: version},
FieldSpecs: []FieldSpec{
{
CreateIfNotPresent: false,
Gvk: in,
Path: strings.Join(append(path, propname, nameKey), "/"),
},
},
})
err = theConfig.AddNamereferenceFieldSpec(
NameBackReferences{
Gvk: gvk.Gvk{Kind: kind, Version: version},
FieldSpecs: []FieldSpec{
makeFs(theGvk, append(path, propName, nameKey))},
})
if err != nil {
return
}
}
}
if property.Ref.GetURL() != nil {
loadCrdIntoConfig(
types, property.Ref.String(), crd, in,
append(path, propname), config)
theConfig, theGvk, theMap,
property.Ref.String(), append(path, propName))
}
}
return nil
}
func makeFs(in gvk.Gvk, path []string) FieldSpec {
return FieldSpec{
CreateIfNotPresent: false,
Gvk: in,
Path: strings.Join(path, "/"),
}
}

View File

@@ -20,9 +20,9 @@ import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/internal/loadertest"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/internal/loadertest"
)
// This defines two CRD's: Bee and MyKind.
@@ -182,8 +182,7 @@ func TestLoadCRDs(t *testing.T) {
NameReference: nbrs,
}
actualTc, err := NewFactory(makeLoader(t)).LoadCRDs(
[]string{"crd.json"})
actualTc, err := LoadConfigFromCRDs(makeLoader(t), []string{"crd.json"})
if err != nil {
t.Fatalf("unexpected error:%v", err)
}

View File

@@ -57,6 +57,11 @@ func (fs FieldSpec) String() string {
"%s:%v:%s", fs.Gvk.String(), fs.CreateIfNotPresent, fs.Path)
}
// If true, the primary key is the same, but other fields might not be.
func (fs FieldSpec) effectivelyEquals(other FieldSpec) bool {
return fs.IsSelected(&other.Gvk) && fs.Path == other.Path
}
// PathSlice converts the path string to a slice of strings,
// separated by a '/'. Forward slash can be contained in a
// fieldname. such as ingress.kubernetes.io/auth-secret in
@@ -76,7 +81,6 @@ func (fs FieldSpec) PathSlice() []string {
if !strings.Contains(fs.Path, escapedForwardSlash) {
return strings.Split(fs.Path, "/")
}
s := strings.Replace(fs.Path, escapedForwardSlash, tempSlashReplacement, -1)
paths := strings.Split(s, "/")
var result []string
@@ -93,3 +97,43 @@ func (s fsSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s fsSlice) Less(i, j int) bool {
return s[i].Gvk.IsLessThan(s[j].Gvk)
}
// mergeAll merges the argument into this, returning the result.
// Items already present are ignored.
// Items that conflict (primary key matches, but remain data differs)
// result in an error.
func (s fsSlice) mergeAll(incoming fsSlice) (result fsSlice, err error) {
result = s
for _, x := range incoming {
result, err = result.mergeOne(x)
if err != nil {
return nil, err
}
}
return result, nil
}
// mergeOne merges the argument into this, returning the result.
// If the item's primary key is already present, and there are no
// conflicts, it is ignored (we don't want duplicates).
// If there is a conflict, the merge fails.
func (s fsSlice) mergeOne(x FieldSpec) (fsSlice, error) {
i := s.index(x)
if i > -1 {
// It's already there.
if s[i].CreateIfNotPresent != x.CreateIfNotPresent {
return nil, fmt.Errorf("conflicting fieldspecs")
}
return s, nil
}
return append(s, x), nil
}
func (s fsSlice) index(fs FieldSpec) int {
for i, x := range s {
if x.effectivelyEquals(fs) {
return i
}
}
return -1
}

View File

@@ -17,8 +17,12 @@ limitations under the License.
package config
import (
"fmt"
"reflect"
"strings"
"testing"
"sigs.k8s.io/kustomize/pkg/gvk"
)
func TestPathSlice(t *testing.T) {
@@ -44,3 +48,133 @@ func TestPathSlice(t *testing.T) {
}
}
}
var mergeTests = []struct {
name string
original fsSlice
incoming fsSlice
err error
result fsSlice
}{
{
"normal",
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: false,
},
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "pear"},
CreateIfNotPresent: false,
},
},
fsSlice{
{
Path: "home",
Gvk: gvk.Gvk{Group: "beans"},
CreateIfNotPresent: false,
},
},
nil,
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: false,
},
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "pear"},
CreateIfNotPresent: false,
},
{
Path: "home",
Gvk: gvk.Gvk{Group: "beans"},
CreateIfNotPresent: false,
},
},
},
{
"ignore copy",
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: false,
},
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "pear"},
CreateIfNotPresent: false,
},
},
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: false,
},
},
nil,
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: false,
},
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "pear"},
CreateIfNotPresent: false,
},
},
},
{
"error on conflict",
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: false,
},
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "pear"},
CreateIfNotPresent: false,
},
},
fsSlice{
{
Path: "whatever",
Gvk: gvk.Gvk{Group: "apple"},
CreateIfNotPresent: true,
},
},
fmt.Errorf("hey"),
fsSlice{},
},
}
func TestFsSlice_MergeAll(t *testing.T) {
for _, item := range mergeTests {
result, err := item.original.mergeAll(item.incoming)
if item.err == nil {
if err != nil {
t.Fatalf("test %s: unexpected err %v", item.name, err)
}
if !reflect.DeepEqual(item.result, result) {
t.Fatalf("test %s: expected: %v\n but got: %v\n",
item.name, item.result, result)
}
} else {
if err == nil {
t.Fatalf("test %s: expected err: %v", item.name, err)
}
if !strings.Contains(err.Error(), "conflicting fieldspecs") {
t.Fatalf("test %s: unexpected err: %v", item.name, err)
}
}
}
}

View File

@@ -52,7 +52,7 @@ import (
// }
type NameBackReferences struct {
gvk.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
FieldSpecs []FieldSpec `json:"FieldSpecs,omitempty" yaml:"FieldSpecs,omitempty"`
FieldSpecs fsSlice `json:"FieldSpecs,omitempty" yaml:"FieldSpecs,omitempty"`
}
func (n NameBackReferences) String() string {
@@ -72,20 +72,27 @@ func (s nbrSlice) Less(i, j int) bool {
return s[i].Gvk.IsLessThan(s[j].Gvk)
}
func (s nbrSlice) mergeAll(o nbrSlice) nbrSlice {
result := s
func (s nbrSlice) mergeAll(o nbrSlice) (result nbrSlice, err error) {
result = s
for _, r := range o {
result = result.mergeOne(r)
result, err = result.mergeOne(r)
if err != nil {
return nil, err
}
}
return result
return result, nil
}
func (s nbrSlice) mergeOne(other NameBackReferences) nbrSlice {
func (s nbrSlice) mergeOne(other NameBackReferences) (nbrSlice, error) {
var result nbrSlice
var err error
found := false
for _, c := range s {
if c.Gvk.Equals(other.Gvk) {
c.FieldSpecs = append(c.FieldSpecs, other.FieldSpecs...)
c.FieldSpecs, err = c.FieldSpecs.mergeAll(other.FieldSpecs)
if err != nil {
return nil, err
}
found = true
}
result = append(result, c)
@@ -94,5 +101,5 @@ func (s nbrSlice) mergeOne(other NameBackReferences) nbrSlice {
if !found {
result = append(result, other)
}
return result
return result, nil
}

View File

@@ -89,17 +89,19 @@ func TestMergeAll(t *testing.T) {
Gvk: gvk.Gvk{
Kind: "ConfigMap",
},
// Current behavior allows repeats of FieldSpec
FieldSpecs: append(fsSlice1, fsSlice1...),
FieldSpecs: fsSlice1,
},
{
Gvk: gvk.Gvk{
Kind: "Secret",
},
FieldSpecs: append(fsSlice2, fsSlice2...),
FieldSpecs: fsSlice2,
},
}
actual := nbrsSlice1.mergeAll(nbrsSlice2)
actual, err := nbrsSlice1.mergeAll(nbrsSlice2)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected\n %v\n but got\n %v\n", expected, actual)
}

View File

@@ -19,7 +19,10 @@ limitations under the License.
package config
import (
"log"
"sort"
"sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig"
)
// TransformerConfig holds the data needed to perform transformations.
@@ -31,6 +34,22 @@ type TransformerConfig struct {
CommonAnnotations fsSlice `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
NameReference nbrSlice `json:"nameReference,omitempty" yaml:"nameReference,omitempty"`
VarReference fsSlice `json:"varReference,omitempty" yaml:"varReference,omitempty"`
Images fsSlice `json:"images,omitempty" yaml:"images,omitempty"`
}
// MakeEmptyConfig returns an empty TransformerConfig object
func MakeEmptyConfig() *TransformerConfig {
return &TransformerConfig{}
}
// MakeDefaultConfig returns a default TransformerConfig.
func MakeDefaultConfig() *TransformerConfig {
c, err := makeTransformerConfigFromBytes(
defaultconfig.GetDefaultFieldSpecs())
if err != nil {
log.Fatalf("Unable to make default transformconfig: %v", err)
}
return c
}
// sortFields provides determinism in logging, tests, etc.
@@ -41,46 +60,81 @@ func (t *TransformerConfig) sortFields() {
sort.Sort(t.CommonAnnotations)
sort.Sort(t.NameReference)
sort.Sort(t.VarReference)
sort.Sort(t.Images)
}
// AddPrefixFieldSpec adds a FieldSpec to NamePrefix
func (t *TransformerConfig) AddPrefixFieldSpec(fs FieldSpec) {
t.NamePrefix = append(t.NamePrefix, fs)
func (t *TransformerConfig) AddPrefixFieldSpec(fs FieldSpec) (err error) {
t.NamePrefix, err = t.NamePrefix.mergeOne(fs)
return err
}
// AddSuffixFieldSpec adds a FieldSpec to NameSuffix
func (t *TransformerConfig) AddSuffixFieldSpec(fs FieldSpec) {
t.NameSuffix = append([]FieldSpec{fs}, t.NameSuffix...)
func (t *TransformerConfig) AddSuffixFieldSpec(fs FieldSpec) (err error) {
t.NameSuffix, err = t.NameSuffix.mergeOne(fs)
return err
}
// AddLabelFieldSpec adds a FieldSpec to CommonLabels
func (t *TransformerConfig) AddLabelFieldSpec(fs FieldSpec) {
t.CommonLabels = append(t.CommonLabels, fs)
func (t *TransformerConfig) AddLabelFieldSpec(fs FieldSpec) (err error) {
t.CommonLabels, err = t.CommonLabels.mergeOne(fs)
return err
}
// AddAnnotationFieldSpec adds a FieldSpec to CommonAnnotations
func (t *TransformerConfig) AddAnnotationFieldSpec(fs FieldSpec) {
t.CommonAnnotations = append(t.CommonAnnotations, fs)
func (t *TransformerConfig) AddAnnotationFieldSpec(fs FieldSpec) (err error) {
t.CommonAnnotations, err = t.CommonAnnotations.mergeOne(fs)
return err
}
// AddNamereferenceFieldSpec adds a NameBackReferences to NameReference
func (t *TransformerConfig) AddNamereferenceFieldSpec(nbrs NameBackReferences) {
t.NameReference = t.NameReference.mergeOne(nbrs)
func (t *TransformerConfig) AddNamereferenceFieldSpec(
nbrs NameBackReferences) (err error) {
t.NameReference, err = t.NameReference.mergeOne(nbrs)
return err
}
// Merge merges two TransformerConfigs objects into a new TransformerConfig object
func (t *TransformerConfig) Merge(input *TransformerConfig) *TransformerConfig {
// Merge merges two TransformerConfigs objects into
// a new TransformerConfig object
func (t *TransformerConfig) Merge(input *TransformerConfig) (
merged *TransformerConfig, err error) {
if input == nil {
return t
return t, nil
}
merged = &TransformerConfig{}
merged.NamePrefix, err = t.NamePrefix.mergeAll(input.NamePrefix)
if err != nil {
return nil, err
}
merged.NameSuffix, err = t.NameSuffix.mergeAll(input.NameSuffix)
if err != nil {
return nil, err
}
merged.NameSpace, err = t.NameSpace.mergeAll(input.NameSpace)
if err != nil {
return nil, err
}
merged.CommonAnnotations, err = t.CommonAnnotations.mergeAll(
input.CommonAnnotations)
if err != nil {
return nil, err
}
merged.CommonLabels, err = t.CommonLabels.mergeAll(input.CommonLabels)
if err != nil {
return nil, err
}
merged.VarReference, err = t.VarReference.mergeAll(input.VarReference)
if err != nil {
return nil, err
}
merged.NameReference, err = t.NameReference.mergeAll(input.NameReference)
if err != nil {
return nil, err
}
merged.Images, err = t.Images.mergeAll(input.Images)
if err != nil {
return nil, err
}
merged := &TransformerConfig{}
merged.NamePrefix = append(t.NamePrefix, input.NamePrefix...)
merged.NameSuffix = append(input.NameSuffix, t.NameSuffix...)
merged.NameSpace = append(t.NameSpace, input.NameSpace...)
merged.CommonAnnotations = append(t.CommonAnnotations, input.CommonAnnotations...)
merged.CommonLabels = append(t.CommonLabels, input.CommonLabels...)
merged.VarReference = append(t.VarReference, input.VarReference...)
merged.NameReference = t.NameReference.mergeAll(input.NameReference)
merged.sortFields()
return merged
return merged, nil
}

View File

@@ -42,7 +42,10 @@ func TestAddNamereferenceFieldSpec(t *testing.T) {
},
}
cfg.AddNamereferenceFieldSpec(nbrs)
err := cfg.AddNamereferenceFieldSpec(nbrs)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(cfg.NameReference) != 1 {
t.Fatal("failed to add namereference FieldSpec")
}
@@ -57,19 +60,31 @@ func TestAddFieldSpecs(t *testing.T) {
CreateIfNotPresent: true,
}
cfg.AddPrefixFieldSpec(fieldSpec)
err := cfg.AddPrefixFieldSpec(fieldSpec)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(cfg.NamePrefix) != 1 {
t.Fatalf("failed to add nameprefix FieldSpec")
}
cfg.AddSuffixFieldSpec(fieldSpec)
err = cfg.AddSuffixFieldSpec(fieldSpec)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(cfg.NameSuffix) != 1 {
t.Fatalf("failed to add namesuffix FieldSpec")
}
cfg.AddLabelFieldSpec(fieldSpec)
err = cfg.AddLabelFieldSpec(fieldSpec)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(cfg.CommonLabels) != 1 {
t.Fatalf("failed to add nameprefix FieldSpec")
}
cfg.AddAnnotationFieldSpec(fieldSpec)
err = cfg.AddAnnotationFieldSpec(fieldSpec)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(cfg.CommonAnnotations) != 1 {
t.Fatalf("failed to add nameprefix FieldSpec")
}
@@ -128,7 +143,10 @@ func TestMerge(t *testing.T) {
cfgb.AddPrefixFieldSpec(fieldSpecs[1])
cfga.AddSuffixFieldSpec(fieldSpecs[1])
actual := cfga.Merge(cfgb)
actual, err := cfga.Merge(cfgb)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if len(actual.NamePrefix) != 2 {
t.Fatal("merge failed for namePrefix FieldSpec")
@@ -154,7 +172,10 @@ func TestMerge(t *testing.T) {
t.Fatalf("expected: %v\n but got: %v\n", expected, actual)
}
actual = cfga.Merge(nil)
actual, err = cfga.Merge(nil)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(actual, cfga) {
t.Fatalf("expected: %v\n but got: %v\n", cfga, actual)
}