github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/template/template_test.go (about)

     1  // Copyright (c) 2021, 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package template
     5  
     6  import (
     7  	"encoding/json"
     8  	asserts "github.com/stretchr/testify/assert"
     9  	k8sapps "k8s.io/api/apps/v1"
    10  	k8score "k8s.io/api/core/v1"
    11  	k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    13  	"k8s.io/apimachinery/pkg/runtime"
    14  	"k8s.io/apimachinery/pkg/runtime/schema"
    15  	"sigs.k8s.io/controller-runtime/pkg/client"
    16  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    17  	"testing"
    18  )
    19  
    20  type Workload struct {
    21  	Name string
    22  }
    23  type Profile struct {
    24  	Name string
    25  }
    26  
    27  // TestPopulateFromKubeResources tests whether a template can be populated with the values from a configmap and secret
    28  // GIVEN that a template processor referencing a configmap and secret in the same namespace
    29  // WHEN the processor executes
    30  // THEN the resulting string should be populated with values from the config map and secret
    31  func TestPopulateFromKubeResources(t *testing.T) {
    32  	c := getTestClient()
    33  	templateText := `data:
    34    template:
    35      - value1: "{{configmap "testNamespace" "testConfigMap" "value1"}}"
    36      - value2: "{{configmap "testNamespace" "testConfigMap" "value2"}}"
    37      - secret: "{{secret "testNamespace" "someSecret" "secret1"}}"
    38  `
    39  	expectedOutput := `data:
    40    template:
    41      - value1: "This is the string associated with value1"
    42      - value2: "This is the string associated with value2"
    43      - secret: "totallySecretValue"
    44  `
    45  	tp := NewProcessor(c, templateText)
    46  	output, err := tp.Process(nil)
    47  	asserts.NoError(t, err, "error executing template processing for config map values")
    48  	asserts.Equal(t, expectedOutput, output)
    49  }
    50  
    51  // TestPopulateFromStructs tests whether a template can be populated with the values from a provided structs
    52  // GIVEN that a template processor referencing multiple structs
    53  // WHEN the processor executes
    54  // THEN the resulting string should be populated with values from the structs provided as inputs
    55  func TestPopulateFromStructs(t *testing.T) {
    56  	c := getTestClient()
    57  	inputs := map[string]interface{}{
    58  		"workload": Workload{Name: "Workload1"},
    59  		"profile":  Profile{Name: "Profile1"},
    60  	}
    61  	templateText := `data:
    62    template:
    63      - workload: "{{.workload.Name}}"
    64      - profile: "{{.profile.Name}}"
    65  `
    66  	expectedOutput := `data:
    67    template:
    68      - workload: "Workload1"
    69      - profile: "Profile1"
    70  `
    71  	tp := NewProcessor(c, templateText)
    72  	output, err := tp.Process(inputs)
    73  	asserts.NoError(t, err, "error executing template processing for config map values")
    74  	asserts.Equal(t, expectedOutput, output)
    75  }
    76  
    77  // TestPopulateFromStructsAndResources tests whether a template can be populated with the values from provided structs and kube resources
    78  // GIVEN that a template processor referencing multiple sources
    79  // WHEN the processor executes
    80  // THEN the resulting string should be populated with values from the sources indicated
    81  func TestPopulateFromStructsAndResources(t *testing.T) {
    82  	c := getTestClient()
    83  	inputs := map[string]interface{}{
    84  		"workload": Workload{Name: "Workload1"},
    85  		"profile":  Profile{Name: "Profile1"},
    86  	}
    87  	templateText := `data:
    88    template:
    89      - value1: "{{configmap "testNamespace" "testConfigMap" "value1"}}"
    90      - value2: "{{configmap "testNamespace" "testConfigMap" "value2"}}"
    91      - secret: "{{secret "testNamespace" "someSecret" "secret1"}}"
    92      - workload: "{{.workload.Name}}"
    93      - profile: "{{.profile.Name}}"
    94  `
    95  	expectedOutput := `data:
    96    template:
    97      - value1: "This is the string associated with value1"
    98      - value2: "This is the string associated with value2"
    99      - secret: "totallySecretValue"
   100      - workload: "Workload1"
   101      - profile: "Profile1"
   102  `
   103  	tp := NewProcessor(c, templateText)
   104  	output, err := tp.Process(inputs)
   105  	asserts.NoError(t, err, "error executing template processing for config map values")
   106  	asserts.Equal(t, expectedOutput, output)
   107  }
   108  
   109  // TestPopulateFromUnstructured tests populating a template from an Unstructured input
   110  // GIVEN a template that references a nested value within an Unstructured
   111  // WHEN the processor executes
   112  // THEN the resulting string should be populated from the Unstructured's values
   113  func TestPopulateFromUnstructured(t *testing.T) {
   114  	assert := asserts.New(t)
   115  	c := getTestClient()
   116  	workload, err := convertToUnstructured(
   117  		k8sapps.Deployment{
   118  			ObjectMeta: k8smeta.ObjectMeta{
   119  				Namespace: "test-namespace-name",
   120  				Name:      "test-workload-name",
   121  			}})
   122  	assert.NoError(err, "error converting resource to unstructured")
   123  	inputs := map[string]interface{}{
   124  		"workload": workload.Object,
   125  	}
   126  	templateText := `<{{.workload.metadata.namespace}}/{{.workload.metadata.name}}>`
   127  	expectedOutput := `<test-namespace-name/test-workload-name>`
   128  	tp := NewProcessor(c, templateText)
   129  	actualOutput, err := tp.Process(inputs)
   130  	assert.NoError(err, "error processing template")
   131  	assert.Equal(expectedOutput, actualOutput, "expected template to be populated correctly from unstructured")
   132  }
   133  
   134  // TestPopulateFromGet tests populating a template from values obtained from a fetched resource
   135  // GIVEN a template that references a function to get a resource
   136  // WHEN the processor executes
   137  // THEN the resulting string should be populated from the resources values
   138  func TestPopulateFromGet(t *testing.T) {
   139  	assert := asserts.New(t)
   140  	c := getTestClient()
   141  	templateText := `{{$cm := get "v1" "ConfigMap" "testNamespace" "testConfigMap"}}<{{$cm.metadata.name}}>`
   142  	expectedOutput := `<testConfigMap>`
   143  	tp := NewProcessor(c, templateText)
   144  	actualOutput, err := tp.Process(nil)
   145  	assert.NoError(err, "error processing template")
   146  	assert.Equal(expectedOutput, actualOutput, "expected template to be populated correctly from unstructured")
   147  }
   148  
   149  // getTestClient returns a controller/kube client with references to simple secret and configmap
   150  func getTestClient() client.Client {
   151  	c := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(
   152  		&k8score.Secret{
   153  			ObjectMeta: k8smeta.ObjectMeta{
   154  				Name:      "someSecret",
   155  				Namespace: "testNamespace",
   156  			},
   157  			Data: map[string][]byte{
   158  				"secret1": []byte("totallySecretValue"),
   159  			},
   160  		},
   161  		&k8score.ConfigMap{
   162  			ObjectMeta: k8smeta.ObjectMeta{
   163  				Name:      "testConfigMap",
   164  				Namespace: "testNamespace",
   165  			},
   166  			Data: map[string]string{
   167  				"value1": "This is the string associated with value1",
   168  				"value2": "This is the string associated with value2",
   169  			},
   170  		}).Build()
   171  	return c
   172  }
   173  
   174  func newScheme() *runtime.Scheme {
   175  	scheme := runtime.NewScheme()
   176  	k8score.SchemeBuilder.AddToScheme(scheme)
   177  	scheme.AddKnownTypes(schema.GroupVersion{
   178  		Version: "v1",
   179  	}, &k8score.Secret{}, &k8score.ConfigMap{})
   180  
   181  	return scheme
   182  }
   183  
   184  // convertToUnstructured converts an object to an Unstructured version
   185  // object - The object to convert to Unstructured
   186  func convertToUnstructured(object interface{}) (unstructured.Unstructured, error) {
   187  	jbytes, err := json.Marshal(object)
   188  	if err != nil {
   189  		return unstructured.Unstructured{}, err
   190  	}
   191  	var u map[string]interface{}
   192  	json.Unmarshal(jbytes, &u)
   193  	return unstructured.Unstructured{Object: u}, nil
   194  }