databricks-cli/bundle/deploy/terraform/init.go

196 lines
4.8 KiB
Go
Raw Normal View History

2022-12-15 14:12:47 +00:00
package terraform
import (
"context"
"fmt"
"os"
2022-12-15 14:12:47 +00:00
"os/exec"
"path/filepath"
"runtime"
"strings"
2022-12-15 14:12:47 +00:00
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config"
"github.com/databricks/cli/libs/log"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
2022-12-15 14:12:47 +00:00
"github.com/hashicorp/terraform-exec/tfexec"
"golang.org/x/exp/maps"
2022-12-15 14:12:47 +00:00
)
type initialize struct{}
func (m *initialize) Name() string {
return "terraform.Initialize"
}
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
}
binDir, err := b.CacheDir("bin")
if err != nil {
return "", err
}
// If the execPath already exists, return it.
execPath := filepath.Join(binDir, product.Terraform.BinaryName())
_, err = os.Stat(execPath)
if err != nil && !os.IsNotExist(err) {
return "", err
}
if err == nil {
tf.ExecPath = execPath
log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath)
return tf.ExecPath, nil
}
// Download Terraform to private bin directory.
installer := &releases.LatestVersion{
Product: product.Terraform,
Constraints: version.MustConstraints(version.NewConstraint("<2.0")),
InstallDir: binDir,
}
execPath, err = installer.Install(ctx)
if err != nil {
return "", fmt.Errorf("error downloading Terraform: %w", err)
}
tf.ExecPath = execPath
log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath)
return tf.ExecPath, nil
}
// This function inherits some environment variables for Terraform CLI.
func inheritEnvVars(env map[string]string) error {
// Include $HOME in set of environment variables to pass along.
home, ok := os.LookupEnv("HOME")
if ok {
env["HOME"] = home
}
// Include $TF_CLI_CONFIG_FILE to override terraform provider in development.
configFile, ok := os.LookupEnv("TF_CLI_CONFIG_FILE")
if ok {
env["TF_CLI_CONFIG_FILE"] = configFile
}
return nil
}
// This function sets temp dir location for terraform to use. If user does not
// specify anything here, we fall back to a `tmp` directory in the bundle's cache
// directory
//
// This is necessary to avoid trying to create temporary files in directories
// the CLI and its dependencies do not have access to.
//
// see: os.TempDir for more context
func setTempDirEnvVars(env map[string]string, b *bundle.Bundle) error {
switch runtime.GOOS {
case "windows":
if v, ok := os.LookupEnv("TMP"); ok {
env["TMP"] = v
} else if v, ok := os.LookupEnv("TEMP"); ok {
env["TEMP"] = v
} else if v, ok := os.LookupEnv("USERPROFILE"); ok {
env["USERPROFILE"] = v
} else {
tmpDir, err := b.CacheDir("tmp")
if err != nil {
return err
}
env["TMP"] = tmpDir
}
default:
// If TMPDIR is not set, we let the process fall back to its default value.
if v, ok := os.LookupEnv("TMPDIR"); ok {
env["TMPDIR"] = v
}
}
return nil
}
// This function passes through all proxy related environment variables.
func setProxyEnvVars(env map[string]string, b *bundle.Bundle) error {
for _, v := range []string{"http_proxy", "https_proxy", "no_proxy"} {
// The case (upper or lower) is notoriously inconsistent for tools on Unix systems.
// We therefore try to read both the upper and lower case versions of the variable.
for _, v := range []string{strings.ToUpper(v), strings.ToLower(v)} {
if val, ok := os.LookupEnv(v); ok {
// Only set uppercase version of the variable.
env[strings.ToUpper(v)] = val
}
}
}
return nil
}
func (m *initialize) Apply(ctx context.Context, b *bundle.Bundle) error {
tfConfig := b.Config.Bundle.Terraform
if tfConfig == nil {
tfConfig = &config.Terraform{}
b.Config.Bundle.Terraform = tfConfig
}
execPath, err := m.findExecPath(ctx, b, tfConfig)
2022-12-15 14:12:47 +00:00
if err != nil {
return err
2022-12-15 14:12:47 +00:00
}
workingDir, err := Dir(b)
2022-12-15 14:12:47 +00:00
if err != nil {
return err
2022-12-15 14:12:47 +00:00
}
tf, err := tfexec.NewTerraform(workingDir, execPath)
if err != nil {
return err
2022-12-15 14:12:47 +00:00
}
env, err := b.AuthEnv()
if err != nil {
return err
}
err = inheritEnvVars(env)
if err != nil {
return err
}
// Set the temporary directory environment variables
err = setTempDirEnvVars(env, b)
if err != nil {
return err
}
// Set the proxy related environment variables
err = setProxyEnvVars(env, b)
if err != nil {
return err
}
// Configure environment variables for auth for Terraform to use.
log.Debugf(ctx, "Environment variables for Terraform: %s", strings.Join(maps.Keys(env), ", "))
err = tf.SetEnv(env)
if err != nil {
return err
}
2022-12-15 14:12:47 +00:00
b.Terraform = tf
return nil
2022-12-15 14:12:47 +00:00
}
func Initialize() bundle.Mutator {
return &initialize{}
}