Skip to content

Commit 531f06a

Browse files
authored
Allow users to override the Terraform version to use (#3069)
## Changes Previously, the CLI would use the Terraform binary at `DATABRICKS_TF_EXEC_PATH` only if the `DATABRICKS_TF_VERSION` environment variable was set and matched the _built-in default version_. It was built such that we could ship all binaries with the VS Code extension to avoid re-downloading (see #1294). This change expands the scope of these variables and makes them composable. Now, you can: 1. Configure `DATABRICKS_TF_VERSION` to override the Terraform version to use 2. Configure `DATABRICKS_TF_EXEC_PATH` to configure the path to an existing Terraform binary If 1 is used alone, the CLI will download the specified version. If 2 is used alone, the CLI will execute the specified binary to confirm that it matches the _built-in default version_. If both are used, the CLI will execute the specified binary to confirm that it matches the specified version. ## Why To allow users to use newer version of the Terraform CLI. ## Tests * Unit tests pass * Integration tests with the latest Terraform version (#3040)
1 parent 68401ba commit 531f06a

File tree

7 files changed

+410
-73
lines changed

7 files changed

+410
-73
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
* When building Python artifacts as part of "bundle deploy" we no longer delete `dist`, `build`, `*egg-info` and `__pycache__` directories. ([#2982](https://github.com/databricks/cli/pull/2982))
1616
* Fix variable resolution for lookup variables with other references ([#3054](https://github.com/databricks/cli/pull/3054))
1717
* Added preset `presets.artifacts_dynamic_version` that automatically enables `dynamic_version: true` on all "whl" artifacts.
18+
* Allow users to override the Terraform version to use by setting the `DATABRICKS_TF_VERSION` environment variable ([#3069](https://github.com/databricks/cli/pull/3069))
1819

1920
### API Changes

bundle/deploy/terraform/init.go

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"path/filepath"
1111
"runtime"
1212
"strings"
13-
"time"
1413

1514
"github.com/databricks/cli/bundle"
1615
"github.com/databricks/cli/bundle/config"
@@ -20,7 +19,6 @@ import (
2019
"github.com/databricks/cli/libs/env"
2120
"github.com/databricks/cli/libs/log"
2221
"github.com/hashicorp/hc-install/product"
23-
"github.com/hashicorp/hc-install/releases"
2422
"github.com/hashicorp/terraform-exec/tfexec"
2523
"golang.org/x/exp/maps"
2624
)
@@ -31,7 +29,7 @@ func (m *initialize) Name() string {
3129
return "terraform.Initialize"
3230
}
3331

34-
func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform) (string, error) {
32+
func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform, installer Installer) (string, error) {
3533
// If set, pass it through [exec.LookPath] to resolve its absolute path.
3634
if tf.ExecPath != "" {
3735
execPath, err := exec.LookPath(tf.ExecPath)
@@ -43,13 +41,52 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con
4341
return tf.ExecPath, nil
4442
}
4543

46-
// Load exec path from the environment if it matches the currently used version.
47-
envExecPath, err := getEnvVarWithMatchingVersion(ctx, TerraformExecPathEnv, TerraformVersionEnv, TerraformVersion.String())
44+
// Resolve the version of the Terraform CLI to use.
45+
tv, isDefault, err := GetTerraformVersion(ctx)
4846
if err != nil {
4947
return "", err
5048
}
51-
if envExecPath != "" {
52-
tf.ExecPath = envExecPath
49+
50+
// Allow the user to specify the path to the Terraform CLI.
51+
// If this is set, verify that the version matches the version we expect.
52+
if execPathValue, ok := env.Lookup(ctx, TerraformExecPathEnv); ok {
53+
tfe, err := getTerraformExec(ctx, b, execPathValue)
54+
if err != nil {
55+
return "", err
56+
}
57+
58+
expectedVersion := tv.Version
59+
actualVersion, _, err := tfe.Version(ctx, false)
60+
if err != nil {
61+
return "", fmt.Errorf("unable to execute %s: %w", TerraformExecPathEnv, err)
62+
}
63+
64+
if !actualVersion.Equal(expectedVersion) {
65+
if isDefault {
66+
return "", fmt.Errorf(
67+
"Terraform binary at %s (from $%s) is %s but expected version is %s. Set %s to %s to continue.",
68+
execPathValue,
69+
TerraformExecPathEnv,
70+
actualVersion.String(),
71+
expectedVersion.String(),
72+
TerraformVersionEnv,
73+
actualVersion.String(),
74+
)
75+
} else {
76+
return "", fmt.Errorf(
77+
"Terraform binary at %s (from $%s) is %s but expected version is %s (from $%s). Update $%s and $%s so that versions match.",
78+
execPathValue,
79+
TerraformExecPathEnv,
80+
actualVersion.String(),
81+
expectedVersion.String(),
82+
TerraformVersionEnv,
83+
TerraformExecPathEnv,
84+
TerraformVersionEnv,
85+
)
86+
}
87+
}
88+
89+
tf.ExecPath = execPathValue
5390
log.Debugf(ctx, "Using Terraform from %s at %s", TerraformExecPathEnv, tf.ExecPath)
5491
return tf.ExecPath, nil
5592
}
@@ -72,13 +109,7 @@ func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *con
72109
}
73110

74111
// Download Terraform to private bin directory.
75-
installer := &releases.ExactVersion{
76-
Product: product.Terraform,
77-
Version: TerraformVersion,
78-
InstallDir: binDir,
79-
Timeout: 1 * time.Minute,
80-
}
81-
execPath, err = installer.Install(ctx)
112+
execPath, err = installer.Install(ctx, binDir, tv.Version)
82113
if err != nil {
83114
return "", fmt.Errorf("error downloading Terraform: %w", err)
84115
}
@@ -251,24 +282,28 @@ func setUserAgentExtraEnvVar(environ map[string]string, b *bundle.Bundle) error
251282
return nil
252283
}
253284

285+
func getTerraformExec(ctx context.Context, b *bundle.Bundle, execPath string) (*tfexec.Terraform, error) {
286+
workingDir, err := Dir(ctx, b)
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
return tfexec.NewTerraform(workingDir, execPath)
292+
}
293+
254294
func (m *initialize) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
255295
tfConfig := b.Config.Bundle.Terraform
256296
if tfConfig == nil {
257297
tfConfig = &config.Terraform{}
258298
b.Config.Bundle.Terraform = tfConfig
259299
}
260300

261-
execPath, err := m.findExecPath(ctx, b, tfConfig)
262-
if err != nil {
263-
return diag.FromErr(err)
264-
}
265-
266-
workingDir, err := Dir(ctx, b)
301+
execPath, err := m.findExecPath(ctx, b, tfConfig, tfInstaller{})
267302
if err != nil {
268303
return diag.FromErr(err)
269304
}
270305

271-
tf, err := tfexec.NewTerraform(workingDir, execPath)
306+
tfe, err := getTerraformExec(ctx, b, execPath)
272307
if err != nil {
273308
return diag.FromErr(err)
274309
}
@@ -302,12 +337,12 @@ func (m *initialize) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnosti
302337

303338
// Configure environment variables for auth for Terraform to use.
304339
log.Debugf(ctx, "Environment variables for Terraform: %s", strings.Join(maps.Keys(environ), ", "))
305-
err = tf.SetEnv(environ)
340+
err = tfe.SetEnv(environ)
306341
if err != nil {
307342
return diag.FromErr(err)
308343
}
309344

310-
b.Terraform = tf
345+
b.Terraform = tfe
311346
return nil
312347
}
313348

0 commit comments

Comments
 (0)