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
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,11 +50,11 @@ func (e ErrBadConfiguration) Error() string {
|
|||||||
|
|
||||||
// ErrMultipleResources returned if multiple resources were found
|
// ErrMultipleResources returned if multiple resources were found
|
||||||
type ErrMultipleResources struct {
|
type ErrMultipleResources struct {
|
||||||
ResList []*resource.Resource
|
ObjRef *types.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMultipleResources) Error() string {
|
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
|
// 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 {
|
func (e ErrSourceNotFound) Error() string {
|
||||||
keys := [5]string{"Group:", "Version:", "Kind:", "Name:", "Namespace:"}
|
return fmt.Sprintf("failed to find any source resources identified by %s", printFields(e.ObjRef))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrTargetNotFound returned if a replacement target resource does not exist in the resource map
|
// 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 {
|
func (e ErrTargetNotFound) Error() string {
|
||||||
keys := [7]string{"Group:", "Version:", "Kind:", "Name:", "Namespace:",
|
return fmt.Sprintf("failed to find any target resources identified by %s", printFields(e.ObjRef))
|
||||||
"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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrPatternSubstring returned in case of issues with sub-string pattern substitution
|
// 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)
|
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
|
// ErrValueNotFound returned if value specified in fieldRef option was not found
|
||||||
type ErrMapNotFound struct {
|
type ErrValueNotFound struct {
|
||||||
Key, Value, ListKey string
|
ID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMapNotFound) Error() string {
|
func (e ErrValueNotFound) Error() string {
|
||||||
return fmt.Sprintf("unable to find map key '%s' with the value '%s' in list under '%s' key",
|
return fmt.Sprintf("unable to find value identified by %s", e.ID)
|
||||||
e.Key, e.Value, e.ListKey)
|
}
|
||||||
|
|
||||||
|
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"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
|
||||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
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"
|
plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types"
|
||||||
"opendev.org/airship/airshipctl/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -115,7 +115,130 @@ func (p *plugin) Transform(m resmap.ResMap) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *plugin) Filter(items []*yaml.RNode) ([]*yaml.RNode, 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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(resources) > 1 {
|
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 {
|
if len(resources) == 0 {
|
||||||
return nil, ErrSourceNotFound{ObjRef: objRef}
|
return nil, ErrSourceNotFound{ObjRef: objRef}
|
||||||
@ -305,7 +432,7 @@ func updateSliceField(m []interface{}, pathToField []string, replacement interfa
|
|||||||
if len(pathToField) == 0 {
|
if len(pathToField) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
path, key, value, isArray := getFirstPathSegment(pathToField[0])
|
_, key, value, isArray := getFirstPathSegment(pathToField[0])
|
||||||
|
|
||||||
if isArray {
|
if isArray {
|
||||||
for _, item := range m {
|
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])
|
index, err := strconv.Atoi(pathToField[0])
|
||||||
|
@ -5,12 +5,14 @@ package replacement_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/document/plugin/replacement"
|
"opendev.org/airship/airshipctl/pkg/document/plugin/replacement"
|
||||||
@ -70,15 +72,14 @@ spec:
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacementTransformer(t *testing.T) {
|
var testCases = []struct {
|
||||||
testCases := []struct {
|
cfg string
|
||||||
cfg string
|
in string
|
||||||
in string
|
expectedOut string
|
||||||
expectedOut string
|
expectedErr string
|
||||||
expectedErr string
|
}{
|
||||||
}{
|
{
|
||||||
{
|
cfg: `
|
||||||
cfg: `
|
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -100,9 +101,9 @@ replacements:
|
|||||||
- spec.template.spec.containers.3.image
|
- spec.template.spec.containers.3.image
|
||||||
`,
|
`,
|
||||||
|
|
||||||
in: `
|
in: `
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
group: apps
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: deploy1
|
name: deploy1
|
||||||
@ -126,7 +127,7 @@ spec:
|
|||||||
- image: alpine:1.8.0
|
- image: alpine:1.8.0
|
||||||
name: init-alpine
|
name: init-alpine
|
||||||
`,
|
`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
group: apps
|
group: apps
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@ -151,9 +152,9 @@ spec:
|
|||||||
- image: alpine:1.8.0
|
- image: alpine:1.8.0
|
||||||
name: init-alpine
|
name: init-alpine
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -168,9 +169,9 @@ replacements:
|
|||||||
- spec.template.spec.containers[name=nginx-tagged].image%1.7.9%
|
- spec.template.spec.containers[name=nginx-tagged].image%1.7.9%
|
||||||
`,
|
`,
|
||||||
|
|
||||||
in: `
|
in: `
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
group: apps
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: deploy1
|
name: deploy1
|
||||||
@ -181,7 +182,7 @@ spec:
|
|||||||
- image: nginx:1.7.9
|
- image: nginx:1.7.9
|
||||||
name: nginx-tagged
|
name: nginx-tagged
|
||||||
`,
|
`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
group: apps
|
group: apps
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@ -193,10 +194,10 @@ spec:
|
|||||||
- image: nginx:1.17.0
|
- image: nginx:1.17.0
|
||||||
name: nginx-tagged
|
name: nginx-tagged
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -212,15 +213,15 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers`,
|
- spec.template.spec.containers`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod
|
name: pod
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- image: busybox
|
||||||
image: busybox
|
name: myapp-container
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@ -232,7 +233,7 @@ kind: Deployment
|
|||||||
metadata:
|
metadata:
|
||||||
name: deploy3
|
name: deploy3
|
||||||
`,
|
`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod
|
name: pod
|
||||||
@ -263,9 +264,9 @@ spec:
|
|||||||
- image: busybox
|
- image: busybox
|
||||||
name: myapp-container
|
name: myapp-container
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -293,13 +294,13 @@ replacements:
|
|||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[image=debian].args.1
|
- spec.template.spec.containers[image=debian].args.1
|
||||||
- spec.template.spec.containers[name=busybox].args.2`,
|
- spec.template.spec.containers[name=busybox].args.2`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: deploy
|
|
||||||
labels:
|
labels:
|
||||||
foo: bar
|
foo: bar
|
||||||
|
name: deploy
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
@ -307,27 +308,28 @@ spec:
|
|||||||
foo: bar
|
foo: bar
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: command-demo-container
|
- args:
|
||||||
image: debian
|
- HOSTNAME
|
||||||
command: ["printenv"]
|
- PORT
|
||||||
args:
|
command:
|
||||||
- HOSTNAME
|
- printenv
|
||||||
- PORT
|
image: debian
|
||||||
- name: busybox
|
name: command-demo-container
|
||||||
image: busybox:latest
|
- args:
|
||||||
args:
|
- echo
|
||||||
- echo
|
- HOSTNAME
|
||||||
- HOSTNAME
|
- PORT
|
||||||
- PORT
|
image: busybox:latest
|
||||||
|
name: busybox
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: cm
|
|
||||||
data:
|
data:
|
||||||
HOSTNAME: example.com
|
HOSTNAME: example.com
|
||||||
PORT: 8080`,
|
PORT: 8080
|
||||||
expectedOut: `apiVersion: apps/v1
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm`,
|
||||||
|
expectedOut: `apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
@ -362,9 +364,9 @@ kind: ConfigMap
|
|||||||
metadata:
|
metadata:
|
||||||
name: cm
|
name: cm
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -384,9 +386,9 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers.3.image`,
|
- spec.template.spec.containers.3.image`,
|
||||||
in: `
|
in: `
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
group: apps
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: deploy1
|
name: deploy1
|
||||||
@ -409,7 +411,7 @@ spec:
|
|||||||
name: nginx-sha256
|
name: nginx-sha256
|
||||||
- image: alpine:1.8.0
|
- image: alpine:1.8.0
|
||||||
name: init-alpine`,
|
name: init-alpine`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
group: apps
|
group: apps
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@ -434,9 +436,9 @@ spec:
|
|||||||
- image: alpine:1.8.0
|
- image: alpine:1.8.0
|
||||||
name: init-alpine
|
name: init-alpine
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -452,15 +454,15 @@ replacements:
|
|||||||
name: pod2
|
name: pod2
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.non.existent.field`,
|
- spec.non.existent.field`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod1
|
name: pod1
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- image: busybox
|
||||||
image: busybox
|
name: myapp-container
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
@ -468,9 +470,9 @@ metadata:
|
|||||||
name: pod2
|
name: pod2
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- image: busybox
|
||||||
image: busybox`,
|
name: myapp-container`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod1
|
name: pod1
|
||||||
@ -491,9 +493,9 @@ spec:
|
|||||||
existent:
|
existent:
|
||||||
field: pod1
|
field: pod1
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -509,15 +511,15 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[name=myapp-container]`,
|
- spec.template.spec.containers[name=myapp-container]`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod
|
name: pod
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: repl
|
- image: repl
|
||||||
image: repl
|
name: repl
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@ -530,7 +532,7 @@ spec:
|
|||||||
- image: busybox
|
- image: busybox
|
||||||
name: myapp-container
|
name: myapp-container
|
||||||
`,
|
`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod
|
name: pod
|
||||||
@ -550,9 +552,9 @@ spec:
|
|||||||
- image: repl
|
- image: repl
|
||||||
name: repl
|
name: repl
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -568,15 +570,15 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[name=myapp-container].image%TAG%`,
|
- spec.template.spec.containers[name=myapp-container].image%TAG%`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod
|
name: pod
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: repl
|
- image: 12345
|
||||||
image: 12345
|
name: repl
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@ -589,7 +591,7 @@ spec:
|
|||||||
- image: busybox:TAG
|
- image: busybox:TAG
|
||||||
name: myapp-container
|
name: myapp-container
|
||||||
`,
|
`,
|
||||||
expectedOut: `apiVersion: v1
|
expectedOut: `apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: pod
|
name: pod
|
||||||
@ -609,9 +611,9 @@ spec:
|
|||||||
- image: busybox:12345
|
- image: busybox:12345
|
||||||
name: myapp-container
|
name: myapp-container
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -625,7 +627,7 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[name=nginx-latest].image`,
|
- spec.template.spec.containers[name=nginx-latest].image`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -643,15 +645,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: "found more than one resources matching from " +
|
expectedErr: "found more than one resources matching identified by Gvk: ~G_~V_Pod",
|
||||||
"[{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"name\":\"pod1\"}," +
|
},
|
||||||
"\"spec\":{\"containers\":[{\"image\":\"busybox\",\"name\":\"myapp-container\"" +
|
{
|
||||||
"}]}}{nsfx:false,beh:unspecified} {\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":" +
|
cfg: `
|
||||||
"{\"name\":\"pod2\"},\"spec\":{\"containers\":[{\"image\":\"busybox\",\"name\":\"myapp-container\"}]}}" +
|
|
||||||
"{nsfx:false,beh:unspecified}]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cfg: `
|
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -660,17 +657,22 @@ replacements:
|
|||||||
- source:
|
- source:
|
||||||
objref:
|
objref:
|
||||||
kind: Pod
|
kind: Pod
|
||||||
name: pod1
|
name: doesNotExists
|
||||||
namespace: default
|
namespace: default
|
||||||
target:
|
target:
|
||||||
objref:
|
objref:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[name=nginx-latest].image`,
|
- spec.template.spec.containers[name=nginx-latest].image`,
|
||||||
expectedErr: "failed to find any source resources identified by Kind:Pod Name:pod1 Namespace:default",
|
in: `apiVersion: v1
|
||||||
},
|
kind: Pod
|
||||||
{
|
metadata:
|
||||||
cfg: `
|
name: pod1`,
|
||||||
|
expectedErr: "failed to find any source resources identified by " +
|
||||||
|
"Gvk: ~G_~V_Pod Name: doesNotExists Namespace: default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -685,7 +687,7 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[name=nginx-latest].image`,
|
- spec.template.spec.containers[name=nginx-latest].image`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -694,10 +696,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: "failed to find any target resources identified by Kind:Deployment",
|
expectedErr: "failed to find any target resources identified by Gvk: ~G_~V_Deployment",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -713,7 +715,7 @@ replacements:
|
|||||||
name: pod2
|
name: pod2
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- labels.somelabel.key1.subkey1`,
|
- labels.somelabel.key1.subkey1`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -733,10 +735,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: `"some string value" is not expected be a primitive type`,
|
expectedErr: `"some string value" is not expected be a primitive type`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -752,7 +754,7 @@ replacements:
|
|||||||
name: pod2
|
name: pod2
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- labels.somelabel[subkey1=val1]`,
|
- labels.somelabel[subkey1=val1]`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -772,10 +774,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: `"some string value" is not expected be a primitive type`,
|
expectedErr: `"some string value" is not expected be a primitive type`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -791,7 +793,7 @@ replacements:
|
|||||||
name: pod2
|
name: pod2
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec[subkey1=val1]`,
|
- spec[subkey1=val1]`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -811,12 +813,12 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: "map[string]interface {}{\"containers\":[]interface " +
|
expectedErr: "map[string]interface {}{\"containers\":[]interface " +
|
||||||
"{}{map[string]interface {}{\"image\":\"busybox\", \"name\":\"myapp-container\"}}} " +
|
"{}{map[string]interface {}{\"image\":\"busybox\", \"name\":\"myapp-container\"}}} " +
|
||||||
"is not expected be a primitive type",
|
"is not expected be a primitive type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -832,7 +834,7 @@ replacements:
|
|||||||
name: pod2
|
name: pod2
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.containers.10`,
|
- spec.containers.10`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -852,10 +854,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: "array index out of bounds: index 10, length 1",
|
expectedErr: "array index out of bounds: index 10, length 1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -871,7 +873,7 @@ replacements:
|
|||||||
name: pod2
|
name: pod2
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.containers.notInteger.name`,
|
- spec.containers.notInteger.name`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -891,10 +893,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: myapp-container
|
- name: myapp-container
|
||||||
image: busybox`,
|
image: busybox`,
|
||||||
expectedErr: `strconv.Atoi: parsing "notInteger": invalid syntax`,
|
expectedErr: `strconv.Atoi: parsing "notInteger": invalid syntax`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -909,7 +911,7 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers%TAG%`,
|
- spec.template.spec.containers%TAG%`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -930,10 +932,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- image: nginx:TAG
|
- image: nginx:TAG
|
||||||
name: nginx-latest`,
|
name: nginx-latest`,
|
||||||
expectedErr: "pattern-based substitution can only be applied to string target fields",
|
expectedErr: "pattern-based substitution can only be applied to string target fields",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -948,7 +950,7 @@ replacements:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.template.spec.containers[name=nginx-latest].image%TAG%`,
|
- spec.template.spec.containers[name=nginx-latest].image%TAG%`,
|
||||||
in: `
|
in: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
@ -969,10 +971,10 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- image: nginx:latest
|
- image: nginx:latest
|
||||||
name: nginx-latest`,
|
name: nginx-latest`,
|
||||||
expectedErr: "pattern 'TAG' is defined in configuration but was not found in target value nginx:latest",
|
expectedErr: "pattern 'TAG' is defined in configuration but was not found in target value nginx:latest",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cfg: `
|
cfg: `
|
||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: ReplacementTransformer
|
kind: ReplacementTransformer
|
||||||
metadata:
|
metadata:
|
||||||
@ -984,11 +986,27 @@ replacements:
|
|||||||
objref:
|
objref:
|
||||||
kind: KubeadmControlPlane
|
kind: KubeadmControlPlane
|
||||||
fieldrefs:
|
fieldrefs:
|
||||||
- spec.kubeadmConfigSpec.files[path=konfigadm].content%{k8s-version}%
|
- spec.kubeadmConfigSpec.files[path=konfigadm].content%{k8s-version}%`,
|
||||||
`,
|
in: `
|
||||||
|
|
||||||
in: `
|
|
||||||
kind: KubeadmControlPlane
|
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:
|
metadata:
|
||||||
name: cluster-controlplane
|
name: cluster-controlplane
|
||||||
spec:
|
spec:
|
||||||
@ -1007,10 +1025,10 @@ spec:
|
|||||||
path: konfigadm_bug_
|
path: konfigadm_bug_
|
||||||
permissions: "0640"
|
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 {
|
for _, tc := range testCases {
|
||||||
cfg := make(map[string]interface{})
|
cfg := make(map[string]interface{})
|
||||||
err := yaml.Unmarshal([]byte(tc.cfg), &cfg)
|
err := yaml.Unmarshal([]byte(tc.cfg), &cfg)
|
||||||
@ -1028,3 +1046,46 @@ spec:
|
|||||||
assert.Equal(t, tc.expectedOut, buf.String())
|
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