From 1f4be625603f8281e6393b92620270c771daaf33 Mon Sep 17 00:00:00 2001 From: Shawn O'Dell Date: Mon, 23 Jan 2023 11:45:38 -0600 Subject: [PATCH] expand environmentt variables in the yaml Signed-off-by: Shawn O'Dell --- pkg/test/utils/kubernetes.go | 5 +- pkg/test/utils/kubernetes_test.go | 22 +++++++ pkg/test/utils/translate.go | 103 ------------------------------ pkg/test/utils/translate_test.go | 62 ------------------ 4 files changed, 25 insertions(+), 167 deletions(-) delete mode 100644 pkg/test/utils/translate.go delete mode 100644 pkg/test/utils/translate_test.go diff --git a/pkg/test/utils/kubernetes.go b/pkg/test/utils/kubernetes.go index 57f75ef7..668f5eae 100644 --- a/pkg/test/utils/kubernetes.go +++ b/pkg/test/utils/kubernetes.go @@ -508,6 +508,9 @@ func LoadYAML(path string, r io.Reader) ([]client.Object, error) { return nil, fmt.Errorf("error reading yaml %s: %w", path, err) } + // replace all variables from the environment + data = []byte(os.ExpandEnv(string(data))) + unstructuredObj := &unstructured.Unstructured{} decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(data), len(data)) @@ -515,8 +518,6 @@ func LoadYAML(path string, r io.Reader) ([]client.Object, error) { return nil, fmt.Errorf("error decoding yaml %s: %w", path, err) } - unstructuredObj.Object = Translate(unstructuredObj.Object).(map[string]interface{}) - obj, err := ConvertUnstructured(unstructuredObj) if err != nil { return nil, fmt.Errorf("error converting unstructured object %s (%s): %w", ResourceID(unstructuredObj), path, err) diff --git a/pkg/test/utils/kubernetes_test.go b/pkg/test/utils/kubernetes_test.go index 2cb1bdb1..e190ead8 100644 --- a/pkg/test/utils/kubernetes_test.go +++ b/pkg/test/utils/kubernetes_test.go @@ -5,6 +5,7 @@ import ( "context" "errors" "os" + "strconv" "testing" "time" @@ -182,6 +183,11 @@ spec: containers: - name: nginx image: nginx:1.7.9 + - name: app + image: app:$APP_VERSION + resources: + requests: + cpu: ${CPU_COUNT} --- apiVersion: v1 kind: Pod @@ -198,6 +204,13 @@ spec: t.Fatal(err) } + appVersion := "0.0.1-alpha" + t.Setenv("APP_VERSION", appVersion) + // test replacing a different type (int64 in this case) + // and nested values + cpuCount := int64(2) + t.Setenv("CPU_COUNT", strconv.Itoa(int(cpuCount))) + objs, err := LoadYAMLFromFile(tmpfile.Name()) assert.Nil(t, err) @@ -216,6 +229,15 @@ spec: "image": "nginx:1.7.9", "name": "nginx", }, + map[string]interface{}{ + "name": "app", + "image": "app:" + appVersion, + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": cpuCount, + }, + }, + }, }, }, }, diff --git a/pkg/test/utils/translate.go b/pkg/test/utils/translate.go deleted file mode 100644 index 0df230a0..00000000 --- a/pkg/test/utils/translate.go +++ /dev/null @@ -1,103 +0,0 @@ -package utils - -import ( - "os" - "reflect" -) - -// Translate traverses an arbitrary struct and translates all strings it encounters -// replacing variable references with environment variables -// the pet name generator also sets the NAMESPACE env var (if not already set) so -// $NAMESPACE references *should* use the generated one -// -// The MIT License (MIT) -// -// # Copyright (c) 2014 Heye Vöcking -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -func Translate(obj interface{}) interface{} { - // Wrap the original in a reflect.Value - original := reflect.ValueOf(obj) - - copy := reflect.New(original.Type()).Elem() - translateRecursive(copy, original) - - // Remove the reflection wrapper - return copy.Interface() -} - -func translateRecursive(copy, original reflect.Value) { - switch original.Kind() { - // The first cases handle nested structures and translate them recursively - - // If it is a pointer we need to unwrap and call once again - case reflect.Ptr: - // To get the actual value of the original we have to call Elem() - // At the same time this unwraps the pointer so we don't end up in - // an infinite recursion - originalValue := original.Elem() - // Check if the pointer is nil - if !originalValue.IsValid() { - return - } - // Allocate a new object and set the pointer to it - copy.Set(reflect.New(originalValue.Type())) - // Unwrap the newly created pointer - translateRecursive(copy.Elem(), originalValue) - - // If it is an interface (which is very similar to a pointer), do basically the - // same as for the pointer. Though a pointer is not the same as an interface so - // note that we have to call Elem() after creating a new object because otherwise - // we would end up with an actual pointer - case reflect.Interface: - // Get rid of the wrapping interface - originalValue := original.Elem() - // Create a new object. Now new gives us a pointer, but we want the value it - // points to, so we have to call Elem() to unwrap it - copyValue := reflect.New(originalValue.Type()).Elem() - translateRecursive(copyValue, originalValue) - copy.Set(copyValue) - - // If it is a struct we translate each field - case reflect.Struct: - for i := 0; i < original.NumField(); i += 1 { - translateRecursive(copy.Field(i), original.Field(i)) - } - - // If it is a slice we create a new slice and translate each element - case reflect.Slice: - copy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap())) - for i := 0; i < original.Len(); i += 1 { - translateRecursive(copy.Index(i), original.Index(i)) - } - - // If it is a map we create a new map and translate each value - case reflect.Map: - copy.Set(reflect.MakeMap(original.Type())) - for _, key := range original.MapKeys() { - originalValue := original.MapIndex(key) - // New gives us a pointer, but again we want the value - copyValue := reflect.New(originalValue.Type()).Elem() - translateRecursive(copyValue, originalValue) - copy.SetMapIndex(key, copyValue) - } - - // Otherwise we cannot traverse anywhere so this finishes the the recursion - - // If it is a string translate it (yay finally we're doing what we came for) - case reflect.String: - copy.SetString(os.ExpandEnv(original.Interface().(string))) - - // And everything else will simply be taken from the original - default: - copy.Set(original) - } -} diff --git a/pkg/test/utils/translate_test.go b/pkg/test/utils/translate_test.go deleted file mode 100644 index 8b53b3f7..00000000 --- a/pkg/test/utils/translate_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package utils - -import ( - "bytes" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/util/yaml" -) - -func testDecoder(s string) *unstructured.Unstructured { - b := []byte(s) - d := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), len(b)) - u := &unstructured.Unstructured{} - if err := d.Decode(u); err != nil { - fmt.Println(err) - } - return u -} - -func TestTranslate(t *testing.T) { - namespace := "foo" - t.Setenv("NAMESPACE", namespace) - baz := "bar" - t.Setenv("BAZ", baz) - - manifestTemplate := ` -apiVersion: example.com/v1 -kind: CustomResource -metadata: - name: test - namespace: $NAMESPACE -status: - ready: true -spec: - key1: - key1: data - key2: $BAZ - key2: - key1: "$NAMESPACE" -` - - manifestTemplated := fmt.Sprintf(` -apiVersion: example.com/v1 -kind: CustomResource -metadata: - name: test - namespace: %s -status: - ready: true -spec: - key1: - key1: data - key2: %s - key2: - key1: %s -`, namespace, baz, namespace) - - assert.Equal(t, fmt.Sprint(testDecoder(manifestTemplated)), fmt.Sprint(Translate(testDecoder(manifestTemplate)))) -}