-
Notifications
You must be signed in to change notification settings - Fork 218
Expand file tree
/
Copy pathinit.go
More file actions
264 lines (234 loc) · 8.09 KB
/
Copy pathinit.go
File metadata and controls
264 lines (234 loc) · 8.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package terraform
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config"
"github.com/databricks/cli/bundle/internal/tf/schema"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/env"
"github.com/databricks/cli/libs/log"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
"github.com/hashicorp/terraform-exec/tfexec"
"golang.org/x/exp/maps"
)
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
}
// Load exec path from the environment if it matches the currently used version.
envExecPath, err := getEnvVarWithMatchingVersion(ctx, TerraformExecPathEnv, TerraformVersionEnv, TerraformVersion.String())
if err != nil {
return "", err
}
if envExecPath != "" {
tf.ExecPath = envExecPath
log.Debugf(ctx, "Using Terraform from %s at %s", TerraformExecPathEnv, tf.ExecPath)
return tf.ExecPath, nil
}
binDir, err := b.CacheDir(context.Background(), "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.ExactVersion{
Product: product.Terraform,
Version: TerraformVersion,
InstallDir: binDir,
Timeout: 1 * time.Minute,
}
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(ctx context.Context, environ map[string]string) error {
// Include $HOME in set of environment variables to pass along.
home, ok := env.Lookup(ctx, "HOME")
if ok {
environ["HOME"] = home
}
// Include $USERPROFILE in set of environment variables to pass along.
// This variable is used by Azure CLI on Windows to find stored credentials and metadata
userProfile, ok := env.Lookup(ctx, "USERPROFILE")
if ok {
environ["USERPROFILE"] = userProfile
}
// Include $PATH in set of environment variables to pass along.
// This is necessary to ensure that our Terraform provider can use the
// same auxiliary programs (e.g. `az`, or `gcloud`) as the CLI.
path, ok := env.Lookup(ctx, "PATH")
if ok {
environ["PATH"] = path
}
// Include $TF_CLI_CONFIG_FILE to override terraform provider in development.
// 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, TerraformCliConfigPathEnv, TerraformProviderVersionEnv, schema.ProviderVersion)
if err != nil {
return err
}
if configFile != "" {
log.Debugf(ctx, "Using Terraform CLI config from %s at %s", TerraformCliConfigPathEnv, configFile)
environ["TF_CLI_CONFIG_FILE"] = configFile
}
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 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)
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 {
return "", err
}
}
return envValue, 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(ctx context.Context, environ map[string]string, b *bundle.Bundle) error {
switch runtime.GOOS {
case "windows":
if v, ok := env.Lookup(ctx, "TMP"); ok {
environ["TMP"] = v
} else if v, ok := env.Lookup(ctx, "TEMP"); ok {
environ["TEMP"] = v
} else {
tmpDir, err := b.CacheDir(ctx, "tmp")
if err != nil {
return err
}
environ["TMP"] = tmpDir
}
default:
// If TMPDIR is not set, we let the process fall back to its default value.
if v, ok := env.Lookup(ctx, "TMPDIR"); ok {
environ["TMPDIR"] = v
}
}
return nil
}
// This function passes through all proxy related environment variables.
func setProxyEnvVars(ctx context.Context, environ 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 := env.Lookup(ctx, v); ok {
// Only set uppercase version of the variable.
environ[strings.ToUpper(v)] = val
}
}
}
return nil
}
func (m *initialize) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
tfConfig := b.Config.Bundle.Terraform
if tfConfig == nil {
tfConfig = &config.Terraform{}
b.Config.Bundle.Terraform = tfConfig
}
execPath, err := m.findExecPath(ctx, b, tfConfig)
if err != nil {
return diag.FromErr(err)
}
workingDir, err := Dir(ctx, b)
if err != nil {
return diag.FromErr(err)
}
tf, err := tfexec.NewTerraform(workingDir, execPath)
if err != nil {
return diag.FromErr(err)
}
environ, err := b.AuthEnv()
if err != nil {
return diag.FromErr(err)
}
err = inheritEnvVars(ctx, environ)
if err != nil {
return diag.FromErr(err)
}
// Set the temporary directory environment variables
err = setTempDirEnvVars(ctx, environ, b)
if err != nil {
return diag.FromErr(err)
}
// Set the proxy related environment variables
err = setProxyEnvVars(ctx, environ, b)
if err != nil {
return diag.FromErr(err)
}
// Configure environment variables for auth for Terraform to use.
log.Debugf(ctx, "Environment variables for Terraform: %s", strings.Join(maps.Keys(environ), ", "))
err = tf.SetEnv(environ)
if err != nil {
return diag.FromErr(err)
}
b.Terraform = tf
return nil
}
func Initialize() bundle.Mutator {
return &initialize{}
}