mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
This commit enhances the UnstructAdapter
* Added support for arbitrary data types rather than just strings * Added support for integer index-able arrays * Improve code coverage for kunstruct - kunstruct around 90% - helper at 100% * Update expansion.Expand method to preserve the original type of the variable * Ensure the int field such .spec.replicas can be used as a source in a first Deployment or as destination of a variable (in the second Deployment variable).
This commit is contained in:
committed by
Jerome Brette
parent
0dbe78149d
commit
ed03818e20
@@ -19,39 +19,66 @@ package kunstruct
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseFields(path string) ([]string, error) {
|
// A PathSection contains a list of nested fields, which may end with an
|
||||||
if !strings.Contains(path, "[") {
|
// indexable value. For instance, foo.bar resolves to a PathSection with 2
|
||||||
return strings.Split(path, "."), nil
|
// fields and no index, while foo[0].bar resolves to two path sections, the
|
||||||
|
// first containing the field foo and the index 0, and the second containing
|
||||||
|
// the field bar, with no index. The latter PathSection references the bar
|
||||||
|
// field of the first item in the foo list
|
||||||
|
type PathSection struct {
|
||||||
|
fields []string
|
||||||
|
idx *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendNonEmpty(section *PathSection, field string) {
|
||||||
|
if len(field) != 0 {
|
||||||
|
section.fields = append(section.fields, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFields(path string) ([]PathSection, error) {
|
||||||
|
section := PathSection{}
|
||||||
|
sectionset := []PathSection{}
|
||||||
|
if !strings.Contains(path, "[") {
|
||||||
|
section.fields = strings.Split(path, ".")
|
||||||
|
sectionset = append(sectionset, section)
|
||||||
|
return sectionset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var fields []string
|
|
||||||
start := 0
|
start := 0
|
||||||
insideParentheses := false
|
insideParentheses := false
|
||||||
for i := range path {
|
for i, c := range path {
|
||||||
switch path[i] {
|
switch c {
|
||||||
case '.':
|
case '.':
|
||||||
if !insideParentheses {
|
if !insideParentheses {
|
||||||
fields = append(fields, path[start:i])
|
appendNonEmpty(§ion, path[start:i])
|
||||||
start = i + 1
|
start = i + 1
|
||||||
}
|
}
|
||||||
case '[':
|
case '[':
|
||||||
if !insideParentheses {
|
if !insideParentheses {
|
||||||
if i == start {
|
appendNonEmpty(§ion, path[start:i])
|
||||||
start = i + 1
|
start = i + 1
|
||||||
} else {
|
|
||||||
fields = append(fields, path[start:i])
|
|
||||||
start = i + 1
|
|
||||||
}
|
|
||||||
insideParentheses = true
|
insideParentheses = true
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("nested parentheses are not allowed: %s", path)
|
return nil, fmt.Errorf("nested parentheses are not allowed: %s", path)
|
||||||
}
|
}
|
||||||
case ']':
|
case ']':
|
||||||
if insideParentheses {
|
if insideParentheses {
|
||||||
fields = append(fields, path[start:i])
|
// Assign this index to the current
|
||||||
|
// PathSection, save it to the set, then begin
|
||||||
|
// a new PathSection
|
||||||
|
tmpIdx, err := strconv.Atoi(path[start:i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid index %s", path)
|
||||||
|
}
|
||||||
|
section.idx = &tmpIdx
|
||||||
|
sectionset = append(sectionset, section)
|
||||||
|
section = PathSection{}
|
||||||
|
|
||||||
start = i + 1
|
start = i + 1
|
||||||
insideParentheses = false
|
insideParentheses = false
|
||||||
} else {
|
} else {
|
||||||
@@ -60,12 +87,16 @@ func parseFields(path string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start < len(path)-1 {
|
if start < len(path)-1 {
|
||||||
fields = append(fields, path[start:])
|
appendNonEmpty(§ion, path[start:])
|
||||||
|
sectionset = append(sectionset, section)
|
||||||
}
|
}
|
||||||
for i, f := range fields {
|
|
||||||
|
for _, section := range sectionset {
|
||||||
|
for i, f := range section.fields {
|
||||||
if strings.HasPrefix(f, "\"") || strings.HasPrefix(f, "'") {
|
if strings.HasPrefix(f, "\"") || strings.HasPrefix(f, "'") {
|
||||||
fields[i] = strings.Trim(f, "\"'")
|
section.fields[i] = strings.Trim(f, "\"'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fields, nil
|
}
|
||||||
|
return sectionset, nil
|
||||||
}
|
}
|
||||||
|
|||||||
170
k8sdeps/kunstruct/helper_test.go
Normal file
170
k8sdeps/kunstruct/helper_test.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kunstruct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PathSectionSlice []PathSection
|
||||||
|
|
||||||
|
func buildPath(idx *int, fields ...string) PathSectionSlice {
|
||||||
|
return PathSectionSlice{PathSection{fields: fields, idx: idx}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a PathSectionSlice) addSection(idx *int, fields ...string) PathSectionSlice {
|
||||||
|
return append(a, PathSection{fields: fields, idx: idx})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseField(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
var one *int = &i
|
||||||
|
j := 0
|
||||||
|
var zero *int = &j
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
expectedValue []PathSection
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "oneField",
|
||||||
|
pathToField: "Kind",
|
||||||
|
expectedValue: buildPath(nil, "Kind"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFields",
|
||||||
|
pathToField: "metadata.name",
|
||||||
|
expectedValue: buildPath(nil, "metadata", "name"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "threeFields",
|
||||||
|
pathToField: "spec.ports.port",
|
||||||
|
expectedValue: buildPath(nil, "spec", "ports", "port"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
pathToField: "",
|
||||||
|
expectedValue: buildPath(nil, ""),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStringIndex",
|
||||||
|
pathToField: "that[1]",
|
||||||
|
expectedValue: buildPath(one, "that"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sliceInSlice",
|
||||||
|
pathToField: "that[1][0]",
|
||||||
|
expectedValue: buildPath(one, "that").addSection(zero),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubField",
|
||||||
|
pathToField: "those[1].field2",
|
||||||
|
expectedValue: buildPath(one, "those").addSection(nil, "field2"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndex",
|
||||||
|
pathToField: "these[1].field2[0]",
|
||||||
|
expectedValue: buildPath(one, "these").addSection(zero, "field2"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1].stringsubfield",
|
||||||
|
expectedValue: buildPath(one, "complextree").addSection(one, "field2").addSection(nil, "stringsubfield"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldNoneIntIndex",
|
||||||
|
pathToField: "complextree[thisisnotanint]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "invalid index complextree[thisisnotanint]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidIndexInIndex",
|
||||||
|
pathToField: "complextree[1[0]]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "nested parentheses are not allowed: complextree[1[0]]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidClosingBrackets",
|
||||||
|
pathToField: "complextree[1]]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "invalid field path complextree[1]]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validFieldsWithQuotes",
|
||||||
|
pathToField: "'complextree'[1].field2[1].'stringsubfield'",
|
||||||
|
expectedValue: buildPath(one, "complextree").addSection(one, "field2").addSection(nil, "stringsubfield"),
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
s, err := parseFields(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedParserError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedParserError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
compareParserValues(t, test.name, test.pathToField, test.expectedValue, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unExpectedError function handles unexpected error
|
||||||
|
func unExpectedParserError(t *testing.T, name string, pathToField string, err error) {
|
||||||
|
t.Fatalf("%q; path %q - unexpected error %v", name, pathToField, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareExpectedError compares the expectedError and the actualError return by parseFields
|
||||||
|
func compareExpectedParserError(t *testing.T, name string, pathToField string, err error, errorMsg string) {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%q; path %q - should return error, but no error returned",
|
||||||
|
name, pathToField)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorMsg != err.Error() {
|
||||||
|
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
|
||||||
|
name, pathToField, errorMsg, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareValues compares the expectedValue and actualValue returned by parseFields
|
||||||
|
func compareParserValues(t *testing.T, name string, pathToField string, expectedValue PathSectionSlice, actualValue []PathSection) {
|
||||||
|
t.Helper()
|
||||||
|
if len(expectedValue) != len(actualValue) {
|
||||||
|
t.Fatalf("%q; Path: %s Got: %v Expected: %v", name, pathToField, actualValue, expectedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, expected := range expectedValue {
|
||||||
|
if !reflect.DeepEqual(expected, actualValue[idx]) {
|
||||||
|
t.Fatalf("%q; Path: %s idx: %v Fields Got: %v Expected: %v", name, pathToField, idx, actualValue[idx], expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ package kunstruct
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"sigs.k8s.io/kustomize/pkg/types"
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@@ -77,14 +78,85 @@ func (fs *UnstructAdapter) SetMap(m map[string]interface{}) {
|
|||||||
fs.Object = m
|
fs.Object = m
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFieldValue returns value at the given fieldpath.
|
func (fs *UnstructAdapter) selectSubtree(path string) (map[string]interface{}, []string, bool, error) {
|
||||||
func (fs *UnstructAdapter) GetFieldValue(path string) (string, error) {
|
sections, err := parseFields(path)
|
||||||
fields, err := parseFields(path)
|
if len(sections) == 0 || (err != nil) {
|
||||||
if err != nil {
|
return nil, nil, false, err
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content := fs.UnstructuredContent()
|
||||||
|
lastSectionIdx := len(sections)
|
||||||
|
|
||||||
|
// There are multiple sections to walk
|
||||||
|
for sectionIdx := 0; sectionIdx < lastSectionIdx; sectionIdx++ {
|
||||||
|
idx := sections[sectionIdx].idx
|
||||||
|
fields := sections[sectionIdx].fields
|
||||||
|
|
||||||
|
if idx == nil {
|
||||||
|
// This section has no index
|
||||||
|
return content, fields, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This section is terminated by an indexed field.
|
||||||
|
// Let's extract the slice first
|
||||||
|
indexedField, found, err := unstructured.NestedFieldNoCopy(content, fields...)
|
||||||
|
if !found || err != nil {
|
||||||
|
return content, fields, found, err
|
||||||
|
}
|
||||||
|
s, ok := indexedField.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return content, fields, false, fmt.Errorf("%v is of the type %T, expected []interface{}", indexedField, indexedField)
|
||||||
|
}
|
||||||
|
if *idx >= len(s) {
|
||||||
|
return content, fields, false, fmt.Errorf("index %d is out of bounds", *idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sectionIdx == lastSectionIdx-1 {
|
||||||
|
// This is the last section. Let's build a fake map
|
||||||
|
// to let the rest of the field extraction to work.
|
||||||
|
idxstring := fmt.Sprintf("[%v]", *idx)
|
||||||
|
newContent := map[string]interface{}{idxstring: s[*idx]}
|
||||||
|
newFields := []string{idxstring}
|
||||||
|
return newContent, newFields, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newContent, ok := s[*idx].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
// Only map are supported here
|
||||||
|
return content, fields, false,
|
||||||
|
fmt.Errorf("%#v is expected to be of type map[string]interface{}", s[*idx])
|
||||||
|
}
|
||||||
|
content = newContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// It seems to be an invalid path
|
||||||
|
return nil, []string{}, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFieldValue returns the value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetFieldValue(path string) (interface{}, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedFieldNoCopy(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetString(path string) (string, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return "", types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
s, found, err := unstructured.NestedString(
|
s, found, err := unstructured.NestedString(
|
||||||
fs.UnstructuredContent(), fields...)
|
content, fields...)
|
||||||
if found || err != nil {
|
if found || err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
@@ -93,14 +165,105 @@ func (fs *UnstructAdapter) GetFieldValue(path string) (string, error) {
|
|||||||
|
|
||||||
// GetStringSlice returns value at the given fieldpath.
|
// GetStringSlice returns value at the given fieldpath.
|
||||||
func (fs *UnstructAdapter) GetStringSlice(path string) ([]string, error) {
|
func (fs *UnstructAdapter) GetStringSlice(path string) ([]string, error) {
|
||||||
fields, err := parseFields(path)
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
if err != nil {
|
if !found || err != nil {
|
||||||
return []string{}, err
|
return []string{}, types.NoFieldError{Field: path}
|
||||||
}
|
}
|
||||||
|
|
||||||
s, found, err := unstructured.NestedStringSlice(
|
s, found, err := unstructured.NestedStringSlice(
|
||||||
fs.UnstructuredContent(), fields...)
|
content, fields...)
|
||||||
if found || err != nil {
|
if found || err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
return []string{}, types.NoFieldError{Field: path}
|
return []string{}, types.NoFieldError{Field: path}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBool returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetBool(path string) (bool, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return false, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedBool(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return false, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetFloat64(path string) (float64, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedFloat64(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return 0, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetInt64(path string) (int64, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return 0, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedInt64(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return 0, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSlice returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetSlice(path string) ([]interface{}, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedSlice(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringMap returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetStringMap(path string) (map[string]string, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedStringMap(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMap returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetMap(path string) (map[string]interface{}, error) {
|
||||||
|
content, fields, found, err := fs.selectSubtree(path)
|
||||||
|
if !found || err != nil {
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, found, err := unstructured.NestedMap(
|
||||||
|
content, fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return nil, types.NoFieldError{Field: path}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,12 +17,11 @@ limitations under the License.
|
|||||||
package kunstruct
|
package kunstruct
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetFieldValue(t *testing.T) {
|
var kunstructured = NewKunstructuredFactoryImpl().FromMap(map[string]interface{}{
|
||||||
factory := NewKunstructuredFactoryImpl()
|
|
||||||
kunstructured := factory.FromMap(map[string]interface{}{
|
|
||||||
"Kind": "Service",
|
"Kind": "Service",
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"labels": map[string]string{
|
"labels": map[string]string{
|
||||||
@@ -37,20 +36,120 @@ func TestGetFieldValue(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"this": map[string]interface{}{
|
"this": map[string]interface{}{
|
||||||
"is": map[string]interface{}{
|
"is": map[string]interface{}{
|
||||||
"aNumber": 1000,
|
"aNumber": int64(1000),
|
||||||
|
"aFloat": float64(1.001),
|
||||||
"aNilValue": nil,
|
"aNilValue": nil,
|
||||||
|
"aBool": true,
|
||||||
"anEmptyMap": map[string]interface{}{},
|
"anEmptyMap": map[string]interface{}{},
|
||||||
|
"anEmptySlice": []interface{}{},
|
||||||
"unrecognizable": testing.InternalExample{
|
"unrecognizable": testing.InternalExample{
|
||||||
Name: "fooBar",
|
Name: "fooBar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"that": []interface{}{
|
||||||
|
"idx0",
|
||||||
|
"idx1",
|
||||||
|
"idx2",
|
||||||
|
"idx3",
|
||||||
|
},
|
||||||
|
"those": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx0foo",
|
||||||
|
"field2": "idx0bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx1foo",
|
||||||
|
"field2": "idx1bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": "idx2foo",
|
||||||
|
"field2": "idx2bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"these": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx010", "idx011"},
|
||||||
|
"field2": []interface{}{"idx020", "idx021"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx110", "idx111"},
|
||||||
|
"field2": []interface{}{"idx120", "idx121"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{"idx210", "idx211"},
|
||||||
|
"field2": []interface{}{"idx220", "idx221"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"complextree": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1010",
|
||||||
|
"intsubfield": int64(1010),
|
||||||
|
"floatsubfield": float64(1.010),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1011",
|
||||||
|
"intsubfield": int64(1011),
|
||||||
|
"floatsubfield": float64(1.011),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"field2": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1020",
|
||||||
|
"intsubfield": int64(1020),
|
||||||
|
"floatsubfield": float64(1.020),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1021",
|
||||||
|
"intsubfield": int64(1021),
|
||||||
|
"floatsubfield": float64(1.021),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"field1": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1110",
|
||||||
|
"intsubfield": int64(1110),
|
||||||
|
"floatsubfield": float64(1.110),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1111",
|
||||||
|
"intsubfield": int64(1111),
|
||||||
|
"floatsubfield": float64(1.111),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"field2": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1120",
|
||||||
|
"intsubfield": int64(1120),
|
||||||
|
"floatsubfield": float64(1.1120),
|
||||||
|
"boolfield": true,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"stringsubfield": "idx1121",
|
||||||
|
"intsubfield": int64(1121),
|
||||||
|
"floatsubfield": float64(1.1121),
|
||||||
|
"boolfield": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func TestGetFieldValue(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
pathToField string
|
pathToField string
|
||||||
expectedValue string
|
expectedValue interface{}
|
||||||
errorExpected bool
|
errorExpected bool
|
||||||
errorMsg string
|
errorMsg string
|
||||||
}{
|
}{
|
||||||
@@ -96,17 +195,202 @@ func TestGetFieldValue(t *testing.T) {
|
|||||||
errorExpected: true,
|
errorExpected: true,
|
||||||
errorMsg: "no field named 'this.is.aDeep.field.that.does.not.exist'",
|
errorMsg: "no field named 'this.is.aDeep.field.that.does.not.exist'",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "emptyMap",
|
||||||
|
pathToField: "this.is.anEmptyMap",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptySlice",
|
||||||
|
pathToField: "this.is.anEmptySlice",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: []interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "numberAsValue",
|
||||||
|
pathToField: "this.is.aNumber",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: int64(1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "floatAsValue",
|
||||||
|
pathToField: "this.is.aFloat",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: float64(1.001),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolAsValue",
|
||||||
|
pathToField: "this.is.aBool",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nilAsValue",
|
||||||
|
pathToField: "this.is.aNilValue",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrecognizable",
|
||||||
|
pathToField: "this.is.unrecognizable.Name",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: ".this.is.unrecognizable.Name accessor error: {fooBar <nil> false} is of the type testing.InternalExample, expected map[string]interface{}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStringIndex",
|
||||||
|
pathToField: "that[2]",
|
||||||
|
expectedValue: "idx2",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "outOfBoundIndex",
|
||||||
|
pathToField: "that[99]",
|
||||||
|
errorMsg: "no field named 'that[99]'",
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknownSlice",
|
||||||
|
pathToField: "unknown[0]",
|
||||||
|
errorMsg: "no field named 'unknown[0]'",
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sliceInSlice",
|
||||||
|
pathToField: "that[2][0]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'that[2][0]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructIndex",
|
||||||
|
pathToField: "those[1]",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: map[string]interface{}{"field1": "idx1foo", "field2": "idx1bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubField",
|
||||||
|
pathToField: "those[1].field2",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx1bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndex",
|
||||||
|
pathToField: "these[1].field2[1]",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx121",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1].stringsubfield",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx1121",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexInvalidName",
|
||||||
|
pathToField: "complextree[1].field2[1].invalidsubfield",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'complextree[1].field2[1].invalidsubfield'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldNoneIntIndex",
|
||||||
|
pathToField: "complextree[thisisnotanint]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'complextree[thisisnotanint]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidNoneIntIndex",
|
||||||
|
pathToField: "complextree[thisisnotanint]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'complextree[thisisnotanint]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidIndexInIndex",
|
||||||
|
pathToField: "complextree[1[0]]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'complextree[1[0]]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidClosingBrackets",
|
||||||
|
pathToField: "complextree[1]]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'complextree[1]]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validFieldsWithQuotes",
|
||||||
|
pathToField: "'complextree'[1].field2[1].'stringsubfield'",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx1121",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
s, err := kunstructured.GetFieldValue(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
expectedValue string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "oneField",
|
||||||
|
pathToField: "Kind",
|
||||||
|
expectedValue: "Service",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFields",
|
||||||
|
pathToField: "metadata.name",
|
||||||
|
expectedValue: "service-name",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "threeFields",
|
||||||
|
pathToField: "spec.ports.port",
|
||||||
|
expectedValue: "80",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "emptyMap",
|
name: "emptyMap",
|
||||||
pathToField: "this.is.anEmptyMap",
|
pathToField: "this.is.anEmptyMap",
|
||||||
errorExpected: true,
|
errorExpected: true,
|
||||||
errorMsg: ".this.is.anEmptyMap accessor error: map[] is of the type map[string]interface {}, expected string",
|
errorMsg: ".this.is.anEmptyMap accessor error: map[] is of the type map[string]interface {}, expected string",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptySlice",
|
||||||
|
pathToField: "this.is.anEmptySlice",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: ".this.is.anEmptySlice accessor error: [] is of the type []interface {}, expected string",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "numberAsValue",
|
name: "numberAsValue",
|
||||||
pathToField: "this.is.aNumber",
|
pathToField: "this.is.aNumber",
|
||||||
errorExpected: true,
|
errorExpected: true,
|
||||||
errorMsg: ".this.is.aNumber accessor error: 1000 is of the type int, expected string",
|
errorMsg: ".this.is.aNumber accessor error: 1000 is of the type int64, expected string",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nilAsValue",
|
name: "nilAsValue",
|
||||||
@@ -115,34 +399,410 @@ func TestGetFieldValue(t *testing.T) {
|
|||||||
errorMsg: ".this.is.aNilValue accessor error: <nil> is of the type <nil>, expected string",
|
errorMsg: ".this.is.aNilValue accessor error: <nil> is of the type <nil>, expected string",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unrecognizable",
|
name: "validStringIndex",
|
||||||
pathToField: "this.is.unrecognizable.Name",
|
pathToField: "that[2]",
|
||||||
|
expectedValue: "idx2",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructIndex",
|
||||||
|
pathToField: "those[1]",
|
||||||
errorExpected: true,
|
errorExpected: true,
|
||||||
errorMsg: ".this.is.unrecognizable.Name accessor error: {fooBar <nil> false} is of the type testing.InternalExample, expected map[string]interface{}",
|
errorMsg: ".[1] accessor error: map[field1:idx1foo field2:idx1bar] is of the type map[string]interface {}, expected string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubField",
|
||||||
|
pathToField: "those[1].field2",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx1bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndex",
|
||||||
|
pathToField: "these[1].field2[1]",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx121",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1].stringsubfield",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: "idx1121",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalidIndexInMap",
|
||||||
|
pathToField: "this.is[1]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'this.is[1]'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anotherInvalidIndexInMap",
|
||||||
|
pathToField: "this.is[1].aString",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'this.is[1].aString'",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
s, err := kunstructured.GetFieldValue(test.pathToField)
|
s, err := kunstructured.GetString(test.pathToField)
|
||||||
if test.errorExpected {
|
if test.errorExpected {
|
||||||
if err == nil {
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
t.Fatalf("%q; path %q - should return error, but no error returned",
|
|
||||||
test.name, test.pathToField)
|
|
||||||
}
|
|
||||||
if test.errorMsg != err.Error() {
|
|
||||||
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
|
|
||||||
test.name, test.pathToField, test.errorMsg, err.Error())
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%q; path %q - unexpected error %v",
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
test.name, test.pathToField, err)
|
}
|
||||||
|
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
||||||
}
|
}
|
||||||
if test.expectedValue != s {
|
|
||||||
t.Fatalf("%q; Got: %s expected: %s",
|
|
||||||
test.name, s, test.expectedValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetInt64(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
expectedValue int64
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "numberAsValue",
|
||||||
|
pathToField: "this.is.aNumber",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: int64(1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1].intsubfield",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: int64(1121),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
s, err := kunstructured.GetInt64(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFloat64(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
expectedValue float64
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "floatAsValue",
|
||||||
|
pathToField: "this.is.aFloat",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: float64(1.001),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1].floatsubfield",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: float64(1.1121),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "index 99 is out of bounds",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
s, err := kunstructured.GetFloat64(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBool(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
expectedValue bool
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "boolAsValue",
|
||||||
|
pathToField: "this.is.aBool",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1].boolfield",
|
||||||
|
errorExpected: false,
|
||||||
|
expectedValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
s, err := kunstructured.GetBool(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
compareValues(t, test.name, test.pathToField, test.expectedValue, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStringMap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "validStringMap",
|
||||||
|
pathToField: "those[2]",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
_, err := kunstructured.GetStringMap(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "validMap",
|
||||||
|
pathToField: "those[2]",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldIndexSubfield",
|
||||||
|
pathToField: "complextree[1].field2[1]",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
_, err := kunstructured.GetMap(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStringSlice(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "validStringSlice",
|
||||||
|
pathToField: "that",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
_, err := kunstructured.GetStringSlice(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSlice(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "validSlice1",
|
||||||
|
pathToField: "that",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validSlice2",
|
||||||
|
pathToField: "those",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validSlice3",
|
||||||
|
pathToField: "these",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validSlice4",
|
||||||
|
pathToField: "complextree",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validStructSubFieldOutOfBoundIndex",
|
||||||
|
pathToField: "these[1].field2[99]",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'these[1].field2[99]'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
_, err := kunstructured.GetSlice(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
compareExpectedError(t, test.name, test.pathToField, err, test.errorMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unExpectedError(t, test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unExpectedError function handles unexpected error
|
||||||
|
func unExpectedError(t *testing.T, name string, pathToField string, err error) {
|
||||||
|
t.Fatalf("%q; path %q - unexpected error %v", name, pathToField, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareExpectedError compares the expectedError and the actualError return by GetFieldValue
|
||||||
|
func compareExpectedError(t *testing.T, name string, pathToField string, err error, errorMsg string) {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%q; path %q - should return error, but no error returned",
|
||||||
|
name, pathToField)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorMsg != err.Error() {
|
||||||
|
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
|
||||||
|
name, pathToField, errorMsg, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareValues compares the expectedValue and actualValue returned by GetFieldValue
|
||||||
|
func compareValues(t *testing.T, name string, pathToField string, expectedValue interface{}, actualValue interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
switch typedV := expectedValue.(type) {
|
||||||
|
case nil, string, bool, float64, int, int64:
|
||||||
|
if expectedValue != actualValue {
|
||||||
|
t.Fatalf("%q; Got: %v Expected: %v", name, actualValue, expectedValue)
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
if !reflect.DeepEqual(expectedValue, actualValue) {
|
||||||
|
t.Fatalf("%q; Got: %v Expected: %v", name, actualValue, expectedValue)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if !reflect.DeepEqual(expectedValue, actualValue) {
|
||||||
|
t.Fatalf("%q; Got: %v Expected: %v", name, actualValue, expectedValue)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Logf("%T value at `%s`", typedV, pathToField)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func (ra *ResAccumulator) MergeAccumulator(other *ResAccumulator) (err error) {
|
|||||||
return ra.varSet.MergeSet(other.varSet)
|
return ra.varSet.MergeSet(other.varSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *ResAccumulator) findValueFromResources(v types.Var) (string, error) {
|
func (ra *ResAccumulator) findVarValueFromResources(v types.Var) (interface{}, error) {
|
||||||
for _, res := range ra.resMap.Resources() {
|
for _, res := range ra.resMap.Resources() {
|
||||||
for _, varName := range res.GetRefVarNames() {
|
for _, varName := range res.GetRefVarNames() {
|
||||||
if varName == v.Name {
|
if varName == v.Name {
|
||||||
@@ -115,10 +115,10 @@ func (ra *ResAccumulator) findValueFromResources(v types.Var) (string, error) {
|
|||||||
// makeVarReplacementMap returns a map of Var names to
|
// makeVarReplacementMap returns a map of Var names to
|
||||||
// their final values. The values are strings intended
|
// their final values. The values are strings intended
|
||||||
// for substitution wherever the $(var.Name) occurs.
|
// for substitution wherever the $(var.Name) occurs.
|
||||||
func (ra *ResAccumulator) makeVarReplacementMap() (map[string]string, error) {
|
func (ra *ResAccumulator) makeVarReplacementMap() (map[string]interface{}, error) {
|
||||||
result := map[string]string{}
|
result := map[string]interface{}{}
|
||||||
for _, v := range ra.Vars() {
|
for _, v := range ra.Vars() {
|
||||||
s, err := ra.findValueFromResources(v)
|
s, err := ra.findVarValueFromResources(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,5 +326,11 @@ func getCommand(r *resource.Resource) string {
|
|||||||
m, _ = m["spec"].(map[string]interface{})
|
m, _ = m["spec"].(map[string]interface{})
|
||||||
c, _ = m["containers"].([]interface{})
|
c, _ = m["containers"].([]interface{})
|
||||||
m, _ = c[0].(map[string]interface{})
|
m, _ = c[0].(map[string]interface{})
|
||||||
return strings.Join(m["command"].([]string), " ")
|
|
||||||
|
cmd, _ := m["command"].([]interface{})
|
||||||
|
n := make([]string, len(cmd))
|
||||||
|
for i, v := range cmd {
|
||||||
|
n[i] = v.(string)
|
||||||
|
}
|
||||||
|
return strings.Join(n, " ")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package expansion
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,13 +39,18 @@ func syntaxWrap(input string) string {
|
|||||||
// for the input is found.
|
// for the input is found.
|
||||||
func MappingFuncFor(
|
func MappingFuncFor(
|
||||||
counts map[string]int,
|
counts map[string]int,
|
||||||
context ...map[string]string) func(string) string {
|
context ...map[string]interface{}) func(string) interface{} {
|
||||||
return func(input string) string {
|
return func(input string) interface{} {
|
||||||
for _, vars := range context {
|
for _, vars := range context {
|
||||||
val, ok := vars[input]
|
val, ok := vars[input]
|
||||||
if ok {
|
if ok {
|
||||||
counts[input]++
|
counts[input]++
|
||||||
return val
|
switch typedV := val.(type) {
|
||||||
|
case string, int64, float64, bool:
|
||||||
|
return typedV
|
||||||
|
default:
|
||||||
|
return syntaxWrap(input)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return syntaxWrap(input)
|
return syntaxWrap(input)
|
||||||
@@ -54,7 +60,7 @@ func MappingFuncFor(
|
|||||||
// Expand replaces variable references in the input string according to
|
// Expand replaces variable references in the input string according to
|
||||||
// the expansion spec using the given mapping function to resolve the
|
// the expansion spec using the given mapping function to resolve the
|
||||||
// values of variables.
|
// values of variables.
|
||||||
func Expand(input string, mapping func(string) string) string {
|
func Expand(input string, mapping func(string) interface{}) interface{} {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
checkpoint := 0
|
checkpoint := 0
|
||||||
for cursor := 0; cursor < len(input); cursor++ {
|
for cursor := 0; cursor < len(input); cursor++ {
|
||||||
@@ -71,7 +77,14 @@ func Expand(input string, mapping func(string) string) string {
|
|||||||
// We were able to read a variable name correctly;
|
// We were able to read a variable name correctly;
|
||||||
// apply the mapping to the variable name and copy the
|
// apply the mapping to the variable name and copy the
|
||||||
// bytes into the buffer
|
// bytes into the buffer
|
||||||
buf.WriteString(mapping(read))
|
mapped := mapping(read)
|
||||||
|
if input == syntaxWrap(read) {
|
||||||
|
// Preserve the type of variable
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable is used in a middle of a string
|
||||||
|
buf.WriteString(fmt.Sprintf("%v", mapped))
|
||||||
} else {
|
} else {
|
||||||
// Not a variable name; copy the read bytes into the buffer
|
// Not a variable name; copy the read bytes into the buffer
|
||||||
buf.WriteString(read)
|
buf.WriteString(read)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package expansion_test
|
package expansion_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/pkg/expansion"
|
. "sigs.k8s.io/kustomize/pkg/expansion"
|
||||||
@@ -30,7 +31,7 @@ type expected struct {
|
|||||||
func TestMapReference(t *testing.T) {
|
func TestMapReference(t *testing.T) {
|
||||||
type env struct {
|
type env struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value interface{}
|
||||||
}
|
}
|
||||||
envs := []env{
|
envs := []env{
|
||||||
{
|
{
|
||||||
@@ -45,25 +46,49 @@ func TestMapReference(t *testing.T) {
|
|||||||
Name: "BLU",
|
Name: "BLU",
|
||||||
Value: "$(ZOO)-2",
|
Value: "$(ZOO)-2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "INT",
|
||||||
|
Value: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ZINT",
|
||||||
|
Value: "$(INT)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "BOOL",
|
||||||
|
Value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ZBOOL",
|
||||||
|
Value: "$(BOOL)",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
declaredEnv := map[string]string{
|
declaredEnv := map[string]interface{}{
|
||||||
"FOO": "bar",
|
"FOO": "bar",
|
||||||
"ZOO": "$(FOO)-1",
|
"ZOO": "$(FOO)-1",
|
||||||
"BLU": "$(ZOO)-2",
|
"BLU": "$(ZOO)-2",
|
||||||
|
"INT": "2",
|
||||||
|
"ZINT": "$(INT)",
|
||||||
|
"BOOL": "true",
|
||||||
|
"ZBOOL": "$(BOOL)",
|
||||||
}
|
}
|
||||||
|
|
||||||
counts := make(map[string]int)
|
counts := make(map[string]int)
|
||||||
mapping := MappingFuncFor(counts, declaredEnv)
|
mapping := MappingFuncFor(counts, declaredEnv)
|
||||||
|
|
||||||
for _, env := range envs {
|
for _, env := range envs {
|
||||||
declaredEnv[env.Name] = Expand(env.Value, mapping)
|
declaredEnv[env.Name] = Expand(fmt.Sprintf("%v", env.Value), mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedEnv := map[string]expected{
|
expectedEnv := map[string]expected{
|
||||||
"FOO": {count: 1, edited: "bar"},
|
"FOO": {count: 1, edited: "bar"},
|
||||||
"ZOO": {count: 1, edited: "bar-1"},
|
"ZOO": {count: 1, edited: "bar-1"},
|
||||||
"BLU": {count: 0, edited: "bar-1-2"},
|
"BLU": {count: 0, edited: "bar-1-2"},
|
||||||
|
"INT": {count: 1, edited: "2"},
|
||||||
|
"ZINT": {count: 0, edited: "2"},
|
||||||
|
"BOOL": {count: 1, edited: "true"},
|
||||||
|
"ZBOOL": {count: 0, edited: "true"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range expectedEnv {
|
for k, v := range expectedEnv {
|
||||||
@@ -81,7 +106,7 @@ func TestMapReference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMapping(t *testing.T) {
|
func TestMapping(t *testing.T) {
|
||||||
context := map[string]string{
|
context := map[string]interface{}{
|
||||||
"VAR_A": "A",
|
"VAR_A": "A",
|
||||||
"VAR_B": "B",
|
"VAR_B": "B",
|
||||||
"VAR_C": "C",
|
"VAR_C": "C",
|
||||||
@@ -92,11 +117,11 @@ func TestMapping(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingDual(t *testing.T) {
|
func TestMappingDual(t *testing.T) {
|
||||||
context := map[string]string{
|
context := map[string]interface{}{
|
||||||
"VAR_A": "A",
|
"VAR_A": "A",
|
||||||
"VAR_EMPTY": "",
|
"VAR_EMPTY": "",
|
||||||
}
|
}
|
||||||
context2 := map[string]string{
|
context2 := map[string]interface{}{
|
||||||
"VAR_B": "B",
|
"VAR_B": "B",
|
||||||
"VAR_C": "C",
|
"VAR_C": "C",
|
||||||
"VAR_REF": "$(VAR_A)",
|
"VAR_REF": "$(VAR_A)",
|
||||||
@@ -105,7 +130,7 @@ func TestMappingDual(t *testing.T) {
|
|||||||
doExpansionTest(t, context, context2)
|
doExpansionTest(t, context, context2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doExpansionTest(t *testing.T, context ...map[string]string) {
|
func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
@@ -325,7 +350,7 @@ func doExpansionTest(t *testing.T, context ...map[string]string) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
counts := make(map[string]int)
|
counts := make(map[string]int)
|
||||||
mapping := MappingFuncFor(counts, context...)
|
mapping := MappingFuncFor(counts, context...)
|
||||||
expanded := Expand(tc.input, mapping)
|
expanded := Expand(fmt.Sprintf("%v", tc.input), mapping)
|
||||||
if e, a := tc.expected, expanded; e != a {
|
if e, a := tc.expected, expanded; e != a {
|
||||||
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,15 @@ type Kunstructured interface {
|
|||||||
Map() map[string]interface{}
|
Map() map[string]interface{}
|
||||||
SetMap(map[string]interface{})
|
SetMap(map[string]interface{})
|
||||||
Copy() Kunstructured
|
Copy() Kunstructured
|
||||||
GetFieldValue(string) (string, error)
|
GetFieldValue(string) (interface{}, error)
|
||||||
|
GetString(string) (string, error)
|
||||||
GetStringSlice(string) ([]string, error)
|
GetStringSlice(string) ([]string, error)
|
||||||
|
GetBool(path string) (bool, error)
|
||||||
|
GetFloat64(path string) (float64, error)
|
||||||
|
GetInt64(path string) (int64, error)
|
||||||
|
GetSlice(path string) ([]interface{}, error)
|
||||||
|
GetStringMap(path string) (map[string]string, error)
|
||||||
|
GetMap(path string) (map[string]interface{}, error)
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
UnmarshalJSON([]byte) error
|
UnmarshalJSON([]byte) error
|
||||||
GetGvk() gvk.Gvk
|
GetGvk() gvk.Gvk
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func (r *Resource) NeedHashSuffix() bool {
|
|||||||
|
|
||||||
// GetNamespace returns the namespace the resource thinks it's in.
|
// GetNamespace returns the namespace the resource thinks it's in.
|
||||||
func (r *Resource) GetNamespace() string {
|
func (r *Resource) GetNamespace() string {
|
||||||
namespace, _ := r.GetFieldValue("metadata.namespace")
|
namespace, _ := r.GetString("metadata.namespace")
|
||||||
// if err, namespace is empty, so no need to check.
|
// if err, namespace is empty, so no need to check.
|
||||||
return namespace
|
return namespace
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func makeTestDeployment() ifc.Kunstructured {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getFieldValue(t *testing.T, obj ifc.Kunstructured, fieldName string) string {
|
func getFieldValue(t *testing.T, obj ifc.Kunstructured, fieldName string) string {
|
||||||
v, err := obj.GetFieldValue(fieldName)
|
v, err := obj.GetString(fieldName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected field error: %v", err)
|
t.Fatalf("unexpected field error: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RefVarTransformer struct {
|
type RefVarTransformer struct {
|
||||||
varMap map[string]string
|
varMap map[string]interface{}
|
||||||
replacementCounts map[string]int
|
replacementCounts map[string]int
|
||||||
fieldSpecs []config.FieldSpec
|
fieldSpecs []config.FieldSpec
|
||||||
mappingFunc func(string) string
|
mappingFunc func(string) interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRefVarTransformer returns a new RefVarTransformer
|
// NewRefVarTransformer returns a new RefVarTransformer
|
||||||
// that replaces $(VAR) style variables with values.
|
// that replaces $(VAR) style variables with values.
|
||||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||||
func NewRefVarTransformer(
|
func NewRefVarTransformer(
|
||||||
varMap map[string]string, fs []config.FieldSpec) *RefVarTransformer {
|
varMap map[string]interface{}, fs []config.FieldSpec) *RefVarTransformer {
|
||||||
return &RefVarTransformer{
|
return &RefVarTransformer{
|
||||||
varMap: varMap,
|
varMap: varMap,
|
||||||
fieldSpecs: fs,
|
fieldSpecs: fs,
|
||||||
@@ -48,7 +48,7 @@ func NewRefVarTransformer(
|
|||||||
func (rv *RefVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
func (rv *RefVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
||||||
switch vt := in.(type) {
|
switch vt := in.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
var xs []string
|
var xs []interface{}
|
||||||
for _, a := range in.([]interface{}) {
|
for _, a := range in.([]interface{}) {
|
||||||
xs = append(xs, expansion.Expand(a.(string), rv.mappingFunc))
|
xs = append(xs, expansion.Expand(a.(string), rv.mappingFunc))
|
||||||
}
|
}
|
||||||
@@ -59,16 +59,26 @@ func (rv *RefVarTransformer) replaceVars(in interface{}) (interface{}, error) {
|
|||||||
for k, v := range inMap {
|
for k, v := range inMap {
|
||||||
s, ok := v.(string)
|
s, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%#v is expected to be %T", v, s)
|
// This field not contain a $(VAR) since it is not
|
||||||
}
|
// of string type. For instance .spec.replicas: 3 in
|
||||||
|
// a Deployment object
|
||||||
|
xs[k] = v
|
||||||
|
} else {
|
||||||
|
// This field can potentially contains a $(VAR) since it is
|
||||||
|
// of string type. For instance .spec.replicas: $(REPLICAS)
|
||||||
|
// in a Deployment object
|
||||||
xs[k] = expansion.Expand(s, rv.mappingFunc)
|
xs[k] = expansion.Expand(s, rv.mappingFunc)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return xs, nil
|
return xs, nil
|
||||||
case interface{}:
|
case interface{}:
|
||||||
s, ok := in.(string)
|
s, ok := in.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
// This field not contain a $(VAR) since it is not of string type.
|
||||||
|
return in, nil
|
||||||
}
|
}
|
||||||
|
// This field can potentially contain a $(VAR) since it is
|
||||||
|
// of string type.
|
||||||
return expansion.Expand(s, rv.mappingFunc), nil
|
return expansion.Expand(s, rv.mappingFunc), nil
|
||||||
case nil:
|
case nil:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func TestVarRef(t *testing.T) {
|
func TestVarRef(t *testing.T) {
|
||||||
type given struct {
|
type given struct {
|
||||||
varMap map[string]string
|
varMap map[string]interface{}
|
||||||
fs []config.FieldSpec
|
fs []config.FieldSpec
|
||||||
res resmap.ResMap
|
res resmap.ResMap
|
||||||
}
|
}
|
||||||
@@ -31,9 +31,11 @@ func TestVarRef(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "var replacement in map[string]",
|
description: "var replacement in map[string]",
|
||||||
given: given{
|
given: given{
|
||||||
varMap: map[string]string{
|
varMap: map[string]interface{}{
|
||||||
"FOO": "replacementForFoo",
|
"FOO": "replacementForFoo",
|
||||||
"BAR": "replacementForBar",
|
"BAR": "replacementForBar",
|
||||||
|
"BAZ": int64(5),
|
||||||
|
"BOO": true,
|
||||||
},
|
},
|
||||||
fs: []config.FieldSpec{
|
fs: []config.FieldSpec{
|
||||||
{Gvk: gvk.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data"},
|
{Gvk: gvk.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data"},
|
||||||
@@ -48,6 +50,10 @@ func TestVarRef(t *testing.T) {
|
|||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"item1": "$(FOO)",
|
"item1": "$(FOO)",
|
||||||
"item2": "bla",
|
"item2": "bla",
|
||||||
|
"item3": "$(BAZ)",
|
||||||
|
"item4": "$(BAZ)+$(BAZ)",
|
||||||
|
"item5": "$(BOO)",
|
||||||
|
"item6": "if $(BOO)",
|
||||||
},
|
},
|
||||||
}).ResMap(),
|
}).ResMap(),
|
||||||
},
|
},
|
||||||
@@ -62,6 +68,10 @@ func TestVarRef(t *testing.T) {
|
|||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"item1": "replacementForFoo",
|
"item1": "replacementForFoo",
|
||||||
"item2": "bla",
|
"item2": "bla",
|
||||||
|
"item3": int64(5),
|
||||||
|
"item4": "5+5",
|
||||||
|
"item5": true,
|
||||||
|
"item6": "if true",
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
unused: []string{"BAR"},
|
unused: []string{"BAR"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -84,10 +84,10 @@ type Kustomization struct {
|
|||||||
Replicas []Replica `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
Replicas []Replica `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||||
|
|
||||||
// Vars allow things modified by kustomize to be injected into a
|
// Vars allow things modified by kustomize to be injected into a
|
||||||
// container specification. A var is a name (e.g. FOO) associated
|
// kubernetes object specification. A var is a name (e.g. FOO) associated
|
||||||
// with a field in a specific resource instance. The field must
|
// with a field in a specific resource instance. The field must
|
||||||
// contain a value of type string, and defaults to the name field
|
// contain a value of type string/bool/int/float, and defaults to the name field
|
||||||
// of the instance. Any appearance of "$(FOO)" in the container
|
// of the instance. Any appearance of "$(FOO)" in the object
|
||||||
// spec will be replaced at kustomize build time, after the final
|
// spec will be replaced at kustomize build time, after the final
|
||||||
// value of the specified field has been determined.
|
// value of the specified field has been determined.
|
||||||
Vars []Var `json:"vars,omitempty" yaml:"vars,omitempty"`
|
Vars []Var `json:"vars,omitempty" yaml:"vars,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user