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:
Ian Howell
2019-06-13 15:32:58 -05:00
committed by Jerome Brette
parent 0dbe78149d
commit ed03818e20
14 changed files with 1203 additions and 108 deletions

View File

@@ -19,6 +19,7 @@ package expansion
import (
"bytes"
"fmt"
)
const (
@@ -38,13 +39,18 @@ func syntaxWrap(input string) string {
// for the input is found.
func MappingFuncFor(
counts map[string]int,
context ...map[string]string) func(string) string {
return func(input string) string {
context ...map[string]interface{}) func(string) interface{} {
return func(input string) interface{} {
for _, vars := range context {
val, ok := vars[input]
if ok {
counts[input]++
return val
switch typedV := val.(type) {
case string, int64, float64, bool:
return typedV
default:
return syntaxWrap(input)
}
}
}
return syntaxWrap(input)
@@ -54,7 +60,7 @@ func MappingFuncFor(
// Expand replaces variable references in the input string according to
// the expansion spec using the given mapping function to resolve the
// 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
checkpoint := 0
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;
// apply the mapping to the variable name and copy the
// 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 {
// Not a variable name; copy the read bytes into the buffer
buf.WriteString(read)

View File

@@ -17,6 +17,7 @@ limitations under the License.
package expansion_test
import (
"fmt"
"testing"
. "sigs.k8s.io/kustomize/pkg/expansion"
@@ -30,7 +31,7 @@ type expected struct {
func TestMapReference(t *testing.T) {
type env struct {
Name string
Value string
Value interface{}
}
envs := []env{
{
@@ -45,25 +46,49 @@ func TestMapReference(t *testing.T) {
Name: "BLU",
Value: "$(ZOO)-2",
},
{
Name: "INT",
Value: 2,
},
{
Name: "ZINT",
Value: "$(INT)",
},
{
Name: "BOOL",
Value: true,
},
{
Name: "ZBOOL",
Value: "$(BOOL)",
},
}
declaredEnv := map[string]string{
"FOO": "bar",
"ZOO": "$(FOO)-1",
"BLU": "$(ZOO)-2",
declaredEnv := map[string]interface{}{
"FOO": "bar",
"ZOO": "$(FOO)-1",
"BLU": "$(ZOO)-2",
"INT": "2",
"ZINT": "$(INT)",
"BOOL": "true",
"ZBOOL": "$(BOOL)",
}
counts := make(map[string]int)
mapping := MappingFuncFor(counts, declaredEnv)
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{
"FOO": {count: 1, edited: "bar"},
"ZOO": {count: 1, edited: "bar-1"},
"BLU": {count: 0, edited: "bar-1-2"},
"FOO": {count: 1, edited: "bar"},
"ZOO": {count: 1, edited: "bar-1"},
"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 {
@@ -81,7 +106,7 @@ func TestMapReference(t *testing.T) {
}
func TestMapping(t *testing.T) {
context := map[string]string{
context := map[string]interface{}{
"VAR_A": "A",
"VAR_B": "B",
"VAR_C": "C",
@@ -92,11 +117,11 @@ func TestMapping(t *testing.T) {
}
func TestMappingDual(t *testing.T) {
context := map[string]string{
context := map[string]interface{}{
"VAR_A": "A",
"VAR_EMPTY": "",
}
context2 := map[string]string{
context2 := map[string]interface{}{
"VAR_B": "B",
"VAR_C": "C",
"VAR_REF": "$(VAR_A)",
@@ -105,7 +130,7 @@ func TestMappingDual(t *testing.T) {
doExpansionTest(t, context, context2)
}
func doExpansionTest(t *testing.T, context ...map[string]string) {
func doExpansionTest(t *testing.T, context ...map[string]interface{}) {
cases := []struct {
name string
input string
@@ -325,7 +350,7 @@ func doExpansionTest(t *testing.T, context ...map[string]string) {
for _, tc := range cases {
counts := make(map[string]int)
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 {
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
}