Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helm integration test framework #237

Merged
merged 18 commits into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 61 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ setup_minikube: &setup_minikube
mkdir -p ${HOME}/.kube
touch ${HOME}/.kube/config

# install helm
curl -Lo helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz
tar -xvf helm.tar.gz
chmod +x linux-amd64/helm
sudo mv linux-amd64/helm /usr/local/bin/

# Install minikube
curl -Lo minikube https://github.com/kubernetes/minikube/releases/download/${MINIKUBE_VERSION}/minikube-linux-amd64
chmod +x minikube
Expand All @@ -59,6 +53,22 @@ setup_minikube: &setup_minikube
)


install_helm: &install_helm
name: install helm
command: |
# install helm
curl -Lo helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz
tar -xvf helm.tar.gz
chmod +x linux-amd64/helm
sudo mv linux-amd64/helm /usr/local/bin/

# Grant the default service account cluster admin rights so helm works
kubectl create clusterrolebinding default-service-account-cluster-admin-binding --clusterrole cluster-admin --serviceaccount kube-system:default

# Deploy Tiller
helm init --wait


install_gruntwork_utils: &install_gruntwork_utils
name: install gruntwork utils
command: |
Expand Down Expand Up @@ -176,7 +186,43 @@ jobs:
# Run the unit tests first, then the integration tests. They are separate because the integration tests
# require additional filtering.
run-go-tests --packages "-tags kubernetes ./modules/k8s" | tee /tmp/logs/test_output.log
run-go-tests --packages "-tags kubernetes -run \"(TestKubernetes)|(TestHelm)\" ./test" | tee -a /tmp/logs/test_output.log
run-go-tests --packages "-tags kubernetes -run TestKubernetes ./test" | tee -a /tmp/logs/test_output.log

- run:
command: ./cmd/bin/terratest_log_parser_linux_amd64 --testlog /tmp/logs/test_output.log --outputdir /tmp/logs
when: always

# Store test result and log artifacts for browsing purposes
- store_artifacts:
path: /tmp/logs
- store_test_results:
path: /tmp/logs


helm_test:
<<: *defaults
steps:
- attach_workspace:
at: /home/circleci

- run:
<<: *install_gruntwork_utils

# The weird way you have to set PATH in Circle 2.0
- run: echo 'export PATH=$HOME/.local/bin:$HOME/terraform:$HOME/packer:$PATH' >> $BASH_ENV

- run:
<<: *setup_minikube

- run:
<<: *install_helm

# Run the Helm tests. These tests are run because the helm build tag is included, and we explicitly
# select the helm tests
- run:
command: |
mkdir -p /tmp/logs
run-go-tests --packages "-tags helm -run TestHelm ./test" | tee -a /tmp/logs/test_output.log

- run:
command: ./cmd/bin/terratest_log_parser_linux_amd64 --testlog /tmp/logs/test_output.log --outputdir /tmp/logs
Expand Down Expand Up @@ -223,10 +269,18 @@ workflows:
tags:
only: /^v.*/

- helm_test:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this run in parallel?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requires:
- setup
filters:
tags:
only: /^v.*/

- deploy:
requires:
- test
- kubernetes_test
- helm_test
filters:
tags:
only: /^v.*/
Expand Down
47 changes: 45 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 13 additions & 8 deletions examples/helm-basic-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,27 @@ There are two kinds of tests you can perform on a helm chart:
works as expected. If you consider the templates to be syntactic tests, these are semantic tests that validate the
behavior of the deployed resources.

The helm chart deploys a single replica Deployment resource given the container image spec. This chart requires the
`containerImageRepo` and `containerImageTag` input values.
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.
<!-- TODO: Append the example with integration tests and deployment instructions once terratest has deploy test functions -->
- [helm_basic_example_integration_test.go](/test/helm_basic_example_integration_test.go): the integration test for this
chart. This test will deploy the Helm Chart and verify the `Service` endpoint.

## 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 kubernetes -run TestHelmBasicExampleTemplate`

**NOTE:** we have build tags to differentiate kubernetes tests from non-kubernetes tests. This is done because minikube
is heavy and can interfere with docker related tests in terratest. To avoid overloading the system, we run the
kubernetes tests separately from the others.
1. `go test -v -tags helm -run TestHelmBasicExampleTemplate` for the template test
1. `go test -v -tags helm -run TestHelmBasicExampleDeployment` for the integration 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.
2 changes: 2 additions & 0 deletions examples/helm-basic-example/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ spec:
{{- $repo := required "containerImageRepo is required" .Values.containerImageRepo }}
{{- $tag := required "containerImageTag is required" .Values.containerImageTag }}
image: "{{ $repo }}:{{ $tag }}"
ports:
- containerPort: 80
20 changes: 20 additions & 0 deletions examples/helm-basic-example/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "helm-basic-example.fullname" . }}
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-basic-example.chart" . }}
app.kubernetes.io/name: {{ include "helm-basic-example.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
selector:
app.kubernetes.io/name: {{ include "helm-basic-example.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
type: NodePort
ports:
- protocol: TCP
targetPort: 80
port: 80
23 changes: 11 additions & 12 deletions modules/helm/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import (
"github.com/gruntwork-io/terratest/modules/shell"
)

// GetCommonArgs extracts common helm options. In this case, these are:
// getCommonArgs extracts common helm options. In this case, these are:
// - kubeconfig path
// - kubeconfig context
// - namespace
// - helm home path
func getCommonArgs(options *Options, args ...string) []string {
if options.KubectlOptions != nil && options.KubectlOptions.ContextName != "" {
Expand All @@ -19,34 +18,34 @@ func getCommonArgs(options *Options, args ...string) []string {
if options.KubectlOptions != nil && options.KubectlOptions.ConfigPath != "" {
args = append(args, "--kubeconfig", options.KubectlOptions.ConfigPath)
}
if options.KubectlOptions != nil && options.KubectlOptions.Namespace != "" {
args = append(args, "--namespace", options.KubectlOptions.Namespace)
}
if options.HomePath != "" {
args = append(args, "--home", options.HomePath)
}
return args
}

// RunHelmCommandAndGetOutputE runs helm with the given arguments and options and returns stdout/stderr.
func RunHelmCommandAndGetOutputE(t *testing.T, options *Options, cmd string, additionalArgs ...string) (string, error) {
args := []string{cmd}
args = getCommonArgs(options, args...)

// getValuesArgsE computes the args to pass in for setting values
func getValuesArgsE(t *testing.T, options *Options, args ...string) ([]string, error) {
args = append(args, formatSetValuesAsArgs(options.SetValues)...)

valuesFilesArgs, err := formatValuesFilesAsArgsE(t, options.ValuesFiles)
if err != nil {
return "", errors.WithStackTrace(err)
return args, errors.WithStackTrace(err)
}
args = append(args, valuesFilesArgs...)

setFilesArgs, err := formatSetFilesAsArgsE(t, options.SetFiles)
if err != nil {
return "", errors.WithStackTrace(err)
return args, errors.WithStackTrace(err)
}
args = append(args, setFilesArgs...)
return args, nil
}

// RunHelmCommandAndGetOutputE runs helm with the given arguments and options and returns stdout/stderr.
func RunHelmCommandAndGetOutputE(t *testing.T, options *Options, cmd string, additionalArgs ...string) (string, error) {
args := []string{cmd}
args = getCommonArgs(options, args...)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refactor was necessary because only install and upgrade take the --set, --set-file, and -f options.

args = append(args, additionalArgs...)

helmCmd := shell.Command{
Expand Down
25 changes: 25 additions & 0 deletions modules/helm/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package helm

import (
"testing"

"github.com/stretchr/testify/require"
)

// Delete will delete the provided release from Tiller. If you set purge to true, Tiller will delete the release object
// as well so that the release name can be reused. This will fail the test if there is an error.
func Delete(t *testing.T, options *Options, releaseName string, purge bool) {
require.NoError(t, DeleteE(t, options, releaseName, purge))
}

// DeleteE will delete the provided release from Tiller. If you set purge to true, Tiller will delete the release object
// as well so that the release name can be reused.
func DeleteE(t *testing.T, options *Options, releaseName string, purge bool) error {
args := []string{}
if purge {
args = append(args, "--purge")
}
args = append(args, releaseName)
_, err := RunHelmCommandAndGetOutputE(t, options, "delete", args...)
return err
}
9 changes: 9 additions & 0 deletions modules/helm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,12 @@ type TemplateFileNotFoundError struct {
func (err TemplateFileNotFoundError) Error() string {
return fmt.Sprintf("Could not resolve template file %s relative to chart path %s", err.Path, err.ChartDir)
}

// ChartNotFoundError is returned when a provided chart dir is not found
type ChartNotFoundError struct {
Path string
}

func (err ChartNotFoundError) Error() string {
return fmt.Sprintf("Could not chart path %s", err.Path)
}
Loading