gruntwork-io / terratest

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.
https://terratest.gruntwork.io/
Apache License 2.0
7.46k stars 1.32k forks source link

Specifying templateFiles on the RenderTemplate call #728

Open ghost opened 3 years ago

ghost commented 3 years ago

Hi

I am running the helm_basic_example_template_test. Works fine. But if I do not specify any templateFiles on the RenderTemplate call, then the test fails. If no templateFiles are specified I would expect all templates to be rendered.

output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{})
var deployment appsv1.Deployment
helm.UnmarshalK8SYaml(t, output, &deployment)
require.Equal(t, namespaceName, deployment.Namespace)

This returns the following. It's as though the deployment variable is not getting set.

minimal_pod_template_test.go:65: 
        Error Trace:    minimal_pod_template_test.go:65
        Error:          Not equal: 
                        expected: "medieval-fm1fsj"
                        actual  : ""

Should it be possible to render all templates?

Thanks

yorinasub17 commented 3 years ago

Unfortunately, this doesn't work because the yaml library we are using (ghodss/yaml) doesn't support unmarshaling multiple objects. Supposedly go-yaml has better support for this, but I am not sure how that works (e.g., does it try each object against the schema? what if there are multiple? does it return the first match?).

I think we need a bit of design work here to figure out the API for unmarshaling when helm returns multiple rendered objects. The ideal interface is probably something like:

func UnmarshalK8SObjectsToYAML(t *testing.T, renderedBytes []bytes, objectMap map[string]interface{})

where objectMap is a map from resource name to the actual object to render to. Then, the implementation should do two passes to unmarshal:

  1. For each document, grab the name metadata field and see if the key exists in objectMap.
  2. If the key does not exist, skip the document.
  3. If the key does exist, unmarshal to the object pointer value for that key.
chainlink commented 3 years ago

I've gotten this working with a bit of code from the kube libraries and goyaml

templates := []string{
    "templates/pachd/storage-secret.yaml",
    "templates/pachd/deployment.yaml",
}
output := helm.RenderTemplate(t, options, helmChartPath, "blah", templates)

files, err := splitYAML(output)

if err != nil {
    t.Fatal(err)
}

decodedKubeObjects := []interface{}{}

decode := scheme.Codecs.UniversalDeserializer().Decode 

for _, f := range files {
    obj, _, err := decode([]byte(f), nil, nil)
    if err != nil {
        t.Fatal(err)
    }
    decodedKubeObjects = append(decodedKubeObjects, obj)

}

for _, f := range decodedKubeObjects {
    fmt.Printf("%#v\n\n", f)
}
// adapted from https://play.golang.org/p/MZNwxdUzxPo
func splitYAML(manifest string) ([]string, error) {
    dec := goyaml.NewDecoder(bytes.NewReader([]byte(manifest)))
    var res []string
    for {
        var value interface{}
        if err := dec.Decode(&value); err == io.EOF {
            break
        } else if err != nil {
            return nil, err
        }
        b, err := goyaml.Marshal(value)
        if err != nil {
            return nil, err
        }
        res = append(res, string(b))
    }
    return res, nil
}