From c3ca75da18c6c919a0c108b8e08bf5a66bea6a3a Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Mon, 18 Mar 2024 17:21:51 +0100 Subject: [PATCH 01/24] Add `bundle dependencies` command - Add `bundle dependencies` command. It prints versions of terraform and databricks terraform provider. - Use DATABRICKS_TF_EXEC_PATH env var to point databricks CLI to terraform bin - Use DATABRICKS_TF_PLUGIN_CACHE_DIR env var to point terraform CLI to the plugin cache dir --- bundle/deploy/terraform/init.go | 17 ++++++- bundle/deploy/terraform/pkg.go | 23 +++++++++ .../tf/codegen/templates/root.go.tmpl | 8 +++- bundle/internal/tf/schema/root.go | 8 +++- cmd/bundle/bundle.go | 1 + cmd/bundle/dependencies.go | 48 +++++++++++++++++++ 6 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 cmd/bundle/dependencies.go diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index 503a1db24f..4302158b64 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -14,7 +14,6 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" - "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/product" "github.com/hashicorp/hc-install/releases" "github.com/hashicorp/terraform-exec/tfexec" @@ -28,6 +27,14 @@ func (m *initialize) Name() string { } func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform) (string, error) { + // Load exec path from the env var if it is not set. + if tf.ExecPath == "" { + envExecPath, ok := env.Lookup(ctx, "DATABRICKS_TF_EXEC_PATH") + if ok { + tf.ExecPath = envExecPath + } + } + // If set, pass it through [exec.LookPath] to resolve its absolute path. if tf.ExecPath != "" { execPath, err := exec.LookPath(tf.ExecPath) @@ -59,7 +66,7 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con // Download Terraform to private bin directory. installer := &releases.ExactVersion{ Product: product.Terraform, - Version: version.Must(version.NewVersion("1.5.5")), + Version: TerraformVersion, InstallDir: binDir, Timeout: 1 * time.Minute, } @@ -102,6 +109,12 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { environ["TF_CLI_CONFIG_FILE"] = configFile } + // This lets us use pre-downloaded Databricks plugin.for Terraform to usefor Terraform to use.. + cacheDir, ok := env.Lookup(ctx, "DATABRICKS_TF_PLUGIN_CACHE_DIR") + if ok { + environ["TF_PLUGIN_CACHE_DIR"] = cacheDir + } + return nil } diff --git a/bundle/deploy/terraform/pkg.go b/bundle/deploy/terraform/pkg.go index 2d9293d1b2..dea6cbcea7 100644 --- a/bundle/deploy/terraform/pkg.go +++ b/bundle/deploy/terraform/pkg.go @@ -1,4 +1,27 @@ package terraform +import ( + "github.com/databricks/cli/bundle/internal/tf/schema" + "github.com/hashicorp/go-version" +) + const TerraformStateFileName = "terraform.tfstate" const TerraformConfigFileName = "bundle.tf.json" + +var TerraformVersion = version.Must(version.NewVersion("1.5.5")) + +type TerraformMetadata struct { + Version string `json:"version"` + ProviderHost string `json:"provider_host"` + ProviderSource string `json:"provider_source"` + ProviderVersion string `json:"provider_version"` +} + +func NewTerraformMetadata() *TerraformMetadata { + return &TerraformMetadata{ + Version: TerraformVersion.String(), + ProviderHost: schema.ProviderHost, + ProviderSource: schema.ProviderSource, + ProviderVersion: schema.ProviderVersion, + } +} diff --git a/bundle/internal/tf/codegen/templates/root.go.tmpl b/bundle/internal/tf/codegen/templates/root.go.tmpl index 57fa71299e..e03e978f08 100644 --- a/bundle/internal/tf/codegen/templates/root.go.tmpl +++ b/bundle/internal/tf/codegen/templates/root.go.tmpl @@ -19,13 +19,17 @@ type Root struct { Resource *Resources `json:"resource,omitempty"` } +const ProviderHost = "registry.terraform.io" +const ProviderSource = "databricks/databricks" +const ProviderVersion = "{{ .ProviderVersion }}" + func NewRoot() *Root { return &Root{ Terraform: map[string]interface{}{ "required_providers": map[string]interface{}{ "databricks": map[string]interface{}{ - "source": "databricks/databricks", - "version": "{{ .ProviderVersion }}", + "source": ProviderSource, + "version": ProviderVersion, }, }, }, diff --git a/bundle/internal/tf/schema/root.go b/bundle/internal/tf/schema/root.go index f0253c2853..1f1f7ef19b 100644 --- a/bundle/internal/tf/schema/root.go +++ b/bundle/internal/tf/schema/root.go @@ -19,13 +19,17 @@ type Root struct { Resource *Resources `json:"resource,omitempty"` } +const ProviderHost = "registry.terraform.io" +const ProviderSource = "databricks/databricks" +const ProviderVersion = "1.37.0" + func NewRoot() *Root { return &Root{ Terraform: map[string]interface{}{ "required_providers": map[string]interface{}{ "databricks": map[string]interface{}{ - "source": "databricks/databricks", - "version": "1.37.0", + "source": ProviderSource, + "version": ProviderVersion, }, }, }, diff --git a/cmd/bundle/bundle.go b/cmd/bundle/bundle.go index 43a9ef680f..6d54198e58 100644 --- a/cmd/bundle/bundle.go +++ b/cmd/bundle/bundle.go @@ -24,6 +24,7 @@ func New() *cobra.Command { cmd.AddCommand(newValidateCommand()) cmd.AddCommand(newInitCommand()) cmd.AddCommand(newSummaryCommand()) + cmd.AddCommand(newDependenciesCommand()) cmd.AddCommand(newGenerateCommand()) cmd.AddCommand(deployment.NewDeploymentCommand()) return cmd diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/dependencies.go new file mode 100644 index 0000000000..514c1c7cd1 --- /dev/null +++ b/cmd/bundle/dependencies.go @@ -0,0 +1,48 @@ +package bundle + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/databricks/cli/bundle/deploy/terraform" + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/flags" + "github.com/spf13/cobra" +) + +type Dependencies struct { + Terraform *terraform.TerraformMetadata `json:"terraform"` +} + +func newDependenciesCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "dependencies", + Short: "Prints CLI dependencies required for the bundle namespace", + Args: root.NoArgs, + // This command is currently intended for the Databricks VSCode extension only + Hidden: true, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + dependencies := &Dependencies{ + Terraform: terraform.NewTerraformMetadata(), + } + switch root.OutputType(cmd) { + case flags.OutputText: + return fmt.Errorf("%w, only json output is supported", errors.ErrUnsupported) + case flags.OutputJSON: + buf, err := json.MarshalIndent(dependencies, "", " ") + if err != nil { + return err + } + cmd.OutOrStdout().Write(buf) + default: + return fmt.Errorf("unknown output type %s", root.OutputType(cmd)) + } + + return nil + } + + return cmd +} From e8ec958fb77830032a3dcce37a8497ad33db71b9 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 19 Mar 2024 10:53:20 +0100 Subject: [PATCH 02/24] Check versions and files/dirs --- bundle/deploy/terraform/init.go | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index 4302158b64..776f5e0cd3 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -27,11 +27,18 @@ func (m *initialize) Name() string { } func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform) (string, error) { - // Load exec path from the env var if it is not set. - if tf.ExecPath == "" { - envExecPath, ok := env.Lookup(ctx, "DATABRICKS_TF_EXEC_PATH") - if ok { + // Load exec path from the environment if it matches the currently used version. + envTFVersion := env.Get(ctx, "DATABRICKS_TF_VERSION") + envExecPath := env.Get(ctx, "DATABRICKS_TF_EXEC_PATH") + if envExecPath != "" && envTFVersion == TerraformVersion.String() { + _, err := os.Stat(envExecPath) + if err != nil && !os.IsNotExist(err) { + return "", err + } + if err == nil { tf.ExecPath = envExecPath + log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath) + return tf.ExecPath, nil } } @@ -109,10 +116,18 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { environ["TF_CLI_CONFIG_FILE"] = configFile } - // This lets us use pre-downloaded Databricks plugin.for Terraform to usefor Terraform to use.. + // Map $DATABRICKS_TF_PLUGIN_CACHE_DIR to $TF_PLUGIN_CACHE_DIR + // This lets us use pre-downloaded Databricks plugins cacheDir, ok := env.Lookup(ctx, "DATABRICKS_TF_PLUGIN_CACHE_DIR") if ok { - environ["TF_PLUGIN_CACHE_DIR"] = cacheDir + _, err := os.Stat(cacheDir) + if err != nil && !os.IsNotExist(err) { + return err + } + if err == nil { + log.Debugf(ctx, "Using Terraform plugin cache dir: %s", cacheDir) + environ["TF_PLUGIN_CACHE_DIR"] = cacheDir + } } return nil From aff7024354264f71af2c7711ed3e2e5698bcd5eb Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 19 Mar 2024 10:55:46 +0100 Subject: [PATCH 03/24] Update log message --- bundle/deploy/terraform/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index 776f5e0cd3..df755da401 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -37,7 +37,7 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con } if err == nil { tf.ExecPath = envExecPath - log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath) + log.Debugf(ctx, "Using Terraform from the environment at %s", tf.ExecPath) return tf.ExecPath, nil } } From d99a00315f32680992114dd6232f0f5c08493d13 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 19 Mar 2024 11:22:12 +0100 Subject: [PATCH 04/24] Add tests for TF_PLUGIN_CACHE_DIR --- bundle/deploy/terraform/init.go | 2 +- bundle/deploy/terraform/init_test.go | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index df755da401..40e4ae3d9a 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -117,7 +117,7 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { } // Map $DATABRICKS_TF_PLUGIN_CACHE_DIR to $TF_PLUGIN_CACHE_DIR - // This lets us use pre-downloaded Databricks plugins + // This lets terraform use pre-downloaded Databricks plugins cacheDir, ok := env.Lookup(ctx, "DATABRICKS_TF_PLUGIN_CACHE_DIR") if ok { _, err := os.Stat(cacheDir) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 4b00e18e47..ff5a6afc8b 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -269,3 +269,20 @@ func TestSetUserProfileFromInheritEnvVars(t *testing.T) { assert.Contains(t, env, "USERPROFILE") assert.Equal(t, env["USERPROFILE"], "c:\\foo\\c") } + +func TestInheritEnvVarsWithAbsentPluginsCacheDir(t *testing.T) { + env := map[string]string{} + t.Setenv("DATABRICKS_TF_PLUGIN_CACHE_DIR", "/tmp/cache") + err := inheritEnvVars(context.Background(), env) + require.NoError(t, err) + require.NotContains(t, env, "TF_PLUGIN_CACHE_DIR") +} + +func TestInheritEnvVarsWithRealPluginsCacheDir(t *testing.T) { + env := map[string]string{} + dir := t.TempDir() + t.Setenv("DATABRICKS_TF_PLUGIN_CACHE_DIR", dir) + err := inheritEnvVars(context.Background(), env) + require.NoError(t, err) + require.Equal(t, env["TF_PLUGIN_CACHE_DIR"], dir) +} From 8a3b0b7edd749e4399944fe65bc75aa234fc66f3 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 19 Mar 2024 11:45:32 +0100 Subject: [PATCH 05/24] Add tests for DATABRICKS_TF_EXEC_PATH --- bundle/deploy/terraform/init_test.go | 66 +++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index ff5a6afc8b..706af9c4f3 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "os/exec" + "path/filepath" "runtime" "strings" "testing" @@ -284,5 +285,68 @@ func TestInheritEnvVarsWithRealPluginsCacheDir(t *testing.T) { t.Setenv("DATABRICKS_TF_PLUGIN_CACHE_DIR", dir) err := inheritEnvVars(context.Background(), env) require.NoError(t, err) - require.Equal(t, env["TF_PLUGIN_CACHE_DIR"], dir) + require.Equal(t, dir, env["TF_PLUGIN_CACHE_DIR"]) +} + +func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { + m := &initialize{} + b := &bundle.Bundle{ + Config: config.Root{ + Path: t.TempDir(), + Bundle: config.Bundle{ + Target: "whatever", + Terraform: &config.Terraform{ + ExecPath: "terraform", + }, + }, + }, + } + t.Setenv("DATABRICKS_TF_VERSION", "1.2.3") + t.Setenv("DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") + _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + require.NoError(t, err) + require.NotEqual(t, "/tmp/terraform", b.Config.Bundle.Terraform.ExecPath) +} + +func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) { + m := &initialize{} + b := &bundle.Bundle{ + Config: config.Root{ + Path: t.TempDir(), + Bundle: config.Bundle{ + Target: "whatever", + Terraform: &config.Terraform{ + ExecPath: "terraform", + }, + }, + }, + } + t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) + t.Setenv("DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") + _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + require.NoError(t, err) + require.NotEqual(t, "/tmp/terraform", b.Config.Bundle.Terraform.ExecPath) +} + +func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { + m := &initialize{} + b := &bundle.Bundle{ + Config: config.Root{ + Path: t.TempDir(), + Bundle: config.Bundle{ + Target: "whatever", + Terraform: &config.Terraform{ + ExecPath: "terraform", + }, + }, + }, + } + tmpBinPath := filepath.Join(t.TempDir(), "terraform") + _, err := os.Create(tmpBinPath) + require.NoError(t, err) + t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) + t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) + _, err = m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + require.NoError(t, err) + require.Equal(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) } From c157a5c594a7335c6ed8c5772e6fbc5392559ea3 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 19 Mar 2024 11:57:47 +0100 Subject: [PATCH 06/24] Improve tests --- bundle/deploy/terraform/init_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 706af9c4f3..6fb12b6181 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -301,11 +301,14 @@ func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { }, }, } + tmpBinPath := filepath.Join(t.TempDir(), "terraform") + _, err := os.Create(tmpBinPath) + require.NoError(t, err) t.Setenv("DATABRICKS_TF_VERSION", "1.2.3") - t.Setenv("DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") - _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) + _, err = m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) require.NoError(t, err) - require.NotEqual(t, "/tmp/terraform", b.Config.Bundle.Terraform.ExecPath) + require.NotEqual(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) } func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) { From f6142ce5a6463f4fd6e28226408f10101a432c0e Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Wed, 20 Mar 2024 12:43:14 +0100 Subject: [PATCH 07/24] Do not override existing execPath from the bundle --- bundle/deploy/terraform/init.go | 22 ++++----- bundle/deploy/terraform/init_test.go | 68 ++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index 40e4ae3d9a..7857802b4e 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -27,6 +27,17 @@ func (m *initialize) Name() string { } func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform) (string, error) { + // If set, pass it through [exec.LookPath] to resolve its absolute path. + if tf.ExecPath != "" { + execPath, err := exec.LookPath(tf.ExecPath) + if err != nil { + return "", err + } + tf.ExecPath = execPath + log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath) + return tf.ExecPath, nil + } + // Load exec path from the environment if it matches the currently used version. envTFVersion := env.Get(ctx, "DATABRICKS_TF_VERSION") envExecPath := env.Get(ctx, "DATABRICKS_TF_EXEC_PATH") @@ -42,17 +53,6 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con } } - // If set, pass it through [exec.LookPath] to resolve its absolute path. - if tf.ExecPath != "" { - execPath, err := exec.LookPath(tf.ExecPath) - if err != nil { - return "", err - } - tf.ExecPath = execPath - log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath) - return tf.ExecPath, nil - } - binDir, err := b.CacheDir(context.Background(), "bin") if err != nil { return "", err diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 6fb12b6181..77453664f5 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/hashicorp/hc-install/product" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -288,27 +289,34 @@ func TestInheritEnvVarsWithRealPluginsCacheDir(t *testing.T) { require.Equal(t, dir, env["TF_PLUGIN_CACHE_DIR"]) } +func createTerraformBinary(t *testing.T, dest string) string { + binPath := filepath.Join(dest, product.Terraform.BinaryName()) + _, err := os.Create(binPath) + require.NoError(t, err) + return binPath +} + func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { m := &initialize{} b := &bundle.Bundle{ Config: config.Root{ Path: t.TempDir(), Bundle: config.Bundle{ - Target: "whatever", - Terraform: &config.Terraform{ - ExecPath: "terraform", - }, + Target: "whatever", + Terraform: &config.Terraform{}, }, }, } - tmpBinPath := filepath.Join(t.TempDir(), "terraform") - _, err := os.Create(tmpBinPath) - require.NoError(t, err) + // Create a pre-existing terraform bin to avoid downloading it + cacheDir, _ := b.CacheDir(context.Background(), "bin") + existingExecPath := createTerraformBinary(t, cacheDir) + // Create a new terraform binary and expose it through env vars + tmpBinPath := createTerraformBinary(t, t.TempDir()) t.Setenv("DATABRICKS_TF_VERSION", "1.2.3") t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) - _, err = m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) require.NoError(t, err) - require.NotEqual(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) + require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) { @@ -317,21 +325,23 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) Config: config.Root{ Path: t.TempDir(), Bundle: config.Bundle{ - Target: "whatever", - Terraform: &config.Terraform{ - ExecPath: "terraform", - }, + Target: "whatever", + Terraform: &config.Terraform{}, }, }, } + // Create a pre-existing terraform bin to avoid downloading it + cacheDir, _ := b.CacheDir(context.Background(), "bin") + existingExecPath := createTerraformBinary(t, cacheDir) + t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) t.Setenv("DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) require.NoError(t, err) - require.NotEqual(t, "/tmp/terraform", b.Config.Bundle.Terraform.ExecPath) + require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } -func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { +func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinaryAndAlreadySetExecPath(t *testing.T) { m := &initialize{} b := &bundle.Bundle{ Config: config.Root{ @@ -344,12 +354,34 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { }, }, } - tmpBinPath := filepath.Join(t.TempDir(), "terraform") - _, err := os.Create(tmpBinPath) + // Create a new terraform binary and expose it through env vars + tmpBinPath := createTerraformBinary(t, t.TempDir()) + t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) + t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) + _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) require.NoError(t, err) + require.NotEqual(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) +} + +func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { + m := &initialize{} + b := &bundle.Bundle{ + Config: config.Root{ + Path: t.TempDir(), + Bundle: config.Bundle{ + Target: "whatever", + Terraform: &config.Terraform{}, + }, + }, + } + // Create a pre-existing terraform bin to avoid downloading it + cacheDir, _ := b.CacheDir(context.Background(), "bin") + createTerraformBinary(t, cacheDir) + // Create a new terraform binary and expose it through env vars + tmpBinPath := createTerraformBinary(t, t.TempDir()) t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) - _, err = m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) require.NoError(t, err) require.Equal(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) } From 91924ed228a1bfd3ef67074f14a1ce84ba3e0bd2 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Wed, 20 Mar 2024 13:49:34 +0100 Subject: [PATCH 08/24] Improve tests --- bundle/deploy/terraform/init_test.go | 30 +++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 77453664f5..01a80c9e13 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -289,9 +289,11 @@ func TestInheritEnvVarsWithRealPluginsCacheDir(t *testing.T) { require.Equal(t, dir, env["TF_PLUGIN_CACHE_DIR"]) } -func createTerraformBinary(t *testing.T, dest string) string { - binPath := filepath.Join(dest, product.Terraform.BinaryName()) - _, err := os.Create(binPath) +func createTerraformBinary(t *testing.T, dest string, name string) string { + binPath := filepath.Join(dest, name) + f, err := os.Create(binPath) + require.NoError(t, err) + err = f.Chmod(0755) require.NoError(t, err) return binPath } @@ -309,9 +311,9 @@ func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { } // Create a pre-existing terraform bin to avoid downloading it cacheDir, _ := b.CacheDir(context.Background(), "bin") - existingExecPath := createTerraformBinary(t, cacheDir) + existingExecPath := createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) // Create a new terraform binary and expose it through env vars - tmpBinPath := createTerraformBinary(t, t.TempDir()) + tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") t.Setenv("DATABRICKS_TF_VERSION", "1.2.3") t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) @@ -332,7 +334,7 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) } // Create a pre-existing terraform bin to avoid downloading it cacheDir, _ := b.CacheDir(context.Background(), "bin") - existingExecPath := createTerraformBinary(t, cacheDir) + existingExecPath := createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) t.Setenv("DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") @@ -347,20 +349,20 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinaryAndAlreadySetExec Config: config.Root{ Path: t.TempDir(), Bundle: config.Bundle{ - Target: "whatever", - Terraform: &config.Terraform{ - ExecPath: "terraform", - }, + Target: "whatever", + Terraform: &config.Terraform{}, }, }, } + existingExecPath := createTerraformBinary(t, t.TempDir(), "terraform-existing") + b.Config.Bundle.Terraform.ExecPath = existingExecPath // Create a new terraform binary and expose it through env vars - tmpBinPath := createTerraformBinary(t, t.TempDir()) + tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) require.NoError(t, err) - require.NotEqual(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) + require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { @@ -376,9 +378,9 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { } // Create a pre-existing terraform bin to avoid downloading it cacheDir, _ := b.CacheDir(context.Background(), "bin") - createTerraformBinary(t, cacheDir) + createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) // Create a new terraform binary and expose it through env vars - tmpBinPath := createTerraformBinary(t, t.TempDir()) + tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) From 55c7808a15840e5bc3ae8fbfe98c557724756008 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Wed, 20 Mar 2024 14:14:22 +0100 Subject: [PATCH 09/24] Close terraform binary file after creation --- bundle/deploy/terraform/init_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 01a80c9e13..338e62f4db 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -293,6 +293,10 @@ func createTerraformBinary(t *testing.T, dest string, name string) string { binPath := filepath.Join(dest, name) f, err := os.Create(binPath) require.NoError(t, err) + defer func() { + err = f.Close() + require.NoError(t, err) + }() err = f.Chmod(0755) require.NoError(t, err) return binPath From 1e549a4bc9ca342102538fb3d9e6d4c26e16eae0 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Wed, 20 Mar 2024 16:39:02 +0100 Subject: [PATCH 10/24] Add debug logs, improve tests --- bundle/deploy/terraform/init.go | 28 +++++++++++++++------- bundle/deploy/terraform/init_test.go | 35 ++++++++++++++++------------ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index 7857802b4e..e4f2ccf39d 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -41,16 +41,24 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con // Load exec path from the environment if it matches the currently used version. envTFVersion := env.Get(ctx, "DATABRICKS_TF_VERSION") envExecPath := env.Get(ctx, "DATABRICKS_TF_EXEC_PATH") - if envExecPath != "" && envTFVersion == TerraformVersion.String() { - _, err := os.Stat(envExecPath) - if err != nil && !os.IsNotExist(err) { - return "", err - } - if err == nil { - tf.ExecPath = envExecPath - log.Debugf(ctx, "Using Terraform from the environment at %s", tf.ExecPath) - return tf.ExecPath, nil + if envExecPath != "" && envTFVersion != "" { + if envTFVersion == TerraformVersion.String() { + _, err := os.Stat(envExecPath) + if err != nil && !os.IsNotExist(err) { + return "", err + } + if err == nil { + tf.ExecPath = envExecPath + log.Debugf(ctx, "Using Terraform from DATABRICKS_TF_EXEC_PATH at %s", tf.ExecPath) + return tf.ExecPath, nil + } else { + log.Debugf(ctx, "Terraform at %s not found, ignoring DATABRICKS_TF_EXEC_PATH", envExecPath) + } + } else { + log.Debugf(ctx, "DATABRICKS_TF_VERSION %s does not match the current version %s, ignoring DATABRICKS_TF_EXEC_PATH", envTFVersion, TerraformVersion.String()) } + } else { + log.Debugf(ctx, "DATABRICKS_TF_EXEC_PATH and DATABRICKS_TF_VERSION aren't defined") } binDir, err := b.CacheDir(context.Background(), "bin") @@ -127,6 +135,8 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { if err == nil { log.Debugf(ctx, "Using Terraform plugin cache dir: %s", cacheDir) environ["TF_PLUGIN_CACHE_DIR"] = cacheDir + } else { + log.Debugf(ctx, "Terraform plugin cache dir %s doesn't exist, ignoring DATABRICKS_TF_PLUGIN_CACHE_DIR", cacheDir) } } diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 338e62f4db..d19063cb30 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/env" "github.com/hashicorp/hc-install/product" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -303,6 +304,7 @@ func createTerraformBinary(t *testing.T, dest string, name string) string { } func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { + ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ Config: config.Root{ @@ -314,18 +316,19 @@ func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { }, } // Create a pre-existing terraform bin to avoid downloading it - cacheDir, _ := b.CacheDir(context.Background(), "bin") + cacheDir, _ := b.CacheDir(ctx, "bin") existingExecPath := createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) // Create a new terraform binary and expose it through env vars tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") - t.Setenv("DATABRICKS_TF_VERSION", "1.2.3") - t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) - _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", "1.2.3") + ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) + _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) require.NoError(t, err) require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) { + ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ Config: config.Root{ @@ -337,17 +340,18 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) }, } // Create a pre-existing terraform bin to avoid downloading it - cacheDir, _ := b.CacheDir(context.Background(), "bin") + cacheDir, _ := b.CacheDir(ctx, "bin") existingExecPath := createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) - t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) - t.Setenv("DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") - _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) + ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") + _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) require.NoError(t, err) require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinaryAndAlreadySetExecPath(t *testing.T) { + ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ Config: config.Root{ @@ -362,14 +366,15 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinaryAndAlreadySetExec b.Config.Bundle.Terraform.ExecPath = existingExecPath // Create a new terraform binary and expose it through env vars tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") - t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) - t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) - _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) + ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) + _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) require.NoError(t, err) require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { + ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ Config: config.Root{ @@ -381,13 +386,13 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { }, } // Create a pre-existing terraform bin to avoid downloading it - cacheDir, _ := b.CacheDir(context.Background(), "bin") + cacheDir, _ := b.CacheDir(ctx, "bin") createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) // Create a new terraform binary and expose it through env vars tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") - t.Setenv("DATABRICKS_TF_VERSION", TerraformVersion.String()) - t.Setenv("DATABRICKS_TF_EXEC_PATH", tmpBinPath) - _, err := m.findExecPath(context.Background(), b, b.Config.Bundle.Terraform) + ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) + ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) + _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) require.NoError(t, err) require.Equal(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) } From 70334a504d572a7949c8b77ad60433c35ab11ff9 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 21 Mar 2024 15:49:41 +0100 Subject: [PATCH 11/24] Use TF_CLI_CONFIG_FILE instead of TF_PLUGIN_CACHE_DIR With plugin cache dir terraform still tries to access the registry --- bundle/deploy/terraform/init.go | 80 ++++++++++++++++------------ bundle/deploy/terraform/init_test.go | 75 ++++++++++++++++---------- 2 files changed, 92 insertions(+), 63 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index e4f2ccf39d..a17c1fe8fb 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -12,6 +12,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/internal/tf/schema" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" "github.com/hashicorp/hc-install/product" @@ -39,26 +40,14 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con } // Load exec path from the environment if it matches the currently used version. - envTFVersion := env.Get(ctx, "DATABRICKS_TF_VERSION") - envExecPath := env.Get(ctx, "DATABRICKS_TF_EXEC_PATH") - if envExecPath != "" && envTFVersion != "" { - if envTFVersion == TerraformVersion.String() { - _, err := os.Stat(envExecPath) - if err != nil && !os.IsNotExist(err) { - return "", err - } - if err == nil { - tf.ExecPath = envExecPath - log.Debugf(ctx, "Using Terraform from DATABRICKS_TF_EXEC_PATH at %s", tf.ExecPath) - return tf.ExecPath, nil - } else { - log.Debugf(ctx, "Terraform at %s not found, ignoring DATABRICKS_TF_EXEC_PATH", envExecPath) - } - } else { - log.Debugf(ctx, "DATABRICKS_TF_VERSION %s does not match the current version %s, ignoring DATABRICKS_TF_EXEC_PATH", envTFVersion, TerraformVersion.String()) - } - } else { - log.Debugf(ctx, "DATABRICKS_TF_EXEC_PATH and DATABRICKS_TF_VERSION aren't defined") + envExecPath, err := getEnvVarWithMatchingVersion(ctx, "DATABRICKS_TF_EXEC_PATH", "DATABRICKS_TF_VERSION", TerraformVersion.String()) + if err != nil { + return "", err + } + if envExecPath != "" { + tf.ExecPath = envExecPath + log.Debugf(ctx, "Using Terraform from DATABRICKS_TF_EXEC_PATH at %s", tf.ExecPath) + return tf.ExecPath, nil } binDir, err := b.CacheDir(context.Background(), "bin") @@ -119,28 +108,49 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { } // Include $TF_CLI_CONFIG_FILE to override terraform provider in development. - configFile, ok := env.Lookup(ctx, "TF_CLI_CONFIG_FILE") + // See: https://developer.hashicorp.com/terraform/cli/config/config-file#explicit-installation-method-configuration + devConfigFile, ok := env.Lookup(ctx, "TF_CLI_CONFIG_FILE") if ok { + environ["TF_CLI_CONFIG_FILE"] = devConfigFile + } + + // Map $DATABRICKS_TF_CLI_CONFIG_FILE to $TF_CLI_CONFIG_FILE + // VSCode extension provides a file with the "provider_installation.filesystem_mirror" configuration. + // We only use it if the provider version matches the currently used version, + // otherwise terraform will fail to download the right version (even with unrestricted internet access). + configFile, err := getEnvVarWithMatchingVersion(ctx, "DATABRICKS_TF_CLI_CONFIG_FILE", "DATABRICKS_TF_PROVIDER_VERSION", schema.ProviderVersion) + if err != nil { + return err + } + if configFile != "" { + log.Debugf(ctx, "Using Terraform CLI config from DATABRICKS_TF_CLI_CONFIG_FILE at %s", configFile) environ["TF_CLI_CONFIG_FILE"] = configFile } - // Map $DATABRICKS_TF_PLUGIN_CACHE_DIR to $TF_PLUGIN_CACHE_DIR - // This lets terraform use pre-downloaded Databricks plugins - cacheDir, ok := env.Lookup(ctx, "DATABRICKS_TF_PLUGIN_CACHE_DIR") - if ok { - _, err := os.Stat(cacheDir) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - log.Debugf(ctx, "Using Terraform plugin cache dir: %s", cacheDir) - environ["TF_PLUGIN_CACHE_DIR"] = cacheDir + return nil +} + +func getEnvVarWithMatchingVersion(ctx context.Context, envVarName string, versionVarName string, currentVersion string) (string, error) { + envValue := env.Get(ctx, envVarName) + versionValue := env.Get(ctx, versionVarName) + if envValue == "" || versionValue == "" { + log.Debugf(ctx, "%s and %s aren't defined", envVarName, versionVarName) + return "", nil + } + if versionValue != currentVersion { + log.Debugf(ctx, "%s as %s does not match the current version %s, ignoring %s", versionVarName, versionValue, currentVersion, envVarName) + return "", nil + } + _, err := os.Stat(envValue) + if err != nil { + if os.IsNotExist(err) { + log.Debugf(ctx, "%s at %s does not exist, ignoring %s", envVarName, envValue, versionVarName) + return "", nil } else { - log.Debugf(ctx, "Terraform plugin cache dir %s doesn't exist, ignoring DATABRICKS_TF_PLUGIN_CACHE_DIR", cacheDir) + return "", err } } - - return nil + return envValue, nil } // This function sets temp dir location for terraform to use. If user does not diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index d19063cb30..aff3a91f6f 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/internal/tf/schema" "github.com/databricks/cli/libs/env" "github.com/hashicorp/hc-install/product" "github.com/stretchr/testify/assert" @@ -273,34 +274,37 @@ func TestSetUserProfileFromInheritEnvVars(t *testing.T) { assert.Equal(t, env["USERPROFILE"], "c:\\foo\\c") } -func TestInheritEnvVarsWithAbsentPluginsCacheDir(t *testing.T) { - env := map[string]string{} - t.Setenv("DATABRICKS_TF_PLUGIN_CACHE_DIR", "/tmp/cache") - err := inheritEnvVars(context.Background(), env) +func TestInheritEnvVarsWithAbsentTFConfigFile(t *testing.T) { + ctx := context.Background() + envMap := map[string]string{} + ctx = env.Set(ctx, "DATABRICKS_TF_PROVIDER_VERSION", schema.ProviderVersion) + ctx = env.Set(ctx, "DATABRICKS_TF_CLI_CONFIG_FILE", "/tmp/config.tfrc") + err := inheritEnvVars(ctx, envMap) require.NoError(t, err) - require.NotContains(t, env, "TF_PLUGIN_CACHE_DIR") + require.NotContains(t, envMap, "TF_CLI_CONFIG_FILE") } -func TestInheritEnvVarsWithRealPluginsCacheDir(t *testing.T) { - env := map[string]string{} - dir := t.TempDir() - t.Setenv("DATABRICKS_TF_PLUGIN_CACHE_DIR", dir) - err := inheritEnvVars(context.Background(), env) +func TestInheritEnvVarsWithWrongTFProviderVersion(t *testing.T) { + ctx := context.Background() + envMap := map[string]string{} + configFile := createTempFile(t, t.TempDir(), "config.tfrc", false) + ctx = env.Set(ctx, "DATABRICKS_TF_PROVIDER_VERSION", "wrong") + ctx = env.Set(ctx, "DATABRICKS_TF_CLI_CONFIG_FILE", configFile) + err := inheritEnvVars(ctx, envMap) require.NoError(t, err) - require.Equal(t, dir, env["TF_PLUGIN_CACHE_DIR"]) + require.NotContains(t, envMap, "TF_CLI_CONFIG_FILE") } -func createTerraformBinary(t *testing.T, dest string, name string) string { - binPath := filepath.Join(dest, name) - f, err := os.Create(binPath) - require.NoError(t, err) - defer func() { - err = f.Close() - require.NoError(t, err) - }() - err = f.Chmod(0755) +func TestInheritEnvVarsWithCorrectTFCLIConfigFile(t *testing.T) { + ctx := context.Background() + envMap := map[string]string{} + configFile := createTempFile(t, t.TempDir(), "config.tfrc", false) + ctx = env.Set(ctx, "DATABRICKS_TF_PROVIDER_VERSION", schema.ProviderVersion) + ctx = env.Set(ctx, "DATABRICKS_TF_CLI_CONFIG_FILE", configFile) + err := inheritEnvVars(ctx, envMap) require.NoError(t, err) - return binPath + require.Contains(t, envMap, "TF_CLI_CONFIG_FILE") + require.Equal(t, configFile, envMap["TF_CLI_CONFIG_FILE"]) } func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { @@ -317,9 +321,9 @@ func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { } // Create a pre-existing terraform bin to avoid downloading it cacheDir, _ := b.CacheDir(ctx, "bin") - existingExecPath := createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) + existingExecPath := createTempFile(t, cacheDir, product.Terraform.BinaryName(), true) // Create a new terraform binary and expose it through env vars - tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") + tmpBinPath := createTempFile(t, t.TempDir(), "terraform-bin", true) ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", "1.2.3") ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) @@ -341,7 +345,7 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) } // Create a pre-existing terraform bin to avoid downloading it cacheDir, _ := b.CacheDir(ctx, "bin") - existingExecPath := createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) + existingExecPath := createTempFile(t, cacheDir, product.Terraform.BinaryName(), true) ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", "/tmp/terraform") @@ -362,10 +366,10 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinaryAndAlreadySetExec }, }, } - existingExecPath := createTerraformBinary(t, t.TempDir(), "terraform-existing") + existingExecPath := createTempFile(t, t.TempDir(), "terraform-existing", true) b.Config.Bundle.Terraform.ExecPath = existingExecPath // Create a new terraform binary and expose it through env vars - tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") + tmpBinPath := createTempFile(t, t.TempDir(), "terraform-bin", true) ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) @@ -387,12 +391,27 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { } // Create a pre-existing terraform bin to avoid downloading it cacheDir, _ := b.CacheDir(ctx, "bin") - createTerraformBinary(t, cacheDir, product.Terraform.BinaryName()) + createTempFile(t, cacheDir, product.Terraform.BinaryName(), true) // Create a new terraform binary and expose it through env vars - tmpBinPath := createTerraformBinary(t, t.TempDir(), "terraform-bin") + tmpBinPath := createTempFile(t, t.TempDir(), "terraform-bin", true) ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) require.NoError(t, err) require.Equal(t, tmpBinPath, b.Config.Bundle.Terraform.ExecPath) } + +func createTempFile(t *testing.T, dest string, name string, executable bool) string { + binPath := filepath.Join(dest, name) + f, err := os.Create(binPath) + require.NoError(t, err) + defer func() { + err = f.Close() + require.NoError(t, err) + }() + if executable { + err = f.Chmod(0777) + require.NoError(t, err) + } + return binPath +} From 20ff8e1a56425ccc96d291c43fbad25868091095 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 21 Mar 2024 17:15:28 +0100 Subject: [PATCH 12/24] Add plain text output --- cmd/bundle/dependencies.go | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/dependencies.go index 514c1c7cd1..1cc098e7b8 100644 --- a/cmd/bundle/dependencies.go +++ b/cmd/bundle/dependencies.go @@ -2,11 +2,11 @@ package bundle import ( "encoding/json" - "errors" "fmt" "github.com/databricks/cli/bundle/deploy/terraform" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" "github.com/spf13/cobra" ) @@ -17,9 +17,36 @@ type Dependencies struct { func newDependenciesCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "dependencies", - Short: "Prints CLI dependencies required for the bundle namespace", + Use: "internal-dependencies", + Short: "Prints CLI terraform dependencies required for the bundle namespace", Args: root.NoArgs, + Annotations: map[string]string{ + "template": `Terraform version: {{.Version}} +Terraform URL: https://releases.hashicorp.com/terraform/{{.Version}} + +Databricks Terraform Provider version: {{.ProviderVersion}} +Databricks Terraform Provider URL: https://github.com/databricks/terraform-provider-databricks/releases/tag/v{{.ProviderVersion}} + +Databricks CLI will download its terraform dependencies automatically. + +If you run the CLI in air-gapped environment, you can download the dependencies manually and setup these environment variables: + DATABRICKS_TF_VERSION={{.Version}} + DATABRICKS_TF_EXEC_PATH=/path/to/terraform/binary + DATABRICKS_TF_PROVIDER_VERSION={{.ProviderVersion}} + DATABRICKS_TF_CLI_CONFIG_FILE=/path/to/terraform/cli/config.tfrc + +Example of the *.tfrc config file: + disable_checkpoint = true + provider_installation { + filesystem_mirror { + path = "/path/to/a/folder/with/databricks/terraform/provider" + } + } + +The folder with the databricks terraform provider should have this structure: /registry.terraform.io/databricks/databricks/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip +Consult terraform docs for more information about the filesystem mirrors: https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror +`, + }, // This command is currently intended for the Databricks VSCode extension only Hidden: true, } @@ -30,7 +57,7 @@ func newDependenciesCommand() *cobra.Command { } switch root.OutputType(cmd) { case flags.OutputText: - return fmt.Errorf("%w, only json output is supported", errors.ErrUnsupported) + cmdio.Render(cmd.Context(), dependencies.Terraform) case flags.OutputJSON: buf, err := json.MarshalIndent(dependencies, "", " ") if err != nil { From 58bbd719f18b9442e61b91c87bb47f5c25334953 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 21 Mar 2024 17:26:20 +0100 Subject: [PATCH 13/24] Remove unnecessary test --- bundle/deploy/terraform/init_test.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index aff3a91f6f..a493918d7e 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -354,29 +354,6 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) } -func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinaryAndAlreadySetExecPath(t *testing.T) { - ctx := context.Background() - m := &initialize{} - b := &bundle.Bundle{ - Config: config.Root{ - Path: t.TempDir(), - Bundle: config.Bundle{ - Target: "whatever", - Terraform: &config.Terraform{}, - }, - }, - } - existingExecPath := createTempFile(t, t.TempDir(), "terraform-existing", true) - b.Config.Bundle.Terraform.ExecPath = existingExecPath - // Create a new terraform binary and expose it through env vars - tmpBinPath := createTempFile(t, t.TempDir(), "terraform-bin", true) - ctx = env.Set(ctx, "DATABRICKS_TF_VERSION", TerraformVersion.String()) - ctx = env.Set(ctx, "DATABRICKS_TF_EXEC_PATH", tmpBinPath) - _, err := m.findExecPath(ctx, b, b.Config.Bundle.Terraform) - require.NoError(t, err) - require.Equal(t, existingExecPath, b.Config.Bundle.Terraform.ExecPath) -} - func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { ctx := context.Background() m := &initialize{} From c5df7af90f04d84150f389b824d40c9caca24a8e Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 21 Mar 2024 17:28:16 +0100 Subject: [PATCH 14/24] Formatting --- cmd/bundle/dependencies.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/dependencies.go index 1cc098e7b8..850d1524e2 100644 --- a/cmd/bundle/dependencies.go +++ b/cmd/bundle/dependencies.go @@ -30,12 +30,14 @@ Databricks Terraform Provider URL: https://github.com/databricks/terraform-provi Databricks CLI will download its terraform dependencies automatically. If you run the CLI in air-gapped environment, you can download the dependencies manually and setup these environment variables: + DATABRICKS_TF_VERSION={{.Version}} DATABRICKS_TF_EXEC_PATH=/path/to/terraform/binary DATABRICKS_TF_PROVIDER_VERSION={{.ProviderVersion}} DATABRICKS_TF_CLI_CONFIG_FILE=/path/to/terraform/cli/config.tfrc Example of the *.tfrc config file: + disable_checkpoint = true provider_installation { filesystem_mirror { @@ -44,6 +46,7 @@ Example of the *.tfrc config file: } The folder with the databricks terraform provider should have this structure: /registry.terraform.io/databricks/databricks/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip + Consult terraform docs for more information about the filesystem mirrors: https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror `, }, From 51175c97f8f1c5211a99d0e2377cb6e225b83e05 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 21 Mar 2024 17:34:16 +0100 Subject: [PATCH 15/24] Better template message --- cmd/bundle/dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/dependencies.go index 850d1524e2..fa8484c3cf 100644 --- a/cmd/bundle/dependencies.go +++ b/cmd/bundle/dependencies.go @@ -45,7 +45,7 @@ Example of the *.tfrc config file: } } -The folder with the databricks terraform provider should have this structure: /registry.terraform.io/databricks/databricks/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip +The folder with the databricks terraform provider should have this structure: /{{.ProviderHost}}/{{.ProviderSource}}/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip Consult terraform docs for more information about the filesystem mirrors: https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror `, From 92e5cefb04405ebefd9c704c3d5d5566f5ef2b1d Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Fri, 22 Mar 2024 11:11:43 +0100 Subject: [PATCH 16/24] Update json names --- bundle/deploy/terraform/pkg.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundle/deploy/terraform/pkg.go b/bundle/deploy/terraform/pkg.go index dea6cbcea7..22901186cf 100644 --- a/bundle/deploy/terraform/pkg.go +++ b/bundle/deploy/terraform/pkg.go @@ -12,9 +12,9 @@ var TerraformVersion = version.Must(version.NewVersion("1.5.5")) type TerraformMetadata struct { Version string `json:"version"` - ProviderHost string `json:"provider_host"` - ProviderSource string `json:"provider_source"` - ProviderVersion string `json:"provider_version"` + ProviderHost string `json:"providerHost"` + ProviderSource string `json:"providerSource"` + ProviderVersion string `json:"providerVersion"` } func NewTerraformMetadata() *TerraformMetadata { From 5f4ddb4355a5d9e45a16c779e60034f2d4c37c38 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 26 Mar 2024 13:05:50 +0100 Subject: [PATCH 17/24] Update text output --- cmd/bundle/dependencies.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/dependencies.go index fa8484c3cf..bef1790583 100644 --- a/cmd/bundle/dependencies.go +++ b/cmd/bundle/dependencies.go @@ -18,7 +18,7 @@ type Dependencies struct { func newDependenciesCommand() *cobra.Command { cmd := &cobra.Command{ Use: "internal-dependencies", - Short: "Prints CLI terraform dependencies required for the bundle namespace", + Short: "Prints Terraform dependencies required for the bundle commands", Args: root.NoArgs, Annotations: map[string]string{ "template": `Terraform version: {{.Version}} @@ -27,16 +27,16 @@ Terraform URL: https://releases.hashicorp.com/terraform/{{.Version}} Databricks Terraform Provider version: {{.ProviderVersion}} Databricks Terraform Provider URL: https://github.com/databricks/terraform-provider-databricks/releases/tag/v{{.ProviderVersion}} -Databricks CLI will download its terraform dependencies automatically. +Databricks CLI downloads its Terraform dependencies automatically. -If you run the CLI in air-gapped environment, you can download the dependencies manually and setup these environment variables: +If you run the CLI in an air-gapped environment, you can download the dependencies manually and set these environment variables: DATABRICKS_TF_VERSION={{.Version}} DATABRICKS_TF_EXEC_PATH=/path/to/terraform/binary DATABRICKS_TF_PROVIDER_VERSION={{.ProviderVersion}} DATABRICKS_TF_CLI_CONFIG_FILE=/path/to/terraform/cli/config.tfrc -Example of the *.tfrc config file: +Here is an example *.tfrc configuration file: disable_checkpoint = true provider_installation { @@ -45,9 +45,9 @@ Example of the *.tfrc config file: } } -The folder with the databricks terraform provider should have this structure: /{{.ProviderHost}}/{{.ProviderSource}}/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip +The filesystem mirror path should point to the folder with this structure: /{{.ProviderHost}}/{{.ProviderSource}}/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip -Consult terraform docs for more information about the filesystem mirrors: https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror +For more information about filesystem mirrors, see the Terraform documentation: https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror `, }, // This command is currently intended for the Databricks VSCode extension only From 9e84685912640105426aba091007f54551c4bd46 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 26 Mar 2024 13:12:46 +0100 Subject: [PATCH 18/24] Update filesystem mirror explaination --- cmd/bundle/dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/dependencies.go index bef1790583..5bc0dcd5bc 100644 --- a/cmd/bundle/dependencies.go +++ b/cmd/bundle/dependencies.go @@ -45,7 +45,7 @@ Here is an example *.tfrc configuration file: } } -The filesystem mirror path should point to the folder with this structure: /{{.ProviderHost}}/{{.ProviderSource}}/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip +The filesystem mirror path should point to the folder with the Databricks Terraform Provider. The folder should have this structure: /{{.ProviderHost}}/{{.ProviderSource}}/terraform-provider-databricks_{{.ProviderVersion}}_ARCH.zip For more information about filesystem mirrors, see the Terraform documentation: https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror `, From e10464f6dc4899a121d3ff22c1a324bc625a3920 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 26 Mar 2024 13:32:56 +0100 Subject: [PATCH 19/24] Move to `debug terraform` command --- cmd/bundle/bundle.go | 2 +- cmd/bundle/debug.go | 18 ++++++++++++++++++ .../{dependencies.go => debug/terraform.go} | 6 +++--- 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 cmd/bundle/debug.go rename cmd/bundle/{dependencies.go => debug/terraform.go} (96%) diff --git a/cmd/bundle/bundle.go b/cmd/bundle/bundle.go index 6d54198e58..1db60d5857 100644 --- a/cmd/bundle/bundle.go +++ b/cmd/bundle/bundle.go @@ -24,8 +24,8 @@ func New() *cobra.Command { cmd.AddCommand(newValidateCommand()) cmd.AddCommand(newInitCommand()) cmd.AddCommand(newSummaryCommand()) - cmd.AddCommand(newDependenciesCommand()) cmd.AddCommand(newGenerateCommand()) + cmd.AddCommand(newDebugCommand()) cmd.AddCommand(deployment.NewDeploymentCommand()) return cmd } diff --git a/cmd/bundle/debug.go b/cmd/bundle/debug.go new file mode 100644 index 0000000000..42d16eab57 --- /dev/null +++ b/cmd/bundle/debug.go @@ -0,0 +1,18 @@ +package bundle + +import ( + "github.com/databricks/cli/cmd/bundle/debug" + "github.com/spf13/cobra" +) + +func newDebugCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "debug", + Short: "Debug information about bundles", + Long: "Debug information about bundles", + // This command group is currently intended for the Databricks VSCode extension only + Hidden: true, + } + cmd.AddCommand(debug.NewTerraformCommand()) + return cmd +} diff --git a/cmd/bundle/dependencies.go b/cmd/bundle/debug/terraform.go similarity index 96% rename from cmd/bundle/dependencies.go rename to cmd/bundle/debug/terraform.go index 5bc0dcd5bc..843ecac4ec 100644 --- a/cmd/bundle/dependencies.go +++ b/cmd/bundle/debug/terraform.go @@ -1,4 +1,4 @@ -package bundle +package debug import ( "encoding/json" @@ -15,9 +15,9 @@ type Dependencies struct { Terraform *terraform.TerraformMetadata `json:"terraform"` } -func newDependenciesCommand() *cobra.Command { +func NewTerraformCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "internal-dependencies", + Use: "terraform", Short: "Prints Terraform dependencies required for the bundle commands", Args: root.NoArgs, Annotations: map[string]string{ From d38d946e57d4dae2affef83a5cc4a7318802397f Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 28 Mar 2024 14:22:23 +0100 Subject: [PATCH 20/24] Add comment for getEnvVarWithMatchingVersion --- bundle/deploy/terraform/init.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index fe6df3063e..1a0efc59ac 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -131,6 +131,10 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { return nil } +// Example: this function will return a value of TF_EXEC_PATH only if the path exists and if TF_VERSION matches the TerraformVersion. +// This function is used for env vars set by the Databricks VSCode extension. The variables are intented to be used by the CLI +// bundled with the extension, but users can use different CLI version in the VSCode terminals, in which case we want to ignore +// the variables if that CLI uses different versions of the dependencies. func getEnvVarWithMatchingVersion(ctx context.Context, envVarName string, versionVarName string, currentVersion string) (string, error) { envValue := env.Get(ctx, envVarName) versionValue := env.Get(ctx, versionVarName) From d99642fd7ba28d5dccac25e98b51c0e13ab6bf2a Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Thu, 28 Mar 2024 14:49:36 +0100 Subject: [PATCH 21/24] Update tests --- bundle/deploy/terraform/init_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 9d80d40ba0..ece897193a 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -311,8 +311,8 @@ func TestFindExecPathFromEnvironmentWithWrongVersion(t *testing.T) { ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ + RootPath: t.TempDir(), Config: config.Root{ - Path: t.TempDir(), Bundle: config.Bundle{ Target: "whatever", Terraform: &config.Terraform{}, @@ -335,8 +335,8 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndNoBinary(t *testing.T) ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ + RootPath: t.TempDir(), Config: config.Root{ - Path: t.TempDir(), Bundle: config.Bundle{ Target: "whatever", Terraform: &config.Terraform{}, @@ -358,8 +358,8 @@ func TestFindExecPathFromEnvironmentWithCorrectVersionAndBinary(t *testing.T) { ctx := context.Background() m := &initialize{} b := &bundle.Bundle{ + RootPath: t.TempDir(), Config: config.Root{ - Path: t.TempDir(), Bundle: config.Bundle{ Target: "whatever", Terraform: &config.Terraform{}, From 7413253c18f19968a4b65308f4c49c541316ffc4 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 2 Apr 2024 10:23:53 +0200 Subject: [PATCH 22/24] Update bundle/deploy/terraform/init.go Co-authored-by: shreyas-goenka <88374338+shreyas-goenka@users.noreply.github.com> --- bundle/deploy/terraform/init.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index 1a0efc59ac..a0a46e613d 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -132,8 +132,8 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { } // Example: this function will return a value of TF_EXEC_PATH only if the path exists and if TF_VERSION matches the TerraformVersion. -// This function is used for env vars set by the Databricks VSCode extension. The variables are intented to be used by the CLI -// bundled with the extension, but users can use different CLI version in the VSCode terminals, in which case we want to ignore +// This function is used for env vars set by the Databricks VSCode extension. The variables are intended to be used by the CLI +// bundled with the Databricks VSCode extension, but users can use different CLI versions in the VSCode terminals, in which case we want to ignore // the variables if that CLI uses different versions of the dependencies. func getEnvVarWithMatchingVersion(ctx context.Context, envVarName string, versionVarName string, currentVersion string) (string, error) { envValue := env.Get(ctx, envVarName) From 138a9e167dab0bfd904aca119a6cf856b2f82d37 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 2 Apr 2024 10:52:19 +0200 Subject: [PATCH 23/24] Move env vars into consts --- bundle/deploy/terraform/init.go | 8 ++++---- bundle/deploy/terraform/pkg.go | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index a0a46e613d..b1bd91e2e1 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -41,13 +41,13 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con } // Load exec path from the environment if it matches the currently used version. - envExecPath, err := getEnvVarWithMatchingVersion(ctx, "DATABRICKS_TF_EXEC_PATH", "DATABRICKS_TF_VERSION", TerraformVersion.String()) + envExecPath, err := getEnvVarWithMatchingVersion(ctx, TerraformExecPathEnv, TerraformVersionEnv, TerraformVersion.String()) if err != nil { return "", err } if envExecPath != "" { tf.ExecPath = envExecPath - log.Debugf(ctx, "Using Terraform from DATABRICKS_TF_EXEC_PATH at %s", tf.ExecPath) + log.Debugf(ctx, "Using Terraform from %s at %s", TerraformExecPathEnv, tf.ExecPath) return tf.ExecPath, nil } @@ -119,12 +119,12 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { // VSCode extension provides a file with the "provider_installation.filesystem_mirror" configuration. // We only use it if the provider version matches the currently used version, // otherwise terraform will fail to download the right version (even with unrestricted internet access). - configFile, err := getEnvVarWithMatchingVersion(ctx, "DATABRICKS_TF_CLI_CONFIG_FILE", "DATABRICKS_TF_PROVIDER_VERSION", schema.ProviderVersion) + configFile, err := getEnvVarWithMatchingVersion(ctx, TerraformCliCofigPathEnv, TerraformProviderVersionEnv, schema.ProviderVersion) if err != nil { return err } if configFile != "" { - log.Debugf(ctx, "Using Terraform CLI config from DATABRICKS_TF_CLI_CONFIG_FILE at %s", configFile) + log.Debugf(ctx, "Using Terraform CLI config from %s at %s", TerraformCliCofigPathEnv, configFile) environ["TF_CLI_CONFIG_FILE"] = configFile } diff --git a/bundle/deploy/terraform/pkg.go b/bundle/deploy/terraform/pkg.go index 22901186cf..559f305c78 100644 --- a/bundle/deploy/terraform/pkg.go +++ b/bundle/deploy/terraform/pkg.go @@ -8,6 +8,13 @@ import ( const TerraformStateFileName = "terraform.tfstate" const TerraformConfigFileName = "bundle.tf.json" +// Users can provide their own terraform binary and databricks terraform provider by setting the following environment variables. +// This allows users to use the CLI in an air-gapped environments. See the `debug terraform` command. +const TerraformExecPathEnv = "DATABRICKS_TF_EXEC_PATH" +const TerraformVersionEnv = "DATABRICKS_TF_VERSION" +const TerraformCliCofigPathEnv = "DATABRICKS_TF_CLI_CONFIG_FILE" +const TerraformProviderVersionEnv = "DATABRICKS_TF_PROVIDER_VERSION" + var TerraformVersion = version.Must(version.NewVersion("1.5.5")) type TerraformMetadata struct { From d921a82a46ec4d60c841468c1f600acd03e6a160 Mon Sep 17 00:00:00 2001 From: Ilia Babanov Date: Tue, 2 Apr 2024 14:07:27 +0200 Subject: [PATCH 24/24] Fix typo --- bundle/deploy/terraform/init.go | 4 ++-- bundle/deploy/terraform/pkg.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index b1bd91e2e1..9f42353104 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -119,12 +119,12 @@ func inheritEnvVars(ctx context.Context, environ map[string]string) error { // VSCode extension provides a file with the "provider_installation.filesystem_mirror" configuration. // We only use it if the provider version matches the currently used version, // otherwise terraform will fail to download the right version (even with unrestricted internet access). - configFile, err := getEnvVarWithMatchingVersion(ctx, TerraformCliCofigPathEnv, TerraformProviderVersionEnv, schema.ProviderVersion) + configFile, err := getEnvVarWithMatchingVersion(ctx, TerraformCliConfigPathEnv, TerraformProviderVersionEnv, schema.ProviderVersion) if err != nil { return err } if configFile != "" { - log.Debugf(ctx, "Using Terraform CLI config from %s at %s", TerraformCliCofigPathEnv, configFile) + log.Debugf(ctx, "Using Terraform CLI config from %s at %s", TerraformCliConfigPathEnv, configFile) environ["TF_CLI_CONFIG_FILE"] = configFile } diff --git a/bundle/deploy/terraform/pkg.go b/bundle/deploy/terraform/pkg.go index 559f305c78..911583f29b 100644 --- a/bundle/deploy/terraform/pkg.go +++ b/bundle/deploy/terraform/pkg.go @@ -12,7 +12,7 @@ const TerraformConfigFileName = "bundle.tf.json" // This allows users to use the CLI in an air-gapped environments. See the `debug terraform` command. const TerraformExecPathEnv = "DATABRICKS_TF_EXEC_PATH" const TerraformVersionEnv = "DATABRICKS_TF_VERSION" -const TerraformCliCofigPathEnv = "DATABRICKS_TF_CLI_CONFIG_FILE" +const TerraformCliConfigPathEnv = "DATABRICKS_TF_CLI_CONFIG_FILE" const TerraformProviderVersionEnv = "DATABRICKS_TF_PROVIDER_VERSION" var TerraformVersion = version.Must(version.NewVersion("1.5.5"))