mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-17 01:39:06 +00:00
Merge branch 'master' into transformer-no-create-arrays
This commit is contained in:
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
23
pkg/transformers/config/defaultconfig/images.go
Normal file
23
pkg/transformers/config/defaultconfig/images.go
Normal 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 = ``
|
||||
)
|
||||
@@ -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
|
||||
`
|
||||
)
|
||||
|
||||
@@ -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
|
||||
`
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, "/"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
202
pkg/transformers/image.go
Normal file
202
pkg/transformers/image.go
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
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 transformers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/image"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||
)
|
||||
|
||||
// imageTransformer replace image names and tags
|
||||
type imageTransformer struct {
|
||||
images []image.Image
|
||||
fieldSpecs []config.FieldSpec
|
||||
}
|
||||
|
||||
var _ Transformer = &imageTransformer{}
|
||||
|
||||
// NewImageTransformer constructs an imageTransformer.
|
||||
func NewImageTransformer(slice []image.Image, fs []config.FieldSpec) (Transformer, error) {
|
||||
return &imageTransformer{slice, fs}, nil
|
||||
}
|
||||
|
||||
// Transform finds the matching images and replaces name, tag and/or digest
|
||||
func (pt *imageTransformer) Transform(m resmap.ResMap) error {
|
||||
if len(pt.images) == 0 {
|
||||
return nil
|
||||
}
|
||||
for id := range m {
|
||||
objMap := m[id].Map()
|
||||
for _, path := range pt.fieldSpecs {
|
||||
if !id.Gvk().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
err := mutateField(objMap, path.PathSlice(), false, pt.mutateImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Kept for backward compatibility
|
||||
if err := pt.findAndReplaceImage(objMap); err != nil && id.Gvk().Kind != `CustomResourceDefinition` {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *imageTransformer) mutateImage(in interface{}) (interface{}, error) {
|
||||
original, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("image path is not of type string but %T", in)
|
||||
}
|
||||
|
||||
for _, img := range pt.images {
|
||||
if !isImageMatched(original, img.Name) {
|
||||
continue
|
||||
}
|
||||
name, tag := split(original)
|
||||
if img.NewName != "" {
|
||||
name = img.NewName
|
||||
}
|
||||
if img.NewTag != "" {
|
||||
tag = ":" + img.NewTag
|
||||
}
|
||||
if img.Digest != "" {
|
||||
tag = "@" + img.Digest
|
||||
}
|
||||
return name + tag, nil
|
||||
}
|
||||
return original, nil
|
||||
}
|
||||
|
||||
/*
|
||||
findAndReplaceImage replaces the image name and tags inside one object
|
||||
It searches the object for container session
|
||||
then loops though all images inside containers session,
|
||||
finds matched ones and update the image name and tag name
|
||||
*/
|
||||
func (pt *imageTransformer) findAndReplaceImage(obj map[string]interface{}) error {
|
||||
paths := []string{"containers", "initContainers"}
|
||||
updated := false
|
||||
for _, path := range paths {
|
||||
containers, found := obj[path]
|
||||
if found {
|
||||
if _, err := pt.updateContainers(containers); err != nil {
|
||||
return err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if !updated {
|
||||
return pt.findContainers(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *imageTransformer) updateContainers(in interface{}) (interface{}, error) {
|
||||
containers, ok := in.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("containers path is not of type []interface{} but %T", in)
|
||||
}
|
||||
for i := range containers {
|
||||
container := containers[i].(map[string]interface{})
|
||||
containerImage, found := container["image"]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
imageName := containerImage.(string)
|
||||
for _, img := range pt.images {
|
||||
if !isImageMatched(imageName, img.Name) {
|
||||
continue
|
||||
}
|
||||
newImage, err := pt.mutateImage(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container["image"] = newImage
|
||||
break
|
||||
}
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (pt *imageTransformer) findContainers(obj map[string]interface{}) error {
|
||||
for key := range obj {
|
||||
switch typedV := obj[key].(type) {
|
||||
case map[string]interface{}:
|
||||
err := pt.findAndReplaceImage(typedV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range typedV {
|
||||
item := typedV[i]
|
||||
typedItem, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
err := pt.findAndReplaceImage(typedItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isImageMatched(s, t string) bool {
|
||||
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$")
|
||||
return pattern.MatchString(s)
|
||||
}
|
||||
|
||||
// split separates and returns the name and tag parts
|
||||
// from the image string using either colon `:` or at `@` separators.
|
||||
// Note that the returned tag keeps its separator.
|
||||
func split(imageName string) (name string, tag string) {
|
||||
// check if image name contains a domain
|
||||
// if domain is present, ignore domain and check for `:`
|
||||
ic := -1
|
||||
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
|
||||
ic = strings.LastIndex(imageName, ":")
|
||||
} else {
|
||||
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
|
||||
// set ic only if `:` is present
|
||||
if lastIc > 0 {
|
||||
ic = slashIndex + lastIc
|
||||
}
|
||||
}
|
||||
ia := strings.LastIndex(imageName, "@")
|
||||
if ic < 0 && ia < 0 {
|
||||
return imageName, ""
|
||||
}
|
||||
|
||||
i := ic
|
||||
if ic < 0 {
|
||||
i = ia
|
||||
}
|
||||
|
||||
name = imageName[:i]
|
||||
tag = imageName[i:]
|
||||
return
|
||||
}
|
||||
@@ -1,126 +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 transformers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
// imageTagTransformer replace image tags
|
||||
type imageTagTransformer struct {
|
||||
imageTags []types.ImageTag
|
||||
}
|
||||
|
||||
var _ Transformer = &imageTagTransformer{}
|
||||
|
||||
// NewImageTagTransformer constructs a imageTagTransformer.
|
||||
func NewImageTagTransformer(slice []types.ImageTag) (Transformer, error) {
|
||||
return &imageTagTransformer{slice}, nil
|
||||
}
|
||||
|
||||
// Transform finds the matching images and replace the tag
|
||||
func (pt *imageTagTransformer) Transform(resources resmap.ResMap) error {
|
||||
if len(pt.imageTags) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, res := range resources {
|
||||
err := pt.findAndReplaceTag(res.Map())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
findAndReplaceTag replaces the image tags inside one object
|
||||
It searches the object for container session
|
||||
then loops though all images inside containers session, finds matched ones and update the tag name
|
||||
*/
|
||||
func (pt *imageTagTransformer) findAndReplaceTag(obj map[string]interface{}) error {
|
||||
paths := []string{"containers", "initContainers"}
|
||||
found := false
|
||||
for _, path := range paths {
|
||||
_, found = obj[path]
|
||||
if found {
|
||||
err := pt.updateContainers(obj, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return pt.findContainers(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *imageTagTransformer) updateContainers(obj map[string]interface{}, path string) error {
|
||||
containers := obj[path].([]interface{})
|
||||
for i := range containers {
|
||||
container := containers[i].(map[string]interface{})
|
||||
image, found := container["image"]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
for _, imagetag := range pt.imageTags {
|
||||
if isImageMatched(image.(string), imagetag.Name) {
|
||||
container["image"] = imagetag.Name + ":" + imagetag.NewTag
|
||||
|
||||
if imagetag.Digest != "" {
|
||||
container["image"] = imagetag.Name + "@" + imagetag.Digest
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *imageTagTransformer) findContainers(obj map[string]interface{}) error {
|
||||
for key := range obj {
|
||||
switch typedV := obj[key].(type) {
|
||||
case map[string]interface{}:
|
||||
err := pt.findAndReplaceTag(typedV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range typedV {
|
||||
item := typedV[i]
|
||||
typedItem, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
err := pt.findAndReplaceTag(typedItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isImageMatched(s, t string) bool {
|
||||
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||
pattern, _ := regexp.Compile("^" + t + "(:[a-zA-Z0-9_.-]*)?$")
|
||||
return pattern.MatchString(s)
|
||||
}
|
||||
@@ -1,189 +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 transformers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/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"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
|
||||
func TestImageTagTransformer(t *testing.T) {
|
||||
var rf = resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
|
||||
m := resmap.ResMap{
|
||||
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{}{
|
||||
"spec": map[string]interface{}{
|
||||
"initContainers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx2",
|
||||
"image": "my-nginx:1.8.0",
|
||||
},
|
||||
},
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:1.7.9",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "replaced-with-digest",
|
||||
"image": "foobar:1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
resid.NewResId(gvk.Gvk{Kind: "randomKind"}, "random"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx1",
|
||||
"image": "nginx",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "myimage",
|
||||
"image": "myprivaterepohostname:1234/my/image:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"spec2": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx3",
|
||||
"image": "nginx:v1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "nginx4",
|
||||
"image": "my-nginx:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
expected := resmap.ResMap{
|
||||
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{}{
|
||||
"spec": map[string]interface{}{
|
||||
"initContainers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx2",
|
||||
"image": "my-nginx:previous",
|
||||
},
|
||||
},
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx",
|
||||
"image": "nginx:v2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "replaced-with-digest",
|
||||
"image": "foobar@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
resid.NewResId(gvk.Gvk{Kind: "randomKind"}, "random"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx1",
|
||||
"image": "nginx:v2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "myimage",
|
||||
"image": "myprivaterepohostname:1234/my/image:v1.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"spec2": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "nginx3",
|
||||
"image": "nginx:v2",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "nginx4",
|
||||
"image": "my-nginx:previous",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
it, err := NewImageTagTransformer([]types.ImageTag{
|
||||
{Name: "nginx", NewTag: "v2"},
|
||||
{Name: "my-nginx", NewTag: "previous"},
|
||||
{Name: "myprivaterepohostname:1234/my/image", NewTag: "v1.0.1"},
|
||||
{Name: "foobar", Digest: "sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = it.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(m, expected) {
|
||||
err = expected.ErrorIfNotEqual(m)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,9 @@ func (o *mapTransformer) Transform(m resmap.ResMap) error {
|
||||
|
||||
func (o *mapTransformer) addMap(in interface{}) (interface{}, error) {
|
||||
m, ok := in.(map[string]interface{})
|
||||
if !ok {
|
||||
if in == nil {
|
||||
m = map[string]interface{}{}
|
||||
} else if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
for k, v := range o.m {
|
||||
|
||||
@@ -44,7 +44,7 @@ var crb = gvk.Gvk{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Clus
|
||||
var sa = gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
|
||||
var ingress = gvk.Gvk{Kind: "Ingress"}
|
||||
var rf = resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
||||
var defaultTransformerConfig = config.NewFactory(nil).DefaultConfig()
|
||||
var defaultTransformerConfig = config.MakeDefaultConfig()
|
||||
|
||||
func TestLabelsRun(t *testing.T) {
|
||||
m := resmap.ResMap{
|
||||
@@ -355,6 +355,12 @@ func TestLabelsRun(t *testing.T) {
|
||||
"spec": map[string]interface{}{
|
||||
"schedule": "* 23 * * *",
|
||||
"jobTemplate": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"label-key1": "label-value1",
|
||||
"label-key2": "label-value2",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
@@ -390,6 +396,12 @@ func TestLabelsRun(t *testing.T) {
|
||||
"spec": map[string]interface{}{
|
||||
"schedule": "* 23 * * *",
|
||||
"jobTemplate": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"label-key1": "label-value1",
|
||||
"label-key2": "label-value2",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
@@ -571,3 +583,48 @@ func TestAnnotationsRun(t *testing.T) {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotaionsRunWithNullValue(t *testing.T) {
|
||||
m := resmap.ResMap{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
"annotations": nil,
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
"annotations": map[string]interface{}{
|
||||
"anno-key1": "anno-value1",
|
||||
"anno-key2": "anno-value2",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
at, err := NewAnnotationsMapTransformer(
|
||||
map[string]string{"anno-key1": "anno-value1", "anno-key2": "anno-value2"},
|
||||
defaultTransformerConfig.CommonAnnotations)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = at.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(m, expected) {
|
||||
err = expected.ErrorIfNotEqual(m)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,10 +17,11 @@ limitations under the License.
|
||||
package transformers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||
)
|
||||
@@ -33,18 +34,31 @@ var _ Transformer = &nameReferenceTransformer{}
|
||||
|
||||
// NewNameReferenceTransformer constructs a nameReferenceTransformer
|
||||
// with a given slice of NameBackReferences.
|
||||
func NewNameReferenceTransformer(
|
||||
br []config.NameBackReferences) (Transformer, error) {
|
||||
func NewNameReferenceTransformer(br []config.NameBackReferences) Transformer {
|
||||
if br == nil {
|
||||
return nil, errors.New("backrefs not expected to be nil")
|
||||
log.Fatal("backrefs not expected to be nil")
|
||||
}
|
||||
return &nameReferenceTransformer{backRefs: br}, nil
|
||||
return &nameReferenceTransformer{backRefs: br}
|
||||
}
|
||||
|
||||
// Transform does the field update according to fieldSpecs.
|
||||
// The old name is in the key in the map and the new name is in the object
|
||||
// associated with the key. e.g. if <k, v> is one of the key-value pair in the map,
|
||||
// then the old name is k.Name and the new name is v.GetName()
|
||||
// Transform updates name references in resource A that refer to resource B,
|
||||
// given that B's name may have changed.
|
||||
//
|
||||
// For example, a HorizontalPodAutoscaler (HPA) necessarily refers to a
|
||||
// Deployment (the thing that the HPA scales). The Deployment name might change
|
||||
// (e.g. prefix added), and the reference in the HPA has to be fixed.
|
||||
//
|
||||
// In the outer loop below, we encounter an HPA. In scanning backrefs, we
|
||||
// find that HPA refers to a Deployment. So we find all resources in the same
|
||||
// namespace as the HPA (and with the same prefix and suffix), and look through
|
||||
// them to find all the Deployments with a resId that has a Name matching the
|
||||
// field in HPA. For each match, we overwrite the HPA name field with the value
|
||||
// found in the Deployment's name field (the name in the raw object - the
|
||||
// modified name - not the unmodified name in the resId).
|
||||
//
|
||||
// This assumes that the name stored in a ResId (the ResMap key) isn't modified
|
||||
// by name transformers. Name transformers should only modify the name in the
|
||||
// body of the resource object (the value in the ResMap).
|
||||
func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
// TODO: Too much looping.
|
||||
// Even more hidden loops in FilterBy,
|
||||
@@ -57,7 +71,7 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
m[id].Map(), fSpec.PathSlice(),
|
||||
fSpec.CreateIfNotPresent,
|
||||
o.updateNameReference(
|
||||
backRef.Gvk, m.FilterBy(id)))
|
||||
id, backRef.Gvk, m.FilterBy(id)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -69,14 +83,14 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
|
||||
func (o *nameReferenceTransformer) updateNameReference(
|
||||
backRef gvk.Gvk, m resmap.ResMap) func(in interface{}) (interface{}, error) {
|
||||
rid resid.ResId, backRef gvk.Gvk, m resmap.ResMap) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch in.(type) {
|
||||
case string:
|
||||
s, _ := in.(string)
|
||||
for id, res := range m {
|
||||
if id.Gvk().IsSelected(&backRef) && id.Name() == s {
|
||||
matchedIds := m.FindByGVKN(id)
|
||||
matchedIds := m.GetMatchingIds(id.GvknEquals)
|
||||
// If there's more than one match, there's no way
|
||||
// to know which one to pick, so emit error.
|
||||
if len(matchedIds) > 1 {
|
||||
@@ -85,6 +99,7 @@ func (o *nameReferenceTransformer) updateNameReference(
|
||||
}
|
||||
// Return transformed name of the object,
|
||||
// complete with prefixes, hashes, etc.
|
||||
res.AppendRefBy(rid)
|
||||
return res.GetName(), nil
|
||||
}
|
||||
}
|
||||
@@ -102,7 +117,7 @@ func (o *nameReferenceTransformer) updateNameReference(
|
||||
for id, res := range m {
|
||||
indexes := indexOf(id.Name(), names)
|
||||
if id.Gvk().IsSelected(&backRef) && len(indexes) > 0 {
|
||||
matchedIds := m.FindByGVKN(id)
|
||||
matchedIds := m.GetMatchingIds(id.GvknEquals)
|
||||
if len(matchedIds) > 1 {
|
||||
return nil, fmt.Errorf(
|
||||
"Multiple matches for name %s:\n %v", id, matchedIds)
|
||||
@@ -110,6 +125,7 @@ func (o *nameReferenceTransformer) updateNameReference(
|
||||
for _, index := range indexes {
|
||||
l[index] = res.GetName()
|
||||
}
|
||||
res.AppendRefBy(rid)
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,9 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
"configMap": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
"name": "secret1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
@@ -184,6 +187,9 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
"configMap": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
"name": "secret1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -235,6 +241,43 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
resid.NewResId(cronjob, "cronjob1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "batch/v1beta1",
|
||||
"kind": "CronJob",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cronjob1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"schedule": "0 14 * * *",
|
||||
"jobTemplate": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "main",
|
||||
"image": "myimage",
|
||||
},
|
||||
},
|
||||
"volumes": map[string]interface{}{
|
||||
"projected": map[string]interface{}{
|
||||
"sources": map[string]interface{}{
|
||||
"configMap": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
"name": "secret1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
expected := resmap.ResMap{}
|
||||
@@ -307,6 +350,9 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
"configMap": map[string]interface{}{
|
||||
"name": "someprefix-cm2-somehash",
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
"name": "someprefix-secret1-somehash",
|
||||
},
|
||||
},
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
@@ -343,6 +389,9 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
"configMap": map[string]interface{}{
|
||||
"name": "someprefix-cm2-somehash",
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
"name": "someprefix-secret1-somehash",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -405,11 +454,45 @@ func TestNameReferenceHappyRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
nrt, err := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = nrt.Transform(m)
|
||||
expected[resid.NewResId(cronjob, "cronjob1")] = rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "batch/v1beta1",
|
||||
"kind": "CronJob",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cronjob1",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"schedule": "0 14 * * *",
|
||||
"jobTemplate": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"template": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"containers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "main",
|
||||
"image": "myimage",
|
||||
},
|
||||
},
|
||||
"volumes": map[string]interface{}{
|
||||
"projected": map[string]interface{}{
|
||||
"sources": map[string]interface{}{
|
||||
"configMap": map[string]interface{}{
|
||||
"name": "someprefix-cm2-somehash",
|
||||
},
|
||||
"secret": map[string]interface{}{
|
||||
"name": "someprefix-secret1-somehash",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
err := nrt.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -471,19 +554,78 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
||||
expectedErr: "is expected to be either a string or a []interface{}"},
|
||||
}
|
||||
|
||||
nrt, err := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
for _, test := range tests {
|
||||
err = nrt.Transform(test.resMap)
|
||||
err := nrt.Transform(test.resMap)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error to happen")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Fatalf("Incorrect error.\nExpected: %s, but got %v", test.expectedErr, err)
|
||||
t.Fatalf("Incorrect error.\nExpected: %s, but got %v",
|
||||
test.expectedErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameReferencePersistentVolumeHappyRun(t *testing.T) {
|
||||
rf := resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
m := resmap.ResMap{
|
||||
resid.NewResId(pv, "volume1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolume",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "someprefix-volume1",
|
||||
},
|
||||
}),
|
||||
|
||||
resid.NewResId(pvc, "claim1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolumeClaim",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "someprefix-claim1",
|
||||
"namespace": "some-namespace",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"volumeName": "volume1",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
expected := resmap.ResMap{
|
||||
resid.NewResId(pv, "volume1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolume",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "someprefix-volume1",
|
||||
},
|
||||
}),
|
||||
|
||||
resid.NewResId(pvc, "claim1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolumeClaim",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "someprefix-claim1",
|
||||
"namespace": "some-namespace",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"volumeName": "someprefix-volume1",
|
||||
},
|
||||
}),
|
||||
}
|
||||
expected[resid.NewResId(pv, "volume1")].AppendRefBy(resid.NewResId(pvc, "claim1"))
|
||||
nrt := NewNameReferenceTransformer(defaultTransformerConfig.NameReference)
|
||||
err := nrt.Transform(m)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(m, expected) {
|
||||
err = expected.ErrorIfNotEqual(m)
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,65 @@ func NewNamespaceTransformer(ns string, cf []config.FieldSpec) Transformer {
|
||||
|
||||
// Transform adds the namespace.
|
||||
func (o *namespaceTransformer) Transform(m resmap.ResMap) error {
|
||||
mf := resmap.ResMap{}
|
||||
mf := o.filterResmap(m)
|
||||
|
||||
for id := range mf {
|
||||
objMap := mf[id].Map()
|
||||
for _, path := range o.fieldSpecsToUse {
|
||||
switch path.Path {
|
||||
// Special casing .metadata.namespace since it is a common metadata field across all runtime.Object
|
||||
// We should add namespace if it's namespaced resource; otherwise, we should not.
|
||||
case "metadata/namespace":
|
||||
if id.Gvk().IsSelected(&path.Gvk) && !id.Gvk().IsClusterKind() {
|
||||
if len(objMap) > 0 {
|
||||
err := mutateField(
|
||||
objMap, path.PathSlice(), path.CreateIfNotPresent,
|
||||
func(_ interface{}) (interface{}, error) {
|
||||
return o.namespace, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
if !id.Gvk().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
// make sure the object is non empty
|
||||
if len(objMap) > 0 {
|
||||
err := mutateField(
|
||||
objMap, path.PathSlice(), path.CreateIfNotPresent,
|
||||
func(_ interface{}) (interface{}, error) {
|
||||
return o.namespace, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !id.Gvk().IsClusterKind() {
|
||||
newid := id.CopyWithNewNamespace(o.namespace)
|
||||
m[newid] = mf[id]
|
||||
} else {
|
||||
m[id] = mf[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
o.updateClusterRoleBinding(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *namespaceTransformer) filterResmap(m resmap.ResMap) resmap.ResMap {
|
||||
mf := resmap.ResMap{}
|
||||
for id := range m {
|
||||
found := false
|
||||
for _, path := range o.fieldSpecsToSkip {
|
||||
if id.Gvk().IsSelected(&path.Gvk) {
|
||||
found = true
|
||||
break
|
||||
mf[id] = m[id]
|
||||
delete(m, id)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
@@ -63,27 +114,7 @@ func (o *namespaceTransformer) Transform(m resmap.ResMap) error {
|
||||
delete(m, id)
|
||||
}
|
||||
}
|
||||
|
||||
for id := range mf {
|
||||
objMap := mf[id].Map()
|
||||
for _, path := range o.fieldSpecsToUse {
|
||||
if !id.Gvk().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := mutateField(objMap, path.PathSlice(), path.CreateIfNotPresent, func(_ interface{}) (interface{}, error) {
|
||||
return o.namespace, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newid := id.CopyWithNewNamespace(o.namespace)
|
||||
m[newid] = mf[id]
|
||||
}
|
||||
|
||||
}
|
||||
o.updateClusterRoleBinding(m)
|
||||
return nil
|
||||
return mf
|
||||
}
|
||||
|
||||
func (o *namespaceTransformer) updateClusterRoleBinding(m resmap.ResMap) {
|
||||
@@ -99,7 +130,10 @@ func (o *namespaceTransformer) updateClusterRoleBinding(m resmap.ResMap) {
|
||||
continue
|
||||
}
|
||||
objMap := m[id].Map()
|
||||
subjects := objMap["subjects"].([]interface{})
|
||||
subjects, ok := objMap["subjects"].([]interface{})
|
||||
if subjects == nil || !ok {
|
||||
continue
|
||||
}
|
||||
for i := range subjects {
|
||||
subject := subjects[i].(map[string]interface{})
|
||||
kind, foundk := subject["kind"]
|
||||
|
||||
@@ -47,6 +47,9 @@ func TestNamespaceRun(t *testing.T) {
|
||||
"namespace": "foo",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(cmap, "cm3"): rf.FromMap(
|
||||
map[string]interface{}{},
|
||||
),
|
||||
resid.NewResId(ns, "ns1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -134,6 +137,9 @@ func TestNamespaceRun(t *testing.T) {
|
||||
"namespace": "test",
|
||||
},
|
||||
}),
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "cm3", "", "test"): rf.FromMap(
|
||||
map[string]interface{}{},
|
||||
),
|
||||
resid.NewResIdWithPrefixNamespace(sa, "default", "", "test"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
|
||||
@@ -19,7 +19,6 @@ package transformers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
@@ -43,20 +42,20 @@ var prefixSuffixFieldSpecsToSkip = []config.FieldSpec{
|
||||
},
|
||||
}
|
||||
|
||||
// deprecateNamePrefixSuffixFieldSpec will be moved into prefixSuffixFieldSpecsToSkip in next release
|
||||
var deprecateNamePrefixSuffixFieldSpec = config.FieldSpec{
|
||||
Gvk: gvk.Gvk{Kind: "Namespace"},
|
||||
}
|
||||
|
||||
// NewNamePrefixSuffixTransformer construct a namePrefixSuffixTransformer.
|
||||
func NewNamePrefixSuffixTransformer(np, ns string, pc []config.FieldSpec) (Transformer, error) {
|
||||
// NewNamePrefixSuffixTransformer makes a namePrefixSuffixTransformer.
|
||||
func NewNamePrefixSuffixTransformer(
|
||||
np, ns string, fieldSpecs []config.FieldSpec) (Transformer, error) {
|
||||
if len(np) == 0 && len(ns) == 0 {
|
||||
return NewNoOpTransformer(), nil
|
||||
}
|
||||
if pc == nil {
|
||||
if fieldSpecs == nil {
|
||||
return nil, errors.New("fieldSpecs is not expected to be nil")
|
||||
}
|
||||
return &namePrefixSuffixTransformer{fieldSpecsToUse: pc, prefix: np, suffix: ns, fieldSpecsToSkip: prefixSuffixFieldSpecsToSkip}, nil
|
||||
return &namePrefixSuffixTransformer{
|
||||
prefix: np,
|
||||
suffix: ns,
|
||||
fieldSpecsToUse: fieldSpecs,
|
||||
fieldSpecsToSkip: prefixSuffixFieldSpecsToSkip}, nil
|
||||
}
|
||||
|
||||
// Transform prepends the name prefix and appends the name suffix.
|
||||
@@ -80,15 +79,16 @@ func (o *namePrefixSuffixTransformer) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
|
||||
for id := range mf {
|
||||
if id.Gvk().IsSelected(&deprecateNamePrefixSuffixFieldSpec.Gvk) {
|
||||
log.Println("Adding nameprefix and namesuffix to Namespace resource will be deprecated in next release.")
|
||||
}
|
||||
objMap := mf[id].Map()
|
||||
for _, path := range o.fieldSpecsToUse {
|
||||
if !id.Gvk().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
err := mutateField(objMap, path.PathSlice(), path.CreateIfNotPresent, o.addPrefixSuffix)
|
||||
err := mutateField(
|
||||
objMap,
|
||||
path.PathSlice(),
|
||||
path.CreateIfNotPresent,
|
||||
o.addPrefixSuffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,7 +99,8 @@ func (o *namePrefixSuffixTransformer) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *namePrefixSuffixTransformer) addPrefixSuffix(in interface{}) (interface{}, error) {
|
||||
func (o *namePrefixSuffixTransformer) addPrefixSuffix(
|
||||
in interface{}) (interface{}, error) {
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
||||
|
||||
@@ -1,66 +1,108 @@
|
||||
/*
|
||||
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 transformers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/expansion"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||
)
|
||||
|
||||
type refvarTransformer struct {
|
||||
fieldSpecs []config.FieldSpec
|
||||
vars map[string]string
|
||||
type RefVarTransformer struct {
|
||||
varMap map[string]string
|
||||
replacementCounts map[string]int
|
||||
fieldSpecs []config.FieldSpec
|
||||
mappingFunc func(string) string
|
||||
}
|
||||
|
||||
// NewRefVarTransformer returns a Trasformer that replaces $(VAR) style variables with values.
|
||||
func NewRefVarTransformer(vars map[string]string, p []config.FieldSpec) Transformer {
|
||||
return &refvarTransformer{
|
||||
vars: vars,
|
||||
fieldSpecs: p,
|
||||
// NewRefVarTransformer returns a new RefVarTransformer
|
||||
// that replaces $(VAR) style variables with values.
|
||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||
func NewRefVarTransformer(
|
||||
varMap map[string]string, fs []config.FieldSpec) *RefVarTransformer {
|
||||
return &RefVarTransformer{
|
||||
varMap: varMap,
|
||||
fieldSpecs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// Transform determines the final values of variables:
|
||||
//
|
||||
// 1. Determine the final value of each variable:
|
||||
// a. If the variable's Value is set, expand the `$(var)` references to other
|
||||
// variables in the .Value field; the sources of variables are the declared
|
||||
// variables of the container and the service environment variables
|
||||
// b. If a source is defined for an environment variable, resolve the source
|
||||
// 2. Create the container's environment in the order variables are declared
|
||||
// 3. Add remaining service environment vars
|
||||
func (rv *refvarTransformer) Transform(resources resmap.ResMap) error {
|
||||
for resId := range resources {
|
||||
objMap := resources[resId].Map()
|
||||
for _, pc := range rv.fieldSpecs {
|
||||
if !resId.Gvk().IsSelected(&pc.Gvk) {
|
||||
continue
|
||||
// replaceVars accepts as 'in' a string, or string array, which can have
|
||||
// embedded instances of $VAR style variables, e.g. a container command string.
|
||||
// The function returns the string with the variables expanded to their final
|
||||
// values.
|
||||
func (rv *RefVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
||||
switch vt := in.(type) {
|
||||
case []interface{}:
|
||||
var xs []string
|
||||
for _, a := range in.([]interface{}) {
|
||||
xs = append(xs, expansion.Expand(a.(string), rv.mappingFunc))
|
||||
}
|
||||
return xs, nil
|
||||
case map[string]interface{}:
|
||||
inMap := in.(map[string]interface{})
|
||||
xs := make(map[string]interface{}, len(inMap))
|
||||
for k, v := range inMap {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", v, s)
|
||||
}
|
||||
err := mutateField(objMap, pc.PathSlice(), false, func(in interface{}) (interface{}, error) {
|
||||
var (
|
||||
mappingFunc = expansion.MappingFuncFor(rv.vars)
|
||||
)
|
||||
switch vt := in.(type) {
|
||||
case []interface{}:
|
||||
var xs []string
|
||||
for _, a := range in.([]interface{}) {
|
||||
xs = append(xs, expansion.Expand(a.(string), mappingFunc))
|
||||
}
|
||||
return xs, nil
|
||||
case interface{}:
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
||||
}
|
||||
runtimeVal := expansion.Expand(s, mappingFunc)
|
||||
return runtimeVal, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid type encountered %T", vt)
|
||||
xs[k] = expansion.Expand(s, rv.mappingFunc)
|
||||
}
|
||||
return xs, nil
|
||||
case interface{}:
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
||||
}
|
||||
return expansion.Expand(s, rv.mappingFunc), nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid type encountered %T", vt)
|
||||
}
|
||||
}
|
||||
|
||||
// UnusedVars returns slice of Var names that were unused
|
||||
// after a Transform run.
|
||||
func (rv *RefVarTransformer) UnusedVars() []string {
|
||||
var unused []string
|
||||
for k := range rv.varMap {
|
||||
_, ok := rv.replacementCounts[k]
|
||||
if !ok {
|
||||
unused = append(unused, k)
|
||||
}
|
||||
}
|
||||
return unused
|
||||
}
|
||||
|
||||
// Transform replaces $(VAR) style variables with values.
|
||||
func (rv *RefVarTransformer) Transform(m resmap.ResMap) error {
|
||||
rv.replacementCounts = make(map[string]int)
|
||||
rv.mappingFunc = expansion.MappingFuncFor(
|
||||
rv.replacementCounts, rv.varMap)
|
||||
for id, res := range m {
|
||||
for _, fieldSpec := range rv.fieldSpecs {
|
||||
if id.Gvk().IsSelected(&fieldSpec.Gvk) {
|
||||
if err := mutateField(
|
||||
res.Map(), fieldSpec.PathSlice(),
|
||||
false, rv.replaceVars); err != nil {
|
||||
return err
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
pkg/transformers/refvars_test.go
Normal file
109
pkg/transformers/refvars_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
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 transformers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||
)
|
||||
|
||||
func TestVarRef(t *testing.T) {
|
||||
type given struct {
|
||||
varMap map[string]string
|
||||
fs []config.FieldSpec
|
||||
res resmap.ResMap
|
||||
}
|
||||
type expected struct {
|
||||
res resmap.ResMap
|
||||
unused []string
|
||||
}
|
||||
testCases := []struct {
|
||||
description string
|
||||
given given
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
description: "var replacement in map[string]",
|
||||
given: given{
|
||||
varMap: map[string]string{
|
||||
"FOO": "replacementForFoo",
|
||||
"BAR": "replacementForBar",
|
||||
},
|
||||
fs: []config.FieldSpec{
|
||||
{Gvk: cmap, Path: "data"},
|
||||
},
|
||||
res: resmap.ResMap{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"item1": "$(FOO)",
|
||||
"item2": "bla",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
expected: expected{
|
||||
res: resmap.ResMap{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"item1": "replacementForFoo",
|
||||
"item2": "bla",
|
||||
},
|
||||
}),
|
||||
},
|
||||
unused: []string{"BAR"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
// arrange
|
||||
tr := NewRefVarTransformer(tc.given.varMap, tc.given.fs)
|
||||
|
||||
// act
|
||||
err := tr.Transform(tc.given.res)
|
||||
|
||||
// assert
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
a, e := tc.given.res, tc.expected.res
|
||||
if !reflect.DeepEqual(a, e) {
|
||||
err = e.ErrorIfNotEqual(a)
|
||||
t.Fatalf("actual doesn't match expected: \nACTUAL:\n%v\nEXPECTED:\n%v\nERR: %v", a, e, err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,17 @@ limitations under the License.
|
||||
// Package transformers has implementations of resmap.ResMap transformers.
|
||||
package transformers
|
||||
|
||||
import "sigs.k8s.io/kustomize/pkg/resmap"
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||
)
|
||||
|
||||
// A Transformer modifies an instance of resmap.ResMap.
|
||||
type Transformer interface {
|
||||
// Transform modifies data in the argument, e.g. adding labels to resources that can be labelled.
|
||||
Transform(m resmap.ResMap) error
|
||||
}
|
||||
|
||||
// A Generator creates an instance of resmap.ResMap.
|
||||
type Generator interface {
|
||||
Generate() (resmap.ResMap, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user