github.com/databricks/cli@v0.203.0/bundle/deploy/terraform/init.go (about)

     1  package terraform
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/databricks/cli/bundle"
    13  	"github.com/databricks/cli/bundle/config"
    14  	"github.com/databricks/cli/libs/log"
    15  	"github.com/hashicorp/go-version"
    16  	"github.com/hashicorp/hc-install/product"
    17  	"github.com/hashicorp/hc-install/releases"
    18  	"github.com/hashicorp/terraform-exec/tfexec"
    19  	"golang.org/x/exp/maps"
    20  )
    21  
    22  type initialize struct{}
    23  
    24  func (m *initialize) Name() string {
    25  	return "terraform.Initialize"
    26  }
    27  
    28  func (m *initialize) findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform) (string, error) {
    29  	// If set, pass it through [exec.LookPath] to resolve its absolute path.
    30  	if tf.ExecPath != "" {
    31  		execPath, err := exec.LookPath(tf.ExecPath)
    32  		if err != nil {
    33  			return "", err
    34  		}
    35  		tf.ExecPath = execPath
    36  		log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath)
    37  		return tf.ExecPath, nil
    38  	}
    39  
    40  	binDir, err := b.CacheDir("bin")
    41  	if err != nil {
    42  		return "", err
    43  	}
    44  
    45  	// If the execPath already exists, return it.
    46  	execPath := filepath.Join(binDir, product.Terraform.BinaryName())
    47  	_, err = os.Stat(execPath)
    48  	if err != nil && !os.IsNotExist(err) {
    49  		return "", err
    50  	}
    51  	if err == nil {
    52  		tf.ExecPath = execPath
    53  		log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath)
    54  		return tf.ExecPath, nil
    55  	}
    56  
    57  	// Download Terraform to private bin directory.
    58  	installer := &releases.LatestVersion{
    59  		Product:     product.Terraform,
    60  		Constraints: version.MustConstraints(version.NewConstraint("<2.0")),
    61  		InstallDir:  binDir,
    62  	}
    63  	execPath, err = installer.Install(ctx)
    64  	if err != nil {
    65  		return "", fmt.Errorf("error downloading Terraform: %w", err)
    66  	}
    67  
    68  	tf.ExecPath = execPath
    69  	log.Debugf(ctx, "Using Terraform at %s", tf.ExecPath)
    70  	return tf.ExecPath, nil
    71  }
    72  
    73  // This function inherits some environment variables for Terraform CLI.
    74  func inheritEnvVars(env map[string]string) error {
    75  	// Include $HOME in set of environment variables to pass along.
    76  	home, ok := os.LookupEnv("HOME")
    77  	if ok {
    78  		env["HOME"] = home
    79  	}
    80  
    81  	// Include $TF_CLI_CONFIG_FILE to override terraform provider in development.
    82  	configFile, ok := os.LookupEnv("TF_CLI_CONFIG_FILE")
    83  	if ok {
    84  		env["TF_CLI_CONFIG_FILE"] = configFile
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // This function sets temp dir location for terraform to use. If user does not
    91  // specify anything here, we fall back to a `tmp` directory in the bundle's cache
    92  // directory
    93  //
    94  // This is necessary to avoid trying to create temporary files in directories
    95  // the CLI and its dependencies do not have access to.
    96  //
    97  // see: os.TempDir for more context
    98  func setTempDirEnvVars(env map[string]string, b *bundle.Bundle) error {
    99  	switch runtime.GOOS {
   100  	case "windows":
   101  		if v, ok := os.LookupEnv("TMP"); ok {
   102  			env["TMP"] = v
   103  		} else if v, ok := os.LookupEnv("TEMP"); ok {
   104  			env["TEMP"] = v
   105  		} else if v, ok := os.LookupEnv("USERPROFILE"); ok {
   106  			env["USERPROFILE"] = v
   107  		} else {
   108  			tmpDir, err := b.CacheDir("tmp")
   109  			if err != nil {
   110  				return err
   111  			}
   112  			env["TMP"] = tmpDir
   113  		}
   114  	default:
   115  		// If TMPDIR is not set, we let the process fall back to its default value.
   116  		if v, ok := os.LookupEnv("TMPDIR"); ok {
   117  			env["TMPDIR"] = v
   118  		}
   119  	}
   120  	return nil
   121  }
   122  
   123  // This function passes through all proxy related environment variables.
   124  func setProxyEnvVars(env map[string]string, b *bundle.Bundle) error {
   125  	for _, v := range []string{"http_proxy", "https_proxy", "no_proxy"} {
   126  		// The case (upper or lower) is notoriously inconsistent for tools on Unix systems.
   127  		// We therefore try to read both the upper and lower case versions of the variable.
   128  		for _, v := range []string{strings.ToUpper(v), strings.ToLower(v)} {
   129  			if val, ok := os.LookupEnv(v); ok {
   130  				// Only set uppercase version of the variable.
   131  				env[strings.ToUpper(v)] = val
   132  			}
   133  		}
   134  	}
   135  	return nil
   136  }
   137  
   138  func (m *initialize) Apply(ctx context.Context, b *bundle.Bundle) error {
   139  	tfConfig := b.Config.Bundle.Terraform
   140  	if tfConfig == nil {
   141  		tfConfig = &config.Terraform{}
   142  		b.Config.Bundle.Terraform = tfConfig
   143  	}
   144  
   145  	execPath, err := m.findExecPath(ctx, b, tfConfig)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	workingDir, err := Dir(b)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	tf, err := tfexec.NewTerraform(workingDir, execPath)
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	env, err := b.AuthEnv()
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	err = inheritEnvVars(env)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	// Set the temporary directory environment variables
   171  	err = setTempDirEnvVars(env, b)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	// Set the proxy related environment variables
   177  	err = setProxyEnvVars(env, b)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	// Configure environment variables for auth for Terraform to use.
   183  	log.Debugf(ctx, "Environment variables for Terraform: %s", strings.Join(maps.Keys(env), ", "))
   184  	err = tf.SetEnv(env)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	b.Terraform = tf
   190  	return nil
   191  }
   192  
   193  func Initialize() bundle.Mutator {
   194  	return &initialize{}
   195  }