Skip to content

Commit

Permalink
test: add test for helm template rendering (#1127)
Browse files Browse the repository at this point in the history
* test: add test for helm template rendering

* add test for tolerations, affinity and nodeselector

* unittest tag

* remove comments

* fix comments
  • Loading branch information
akshaysngupta committed Feb 4, 2021
1 parent 16937d9 commit f2d1ef7
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Expand Up @@ -16,12 +16,14 @@ require (
github.com/googleapis/gnostic v0.3.1 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/knative/pkg v0.0.0-20190619032946-d90a9bc97dde
github.com/kylelemons/godebug v1.1.0
github.com/onsi/ginkgo v1.11.0
github.com/onsi/gomega v1.7.0
github.com/pkg/errors v0.8.1 // indirect
github.com/prometheus/client_golang v1.1.0
github.com/spf13/pflag v1.0.5
google.golang.org/appengine v1.6.1 // indirect
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.0.0-20200326015715-b5bd82427fa8
k8s.io/apimachinery v0.0.0-20200326015016-e92250ad09d8
k8s.io/client-go v0.16.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -150,6 +150,8 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a h1:TpvdAwDAt1K4ANVOfcihouRdvP+MgAfDWwBuct4l6ZY=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
Expand Down
69 changes: 69 additions & 0 deletions helm/ingress-azure/tests/chart_test.go
@@ -0,0 +1,69 @@
// -------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// --------------------------------------------------------------------------------------------

// +build unittest

package tests

import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)

const valuesDir = "fixtures"

// TestChart is a simple snapshot-style regression test.
//
// This approach proves that the chart can be rendered successfully given various inputs, and that
// the resulting manifests haven't changed unexpectedly since a known good state.
//
// When making a change to the chart, the test snapshots can be generated by running this test case
// with RENDER_SNAPSHOTS=true. Then, `git diff` the new snapshots to see if the changes are expected.
func TestChart(t *testing.T) {
snapshots := []string{}
err := filepath.Walk(valuesDir, func(path string, f os.FileInfo, err error) error {
if f != nil && !f.IsDir() {
snapshots = append(snapshots, path)
}
return nil
})

if err != nil {
t.Fatalf("unable to list snapshots: %v", err)
}

for _, snapshot := range snapshots {
snapshotName, _ := filepath.Rel(valuesDir, snapshot)
name := strings.TrimRight(snapshotName, ".json")

t.Run(name, func(t *testing.T) {
snapshotDir := fmt.Sprintf("snapshots/%s", name)

if os.Getenv("RENDER_SNAPSHOTS") != "" {
err := RenderChart("..", snapshot, snapshotDir)
if err != nil {
t.Fatalf("unable to render chart: %v", err)
}

return
}

actual, err := CaptureSnapshot("..", snapshot)
if err != nil {
t.Fatalf("unable to capture snapshot: %v", err)
}

expected, err := LoadSnapshot(snapshotDir)
if err != nil {
t.Fatalf("unable to load snapshot: %v", err)
}

actual.Diff(t, expected)
})
}
}
66 changes: 66 additions & 0 deletions helm/ingress-azure/tests/fixtures/sample-config.json
@@ -0,0 +1,66 @@
{
"verbosityLevel": 3,
"appgw": {
"subscriptionId": "0000-0000-0000-0000-00000000",
"resourceGroup": "resgp",
"name": "gateway",
"usePrivateIP": false,
"shared": false
},
"armAuth": {
"type": "aadPodIdentity",
"identityResourceID": "/a/b/c",
"identityClientID": "0000-0000-0000-0000-00000000"
},
"rbac": {
"enabled": false
},
"kubernetes": {
"watchNamespace": "a,b,c",
"nodeSelector": {
"beta.kubernetes.io/os": "linux"
},
"podAnnotations": {
"custom-annotation": "custom-value"
},
"tolerations": [
{
"key": "CriticalAppsOnly",
"operator": "Exists"
}
],
"affinity": {
"nodeAffinity": {
"preferredDuringSchedulingIgnoredDuringExecution": [
{
"weight": 100,
"preference": {
"matchExpressions": [
{
"key": "kubernetes.cloud.com/mode",
"operator": "In",
"values": [
"system"
]
}
]
}
}
],
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [
{
"labelSelector": null,
"matchExpressions": [
{
"key": "kubernetes.cloud.com/cluster",
"operator": "Exists"
}
]
}
]
}
}
}
}
}
179 changes: 179 additions & 0 deletions helm/ingress-azure/tests/snapshots.go
@@ -0,0 +1,179 @@
// -------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// --------------------------------------------------------------------------------------------

package tests

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/kylelemons/godebug/pretty"
"gopkg.in/yaml.v2"
)

// Snapshot represents a rendered Helm chart.
type Snapshot struct {
files map[string][]map[interface{}]interface{}
}

// Diff takes an expected snapshot, and compares it to itself.
func (s *Snapshot) Diff(t *testing.T, expected *Snapshot) {
for file, parsedFile := range s.files {
parsedExpFile, ok := expected.files[file]
if !ok {
t.Errorf("missing manifest: %s", file)
continue
}

diff := pretty.Compare(parsedFile, parsedExpFile)
if len(diff) == 0 {
continue
}

t.Errorf("file %q doesn't match expectation\n%s", file, diff)
}

for expFile := range expected.files {
if _, ok := s.files[expFile]; !ok {
t.Errorf("expected snapshot has file missing in actual: %s", expFile)
}
}
}

// RenderChart renders a Helm chart to a given directory.
func RenderChart(chart, values, dir string) error {
err := os.MkdirAll(dir, 0755)
if err != nil {
return fmt.Errorf("unable to create output dir %q: %v", dir, err)
}

helmCmdFile := os.Getenv("HELM_CMD")
if helmCmdFile == "" {
helmCmdFile = "helm"
}
out, err := exec.Command(helmCmdFile, "template", "..", "--output-dir", dir, "--values", values).CombinedOutput()
if err != nil {
return &HelmError{
RawOutput: out,
CommandError: err,
}
}

return StripNonDeterministic(dir)
}

// CaptureSnapshot renders a new snapshot from a given Helm chart and values file.
func CaptureSnapshot(chart, values string) (*Snapshot, error) {
dir, err := ioutil.TempDir("", "")
if err != nil {
return nil, fmt.Errorf("creating tempdir: %v", err)
}

defer func() {
err := os.RemoveAll(dir)
if err != nil {
panic(fmt.Errorf("unable to clean up tempdir for chart %q with values %q: %v", chart, values, err))
}
}()

if err := RenderChart(chart, values, dir); err != nil {
return nil, err
}

return LoadSnapshot(dir)
}

// LoadSnapshot loads a snapshot from disk. Expects the directory to be a chart rendered with
// `helm template --output-dir`.
func LoadSnapshot(dir string) (*Snapshot, error) {
s := &Snapshot{
files: map[string][]map[interface{}]interface{}{},
}
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}

file, err := os.Open(path)
if err != nil {
return fmt.Errorf("opening file %q: %v", path, err)
}
defer file.Close()
shortPath := strings.TrimPrefix(path, dir+"/")

dec := yaml.NewDecoder(file)
for {
obj := map[interface{}]interface{}{}
err = dec.Decode(&obj)
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("parsing file %q: %v", shortPath, err)
}
s.files[shortPath] = append(s.files[shortPath], obj)
}

return nil
})
if err != nil {
return nil, fmt.Errorf("walking rendered chart: %v", err)
}

return s, nil
}

// StripNonDeterministic removes properties from all of the manifests in a given directory.
func StripNonDeterministic(path string) error {
return filepath.Walk(path,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !strings.HasSuffix(info.Name(), ".yaml") {
return nil
}

file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

lines := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := scanner.Text()
if strings.Contains(text, "checksum/") ||
strings.Contains(text, "aks.microsoft.com/release-time") {
continue
}
lines = append(lines, text)
}

return ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644)
})
}

// HelmError represents an error returned by the Helm CLI.
type HelmError struct {
RawOutput []byte
CommandError error
}

// Error satisfies the error interface.
func (h *HelmError) Error() string {
return fmt.Sprintf("Helm CLI error: %q - raw output:\n%s", h.CommandError.Error(), h.RawOutput)
}
@@ -0,0 +1,10 @@
---
# Source: ingress-azure/templates/aadpodidbinding.yaml
# Please see https://github.com/Azure/aad-pod-identity for more inromation
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
name: RELEASE-NAME-azidbinding-ingress-azure
spec:
azureIdentity: RELEASE-NAME-azid-ingress-azure
selector: RELEASE-NAME-ingress-azure
@@ -0,0 +1,11 @@
---
# Source: ingress-azure/templates/aadpodidentity.yaml
# Please see https://github.com/Azure/aad-pod-identity for more information
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
name: RELEASE-NAME-azid-ingress-azure
spec:
type: 0
resourceID: /a/b/c
clientID: 0000-0000-0000-0000-00000000
@@ -0,0 +1,21 @@
---
# Source: ingress-azure/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: RELEASE-NAME-cm-ingress-azure
labels:
app: ingress-azure
chart: ingress-azure-1.2.1
heritage: Helm
release: RELEASE-NAME
data:
APPGW_VERBOSITY_LEVEL: "3"
HTTP_SERVICE_PORT: "8123"
APPGW_SUBSCRIPTION_ID: "0000-0000-0000-0000-00000000"
APPGW_RESOURCE_GROUP: "resgp"
APPGW_NAME: "gateway"
APPGW_SUBNET_NAME: "gateway-subnet"
KUBERNETES_WATCHNAMESPACE: "a,b,c"
AZURE_CLIENT_ID: "0000-0000-0000-0000-00000000"
USE_MANAGED_IDENTITY_FOR_POD: "true"

0 comments on commit f2d1ef7

Please sign in to comment.