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 }