diff --git a/modules/helm/install.go b/modules/helm/install.go index e7272eb79..e05c00048 100644 --- a/modules/helm/install.go +++ b/modules/helm/install.go @@ -28,6 +28,12 @@ func InstallE(t testing.TestingT, options *Options, chart string, releaseName st chart = absChartDir } + // build chart dependencies + if options.BuildDependencies { + if _, err := RunHelmCommandAndGetOutputE(t, options, "dependency", "build", chart); err != nil { + return errors.WithStackTrace(err) + } + } // Now call out to helm install to install the charts with the provided options // Declare err here so that we can update args later var err error diff --git a/modules/helm/install_test.go b/modules/helm/install_test.go index d0bc6f79c..5c0c4bf5e 100644 --- a/modules/helm/install_test.go +++ b/modules/helm/install_test.go @@ -11,10 +11,13 @@ package helm import ( "crypto/tls" "fmt" + "path/filepath" "strings" "testing" "time" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" @@ -99,6 +102,54 @@ func TestRemoteChartInstall(t *testing.T) { ) } +// Test deployment of helm chart with dependencies. +func TestHelmDependencyInstall(t *testing.T) { + t.Parallel() + + // Path to the helm chart with dependencies which we will test + helmChartPath, err := filepath.Abs("../../examples/helm-dependency-example") + require.NoError(t, err) + + // Custom namespace name. + namespaceName := fmt.Sprintf("helm-dependency-example-%s", strings.ToLower(random.UniqueId())) + + // Setup the kubectl config and context. Here we choose to use the defaults, which is: + // - HOME/.kube/config for the kubectl config file + // - Current context of the kubectl config file + kubectlOptions := k8s.NewKubectlOptions("", "", namespaceName) + + k8s.CreateNamespace(t, kubectlOptions, namespaceName) + defer k8s.DeleteNamespace(t, kubectlOptions, namespaceName) + + // Helm chart deployment options. + options := &Options{ + KubectlOptions: kubectlOptions, + SetValues: map[string]string{ + "containerImageRepo": "nginx", + "containerImageTag": "1.15.8", + "basic.containerImageRepo": "nginx", + "basic.containerImageTag": "1.15.8", + }, + BuildDependencies: true, + } + // We generate a unique release name so that we can refer to after deployment. + // By doing so, we can schedule the delete call here so that at the end of the test, we run + // `helm delete RELEASE_NAME` to clean up any resources that were created. + releaseName := fmt.Sprintf( + "helm-dependency-example-%s", + strings.ToLower(random.UniqueId()), + ) + defer Delete(t, options, releaseName, true) + + // Deploy the chart using `helm install`. + err = InstallE(t, options, helmChartPath, releaseName) + assert.NoError(t, err) + + // Verify that Kubernetes service is available after helm chart deployment. + _, err = k8s.GetServiceE(t, kubectlOptions, releaseName) + assert.NoError(t, err) +} + func waitForRemoteChartPods(t *testing.T, kubectlOptions *k8s.KubectlOptions, releaseName string, podCount int) { // Get pod and wait for it to be avaialable // To get the pod, we need to filter it using the labels that the helm chart creates diff --git a/modules/helm/options.go b/modules/helm/options.go index 7f6f1270e..bafdedea4 100644 --- a/modules/helm/options.go +++ b/modules/helm/options.go @@ -6,15 +6,16 @@ import ( ) type Options struct { - ValuesFiles []string // List of values files to render. - SetValues map[string]string // Values that should be set via the command line. - SetStrValues map[string]string // Values that should be set via the command line explicitly as `string` types. - SetJsonValues map[string]string // Values that should be set via the command line in JSON format. - SetFiles map[string]string // Values that should be set from a file. These should be file paths. Use to avoid logging secrets. - KubectlOptions *k8s.KubectlOptions // KubectlOptions to control how to authenticate to kubernetes cluster. `nil` => use defaults. - HomePath string // The path to the helm home to use when calling out to helm. Empty string means use default ($HOME/.helm). - EnvVars map[string]string // Environment variables to set when running helm - Version string // Version of chart - Logger *logger.Logger // Set a non-default logger that should be used. See the logger package for more info. Use logger.Discard to not print the output while executing the command. - ExtraArgs map[string][]string // Extra arguments to pass to the helm install/upgrade/rollback/delete and helm repo add commands. The key signals the command (e.g., install) while the values are the extra arguments to pass through. + ValuesFiles []string // List of values files to render. + SetValues map[string]string // Values that should be set via the command line. + SetStrValues map[string]string // Values that should be set via the command line explicitly as `string` types. + SetJsonValues map[string]string // Values that should be set via the command line in JSON format. + SetFiles map[string]string // Values that should be set from a file. These should be file paths. Use to avoid logging secrets. + KubectlOptions *k8s.KubectlOptions // KubectlOptions to control how to authenticate to kubernetes cluster. `nil` => use defaults. + HomePath string // The path to the helm home to use when calling out to helm. Empty string means use default ($HOME/.helm). + EnvVars map[string]string // Environment variables to set when running helm + Version string // Version of chart + Logger *logger.Logger // Set a non-default logger that should be used. See the logger package for more info. Use logger.Discard to not print the output while executing the command. + ExtraArgs map[string][]string // Extra arguments to pass to the helm install/upgrade/rollback/delete and helm repo add commands. The key signals the command (e.g., install) while the values are the extra arguments to pass through. + BuildDependencies bool // If true, helm dependencies will be built before rendering template, installing or upgrade the chart. } diff --git a/modules/helm/template.go b/modules/helm/template.go index b70b04dea..f6b3624cf 100644 --- a/modules/helm/template.go +++ b/modules/helm/template.go @@ -35,8 +35,10 @@ func RenderTemplateE(t testing.TestingT, options *Options, chartDir string, rele } // check chart dependencies - if _, err := RunHelmCommandAndGetOutputE(t, options, "dependency", "build", chartDir); err != nil { - return "", errors.WithStackTrace(err) + if options.BuildDependencies { + if _, err := RunHelmCommandAndGetOutputE(t, options, "dependency", "build", chartDir); err != nil { + return "", errors.WithStackTrace(err) + } } // Now construct the args diff --git a/modules/helm/upgrade.go b/modules/helm/upgrade.go index d44aa45f2..13ccb3ae6 100644 --- a/modules/helm/upgrade.go +++ b/modules/helm/upgrade.go @@ -27,6 +27,12 @@ func UpgradeE(t testing.TestingT, options *Options, chart string, releaseName st chart = absChartDir } + // build chart dependencies + if options.BuildDependencies { + if _, err := RunHelmCommandAndGetOutputE(t, options, "dependency", "build", chart); err != nil { + return errors.WithStackTrace(err) + } + } var err error args := []string{} if options.ExtraArgs != nil { diff --git a/modules/helm/upgrade_test.go b/modules/helm/upgrade_test.go index afef4f7c4..c4da70da9 100644 --- a/modules/helm/upgrade_test.go +++ b/modules/helm/upgrade_test.go @@ -11,10 +11,14 @@ package helm import ( "crypto/tls" "fmt" + "path/filepath" "strings" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + http_helper "github.com/gruntwork-io/terratest/modules/http-helper" "github.com/gruntwork-io/terratest/modules/k8s" "github.com/gruntwork-io/terratest/modules/random" @@ -95,3 +99,51 @@ func TestRemoteChartInstallUpgradeRollback(t *testing.T) { Rollback(t, options, releaseName, "") waitForRemoteChartPods(t, kubectlOptions, releaseName, 1) } + +// Test deployment of helm chart with dependencies. +func TestHelmDependencyUpgrade(t *testing.T) { + t.Parallel() + + // Path to the helm chart with dependencies which we will test + helmChartPath, err := filepath.Abs("../../examples/helm-dependency-example") + require.NoError(t, err) + + // Custom namespace name. + namespaceName := fmt.Sprintf("helm-dependency-example-%s", strings.ToLower(random.UniqueId())) + + // Setup the kubectl config and context. Here we choose to use the defaults, which is: + // - HOME/.kube/config for the kubectl config file + // - Current context of the kubectl config file + kubectlOptions := k8s.NewKubectlOptions("", "", namespaceName) + + k8s.CreateNamespace(t, kubectlOptions, namespaceName) + defer k8s.DeleteNamespace(t, kubectlOptions, namespaceName) + + // Helm chart deployment options. + options := &Options{ + KubectlOptions: kubectlOptions, + SetValues: map[string]string{ + "containerImageRepo": "nginx", + "containerImageTag": "1.15.8", + "basic.containerImageRepo": "nginx", + "basic.containerImageTag": "1.15.8", + }, + BuildDependencies: true, + } + // We generate a unique release name so that we can refer to after deployment. + // By doing so, we can schedule the delete call here so that at the end of the test, we run + // `helm delete RELEASE_NAME` to clean up any resources that were created. + releaseName := fmt.Sprintf( + "helm-dependency-example-%s", + strings.ToLower(random.UniqueId()), + ) + defer Delete(t, options, releaseName, true) + + // Deploy the chart using `helm install`. + err = InstallE(t, options, helmChartPath, releaseName) + assert.NoError(t, err) + + // Verify that upgrade is working as expected. + err = UpgradeE(t, options, helmChartPath, releaseName) + assert.NoError(t, err) +} diff --git a/test/helm_dependency_example_template_test.go b/test/helm_dependency_example_template_test.go index 73125579b..d751c2644 100644 --- a/test/helm_dependency_example_template_test.go +++ b/test/helm_dependency_example_template_test.go @@ -57,7 +57,8 @@ func TestHelmDependencyExampleTemplateRenderedDeployment(t *testing.T) { "basic.containerImageRepo": "nginx", "basic.containerImageTag": "1.15.8", }, - KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + BuildDependencies: true, } testCases := []struct {