Skip to content

Commit

Permalink
: manifest envsubst
Browse files Browse the repository at this point in the history
Signed-off-by: Shawn O'Dell <[email protected]>
  • Loading branch information
Shawn O'Dell committed Oct 25, 2021
1 parent d2cd0d5 commit 00a12fe
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pkg/test/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
Expand Down Expand Up @@ -383,6 +384,10 @@ func (t *Case) determineNamespace() *namespace {
ns.Name = fmt.Sprintf("kuttl-test-%s", petname.Generate(2, "-"))
ns.AutoCreated = true
}

if _, ok := os.LookupEnv("NAMESPACE"); ok { // user can supply this environment variable
os.Setenv("NAMESPACE", t.PreferredNamespace) // set this environment variable so it can be templated by other 'steps'
}
// if we have a preferred namespace, we do NOT auto-create
return ns
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/test/utils/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ 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)
Expand Down
11 changes: 11 additions & 0 deletions pkg/test/utils/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package utils

// TrimLeftChar trims the left character from a string
func TrimLeftChar(s string) string {
for i := range s {
if i > 0 {
return s[i:]
}
}
return s[:0]
}
111 changes: 111 additions & 0 deletions pkg/test/utils/translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package utils

import (
"os"
"reflect"
"strings"
)

// 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:
translatedString := original.Interface().(string)
if strings.HasPrefix(translatedString, "$") {
variable := TrimLeftChar(translatedString)
if val, ok := os.LookupEnv(variable); ok {
translatedString = val
}
}
copy.SetString(translatedString)

// And everything else will simply be taken from the original
default:
copy.Set(original)
}
}

0 comments on commit 00a12fe

Please sign in to comment.