-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1092 from xhu-cloudera/issue-342
Issue 342: Add support for helm chart dependency
- Loading branch information
Showing
9 changed files
with
320 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
charts/ | ||
requirements.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
apiVersion: v1 | ||
name: helm-dependency-example | ||
description: A minimal Helm chart to demonstrate how to use terratest to test helm charts with dependency | ||
version: 0.0.1 | ||
dependencies: | ||
- name: helm-basic-example | ||
alias: basic | ||
repository: file://../helm-basic-example | ||
condition: basic.enabled | ||
version: 0.0.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Helm Dependency Example | ||
|
||
This folder contains a minimal helm chart to demonstrate how you can use Terratest to test your helm charts with dependencies. | ||
|
||
There are two kinds of tests you can perform on a helm chart: | ||
|
||
- Helm Template tests are tests designed to test the logic of the templates. These tests should run `helm template` with | ||
various input values and parse the yaml to validate any logic embedded in the templates (e.g by reading them in using | ||
client-go). Since templates are not statically typed, the goal of these tests is to promote fast cycle time | ||
|
||
The helm chart deploys a single replica `Deployment` resource given the container image spec and a `Service` that | ||
exposes it. This chart requires the `containerImageRepo` and `containerImageTag` input values. | ||
|
||
See the corresponding terratest code for an example of how to test this chart: | ||
|
||
- [helm_basic_example_template_test.go](/test/helm_basic_example_template_test.go): the template tests for this chart. | ||
|
||
## Running automated tests against this Helm Chart | ||
|
||
1. Install and setup [helm](https://docs.helm.sh/using_helm/#installing-helm) | ||
1. Install [Golang](https://golang.org/) and make sure this code is checked out into your `GOPATH`. | ||
1. `cd test` | ||
1. `dep ensure` | ||
1. `go test -v -tags helm -run TestHelmDependencyExampleTemplate` for the template test | ||
|
||
**NOTE**: we have build tags to differentiate kubernetes tests from non-kubernetes tests, and further differentiate helm | ||
tests. This is done because minikube is heavy and can interfere with docker related tests in terratest. Similarly, helm | ||
can overload the minikube system and thus interfere with the other kubernetes tests. Specifically, many of the tests | ||
start to fail with `connection refused` errors from `minikube`. To avoid overloading the system, we run the kubernetes | ||
tests and helm tests separately from the others. This may not be necessary if you have a sufficiently powerful machine. | ||
We recommend at least 4 cores and 16GB of RAM if you want to run all the tests together. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{{/* vim: set filetype=mustache: */}} | ||
{{/* | ||
Expand the name of the chart. | ||
*/}} | ||
{{- define "helm-dependency-example.name" -}} | ||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} | ||
{{- end -}} | ||
|
||
{{/* | ||
Create a default fully qualified app name. | ||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). | ||
If release name contains chart name it will be used as a full name. | ||
*/}} | ||
{{- define "helm-dependency-example.fullname" -}} | ||
{{- if .Values.fullnameOverride -}} | ||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} | ||
{{- else -}} | ||
{{- $name := default .Chart.Name .Values.nameOverride -}} | ||
{{- if contains $name .Release.Name -}} | ||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}} | ||
{{- else -}} | ||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} | ||
{{- end -}} | ||
{{- end -}} | ||
{{- end -}} | ||
|
||
{{/* | ||
Create chart name and version as used by the chart label. | ||
*/}} | ||
{{- define "helm-dependency-example.chart" -}} | ||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} | ||
{{- end -}} |
31 changes: 31 additions & 0 deletions
31
examples/helm-dependency-example/templates/deployment.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: {{ include "helm-dependency-example.fullname" . }} | ||
namespace: {{ .Release.Namespace }} | ||
labels: | ||
# These labels are required by helm. You can read more about required labels in the chart best pracices guide: | ||
# https://docs.helm.sh/chart_best_practices/#standard-labels | ||
helm.sh/chart: {{ include "helm-dependency-example.chart" . }} | ||
app.kubernetes.io/name: {{ include "helm-dependency-example.name" . }} | ||
app.kubernetes.io/instance: {{ .Release.Name }} | ||
app.kubernetes.io/managed-by: {{ .Release.Service }} | ||
spec: | ||
replicas: 1 | ||
selector: | ||
matchLabels: | ||
app.kubernetes.io/name: {{ include "helm-dependency-example.name" . }} | ||
app.kubernetes.io/instance: {{ .Release.Name }} | ||
template: | ||
metadata: | ||
labels: | ||
app.kubernetes.io/name: {{ include "helm-dependency-example.name" . }} | ||
app.kubernetes.io/instance: {{ .Release.Name }} | ||
spec: | ||
containers: | ||
- name: app | ||
{{- $repo := required "containerImageRepo is required" .Values.containerImageRepo }} | ||
{{- $tag := required "containerImageTag is required" .Values.containerImageTag }} | ||
image: "{{ $repo }}:{{ $tag }}" | ||
ports: | ||
- containerPort: 80 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: {{ include "helm-dependency-example.fullname" . }} | ||
labels: | ||
# These labels are required by helm. You can read more about required labels in the chart best practices guide: | ||
# https://docs.helm.sh/chart_best_practices/#standard-labels | ||
helm.sh/chart: {{ include "helm-dependency-example.chart" . }} | ||
app.kubernetes.io/name: {{ include "helm-dependency-example.name" . }} | ||
app.kubernetes.io/instance: {{ .Release.Name }} | ||
app.kubernetes.io/managed-by: {{ .Release.Service }} | ||
spec: | ||
selector: | ||
app.kubernetes.io/name: {{ include "helm-dependency-example.name" . }} | ||
app.kubernetes.io/instance: {{ .Release.Name }} | ||
type: NodePort | ||
ports: | ||
- protocol: TCP | ||
targetPort: 80 | ||
port: 80 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# This chart purposefully does not provide any values, to demonstrate how to test required values. | ||
# Note that the following two values must be specified if you wish to deploy this chart: | ||
|
||
# containerImageRepo is a string that describes the image repository to pull the container image from. | ||
# containerImageRepo: nginx | ||
|
||
# containerImageTag is a string that describes the image tag to use when pulling the container image. | ||
# containerImageTag: v1.15.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
//go:build kubeall || helm | ||
// +build kubeall helm | ||
|
||
// **NOTE**: we have build tags to differentiate kubernetes tests from non-kubernetes tests, and further differentiate helm | ||
// tests. This is done because minikube is heavy and can interfere with docker related tests in terratest. Similarly, helm | ||
// can overload the minikube system and thus interfere with the other kubernetes tests. Specifically, many of the tests | ||
// start to fail with `connection refused` errors from `minikube`. To avoid overloading the system, we run the kubernetes | ||
// tests and helm tests separately from the others. This may not be necessary if you have a sufficiently powerful machine. | ||
// We recommend at least 4 cores and 16GB of RAM if you want to run all the tests together. | ||
|
||
package test | ||
|
||
import ( | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
appsv1 "k8s.io/api/apps/v1" | ||
|
||
"github.com/gruntwork-io/terratest/modules/helm" | ||
"github.com/gruntwork-io/terratest/modules/k8s" | ||
"github.com/gruntwork-io/terratest/modules/logger" | ||
"github.com/gruntwork-io/terratest/modules/random" | ||
) | ||
|
||
// This file contains examples of how to use terratest to test helm chart template logic by rendering the templates | ||
// using `helm template`, and then reading in the rendered templates. | ||
// There are two tests: | ||
// - TestHelmBasicExampleTemplateRenderedDeployment: An example of how to read in the rendered object and check the | ||
// computed values. | ||
// - TestHelmBasicExampleTemplateRequiredTemplateArgs: An example of how to check that the required args are indeed | ||
// required for the template to render. | ||
|
||
// An example of how to verify the rendered template object of a Helm Chart given various inputs. | ||
func TestHelmDependencyExampleTemplateRenderedDeployment(t *testing.T) { | ||
t.Parallel() | ||
|
||
// Path to the helm chart we will test | ||
helmChartPath, err := filepath.Abs("../examples/helm-dependency-example") | ||
releaseName := "helm-dependency" | ||
require.NoError(t, err) | ||
|
||
// Since we aren't deploying any resources, there is no need to setup kubectl authentication or helm home. | ||
|
||
// Set up the namespace; confirm that the template renders the expected value for the namespace. | ||
namespaceName := "medieval-" + strings.ToLower(random.UniqueId()) | ||
logger.Logf(t, "Namespace: %s\n", namespaceName) | ||
|
||
// Setup the args. For this test, we will set the following input values: | ||
// - containerImageRepo=nginx | ||
// - containerImageTag=1.15.8 | ||
options := &helm.Options{ | ||
SetValues: map[string]string{ | ||
"containerImageRepo": "nginx", | ||
"containerImageTag": "1.15.8", | ||
"basic.containerImageRepo": "nginx", | ||
"basic.containerImageTag": "1.15.8", | ||
}, | ||
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), | ||
} | ||
|
||
testCases := []struct { | ||
name string | ||
templateName string | ||
}{ | ||
{ | ||
"dependent chart", | ||
"templates/deployment.yaml", | ||
}, | ||
{ | ||
"basic chart", | ||
"charts/basic/templates/deployment.yaml", | ||
}, | ||
} | ||
|
||
for _, testCase := range testCases { | ||
testCase := testCase | ||
t.Run(testCase.name, func(subT *testing.T) { | ||
// subT.Parallel() | ||
// Run RenderTemplate to render the template and capture the output. Note that we use the version without `E`, since | ||
// we want to assert that the template renders without any errors. | ||
// Additionally, although we know there is only one yaml file in the template, we deliberately path a templateFiles | ||
// arg to demonstrate how to select individual templates to render. | ||
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{testCase.templateName}) | ||
|
||
// Now we use kubernetes/client-go library to render the template output into the Deployment struct. This will | ||
// ensure the Deployment resource is rendered correctly. | ||
var deployment appsv1.Deployment | ||
helm.UnmarshalK8SYaml(t, output, &deployment) | ||
|
||
// Verify the namespace matches the expected supplied namespace. | ||
require.Equal(t, namespaceName, deployment.Namespace) | ||
|
||
// Finally, we verify the deployment pod template spec is set to the expected container image value | ||
expectedContainerImage := "nginx:1.15.8" | ||
deploymentContainers := deployment.Spec.Template.Spec.Containers | ||
require.Equal(t, len(deploymentContainers), 1) | ||
require.Equal(t, deploymentContainers[0].Image, expectedContainerImage) | ||
|
||
}) | ||
} | ||
} | ||
|
||
// An example of how to verify required values for a helm chart. | ||
func TestHelmDependencyExampleTemplateRequiredTemplateArgs(t *testing.T) { | ||
t.Parallel() | ||
|
||
// Path to the helm chart we will test | ||
helmChartPath, err := filepath.Abs("../examples/helm-dependency-example") | ||
releaseName := "helm-dependency" | ||
require.NoError(t, err) | ||
|
||
// Since we aren't deploying any resources, there is no need to setup kubectl authentication, helm home, or | ||
// namespaces | ||
|
||
// Here, we use a table driven test to iterate through all the required values as subtests. You can learn more about | ||
// go subtests here: https://blog.golang.org/subtests | ||
// The struct captures the inputs that we will pass to helm template and a human friendly name so we can identify it | ||
// in the test output. In this case, each test case will be a complete values input except for one of the required | ||
// values missing, to test that neglecting a required value will cause the template rendering to fail. | ||
testCases := []struct { | ||
name string | ||
values map[string]string | ||
}{ | ||
{ | ||
"MissingContainerImageRepo in dependent chart", | ||
map[string]string{ | ||
"containerImageTag": "1.15.8", | ||
"basic.containerImageRepo": "nginx", | ||
"basic.containerImageTag": "1.15.8", | ||
}, | ||
}, | ||
{ | ||
"MissingContainerImageRepo in basic chart", | ||
map[string]string{ | ||
"basic.containerImageTag": "1.15.8", | ||
"containerImageRepo": "nginx", | ||
"containerImageTag": "1.15.8", | ||
}, | ||
}, | ||
{ | ||
"MissingContainerImageTag in dependent chart", | ||
map[string]string{ | ||
"containerImageRepo": "nginx", | ||
"basic.containerImageRepo": "nginx", | ||
"basic.containerImageTag": "1.15.8", | ||
}, | ||
}, | ||
{ | ||
"MissingContainerImageTag in basic chart", | ||
map[string]string{ | ||
"basic.containerImageRepo": "nginx", | ||
"containerImageRepo": "nginx", | ||
"containerImageTag": "1.15.8", | ||
}, | ||
}, | ||
} | ||
|
||
// Now we iterate over each test case and spawn a sub test | ||
for _, testCase := range testCases { | ||
// Here, we capture the range variable and force it into the scope of this block. If we don't do this, when the | ||
// subtest switches contexts (because of t.Parallel), the testCase value will have been updated by the for loop | ||
// and will be the next testCase! | ||
testCase := testCase | ||
|
||
// The actual sub test spawning. We name the sub test using the human friendly name. Note that we name the sub | ||
// test T struct to subT to make it clear which T struct corresponds to which test. However, in most cases you | ||
// will not reference the main test T so you can name it the same. | ||
t.Run(testCase.name, func(subT *testing.T) { | ||
// subT.Parallel() | ||
|
||
// Now we try rendering the template, but verify we get an error | ||
options := &helm.Options{SetValues: testCase.values} | ||
_, err := helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{}) | ||
require.Error(t, err) | ||
}) | ||
} | ||
} |