Repository: gruntwork-io/terratest (language: go)

Issue:
Disable helm dependency build on template rendering
Hi,

I'm running a loop rendering more than a few templates and on each 'helm dependency build' command is executed which slows down the execution of tests. Here I see: 

https://github.com/gruntwork-io/terratest/blob/0dd0f81e8cee879995a10660f6e084d9334a8e53/modules/helm/template.go#L38

Could we have an option to disable this and in that case I could probably do something like this:

`helm.RenderTemplate(s.T(), options, s.ChartPath, s.Release, s.Templates, "--dependency-update")`

This will download dependencies only once. 

Is there an another way to solve this issue that I'm not aware off? 

There is one similar issue already created here https://github.com/gruntwork-io/terratest/issues/342.



Discussion / hints:
In our case our test cases are now failing, since we download dependencies from a private Helm repository before executing the tests. It would be great if this feature was optional so we don't have to modify all our tests to be able to pull from our private repositories.
I agree, there are similar issues already: https://github.com/gruntwork-io/terratest/issues/1193#issuecomment-1434782474
I'm cross-posting this from #1193.  To further address the "default behavior" concern, should not the default behavior of Template and Install have some parity?

Even more strange is the helm.Install workflow does not do ANY dep build; the user must do this before installing (typically for an integration test).  It is counterintuitive to have the Template workflow forcibly do the helm dep build vs. the Install workflow not doing it.  My preferred solution would be to have the Template workflow omit the dep build, and I'd suggest the user is instructed to do the dep build before any Template workflow as it is with the Install workflow.

Fix patch:
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/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 {


Test patch:
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/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 {