Migrate Replacement Transformer plugin
Plugin extended to support new kustomize plugin framework which consider each plugin as a container Change-Id: If55b7093f711401165b7d4fd3f5b1059fde464ff Relates-To: #340
This commit is contained in:
parent
025c2172d6
commit
ca1a3a2d0b
pkg/document/plugin
97
pkg/document/plugin/kyamlutils/document_selector.go
Normal file
97
pkg/document/plugin/kyamlutils/document_selector.go
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
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
|
||||
|
||||
https://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 kyamlutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var _ kio.Filter = DocumentSelector{}
|
||||
|
||||
// DocumentSelector RNode objects
|
||||
type DocumentSelector struct {
|
||||
filters []kio.Filter
|
||||
}
|
||||
|
||||
// Filters return list of defined filters for the selector
|
||||
func (f DocumentSelector) Filters() []kio.Filter {
|
||||
return f.filters
|
||||
}
|
||||
|
||||
func (f DocumentSelector) byPath(path []string, val string) DocumentSelector {
|
||||
// Need to have exact match of the value since grep filter considers Value
|
||||
// as a regular expression
|
||||
f.filters = append(f.filters, filters.GrepFilter{Path: path, Value: "^" + val + "$"})
|
||||
return f
|
||||
}
|
||||
|
||||
// ByKey adds filter by specific yaml manifest key and value
|
||||
func (f DocumentSelector) ByKey(key, val string) DocumentSelector {
|
||||
return f.byPath([]string{key}, val)
|
||||
}
|
||||
|
||||
// ByAPIVersion adds filter by 'apiVersion' field value
|
||||
func (f DocumentSelector) ByAPIVersion(apiver string) DocumentSelector {
|
||||
if apiver != "" {
|
||||
return f.ByKey(yaml.APIVersionField, apiver)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// ByGVK adds filters by 'apiVersion' and 'kind; field values
|
||||
func (f DocumentSelector) ByGVK(group, version, kind string) DocumentSelector {
|
||||
apiver := fmt.Sprintf("%s/%s", group, version)
|
||||
// Remove '/' if group or version is empty
|
||||
apiver = strings.TrimPrefix(apiver, "/")
|
||||
apiver = strings.TrimSuffix(apiver, "/")
|
||||
newFlt := f.ByAPIVersion(apiver)
|
||||
if kind != "" {
|
||||
return newFlt.ByKey(yaml.KindField, kind)
|
||||
}
|
||||
return newFlt
|
||||
}
|
||||
|
||||
// ByName adds filter by 'metadata.name' field value
|
||||
func (f DocumentSelector) ByName(name string) DocumentSelector {
|
||||
if name != "" {
|
||||
return f.byPath([]string{yaml.MetadataField, yaml.NameField}, name)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// ByNamespace adds filter by 'metadata.namespace' field value
|
||||
func (f DocumentSelector) ByNamespace(ns string) DocumentSelector {
|
||||
if ns != "" {
|
||||
return f.byPath([]string{yaml.MetadataField, yaml.NamespaceField}, ns)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Filter RNode objects
|
||||
func (f DocumentSelector) Filter(items []*yaml.RNode) (result []*yaml.RNode, err error) {
|
||||
result = items
|
||||
for i := range f.filters {
|
||||
result, err = f.filters[i].Filter(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
155
pkg/document/plugin/kyamlutils/document_selector_test.go
Normal file
155
pkg/document/plugin/kyamlutils/document_selector_test.go
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
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
|
||||
|
||||
https://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 kyamlutils_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/plugin/kyamlutils"
|
||||
)
|
||||
|
||||
func documents(t *testing.T) []*yaml.RNode {
|
||||
docs := `---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p1
|
||||
namespace: capi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p2
|
||||
namespace: capi
|
||||
---
|
||||
apiVersion: v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: p1
|
||||
`
|
||||
rns, err := (&kio.ByteReader{Reader: bytes.NewBufferString(docs)}).Read()
|
||||
require.NoError(t, err)
|
||||
return rns
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
docs := documents(t)
|
||||
testCases := []struct {
|
||||
name string
|
||||
selector kyamlutils.DocumentSelector
|
||||
expectedErr error
|
||||
expectedDocs string
|
||||
}{
|
||||
{
|
||||
name: "Get by GVK + name + namespace",
|
||||
selector: kyamlutils.DocumentSelector{}.
|
||||
ByGVK("", "v1", "Pod").
|
||||
ByName("p1").
|
||||
ByNamespace("capi"),
|
||||
expectedDocs: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p1
|
||||
namespace: capi`,
|
||||
},
|
||||
{
|
||||
name: "No filters",
|
||||
selector: kyamlutils.DocumentSelector{},
|
||||
expectedDocs: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p1
|
||||
namespace: capi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p2
|
||||
namespace: capi
|
||||
---
|
||||
apiVersion: v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: p1`,
|
||||
},
|
||||
{
|
||||
name: "Get by apiVersion",
|
||||
selector: kyamlutils.DocumentSelector{}.ByAPIVersion("v1beta1"),
|
||||
expectedDocs: `apiVersion: v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: p1`,
|
||||
},
|
||||
{
|
||||
name: "Get by empty name",
|
||||
selector: kyamlutils.DocumentSelector{}.ByAPIVersion("v1beta1").ByName(""),
|
||||
expectedDocs: `apiVersion: v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: p1`,
|
||||
},
|
||||
{
|
||||
name: "Get by version only",
|
||||
selector: kyamlutils.DocumentSelector{}.ByGVK("", "v1", ""),
|
||||
expectedDocs: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p1
|
||||
namespace: capi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: p2
|
||||
namespace: capi`,
|
||||
},
|
||||
{
|
||||
name: "Get by kind only",
|
||||
selector: kyamlutils.DocumentSelector{}.ByGVK("", "", "Deployment"),
|
||||
expectedDocs: `apiVersion: v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: p1`,
|
||||
},
|
||||
{
|
||||
name: "Get by empty namespace",
|
||||
selector: kyamlutils.DocumentSelector{}.ByGVK("", "v1beta1", "Deployment").ByNamespace(""),
|
||||
expectedDocs: `apiVersion: v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: p1`,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
filteredDocs, err := tc.selector.Filter(docs)
|
||||
assert.Equal(t, err, tc.expectedErr)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = kio.ByteWriter{Writer: buf}.Write(filteredDocs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedDocs, strings.TrimSuffix(buf.String(), "\n"))
|
||||
})
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@ package replacement
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
@ -50,11 +50,11 @@ func (e ErrBadConfiguration) Error() string {
|
||||
|
||||
// ErrMultipleResources returned if multiple resources were found
|
||||
type ErrMultipleResources struct {
|
||||
ResList []*resource.Resource
|
||||
ObjRef *types.Target
|
||||
}
|
||||
|
||||
func (e ErrMultipleResources) Error() string {
|
||||
return fmt.Sprintf("found more than one resources matching from %v", e.ResList)
|
||||
return fmt.Sprintf("found more than one resources matching identified by %s", printFields(e.ObjRef))
|
||||
}
|
||||
|
||||
// ErrSourceNotFound returned if a replacement source resource does not exist in resource map
|
||||
@ -63,16 +63,7 @@ type ErrSourceNotFound struct {
|
||||
}
|
||||
|
||||
func (e ErrSourceNotFound) Error() string {
|
||||
keys := [5]string{"Group:", "Version:", "Kind:", "Name:", "Namespace:"}
|
||||
values := [5]string{e.ObjRef.Group, e.ObjRef.Version, e.ObjRef.Kind, e.ObjRef.Name, e.ObjRef.Namespace}
|
||||
|
||||
var resFilter string
|
||||
for i, key := range keys {
|
||||
if values[i] != "" {
|
||||
resFilter += key + values[i] + " "
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("failed to find any source resources identified by %s", strings.TrimSpace(resFilter))
|
||||
return fmt.Sprintf("failed to find any source resources identified by %s", printFields(e.ObjRef))
|
||||
}
|
||||
|
||||
// ErrTargetNotFound returned if a replacement target resource does not exist in the resource map
|
||||
@ -81,18 +72,7 @@ type ErrTargetNotFound struct {
|
||||
}
|
||||
|
||||
func (e ErrTargetNotFound) Error() string {
|
||||
keys := [7]string{"Group:", "Version:", "Kind:", "Name:", "Namespace:",
|
||||
"AnnotationSelector:", "LabelSelector:"}
|
||||
values := [7]string{e.ObjRef.Group, e.ObjRef.Version, e.ObjRef.Kind, e.ObjRef.Name,
|
||||
e.ObjRef.Namespace, e.ObjRef.AnnotationSelector, e.ObjRef.LabelSelector}
|
||||
|
||||
var resFilter string
|
||||
for i, key := range keys {
|
||||
if values[i] != "" {
|
||||
resFilter += key + values[i] + " "
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("failed to find any target resources identified by %s", strings.TrimSpace(resFilter))
|
||||
return fmt.Sprintf("failed to find any target resources identified by %s", printFields(e.ObjRef))
|
||||
}
|
||||
|
||||
// ErrPatternSubstring returned in case of issues with sub-string pattern substitution
|
||||
@ -114,12 +94,24 @@ func (e ErrIndexOutOfBound) Error() string {
|
||||
return fmt.Sprintf("array index out of bounds: index %d, length %d", e.Index, e.Length)
|
||||
}
|
||||
|
||||
// ErrMapNotFound returned if map specified in fieldRef option was not found in a list
|
||||
type ErrMapNotFound struct {
|
||||
Key, Value, ListKey string
|
||||
// ErrValueNotFound returned if value specified in fieldRef option was not found
|
||||
type ErrValueNotFound struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (e ErrMapNotFound) Error() string {
|
||||
return fmt.Sprintf("unable to find map key '%s' with the value '%s' in list under '%s' key",
|
||||
e.Key, e.Value, e.ListKey)
|
||||
func (e ErrValueNotFound) Error() string {
|
||||
return fmt.Sprintf("unable to find value identified by %s", e.ID)
|
||||
}
|
||||
|
||||
func printFields(objRef interface{}) string {
|
||||
val := reflect.ValueOf(objRef).Elem()
|
||||
valType := val.Type()
|
||||
var res []string
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
if field.String() != "" {
|
||||
res = append(res, fmt.Sprintf("%s: %v", valType.Field(i).Name, field.Interface()))
|
||||
}
|
||||
}
|
||||
return strings.Join(res, " ")
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ import (
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/document/plugin/kyamlutils"
|
||||
plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types"
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -115,7 +115,130 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
|
||||
func (p *plugin) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return nil, errors.ErrNotImplemented{What: "`Exec` method for replacement transformer"}
|
||||
for _, r := range p.Replacements {
|
||||
val, err := getValue(items, r.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := replace(items, r.Target, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func getValue(items []*yaml.RNode, source *types.ReplSource) (*yaml.RNode, error) {
|
||||
if source.Value != "" {
|
||||
return yaml.NewScalarRNode(source.Value), nil
|
||||
}
|
||||
sources, err := kyamlutils.DocumentSelector{}.
|
||||
ByAPIVersion(source.ObjRef.APIVersion).
|
||||
ByGVK(source.ObjRef.Group, source.ObjRef.Version, source.ObjRef.Kind).
|
||||
ByName(source.ObjRef.Name).
|
||||
ByNamespace(source.ObjRef.Namespace).
|
||||
Filter(items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(sources) > 1 {
|
||||
return nil, ErrMultipleResources{ObjRef: source.ObjRef}
|
||||
}
|
||||
if len(sources) == 0 {
|
||||
return nil, ErrSourceNotFound{ObjRef: source.ObjRef}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("{.%s.%s}", yaml.MetadataField, yaml.NameField)
|
||||
if source.FieldRef != "" {
|
||||
path = source.FieldRef
|
||||
}
|
||||
return sources[0].Pipe(kyamlutils.JSONPathFilter{Path: path})
|
||||
}
|
||||
|
||||
func mutateField(rnSource *yaml.RNode) func([]*yaml.RNode) error {
|
||||
return func(rns []*yaml.RNode) error {
|
||||
for _, rn := range rns {
|
||||
rn.SetYNode(rnSource.YNode())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func replace(items []*yaml.RNode, target *types.ReplTarget, value *yaml.RNode) error {
|
||||
targets, err := kyamlutils.DocumentSelector{}.
|
||||
ByGVK(target.ObjRef.Group, target.ObjRef.Version, target.ObjRef.Kind).
|
||||
ByName(target.ObjRef.Name).
|
||||
ByNamespace(target.ObjRef.Namespace).
|
||||
Filter(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
return ErrTargetNotFound{ObjRef: target.ObjRef}
|
||||
}
|
||||
for _, tgt := range targets {
|
||||
for _, fieldRef := range target.FieldRefs {
|
||||
// fieldref can contain substring pattern for regexp - we need to get it
|
||||
groups := substringPatternRegex.FindStringSubmatch(fieldRef)
|
||||
// if there is no substring pattern
|
||||
if len(groups) != 3 {
|
||||
filter := kyamlutils.JSONPathFilter{Path: fieldRef, Mutator: mutateField(value), Create: true}
|
||||
if _, err := tgt.Pipe(filter); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := substituteSubstring(tgt, groups[1], groups[2], value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func substituteSubstring(tgt *yaml.RNode, fieldRef, substringPattern string, value *yaml.RNode) error {
|
||||
if err := yaml.ErrorIfInvalid(value, yaml.ScalarNode); err != nil {
|
||||
return err
|
||||
}
|
||||
curVal, err := tgt.Pipe(kyamlutils.JSONPathFilter{Path: fieldRef})
|
||||
if yaml.IsMissingOrError(curVal, err) {
|
||||
return err
|
||||
}
|
||||
switch curVal.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
p := regexp.MustCompile(substringPattern)
|
||||
if !p.MatchString(yaml.GetValue(curVal)) {
|
||||
return ErrPatternSubstring{
|
||||
Msg: fmt.Sprintf("pattern '%s' is defined in configuration but was not found in target value %s",
|
||||
substringPattern, yaml.GetValue(curVal)),
|
||||
}
|
||||
}
|
||||
curVal.YNode().Value = p.ReplaceAllString(yaml.GetValue(curVal), yaml.GetValue(value))
|
||||
|
||||
case yaml.SequenceNode:
|
||||
items, err := curVal.Elements()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range items {
|
||||
if err := yaml.ErrorIfInvalid(item, yaml.ScalarNode); err != nil {
|
||||
return err
|
||||
}
|
||||
p := regexp.MustCompile(substringPattern)
|
||||
if !p.MatchString(yaml.GetValue(item)) {
|
||||
return ErrPatternSubstring{
|
||||
Msg: fmt.Sprintf("pattern '%s' is defined in configuration but was not found in target value %s",
|
||||
substringPattern, yaml.GetValue(item)),
|
||||
}
|
||||
}
|
||||
item.YNode().Value = p.ReplaceAllString(yaml.GetValue(item), yaml.GetValue(value))
|
||||
}
|
||||
default:
|
||||
return ErrPatternSubstring{Msg: fmt.Sprintf("value identified by %s expected to be string", fieldRef)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReplacement(m resmap.ResMap, objRef *types.Target, fieldRef string) (interface{}, error) {
|
||||
@ -129,7 +252,11 @@ func getReplacement(m resmap.ResMap, objRef *types.Target, fieldRef string) (int
|
||||
return nil, err
|
||||
}
|
||||
if len(resources) > 1 {
|
||||
return nil, ErrMultipleResources{ResList: resources}
|
||||
resList := make([]string, len(resources))
|
||||
for i := range resources {
|
||||
resList[i] = resources[i].String()
|
||||
}
|
||||
return nil, ErrMultipleResources{ObjRef: objRef}
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
return nil, ErrSourceNotFound{ObjRef: objRef}
|
||||
@ -305,7 +432,7 @@ func updateSliceField(m []interface{}, pathToField []string, replacement interfa
|
||||
if len(pathToField) == 0 {
|
||||
return nil
|
||||
}
|
||||
path, key, value, isArray := getFirstPathSegment(pathToField[0])
|
||||
_, key, value, isArray := getFirstPathSegment(pathToField[0])
|
||||
|
||||
if isArray {
|
||||
for _, item := range m {
|
||||
@ -319,7 +446,7 @@ func updateSliceField(m []interface{}, pathToField []string, replacement interfa
|
||||
}
|
||||
}
|
||||
}
|
||||
return ErrMapNotFound{Key: key, Value: value, ListKey: path}
|
||||
return nil
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(pathToField[0])
|
||||
|
@ -5,12 +5,14 @@ package replacement_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/plugin/replacement"
|
||||
@ -70,15 +72,14 @@ spec:
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReplacementTransformer(t *testing.T) {
|
||||
testCases := []struct {
|
||||
cfg string
|
||||
in string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
cfg: `
|
||||
var testCases = []struct {
|
||||
cfg string
|
||||
in string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -100,9 +101,9 @@ replacements:
|
||||
- spec.template.spec.containers.3.image
|
||||
`,
|
||||
|
||||
in: `
|
||||
group: apps
|
||||
in: `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
@ -126,7 +127,7 @@ spec:
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOut: `apiVersion: v1
|
||||
expectedOut: `apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@ -151,9 +152,9 @@ spec:
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -168,9 +169,9 @@ replacements:
|
||||
- spec.template.spec.containers[name=nginx-tagged].image%1.7.9%
|
||||
`,
|
||||
|
||||
in: `
|
||||
group: apps
|
||||
in: `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
@ -181,7 +182,7 @@ spec:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
`,
|
||||
expectedOut: `apiVersion: v1
|
||||
expectedOut: `apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@ -193,10 +194,10 @@ spec:
|
||||
- image: nginx:1.17.0
|
||||
name: nginx-tagged
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
cfg: `
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -212,15 +213,15 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -232,7 +233,7 @@ kind: Deployment
|
||||
metadata:
|
||||
name: deploy3
|
||||
`,
|
||||
expectedOut: `apiVersion: v1
|
||||
expectedOut: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
@ -263,9 +264,9 @@ spec:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -293,13 +294,13 @@ replacements:
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[image=debian].args.1
|
||||
- spec.template.spec.containers[name=busybox].args.2`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy
|
||||
labels:
|
||||
foo: bar
|
||||
name: deploy
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
@ -307,27 +308,28 @@ spec:
|
||||
foo: bar
|
||||
spec:
|
||||
containers:
|
||||
- name: command-demo-container
|
||||
image: debian
|
||||
command: ["printenv"]
|
||||
args:
|
||||
- HOSTNAME
|
||||
- PORT
|
||||
- name: busybox
|
||||
image: busybox:latest
|
||||
args:
|
||||
- echo
|
||||
- HOSTNAME
|
||||
- PORT
|
||||
- args:
|
||||
- HOSTNAME
|
||||
- PORT
|
||||
command:
|
||||
- printenv
|
||||
image: debian
|
||||
name: command-demo-container
|
||||
- args:
|
||||
- echo
|
||||
- HOSTNAME
|
||||
- PORT
|
||||
image: busybox:latest
|
||||
name: busybox
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm
|
||||
data:
|
||||
HOSTNAME: example.com
|
||||
PORT: 8080`,
|
||||
expectedOut: `apiVersion: apps/v1
|
||||
PORT: 8080
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm`,
|
||||
expectedOut: `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
@ -362,9 +364,9 @@ kind: ConfigMap
|
||||
metadata:
|
||||
name: cm
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -384,9 +386,9 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers.3.image`,
|
||||
in: `
|
||||
group: apps
|
||||
in: `
|
||||
apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
@ -409,7 +411,7 @@ spec:
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine`,
|
||||
expectedOut: `apiVersion: v1
|
||||
expectedOut: `apiVersion: v1
|
||||
group: apps
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@ -434,9 +436,9 @@ spec:
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -452,15 +454,15 @@ replacements:
|
||||
name: pod2
|
||||
fieldrefs:
|
||||
- spec.non.existent.field`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
@ -468,9 +470,9 @@ metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedOut: `apiVersion: v1
|
||||
- image: busybox
|
||||
name: myapp-container`,
|
||||
expectedOut: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
@ -491,9 +493,9 @@ spec:
|
||||
existent:
|
||||
field: pod1
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -509,15 +511,15 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[name=myapp-container]`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- name: repl
|
||||
image: repl
|
||||
- image: repl
|
||||
name: repl
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -530,7 +532,7 @@ spec:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
`,
|
||||
expectedOut: `apiVersion: v1
|
||||
expectedOut: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
@ -550,9 +552,9 @@ spec:
|
||||
- image: repl
|
||||
name: repl
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -568,15 +570,15 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[name=myapp-container].image%TAG%`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- name: repl
|
||||
image: 12345
|
||||
- image: 12345
|
||||
name: repl
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -589,7 +591,7 @@ spec:
|
||||
- image: busybox:TAG
|
||||
name: myapp-container
|
||||
`,
|
||||
expectedOut: `apiVersion: v1
|
||||
expectedOut: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
@ -609,9 +611,9 @@ spec:
|
||||
- image: busybox:12345
|
||||
name: myapp-container
|
||||
`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -625,7 +627,7 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[name=nginx-latest].image`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -643,15 +645,10 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: "found more than one resources matching from " +
|
||||
"[{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"name\":\"pod1\"}," +
|
||||
"\"spec\":{\"containers\":[{\"image\":\"busybox\",\"name\":\"myapp-container\"" +
|
||||
"}]}}{nsfx:false,beh:unspecified} {\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":" +
|
||||
"{\"name\":\"pod2\"},\"spec\":{\"containers\":[{\"image\":\"busybox\",\"name\":\"myapp-container\"}]}}" +
|
||||
"{nsfx:false,beh:unspecified}]",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: "found more than one resources matching identified by Gvk: ~G_~V_Pod",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -660,17 +657,22 @@ replacements:
|
||||
- source:
|
||||
objref:
|
||||
kind: Pod
|
||||
name: pod1
|
||||
name: doesNotExists
|
||||
namespace: default
|
||||
target:
|
||||
objref:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[name=nginx-latest].image`,
|
||||
expectedErr: "failed to find any source resources identified by Kind:Pod Name:pod1 Namespace:default",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
in: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1`,
|
||||
expectedErr: "failed to find any source resources identified by " +
|
||||
"Gvk: ~G_~V_Pod Name: doesNotExists Namespace: default",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -685,7 +687,7 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[name=nginx-latest].image`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -694,10 +696,10 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: "failed to find any target resources identified by Kind:Deployment",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: "failed to find any target resources identified by Gvk: ~G_~V_Deployment",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -713,7 +715,7 @@ replacements:
|
||||
name: pod2
|
||||
fieldrefs:
|
||||
- labels.somelabel.key1.subkey1`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -733,10 +735,10 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: `"some string value" is not expected be a primitive type`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: `"some string value" is not expected be a primitive type`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -752,7 +754,7 @@ replacements:
|
||||
name: pod2
|
||||
fieldrefs:
|
||||
- labels.somelabel[subkey1=val1]`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -772,10 +774,10 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: `"some string value" is not expected be a primitive type`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: `"some string value" is not expected be a primitive type`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -791,7 +793,7 @@ replacements:
|
||||
name: pod2
|
||||
fieldrefs:
|
||||
- spec[subkey1=val1]`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -811,12 +813,12 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: "map[string]interface {}{\"containers\":[]interface " +
|
||||
"{}{map[string]interface {}{\"image\":\"busybox\", \"name\":\"myapp-container\"}}} " +
|
||||
"is not expected be a primitive type",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: "map[string]interface {}{\"containers\":[]interface " +
|
||||
"{}{map[string]interface {}{\"image\":\"busybox\", \"name\":\"myapp-container\"}}} " +
|
||||
"is not expected be a primitive type",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -832,7 +834,7 @@ replacements:
|
||||
name: pod2
|
||||
fieldrefs:
|
||||
- spec.containers.10`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -852,10 +854,10 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: "array index out of bounds: index 10, length 1",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: "array index out of bounds: index 10, length 1",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -871,7 +873,7 @@ replacements:
|
||||
name: pod2
|
||||
fieldrefs:
|
||||
- spec.containers.notInteger.name`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -891,10 +893,10 @@ spec:
|
||||
containers:
|
||||
- name: myapp-container
|
||||
image: busybox`,
|
||||
expectedErr: `strconv.Atoi: parsing "notInteger": invalid syntax`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: `strconv.Atoi: parsing "notInteger": invalid syntax`,
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -909,7 +911,7 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers%TAG%`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -930,10 +932,10 @@ spec:
|
||||
containers:
|
||||
- image: nginx:TAG
|
||||
name: nginx-latest`,
|
||||
expectedErr: "pattern-based substitution can only be applied to string target fields",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: "pattern-based substitution can only be applied to string target fields",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -948,7 +950,7 @@ replacements:
|
||||
kind: Deployment
|
||||
fieldrefs:
|
||||
- spec.template.spec.containers[name=nginx-latest].image%TAG%`,
|
||||
in: `
|
||||
in: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@ -969,10 +971,10 @@ spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: nginx-latest`,
|
||||
expectedErr: "pattern 'TAG' is defined in configuration but was not found in target value nginx:latest",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
expectedErr: "pattern 'TAG' is defined in configuration but was not found in target value nginx:latest",
|
||||
},
|
||||
{
|
||||
cfg: `
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: ReplacementTransformer
|
||||
metadata:
|
||||
@ -984,11 +986,27 @@ replacements:
|
||||
objref:
|
||||
kind: KubeadmControlPlane
|
||||
fieldrefs:
|
||||
- spec.kubeadmConfigSpec.files[path=konfigadm].content%{k8s-version}%
|
||||
`,
|
||||
|
||||
in: `
|
||||
- spec.kubeadmConfigSpec.files[path=konfigadm].content%{k8s-version}%`,
|
||||
in: `
|
||||
kind: KubeadmControlPlane
|
||||
metadata:
|
||||
name: cluster-controlplane
|
||||
spec:
|
||||
infrastructureTemplate:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
|
||||
kind: Metal3MachineTemplate
|
||||
name: $(cluster-name)
|
||||
kubeadmConfigSpec:
|
||||
files:
|
||||
- content: |
|
||||
kubernetes:
|
||||
version: {k8s-version}
|
||||
container_runtime:
|
||||
type: docker
|
||||
owner: root:root
|
||||
path: konfigadm_bug_
|
||||
permissions: "0640"`,
|
||||
expectedOut: `kind: KubeadmControlPlane
|
||||
metadata:
|
||||
name: cluster-controlplane
|
||||
spec:
|
||||
@ -1007,10 +1025,10 @@ spec:
|
||||
path: konfigadm_bug_
|
||||
permissions: "0640"
|
||||
`,
|
||||
expectedErr: "unable to find map key 'path' with the value 'konfigadm' in list under 'files' key",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func TestReplacementTransformer(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
cfg := make(map[string]interface{})
|
||||
err := yaml.Unmarshal([]byte(tc.cfg), &cfg)
|
||||
@ -1028,3 +1046,46 @@ spec:
|
||||
assert.Equal(t, tc.expectedOut, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
// TODO (dukov) Remove this once we migrate to new kustomize plugin approach
|
||||
// NOTE (dukov) we need this since error format is different for new kustomize plugins
|
||||
testCases[11].expectedErr = "wrong Node Kind for labels.somelabel expected: " +
|
||||
"MappingNode was ScalarNode: value: {'some string value'}"
|
||||
testCases[12].expectedErr = "wrong Node Kind for labels.somelabel expected: " +
|
||||
"SequenceNode was ScalarNode: value: {'some string value'}"
|
||||
testCases[13].expectedErr = "wrong Node Kind for spec expected: " +
|
||||
"SequenceNode was MappingNode: value: {containers:\n- name: myapp-container\n image: busybox}"
|
||||
testCases[15].expectedErr = "wrong Node Kind for spec.containers expected: " +
|
||||
"MappingNode was SequenceNode: value: {- name: myapp-container\n image: busybox}"
|
||||
testCases[16].expectedErr = "wrong Node Kind for expected: " +
|
||||
"ScalarNode was MappingNode: value: {image: nginx:TAG\nname: nginx-latest}"
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("Test Case %d", i), func(t *testing.T) {
|
||||
cfg := make(map[string]interface{})
|
||||
err := yaml.Unmarshal([]byte(tc.cfg), &cfg)
|
||||
require.NoError(t, err)
|
||||
plugin, err := replacement.New(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
p := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(tc.in)}},
|
||||
Filters: []kio.Filter{plugin},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: buf}},
|
||||
}
|
||||
err = p.Execute()
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expectedErr, errString)
|
||||
assert.Equal(t, tc.expectedOut, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user