aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/internal/initwd
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/internal/initwd')
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/copy_dir.go125
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/doc.go7
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go363
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/getter.go210
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/inode.go21
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/inode_freebsd.go21
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/inode_windows.go8
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/load_config.go56
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/module_install.go558
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/module_install_hooks.go36
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/testing.go73
-rw-r--r--vendor/github.com/hashicorp/terraform/internal/initwd/version_required.go83
12 files changed, 1561 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/copy_dir.go b/vendor/github.com/hashicorp/terraform/internal/initwd/copy_dir.go
new file mode 100644
index 0000000..7096ff7
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/copy_dir.go
@@ -0,0 +1,125 @@
1package initwd
2
3import (
4 "io"
5 "os"
6 "path/filepath"
7 "strings"
8)
9
10// copyDir copies the src directory contents into dst. Both directories
11// should already exist.
12func copyDir(dst, src string) error {
13 src, err := filepath.EvalSymlinks(src)
14 if err != nil {
15 return err
16 }
17
18 walkFn := func(path string, info os.FileInfo, err error) error {
19 if err != nil {
20 return err
21 }
22
23 if path == src {
24 return nil
25 }
26
27 if strings.HasPrefix(filepath.Base(path), ".") {
28 // Skip any dot files
29 if info.IsDir() {
30 return filepath.SkipDir
31 } else {
32 return nil
33 }
34 }
35
36 // The "path" has the src prefixed to it. We need to join our
37 // destination with the path without the src on it.
38 dstPath := filepath.Join(dst, path[len(src):])
39
40 // we don't want to try and copy the same file over itself.
41 if eq, err := sameFile(path, dstPath); eq {
42 return nil
43 } else if err != nil {
44 return err
45 }
46
47 // If we have a directory, make that subdirectory, then continue
48 // the walk.
49 if info.IsDir() {
50 if path == filepath.Join(src, dst) {
51 // dst is in src; don't walk it.
52 return nil
53 }
54
55 if err := os.MkdirAll(dstPath, 0755); err != nil {
56 return err
57 }
58
59 return nil
60 }
61
62 // If the current path is a symlink, recreate the symlink relative to
63 // the dst directory
64 if info.Mode()&os.ModeSymlink == os.ModeSymlink {
65 target, err := os.Readlink(path)
66 if err != nil {
67 return err
68 }
69
70 return os.Symlink(target, dstPath)
71 }
72
73 // If we have a file, copy the contents.
74 srcF, err := os.Open(path)
75 if err != nil {
76 return err
77 }
78 defer srcF.Close()
79
80 dstF, err := os.Create(dstPath)
81 if err != nil {
82 return err
83 }
84 defer dstF.Close()
85
86 if _, err := io.Copy(dstF, srcF); err != nil {
87 return err
88 }
89
90 // Chmod it
91 return os.Chmod(dstPath, info.Mode())
92 }
93
94 return filepath.Walk(src, walkFn)
95}
96
97// sameFile tried to determine if to paths are the same file.
98// If the paths don't match, we lookup the inode on supported systems.
99func sameFile(a, b string) (bool, error) {
100 if a == b {
101 return true, nil
102 }
103
104 aIno, err := inode(a)
105 if err != nil {
106 if os.IsNotExist(err) {
107 return false, nil
108 }
109 return false, err
110 }
111
112 bIno, err := inode(b)
113 if err != nil {
114 if os.IsNotExist(err) {
115 return false, nil
116 }
117 return false, err
118 }
119
120 if aIno > 0 && aIno == bIno {
121 return true, nil
122 }
123
124 return false, nil
125}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/doc.go b/vendor/github.com/hashicorp/terraform/internal/initwd/doc.go
new file mode 100644
index 0000000..b9d938d
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/doc.go
@@ -0,0 +1,7 @@
1// Package initwd contains various helper functions used by the "terraform init"
2// command to initialize a working directory.
3//
4// These functions may also be used from testing code to simulate the behaviors
5// of "terraform init" against test fixtures, but should not be used elsewhere
6// in the main code.
7package initwd
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go b/vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go
new file mode 100644
index 0000000..6b40d08
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go
@@ -0,0 +1,363 @@
1package initwd
2
3import (
4 "fmt"
5 "github.com/hashicorp/terraform/internal/earlyconfig"
6 "io/ioutil"
7 "log"
8 "os"
9 "path/filepath"
10 "sort"
11 "strings"
12
13 version "github.com/hashicorp/go-version"
14 "github.com/hashicorp/terraform-config-inspect/tfconfig"
15 "github.com/hashicorp/terraform/internal/modsdir"
16 "github.com/hashicorp/terraform/registry"
17 "github.com/hashicorp/terraform/tfdiags"
18)
19
20const initFromModuleRootCallName = "root"
21const initFromModuleRootKeyPrefix = initFromModuleRootCallName + "."
22
23// DirFromModule populates the given directory (which must exist and be
24// empty) with the contents of the module at the given source address.
25//
26// It does this by installing the given module and all of its descendent
27// modules in a temporary root directory and then copying the installed
28// files into suitable locations. As a consequence, any diagnostics it
29// generates will reveal the location of this temporary directory to the
30// user.
31//
32// This rather roundabout installation approach is taken to ensure that
33// installation proceeds in a manner identical to normal module installation.
34//
35// If the given source address specifies a sub-directory of the given
36// package then only the sub-directory and its descendents will be copied
37// into the given root directory, which will cause any relative module
38// references using ../ from that module to be unresolvable. Error diagnostics
39// are produced in that case, to prompt the user to rewrite the source strings
40// to be absolute references to the original remote module.
41func DirFromModule(rootDir, modulesDir, sourceAddr string, reg *registry.Client, hooks ModuleInstallHooks) tfdiags.Diagnostics {
42 var diags tfdiags.Diagnostics
43
44 // The way this function works is pretty ugly, but we accept it because
45 // -from-module is a less important case than normal module installation
46 // and so it's better to keep this ugly complexity out here rather than
47 // adding even more complexity to the normal module installer.
48
49 // The target directory must exist but be empty.
50 {
51 entries, err := ioutil.ReadDir(rootDir)
52 if err != nil {
53 if os.IsNotExist(err) {
54 diags = diags.Append(tfdiags.Sourceless(
55 tfdiags.Error,
56 "Target directory does not exist",
57 fmt.Sprintf("Cannot initialize non-existent directory %s.", rootDir),
58 ))
59 } else {
60 diags = diags.Append(tfdiags.Sourceless(
61 tfdiags.Error,
62 "Failed to read target directory",
63 fmt.Sprintf("Error reading %s to ensure it is empty: %s.", rootDir, err),
64 ))
65 }
66 return diags
67 }
68 haveEntries := false
69 for _, entry := range entries {
70 if entry.Name() == "." || entry.Name() == ".." || entry.Name() == ".terraform" {
71 continue
72 }
73 haveEntries = true
74 }
75 if haveEntries {
76 diags = diags.Append(tfdiags.Sourceless(
77 tfdiags.Error,
78 "Can't populate non-empty directory",
79 fmt.Sprintf("The target directory %s is not empty, so it cannot be initialized with the -from-module=... option.", rootDir),
80 ))
81 return diags
82 }
83 }
84
85 instDir := filepath.Join(rootDir, ".terraform/init-from-module")
86 inst := NewModuleInstaller(instDir, reg)
87 log.Printf("[DEBUG] installing modules in %s to initialize working directory from %q", instDir, sourceAddr)
88 os.RemoveAll(instDir) // if this fails then we'll fail on MkdirAll below too
89 err := os.MkdirAll(instDir, os.ModePerm)
90 if err != nil {
91 diags = diags.Append(tfdiags.Sourceless(
92 tfdiags.Error,
93 "Failed to create temporary directory",
94 fmt.Sprintf("Failed to create temporary directory %s: %s.", instDir, err),
95 ))
96 return diags
97 }
98
99 instManifest := make(modsdir.Manifest)
100 retManifest := make(modsdir.Manifest)
101
102 fakeFilename := fmt.Sprintf("-from-module=%q", sourceAddr)
103 fakePos := tfconfig.SourcePos{
104 Filename: fakeFilename,
105 Line: 1,
106 }
107
108 // -from-module allows relative paths but it's different than a normal
109 // module address where it'd be resolved relative to the module call
110 // (which is synthetic, here.) To address this, we'll just patch up any
111 // relative paths to be absolute paths before we run, ensuring we'll
112 // get the right result. This also, as an important side-effect, ensures
113 // that the result will be "downloaded" with go-getter (copied from the
114 // source location), rather than just recorded as a relative path.
115 {
116 maybePath := filepath.ToSlash(sourceAddr)
117 if maybePath == "." || strings.HasPrefix(maybePath, "./") || strings.HasPrefix(maybePath, "../") {
118 if wd, err := os.Getwd(); err == nil {
119 sourceAddr = filepath.Join(wd, sourceAddr)
120 log.Printf("[TRACE] -from-module relative path rewritten to absolute path %s", sourceAddr)
121 }
122 }
123 }
124
125 // Now we need to create an artificial root module that will seed our
126 // installation process.
127 fakeRootModule := &tfconfig.Module{
128 ModuleCalls: map[string]*tfconfig.ModuleCall{
129 initFromModuleRootCallName: {
130 Name: initFromModuleRootCallName,
131 Source: sourceAddr,
132 Pos: fakePos,
133 },
134 },
135 }
136
137 // wrapHooks filters hook notifications to only include Download calls
138 // and to trim off the initFromModuleRootCallName prefix. We'll produce
139 // our own Install notifications directly below.
140 wrapHooks := installHooksInitDir{
141 Wrapped: hooks,
142 }
143 getter := reusingGetter{}
144 _, instDiags := inst.installDescendentModules(fakeRootModule, rootDir, instManifest, true, wrapHooks, getter)
145 diags = append(diags, instDiags...)
146 if instDiags.HasErrors() {
147 return diags
148 }
149
150 // If all of that succeeded then we'll now migrate what was installed
151 // into the final directory structure.
152 err = os.MkdirAll(modulesDir, os.ModePerm)
153 if err != nil {
154 diags = diags.Append(tfdiags.Sourceless(
155 tfdiags.Error,
156 "Failed to create local modules directory",
157 fmt.Sprintf("Failed to create modules directory %s: %s.", modulesDir, err),
158 ))
159 return diags
160 }
161
162 recordKeys := make([]string, 0, len(instManifest))
163 for k := range instManifest {
164 recordKeys = append(recordKeys, k)
165 }
166 sort.Strings(recordKeys)
167
168 for _, recordKey := range recordKeys {
169 record := instManifest[recordKey]
170
171 if record.Key == initFromModuleRootCallName {
172 // We've found the module the user requested, which we must
173 // now copy into rootDir so it can be used directly.
174 log.Printf("[TRACE] copying new root module from %s to %s", record.Dir, rootDir)
175 err := copyDir(rootDir, record.Dir)
176 if err != nil {
177 diags = diags.Append(tfdiags.Sourceless(
178 tfdiags.Error,
179 "Failed to copy root module",
180 fmt.Sprintf("Error copying root module %q from %s to %s: %s.", sourceAddr, record.Dir, rootDir, err),
181 ))
182 continue
183 }
184
185 // We'll try to load the newly-copied module here just so we can
186 // sniff for any module calls that ../ out of the root directory
187 // and must thus be rewritten to be absolute addresses again.
188 // For now we can't do this rewriting automatically, but we'll
189 // generate an error to help the user do it manually.
190 mod, _ := earlyconfig.LoadModule(rootDir) // ignore diagnostics since we're just doing value-add here anyway
191 if mod != nil {
192 for _, mc := range mod.ModuleCalls {
193 if pathTraversesUp(mc.Source) {
194 packageAddr, givenSubdir := splitAddrSubdir(sourceAddr)
195 newSubdir := filepath.Join(givenSubdir, mc.Source)
196 if pathTraversesUp(newSubdir) {
197 // This should never happen in any reasonable
198 // configuration since this suggests a path that
199 // traverses up out of the package root. We'll just
200 // ignore this, since we'll fail soon enough anyway
201 // trying to resolve this path when this module is
202 // loaded.
203 continue
204 }
205
206 var newAddr = packageAddr
207 if newSubdir != "" {
208 newAddr = fmt.Sprintf("%s//%s", newAddr, filepath.ToSlash(newSubdir))
209 }
210 diags = diags.Append(tfdiags.Sourceless(
211 tfdiags.Error,
212 "Root module references parent directory",
213 fmt.Sprintf("The requested module %q refers to a module via its parent directory. To use this as a new root module this source string must be rewritten as a remote source address, such as %q.", sourceAddr, newAddr),
214 ))
215 continue
216 }
217 }
218 }
219
220 retManifest[""] = modsdir.Record{
221 Key: "",
222 Dir: rootDir,
223 }
224 continue
225 }
226
227 if !strings.HasPrefix(record.Key, initFromModuleRootKeyPrefix) {
228 // Ignore the *real* root module, whose key is empty, since
229 // we're only interested in the module named "root" and its
230 // descendents.
231 continue
232 }
233
234 newKey := record.Key[len(initFromModuleRootKeyPrefix):]
235 instPath := filepath.Join(modulesDir, newKey)
236 tempPath := filepath.Join(instDir, record.Key)
237
238 // tempPath won't be present for a module that was installed from
239 // a relative path, so in that case we just record the installation
240 // directory and assume it was already copied into place as part
241 // of its parent.
242 if _, err := os.Stat(tempPath); err != nil {
243 if !os.IsNotExist(err) {
244 diags = diags.Append(tfdiags.Sourceless(
245 tfdiags.Error,
246 "Failed to stat temporary module install directory",
247 fmt.Sprintf("Error from stat %s for module %s: %s.", instPath, newKey, err),
248 ))
249 continue
250 }
251
252 var parentKey string
253 if lastDot := strings.LastIndexByte(newKey, '.'); lastDot != -1 {
254 parentKey = newKey[:lastDot]
255 } else {
256 parentKey = "" // parent is the root module
257 }
258
259 parentOld := instManifest[initFromModuleRootKeyPrefix+parentKey]
260 parentNew := retManifest[parentKey]
261
262 // We need to figure out which portion of our directory is the
263 // parent package path and which portion is the subdirectory
264 // under that.
265 baseDirRel, err := filepath.Rel(parentOld.Dir, record.Dir)
266 if err != nil {
267 // Should never happen, because we constructed both directories
268 // from the same base and so they must have a common prefix.
269 panic(err)
270 }
271
272 newDir := filepath.Join(parentNew.Dir, baseDirRel)
273 log.Printf("[TRACE] relative reference for %s rewritten from %s to %s", newKey, record.Dir, newDir)
274 newRecord := record // shallow copy
275 newRecord.Dir = newDir
276 newRecord.Key = newKey
277 retManifest[newKey] = newRecord
278 hooks.Install(newRecord.Key, newRecord.Version, newRecord.Dir)
279 continue
280 }
281
282 err = os.MkdirAll(instPath, os.ModePerm)
283 if err != nil {
284 diags = diags.Append(tfdiags.Sourceless(
285 tfdiags.Error,
286 "Failed to create module install directory",
287 fmt.Sprintf("Error creating directory %s for module %s: %s.", instPath, newKey, err),
288 ))
289 continue
290 }
291
292 // We copy rather than "rename" here because renaming between directories
293 // can be tricky in edge-cases like network filesystems, etc.
294 log.Printf("[TRACE] copying new module %s from %s to %s", newKey, record.Dir, instPath)
295 err := copyDir(instPath, tempPath)
296 if err != nil {
297 diags = diags.Append(tfdiags.Sourceless(
298 tfdiags.Error,
299 "Failed to copy descendent module",
300 fmt.Sprintf("Error copying module %q from %s to %s: %s.", newKey, tempPath, rootDir, err),
301 ))
302 continue
303 }
304
305 subDir, err := filepath.Rel(tempPath, record.Dir)
306 if err != nil {
307 // Should never happen, because we constructed both directories
308 // from the same base and so they must have a common prefix.
309 panic(err)
310 }
311
312 newRecord := record // shallow copy
313 newRecord.Dir = filepath.Join(instPath, subDir)
314 newRecord.Key = newKey
315 retManifest[newKey] = newRecord
316 hooks.Install(newRecord.Key, newRecord.Version, newRecord.Dir)
317 }
318
319 retManifest.WriteSnapshotToDir(modulesDir)
320 if err != nil {
321 diags = diags.Append(tfdiags.Sourceless(
322 tfdiags.Error,
323 "Failed to write module manifest",
324 fmt.Sprintf("Error writing module manifest: %s.", err),
325 ))
326 }
327
328 if !diags.HasErrors() {
329 // Try to clean up our temporary directory, but don't worry if we don't
330 // succeed since it shouldn't hurt anything.
331 os.RemoveAll(instDir)
332 }
333
334 return diags
335}
336
337func pathTraversesUp(path string) bool {
338 return strings.HasPrefix(filepath.ToSlash(path), "../")
339}
340
341// installHooksInitDir is an adapter wrapper for an InstallHooks that
342// does some fakery to make downloads look like they are happening in their
343// final locations, rather than in the temporary loader we use.
344//
345// It also suppresses "Install" calls entirely, since InitDirFromModule
346// does its own installation steps after the initial installation pass
347// has completed.
348type installHooksInitDir struct {
349 Wrapped ModuleInstallHooks
350 ModuleInstallHooksImpl
351}
352
353func (h installHooksInitDir) Download(moduleAddr, packageAddr string, version *version.Version) {
354 if !strings.HasPrefix(moduleAddr, initFromModuleRootKeyPrefix) {
355 // We won't announce the root module, since hook implementations
356 // don't expect to see that and the caller will usually have produced
357 // its own user-facing notification about what it's doing anyway.
358 return
359 }
360
361 trimAddr := moduleAddr[len(initFromModuleRootKeyPrefix):]
362 h.Wrapped.Download(trimAddr, packageAddr, version)
363}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/getter.go b/vendor/github.com/hashicorp/terraform/internal/initwd/getter.go
new file mode 100644
index 0000000..50e2572
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/getter.go
@@ -0,0 +1,210 @@
1package initwd
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "path/filepath"
8 "strings"
9
10 cleanhttp "github.com/hashicorp/go-cleanhttp"
11 getter "github.com/hashicorp/go-getter"
12 "github.com/hashicorp/terraform/registry/regsrc"
13)
14
15// We configure our own go-getter detector and getter sets here, because
16// the set of sources we support is part of Terraform's documentation and
17// so we don't want any new sources introduced in go-getter to sneak in here
18// and work even though they aren't documented. This also insulates us from
19// any meddling that might be done by other go-getter callers linked into our
20// executable.
21
22var goGetterDetectors = []getter.Detector{
23 new(getter.GitHubDetector),
24 new(getter.BitBucketDetector),
25 new(getter.S3Detector),
26 new(getter.FileDetector),
27}
28
29var goGetterNoDetectors = []getter.Detector{}
30
31var goGetterDecompressors = map[string]getter.Decompressor{
32 "bz2": new(getter.Bzip2Decompressor),
33 "gz": new(getter.GzipDecompressor),
34 "xz": new(getter.XzDecompressor),
35 "zip": new(getter.ZipDecompressor),
36
37 "tar.bz2": new(getter.TarBzip2Decompressor),
38 "tar.tbz2": new(getter.TarBzip2Decompressor),
39
40 "tar.gz": new(getter.TarGzipDecompressor),
41 "tgz": new(getter.TarGzipDecompressor),
42
43 "tar.xz": new(getter.TarXzDecompressor),
44 "txz": new(getter.TarXzDecompressor),
45}
46
47var goGetterGetters = map[string]getter.Getter{
48 "file": new(getter.FileGetter),
49 "git": new(getter.GitGetter),
50 "hg": new(getter.HgGetter),
51 "s3": new(getter.S3Getter),
52 "http": getterHTTPGetter,
53 "https": getterHTTPGetter,
54}
55
56var getterHTTPClient = cleanhttp.DefaultClient()
57
58var getterHTTPGetter = &getter.HttpGetter{
59 Client: getterHTTPClient,
60 Netrc: true,
61}
62
63// A reusingGetter is a helper for the module installer that remembers
64// the final resolved addresses of all of the sources it has already been
65// asked to install, and will copy from a prior installation directory if
66// it has the same resolved source address.
67//
68// The keys in a reusingGetter are resolved and trimmed source addresses
69// (with a scheme always present, and without any "subdir" component),
70// and the values are the paths where each source was previously installed.
71type reusingGetter map[string]string
72
73// getWithGoGetter retrieves the package referenced in the given address
74// into the installation path and then returns the full path to any subdir
75// indicated in the address.
76//
77// The errors returned by this function are those surfaced by the underlying
78// go-getter library, which have very inconsistent quality as
79// end-user-actionable error messages. At this time we do not have any
80// reasonable way to improve these error messages at this layer because
81// the underlying errors are not separately recognizable.
82func (g reusingGetter) getWithGoGetter(instPath, addr string) (string, error) {
83 packageAddr, subDir := splitAddrSubdir(addr)
84
85 log.Printf("[DEBUG] will download %q to %s", packageAddr, instPath)
86
87 realAddr, err := getter.Detect(packageAddr, instPath, getter.Detectors)
88 if err != nil {
89 return "", err
90 }
91
92 if isMaybeRelativeLocalPath(realAddr) {
93 return "", &MaybeRelativePathErr{addr}
94 }
95
96 var realSubDir string
97 realAddr, realSubDir = splitAddrSubdir(realAddr)
98 if realSubDir != "" {
99 subDir = filepath.Join(realSubDir, subDir)
100 }
101
102 if realAddr != packageAddr {
103 log.Printf("[TRACE] go-getter detectors rewrote %q to %q", packageAddr, realAddr)
104 }
105
106 if prevDir, exists := g[realAddr]; exists {
107 log.Printf("[TRACE] copying previous install %s to %s", prevDir, instPath)
108 err := os.Mkdir(instPath, os.ModePerm)
109 if err != nil {
110 return "", fmt.Errorf("failed to create directory %s: %s", instPath, err)
111 }
112 err = copyDir(instPath, prevDir)
113 if err != nil {
114 return "", fmt.Errorf("failed to copy from %s to %s: %s", prevDir, instPath, err)
115 }
116 } else {
117 log.Printf("[TRACE] fetching %q to %q", realAddr, instPath)
118 client := getter.Client{
119 Src: realAddr,
120 Dst: instPath,
121 Pwd: instPath,
122
123 Mode: getter.ClientModeDir,
124
125 Detectors: goGetterNoDetectors, // we already did detection above
126 Decompressors: goGetterDecompressors,
127 Getters: goGetterGetters,
128 }
129 err = client.Get()
130 if err != nil {
131 return "", err
132 }
133 // Remember where we installed this so we might reuse this directory
134 // on subsequent calls to avoid re-downloading.
135 g[realAddr] = instPath
136 }
137
138 // Our subDir string can contain wildcards until this point, so that
139 // e.g. a subDir of * can expand to one top-level directory in a .tar.gz
140 // archive. Now that we've expanded the archive successfully we must
141 // resolve that into a concrete path.
142 var finalDir string
143 if subDir != "" {
144 finalDir, err = getter.SubdirGlob(instPath, subDir)
145 log.Printf("[TRACE] expanded %q to %q", subDir, finalDir)
146 if err != nil {
147 return "", err
148 }
149 } else {
150 finalDir = instPath
151 }
152
153 // If we got this far then we have apparently succeeded in downloading
154 // the requested object!
155 return filepath.Clean(finalDir), nil
156}
157
158// splitAddrSubdir splits the given address (which is assumed to be a
159// registry address or go-getter-style address) into a package portion
160// and a sub-directory portion.
161//
162// The package portion defines what should be downloaded and then the
163// sub-directory portion, if present, specifies a sub-directory within
164// the downloaded object (an archive, VCS repository, etc) that contains
165// the module's configuration files.
166//
167// The subDir portion will be returned as empty if no subdir separator
168// ("//") is present in the address.
169func splitAddrSubdir(addr string) (packageAddr, subDir string) {
170 return getter.SourceDirSubdir(addr)
171}
172
173var localSourcePrefixes = []string{
174 "./",
175 "../",
176 ".\\",
177 "..\\",
178}
179
180func isLocalSourceAddr(addr string) bool {
181 for _, prefix := range localSourcePrefixes {
182 if strings.HasPrefix(addr, prefix) {
183 return true
184 }
185 }
186 return false
187}
188
189func isRegistrySourceAddr(addr string) bool {
190 _, err := regsrc.ParseModuleSource(addr)
191 return err == nil
192}
193
194type MaybeRelativePathErr struct {
195 Addr string
196}
197
198func (e *MaybeRelativePathErr) Error() string {
199 return fmt.Sprintf("Terraform cannot determine the module source for %s", e.Addr)
200}
201
202func isMaybeRelativeLocalPath(addr string) bool {
203 if strings.HasPrefix(addr, "file://") {
204 _, err := os.Stat(addr[7:])
205 if err != nil {
206 return true
207 }
208 }
209 return false
210}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/inode.go b/vendor/github.com/hashicorp/terraform/internal/initwd/inode.go
new file mode 100644
index 0000000..1150b09
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/inode.go
@@ -0,0 +1,21 @@
1// +build linux darwin openbsd netbsd solaris dragonfly
2
3package initwd
4
5import (
6 "fmt"
7 "os"
8 "syscall"
9)
10
11// lookup the inode of a file on posix systems
12func inode(path string) (uint64, error) {
13 stat, err := os.Stat(path)
14 if err != nil {
15 return 0, err
16 }
17 if st, ok := stat.Sys().(*syscall.Stat_t); ok {
18 return st.Ino, nil
19 }
20 return 0, fmt.Errorf("could not determine file inode")
21}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/inode_freebsd.go b/vendor/github.com/hashicorp/terraform/internal/initwd/inode_freebsd.go
new file mode 100644
index 0000000..30532f5
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/inode_freebsd.go
@@ -0,0 +1,21 @@
1// +build freebsd
2
3package initwd
4
5import (
6 "fmt"
7 "os"
8 "syscall"
9)
10
11// lookup the inode of a file on posix systems
12func inode(path string) (uint64, error) {
13 stat, err := os.Stat(path)
14 if err != nil {
15 return 0, err
16 }
17 if st, ok := stat.Sys().(*syscall.Stat_t); ok {
18 return uint64(st.Ino), nil
19 }
20 return 0, fmt.Errorf("could not determine file inode")
21}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/inode_windows.go b/vendor/github.com/hashicorp/terraform/internal/initwd/inode_windows.go
new file mode 100644
index 0000000..3ed58e4
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/inode_windows.go
@@ -0,0 +1,8 @@
1// +build windows
2
3package initwd
4
5// no syscall.Stat_t on windows, return 0 for inodes
6func inode(path string) (uint64, error) {
7 return 0, nil
8}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/load_config.go b/vendor/github.com/hashicorp/terraform/internal/initwd/load_config.go
new file mode 100644
index 0000000..6f77dcd
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/load_config.go
@@ -0,0 +1,56 @@
1package initwd
2
3import (
4 "fmt"
5
6 version "github.com/hashicorp/go-version"
7 "github.com/hashicorp/terraform-config-inspect/tfconfig"
8 "github.com/hashicorp/terraform/internal/earlyconfig"
9 "github.com/hashicorp/terraform/internal/modsdir"
10 "github.com/hashicorp/terraform/tfdiags"
11)
12
13// LoadConfig loads a full configuration tree that has previously had all of
14// its dependent modules installed to the given modulesDir using a
15// ModuleInstaller.
16//
17// This uses the early configuration loader and thus only reads top-level
18// metadata from the modules in the configuration. Most callers should use
19// the configs/configload package to fully load a configuration.
20func LoadConfig(rootDir, modulesDir string) (*earlyconfig.Config, tfdiags.Diagnostics) {
21 rootMod, diags := earlyconfig.LoadModule(rootDir)
22 if rootMod == nil {
23 return nil, diags
24 }
25
26 manifest, err := modsdir.ReadManifestSnapshotForDir(modulesDir)
27 if err != nil {
28 diags = diags.Append(tfdiags.Sourceless(
29 tfdiags.Error,
30 "Failed to read module manifest",
31 fmt.Sprintf("Terraform failed to read its manifest of locally-cached modules: %s.", err),
32 ))
33 return nil, diags
34 }
35
36 return earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
37 func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
38 var diags tfdiags.Diagnostics
39
40 key := manifest.ModuleKey(req.Path)
41 record, exists := manifest[key]
42 if !exists {
43 diags = diags.Append(tfdiags.Sourceless(
44 tfdiags.Error,
45 "Module not installed",
46 fmt.Sprintf("Module %s is not yet installed. Run \"terraform init\" to install all modules required by this configuration.", req.Path.String()),
47 ))
48 return nil, nil, diags
49 }
50
51 mod, mDiags := earlyconfig.LoadModule(record.Dir)
52 diags = diags.Append(mDiags)
53 return mod, record.Version, diags
54 },
55 ))
56}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/module_install.go b/vendor/github.com/hashicorp/terraform/internal/initwd/module_install.go
new file mode 100644
index 0000000..531310a
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/module_install.go
@@ -0,0 +1,558 @@
1package initwd
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "path/filepath"
8 "strings"
9
10 version "github.com/hashicorp/go-version"
11 "github.com/hashicorp/terraform-config-inspect/tfconfig"
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/internal/earlyconfig"
14 "github.com/hashicorp/terraform/internal/modsdir"
15 "github.com/hashicorp/terraform/registry"
16 "github.com/hashicorp/terraform/registry/regsrc"
17 "github.com/hashicorp/terraform/tfdiags"
18)
19
20type ModuleInstaller struct {
21 modsDir string
22 reg *registry.Client
23}
24
25func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
26 return &ModuleInstaller{
27 modsDir: modsDir,
28 reg: reg,
29 }
30}
31
32// InstallModules analyses the root module in the given directory and installs
33// all of its direct and transitive dependencies into the given modules
34// directory, which must already exist.
35//
36// Since InstallModules makes possibly-time-consuming calls to remote services,
37// a hook interface is supported to allow the caller to be notified when
38// each module is installed and, for remote modules, when downloading begins.
39// LoadConfig guarantees that two hook calls will not happen concurrently but
40// it does not guarantee any particular ordering of hook calls. This mechanism
41// is for UI feedback only and does not give the caller any control over the
42// process.
43//
44// If modules are already installed in the target directory, they will be
45// skipped unless their source address or version have changed or unless
46// the upgrade flag is set.
47//
48// InstallModules never deletes any directory, except in the case where it
49// needs to replace a directory that is already present with a newly-extracted
50// package.
51//
52// If the returned diagnostics contains errors then the module installation
53// may have wholly or partially completed. Modules must be loaded in order
54// to find their dependencies, so this function does many of the same checks
55// as LoadConfig as a side-effect.
56//
57// If successful (the returned diagnostics contains no errors) then the
58// first return value is the early configuration tree that was constructed by
59// the installation process.
60func (i *ModuleInstaller) InstallModules(rootDir string, upgrade bool, hooks ModuleInstallHooks) (*earlyconfig.Config, tfdiags.Diagnostics) {
61 log.Printf("[TRACE] ModuleInstaller: installing child modules for %s into %s", rootDir, i.modsDir)
62
63 rootMod, diags := earlyconfig.LoadModule(rootDir)
64 if rootMod == nil {
65 return nil, diags
66 }
67
68 manifest, err := modsdir.ReadManifestSnapshotForDir(i.modsDir)
69 if err != nil {
70 diags = diags.Append(tfdiags.Sourceless(
71 tfdiags.Error,
72 "Failed to read modules manifest file",
73 fmt.Sprintf("Error reading manifest for %s: %s.", i.modsDir, err),
74 ))
75 return nil, diags
76 }
77
78 getter := reusingGetter{}
79 cfg, instDiags := i.installDescendentModules(rootMod, rootDir, manifest, upgrade, hooks, getter)
80 diags = append(diags, instDiags...)
81
82 return cfg, diags
83}
84
85func (i *ModuleInstaller) installDescendentModules(rootMod *tfconfig.Module, rootDir string, manifest modsdir.Manifest, upgrade bool, hooks ModuleInstallHooks, getter reusingGetter) (*earlyconfig.Config, tfdiags.Diagnostics) {
86 var diags tfdiags.Diagnostics
87
88 if hooks == nil {
89 // Use our no-op implementation as a placeholder
90 hooks = ModuleInstallHooksImpl{}
91 }
92
93 // Create a manifest record for the root module. This will be used if
94 // there are any relative-pathed modules in the root.
95 manifest[""] = modsdir.Record{
96 Key: "",
97 Dir: rootDir,
98 }
99
100 cfg, cDiags := earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
101 func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
102
103 key := manifest.ModuleKey(req.Path)
104 instPath := i.packageInstallPath(req.Path)
105
106 log.Printf("[DEBUG] Module installer: begin %s", key)
107
108 // First we'll check if we need to upgrade/replace an existing
109 // installed module, and delete it out of the way if so.
110 replace := upgrade
111 if !replace {
112 record, recorded := manifest[key]
113 switch {
114 case !recorded:
115 log.Printf("[TRACE] ModuleInstaller: %s is not yet installed", key)
116 replace = true
117 case record.SourceAddr != req.SourceAddr:
118 log.Printf("[TRACE] ModuleInstaller: %s source address has changed from %q to %q", key, record.SourceAddr, req.SourceAddr)
119 replace = true
120 case record.Version != nil && !req.VersionConstraints.Check(record.Version):
121 log.Printf("[TRACE] ModuleInstaller: %s version %s no longer compatible with constraints %s", key, record.Version, req.VersionConstraints)
122 replace = true
123 }
124 }
125
126 // If we _are_ planning to replace this module, then we'll remove
127 // it now so our installation code below won't conflict with any
128 // existing remnants.
129 if replace {
130 if _, recorded := manifest[key]; recorded {
131 log.Printf("[TRACE] ModuleInstaller: discarding previous record of %s prior to reinstall", key)
132 }
133 delete(manifest, key)
134 // Deleting a module invalidates all of its descendent modules too.
135 keyPrefix := key + "."
136 for subKey := range manifest {
137 if strings.HasPrefix(subKey, keyPrefix) {
138 if _, recorded := manifest[subKey]; recorded {
139 log.Printf("[TRACE] ModuleInstaller: also discarding downstream %s", subKey)
140 }
141 delete(manifest, subKey)
142 }
143 }
144 }
145
146 record, recorded := manifest[key]
147 if !recorded {
148 // Clean up any stale cache directory that might be present.
149 // If this is a local (relative) source then the dir will
150 // not exist, but we'll ignore that.
151 log.Printf("[TRACE] ModuleInstaller: cleaning directory %s prior to install of %s", instPath, key)
152 err := os.RemoveAll(instPath)
153 if err != nil && !os.IsNotExist(err) {
154 log.Printf("[TRACE] ModuleInstaller: failed to remove %s: %s", key, err)
155 diags = diags.Append(tfdiags.Sourceless(
156 tfdiags.Error,
157 "Failed to remove local module cache",
158 fmt.Sprintf(
159 "Terraform tried to remove %s in order to reinstall this module, but encountered an error: %s",
160 instPath, err,
161 ),
162 ))
163 return nil, nil, diags
164 }
165 } else {
166 // If this module is already recorded and its root directory
167 // exists then we will just load what's already there and
168 // keep our existing record.
169 info, err := os.Stat(record.Dir)
170 if err == nil && info.IsDir() {
171 mod, mDiags := earlyconfig.LoadModule(record.Dir)
172 diags = diags.Append(mDiags)
173
174 log.Printf("[TRACE] ModuleInstaller: Module installer: %s %s already installed in %s", key, record.Version, record.Dir)
175 return mod, record.Version, diags
176 }
177 }
178
179 // If we get down here then it's finally time to actually install
180 // the module. There are some variants to this process depending
181 // on what type of module source address we have.
182 switch {
183
184 case isLocalSourceAddr(req.SourceAddr):
185 log.Printf("[TRACE] ModuleInstaller: %s has local path %q", key, req.SourceAddr)
186 mod, mDiags := i.installLocalModule(req, key, manifest, hooks)
187 diags = append(diags, mDiags...)
188 return mod, nil, diags
189
190 case isRegistrySourceAddr(req.SourceAddr):
191 addr, err := regsrc.ParseModuleSource(req.SourceAddr)
192 if err != nil {
193 // Should never happen because isRegistrySourceAddr already validated
194 panic(err)
195 }
196 log.Printf("[TRACE] ModuleInstaller: %s is a registry module at %s", key, addr)
197
198 mod, v, mDiags := i.installRegistryModule(req, key, instPath, addr, manifest, hooks, getter)
199 diags = append(diags, mDiags...)
200 return mod, v, diags
201
202 default:
203 log.Printf("[TRACE] ModuleInstaller: %s address %q will be handled by go-getter", key, req.SourceAddr)
204
205 mod, mDiags := i.installGoGetterModule(req, key, instPath, manifest, hooks, getter)
206 diags = append(diags, mDiags...)
207 return mod, nil, diags
208 }
209
210 },
211 ))
212 diags = append(diags, cDiags...)
213
214 err := manifest.WriteSnapshotToDir(i.modsDir)
215 if err != nil {
216 diags = diags.Append(tfdiags.Sourceless(
217 tfdiags.Error,
218 "Failed to update module manifest",
219 fmt.Sprintf("Unable to write the module manifest file: %s", err),
220 ))
221 }
222
223 return cfg, diags
224}
225
226func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key string, manifest modsdir.Manifest, hooks ModuleInstallHooks) (*tfconfig.Module, tfdiags.Diagnostics) {
227 var diags tfdiags.Diagnostics
228
229 parentKey := manifest.ModuleKey(req.Parent.Path)
230 parentRecord, recorded := manifest[parentKey]
231 if !recorded {
232 // This is indicative of a bug rather than a user-actionable error
233 panic(fmt.Errorf("missing manifest record for parent module %s", parentKey))
234 }
235
236 if len(req.VersionConstraints) != 0 {
237 diags = diags.Append(tfdiags.Sourceless(
238 tfdiags.Error,
239 "Invalid version constraint",
240 fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a relative local path.", req.Name, req.CallPos.Filename, req.CallPos.Line),
241 ))
242 }
243
244 // For local sources we don't actually need to modify the
245 // filesystem at all because the parent already wrote
246 // the files we need, and so we just load up what's already here.
247 newDir := filepath.Join(parentRecord.Dir, req.SourceAddr)
248
249 log.Printf("[TRACE] ModuleInstaller: %s uses directory from parent: %s", key, newDir)
250 // it is possible that the local directory is a symlink
251 newDir, err := filepath.EvalSymlinks(newDir)
252 if err != nil {
253 diags = diags.Append(tfdiags.Sourceless(
254 tfdiags.Error,
255 "Unreadable module directory",
256 fmt.Sprintf("Unable to evaluate directory symlink: %s", err.Error()),
257 ))
258 }
259
260 mod, mDiags := earlyconfig.LoadModule(newDir)
261 if mod == nil {
262 // nil indicates missing or unreadable directory, so we'll
263 // discard the returned diags and return a more specific
264 // error message here.
265 diags = diags.Append(tfdiags.Sourceless(
266 tfdiags.Error,
267 "Unreadable module directory",
268 fmt.Sprintf("The directory %s could not be read for module %q at %s:%d.", newDir, req.Name, req.CallPos.Filename, req.CallPos.Line),
269 ))
270 } else {
271 diags = diags.Append(mDiags)
272 }
273
274 // Note the local location in our manifest.
275 manifest[key] = modsdir.Record{
276 Key: key,
277 Dir: newDir,
278 SourceAddr: req.SourceAddr,
279 }
280 log.Printf("[DEBUG] Module installer: %s installed at %s", key, newDir)
281 hooks.Install(key, nil, newDir)
282
283 return mod, diags
284}
285
286func (i *ModuleInstaller) installRegistryModule(req *earlyconfig.ModuleRequest, key string, instPath string, addr *regsrc.Module, manifest modsdir.Manifest, hooks ModuleInstallHooks, getter reusingGetter) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
287 var diags tfdiags.Diagnostics
288
289 hostname, err := addr.SvcHost()
290 if err != nil {
291 // If it looks like the user was trying to use punycode then we'll generate
292 // a specialized error for that case. We require the unicode form of
293 // hostname so that hostnames are always human-readable in configuration
294 // and punycode can't be used to hide a malicious module hostname.
295 if strings.HasPrefix(addr.RawHost.Raw, "xn--") {
296 diags = diags.Append(tfdiags.Sourceless(
297 tfdiags.Error,
298 "Invalid module registry hostname",
299 fmt.Sprintf("The hostname portion of the module %q source address (at %s:%d) is not an acceptable hostname. Internationalized domain names must be given in unicode form rather than ASCII (\"punycode\") form.", req.Name, req.CallPos.Filename, req.CallPos.Line),
300 ))
301 } else {
302 diags = diags.Append(tfdiags.Sourceless(
303 tfdiags.Error,
304 "Invalid module registry hostname",
305 fmt.Sprintf("The hostname portion of the module %q source address (at %s:%d) is not a valid hostname.", req.Name, req.CallPos.Filename, req.CallPos.Line),
306 ))
307 }
308 return nil, nil, diags
309 }
310
311 reg := i.reg
312
313 log.Printf("[DEBUG] %s listing available versions of %s at %s", key, addr, hostname)
314 resp, err := reg.ModuleVersions(addr)
315 if err != nil {
316 if registry.IsModuleNotFound(err) {
317 diags = diags.Append(tfdiags.Sourceless(
318 tfdiags.Error,
319 "Module not found",
320 fmt.Sprintf("Module %q (from %s:%d) cannot be found in the module registry at %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname),
321 ))
322 } else {
323 diags = diags.Append(tfdiags.Sourceless(
324 tfdiags.Error,
325 "Error accessing remote module registry",
326 fmt.Sprintf("Failed to retrieve available versions for module %q (%s:%d) from %s: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname, err),
327 ))
328 }
329 return nil, nil, diags
330 }
331
332 // The response might contain information about dependencies to allow us
333 // to potentially optimize future requests, but we don't currently do that
334 // and so for now we'll just take the first item which is guaranteed to
335 // be the address we requested.
336 if len(resp.Modules) < 1 {
337 // Should never happen, but since this is a remote service that may
338 // be implemented by third-parties we will handle it gracefully.
339 diags = diags.Append(tfdiags.Sourceless(
340 tfdiags.Error,
341 "Invalid response from remote module registry",
342 fmt.Sprintf("The registry at %s returned an invalid response when Terraform requested available versions for module %q (%s:%d).", hostname, req.Name, req.CallPos.Filename, req.CallPos.Line),
343 ))
344 return nil, nil, diags
345 }
346
347 modMeta := resp.Modules[0]
348
349 var latestMatch *version.Version
350 var latestVersion *version.Version
351 for _, mv := range modMeta.Versions {
352 v, err := version.NewVersion(mv.Version)
353 if err != nil {
354 // Should never happen if the registry server is compliant with
355 // the protocol, but we'll warn if not to assist someone who
356 // might be developing a module registry server.
357 diags = diags.Append(tfdiags.Sourceless(
358 tfdiags.Warning,
359 "Invalid response from remote module registry",
360 fmt.Sprintf("The registry at %s returned an invalid version string %q for module %q (%s:%d), which Terraform ignored.", hostname, mv.Version, req.Name, req.CallPos.Filename, req.CallPos.Line),
361 ))
362 continue
363 }
364
365 // If we've found a pre-release version then we'll ignore it unless
366 // it was exactly requested.
367 if v.Prerelease() != "" && req.VersionConstraints.String() != v.String() {
368 log.Printf("[TRACE] ModuleInstaller: %s ignoring %s because it is a pre-release and was not requested exactly", key, v)
369 continue
370 }
371
372 if latestVersion == nil || v.GreaterThan(latestVersion) {
373 latestVersion = v
374 }
375
376 if req.VersionConstraints.Check(v) {
377 if latestMatch == nil || v.GreaterThan(latestMatch) {
378 latestMatch = v
379 }
380 }
381 }
382
383 if latestVersion == nil {
384 diags = diags.Append(tfdiags.Sourceless(
385 tfdiags.Error,
386 "Module has no versions",
387 fmt.Sprintf("Module %q (%s:%d) has no versions available on %s.", addr, req.CallPos.Filename, req.CallPos.Line, hostname),
388 ))
389 return nil, nil, diags
390 }
391
392 if latestMatch == nil {
393 diags = diags.Append(tfdiags.Sourceless(
394 tfdiags.Error,
395 "Unresolvable module version constraint",
396 fmt.Sprintf("There is no available version of module %q (%s:%d) which matches the given version constraint. The newest available version is %s.", addr, req.CallPos.Filename, req.CallPos.Line, latestVersion),
397 ))
398 return nil, nil, diags
399 }
400
401 // Report up to the caller that we're about to start downloading.
402 packageAddr, _ := splitAddrSubdir(req.SourceAddr)
403 hooks.Download(key, packageAddr, latestMatch)
404
405 // If we manage to get down here then we've found a suitable version to
406 // install, so we need to ask the registry where we should download it from.
407 // The response to this is a go-getter-style address string.
408 dlAddr, err := reg.ModuleLocation(addr, latestMatch.String())
409 if err != nil {
410 log.Printf("[ERROR] %s from %s %s: %s", key, addr, latestMatch, err)
411 diags = diags.Append(tfdiags.Sourceless(
412 tfdiags.Error,
413 "Invalid response from remote module registry",
414 fmt.Sprintf("The remote registry at %s failed to return a download URL for %s %s.", hostname, addr, latestMatch),
415 ))
416 return nil, nil, diags
417 }
418
419 log.Printf("[TRACE] ModuleInstaller: %s %s %s is available at %q", key, addr, latestMatch, dlAddr)
420
421 modDir, err := getter.getWithGoGetter(instPath, dlAddr)
422 if err != nil {
423 // Errors returned by go-getter have very inconsistent quality as
424 // end-user error messages, but for now we're accepting that because
425 // we have no way to recognize any specific errors to improve them
426 // and masking the error entirely would hide valuable diagnostic
427 // information from the user.
428 diags = diags.Append(tfdiags.Sourceless(
429 tfdiags.Error,
430 "Failed to download module",
431 fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, dlAddr, err),
432 ))
433 return nil, nil, diags
434 }
435
436 log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, dlAddr, modDir)
437
438 if addr.RawSubmodule != "" {
439 // Append the user's requested subdirectory to any subdirectory that
440 // was implied by any of the nested layers we expanded within go-getter.
441 modDir = filepath.Join(modDir, addr.RawSubmodule)
442 }
443
444 log.Printf("[TRACE] ModuleInstaller: %s should now be at %s", key, modDir)
445
446 // Finally we are ready to try actually loading the module.
447 mod, mDiags := earlyconfig.LoadModule(modDir)
448 if mod == nil {
449 // nil indicates missing or unreadable directory, so we'll
450 // discard the returned diags and return a more specific
451 // error message here. For registry modules this actually
452 // indicates a bug in the code above, since it's not the
453 // user's responsibility to create the directory in this case.
454 diags = diags.Append(tfdiags.Sourceless(
455 tfdiags.Error,
456 "Unreadable module directory",
457 fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
458 ))
459 } else {
460 diags = append(diags, mDiags...)
461 }
462
463 // Note the local location in our manifest.
464 manifest[key] = modsdir.Record{
465 Key: key,
466 Version: latestMatch,
467 Dir: modDir,
468 SourceAddr: req.SourceAddr,
469 }
470 log.Printf("[DEBUG] Module installer: %s installed at %s", key, modDir)
471 hooks.Install(key, latestMatch, modDir)
472
473 return mod, latestMatch, diags
474}
475
476func (i *ModuleInstaller) installGoGetterModule(req *earlyconfig.ModuleRequest, key string, instPath string, manifest modsdir.Manifest, hooks ModuleInstallHooks, getter reusingGetter) (*tfconfig.Module, tfdiags.Diagnostics) {
477 var diags tfdiags.Diagnostics
478
479 // Report up to the caller that we're about to start downloading.
480 packageAddr, _ := splitAddrSubdir(req.SourceAddr)
481 hooks.Download(key, packageAddr, nil)
482
483 if len(req.VersionConstraints) != 0 {
484 diags = diags.Append(tfdiags.Sourceless(
485 tfdiags.Error,
486 "Invalid version constraint",
487 fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a non Registry URL.", req.Name, req.CallPos.Filename, req.CallPos.Line),
488 ))
489 return nil, diags
490 }
491
492 modDir, err := getter.getWithGoGetter(instPath, req.SourceAddr)
493 if err != nil {
494 if _, ok := err.(*MaybeRelativePathErr); ok {
495 log.Printf(
496 "[TRACE] ModuleInstaller: %s looks like a local path but is missing ./ or ../",
497 req.SourceAddr,
498 )
499 diags = diags.Append(tfdiags.Sourceless(
500 tfdiags.Error,
501 "Module not found",
502 fmt.Sprintf(
503 "The module address %q could not be resolved.\n\n"+
504 "If you intended this as a path relative to the current "+
505 "module, use \"./%s\" instead. The \"./\" prefix "+
506 "indicates that the address is a relative filesystem path.",
507 req.SourceAddr, req.SourceAddr,
508 ),
509 ))
510 } else {
511 // Errors returned by go-getter have very inconsistent quality as
512 // end-user error messages, but for now we're accepting that because
513 // we have no way to recognize any specific errors to improve them
514 // and masking the error entirely would hide valuable diagnostic
515 // information from the user.
516 diags = diags.Append(tfdiags.Sourceless(
517 tfdiags.Error,
518 "Failed to download module",
519 fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s", req.Name, req.CallPos.Filename, req.CallPos.Line, packageAddr, err),
520 ))
521 }
522 return nil, diags
523
524 }
525
526 log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, req.SourceAddr, modDir)
527
528 mod, mDiags := earlyconfig.LoadModule(modDir)
529 if mod == nil {
530 // nil indicates missing or unreadable directory, so we'll
531 // discard the returned diags and return a more specific
532 // error message here. For go-getter modules this actually
533 // indicates a bug in the code above, since it's not the
534 // user's responsibility to create the directory in this case.
535 diags = diags.Append(tfdiags.Sourceless(
536 tfdiags.Error,
537 "Unreadable module directory",
538 fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
539 ))
540 } else {
541 diags = append(diags, mDiags...)
542 }
543
544 // Note the local location in our manifest.
545 manifest[key] = modsdir.Record{
546 Key: key,
547 Dir: modDir,
548 SourceAddr: req.SourceAddr,
549 }
550 log.Printf("[DEBUG] Module installer: %s installed at %s", key, modDir)
551 hooks.Install(key, nil, modDir)
552
553 return mod, diags
554}
555
556func (i *ModuleInstaller) packageInstallPath(modulePath addrs.Module) string {
557 return filepath.Join(i.modsDir, strings.Join(modulePath, "."))
558}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/module_install_hooks.go b/vendor/github.com/hashicorp/terraform/internal/initwd/module_install_hooks.go
new file mode 100644
index 0000000..817a6dc
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/module_install_hooks.go
@@ -0,0 +1,36 @@
1package initwd
2
3import (
4 version "github.com/hashicorp/go-version"
5)
6
7// ModuleInstallHooks is an interface used to provide notifications about the
8// installation process being orchestrated by InstallModules.
9//
10// This interface may have new methods added in future, so implementers should
11// embed InstallHooksImpl to get no-op implementations of any unimplemented
12// methods.
13type ModuleInstallHooks interface {
14 // Download is called for modules that are retrieved from a remote source
15 // before that download begins, to allow a caller to give feedback
16 // on progress through a possibly-long sequence of downloads.
17 Download(moduleAddr, packageAddr string, version *version.Version)
18
19 // Install is called for each module that is installed, even if it did
20 // not need to be downloaded from a remote source.
21 Install(moduleAddr string, version *version.Version, localPath string)
22}
23
24// ModuleInstallHooksImpl is a do-nothing implementation of InstallHooks that
25// can be embedded in another implementation struct to allow only partial
26// implementation of the interface.
27type ModuleInstallHooksImpl struct {
28}
29
30func (h ModuleInstallHooksImpl) Download(moduleAddr, packageAddr string, version *version.Version) {
31}
32
33func (h ModuleInstallHooksImpl) Install(moduleAddr string, version *version.Version, localPath string) {
34}
35
36var _ ModuleInstallHooks = ModuleInstallHooksImpl{}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/testing.go b/vendor/github.com/hashicorp/terraform/internal/initwd/testing.go
new file mode 100644
index 0000000..8cef80a
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/testing.go
@@ -0,0 +1,73 @@
1package initwd
2
3import (
4 "github.com/hashicorp/terraform/registry"
5 "testing"
6
7 "github.com/hashicorp/terraform/configs"
8 "github.com/hashicorp/terraform/configs/configload"
9 "github.com/hashicorp/terraform/tfdiags"
10)
11
12// LoadConfigForTests is a convenience wrapper around configload.NewLoaderForTests,
13// ModuleInstaller.InstallModules and configload.Loader.LoadConfig that allows
14// a test configuration to be loaded in a single step.
15//
16// If module installation fails, t.Fatal (or similar) is called to halt
17// execution of the test, under the assumption that installation failures are
18// not expected. If installation failures _are_ expected then use
19// NewLoaderForTests and work with the loader object directly. If module
20// installation succeeds but generates warnings, these warnings are discarded.
21//
22// If installation succeeds but errors are detected during loading then a
23// possibly-incomplete config is returned along with error diagnostics. The
24// test run is not aborted in this case, so that the caller can make assertions
25// against the returned diagnostics.
26//
27// As with NewLoaderForTests, a cleanup function is returned which must be
28// called before the test completes in order to remove the temporary
29// modules directory.
30func LoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configload.Loader, func(), tfdiags.Diagnostics) {
31 t.Helper()
32
33 var diags tfdiags.Diagnostics
34
35 loader, cleanup := configload.NewLoaderForTests(t)
36 inst := NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
37
38 _, moreDiags := inst.InstallModules(rootDir, true, ModuleInstallHooksImpl{})
39 diags = diags.Append(moreDiags)
40 if diags.HasErrors() {
41 cleanup()
42 t.Fatal(diags.Err())
43 return nil, nil, func() {}, diags
44 }
45
46 // Since module installer has modified the module manifest on disk, we need
47 // to refresh the cache of it in the loader.
48 if err := loader.RefreshModules(); err != nil {
49 t.Fatalf("failed to refresh modules after installation: %s", err)
50 }
51
52 config, hclDiags := loader.LoadConfig(rootDir)
53 diags = diags.Append(hclDiags)
54 return config, loader, cleanup, diags
55}
56
57// MustLoadConfigForTests is a variant of LoadConfigForTests which calls
58// t.Fatal (or similar) if there are any errors during loading, and thus
59// does not return diagnostics at all.
60//
61// This is useful for concisely writing tests that don't expect errors at
62// all. For tests that expect errors and need to assert against them, use
63// LoadConfigForTests instead.
64func MustLoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configload.Loader, func()) {
65 t.Helper()
66
67 config, loader, cleanup, diags := LoadConfigForTests(t, rootDir)
68 if diags.HasErrors() {
69 cleanup()
70 t.Fatal(diags.Err())
71 }
72 return config, loader, cleanup
73}
diff --git a/vendor/github.com/hashicorp/terraform/internal/initwd/version_required.go b/vendor/github.com/hashicorp/terraform/internal/initwd/version_required.go
new file mode 100644
index 0000000..104840b
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/internal/initwd/version_required.go
@@ -0,0 +1,83 @@
1package initwd
2
3import (
4 "fmt"
5
6 version "github.com/hashicorp/go-version"
7 "github.com/hashicorp/terraform/internal/earlyconfig"
8 "github.com/hashicorp/terraform/tfdiags"
9 tfversion "github.com/hashicorp/terraform/version"
10)
11
12// CheckCoreVersionRequirements visits each of the modules in the given
13// early configuration tree and verifies that any given Core version constraints
14// match with the version of Terraform Core that is being used.
15//
16// The returned diagnostics will contain errors if any constraints do not match.
17// The returned diagnostics might also return warnings, which should be
18// displayed to the user.
19func CheckCoreVersionRequirements(earlyConfig *earlyconfig.Config) tfdiags.Diagnostics {
20 if earlyConfig == nil {
21 return nil
22 }
23
24 var diags tfdiags.Diagnostics
25 module := earlyConfig.Module
26
27 var constraints version.Constraints
28 for _, constraintStr := range module.RequiredCore {
29 constraint, err := version.NewConstraint(constraintStr)
30 if err != nil {
31 // Unfortunately the early config parser doesn't preserve a source
32 // location for this, so we're unable to indicate a specific
33 // location where this constraint came from, but we can at least
34 // say which module set it.
35 switch {
36 case len(earlyConfig.Path) == 0:
37 diags = diags.Append(tfdiags.Sourceless(
38 tfdiags.Error,
39 "Invalid provider version constraint",
40 fmt.Sprintf("Invalid version core constraint %q in the root module.", constraintStr),
41 ))
42 default:
43 diags = diags.Append(tfdiags.Sourceless(
44 tfdiags.Error,
45 "Invalid provider version constraint",
46 fmt.Sprintf("Invalid version core constraint %q in %s.", constraintStr, earlyConfig.Path),
47 ))
48 }
49 continue
50 }
51 constraints = append(constraints, constraint...)
52 }
53
54 if !constraints.Check(tfversion.SemVer) {
55 switch {
56 case len(earlyConfig.Path) == 0:
57 diags = diags.Append(tfdiags.Sourceless(
58 tfdiags.Error,
59 "Unsupported Terraform Core version",
60 fmt.Sprintf(
61 "This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update the root module's version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
62 tfversion.String(),
63 ),
64 ))
65 default:
66 diags = diags.Append(tfdiags.Sourceless(
67 tfdiags.Error,
68 "Unsupported Terraform Core version",
69 fmt.Sprintf(
70 "Module %s (from %q) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update the module's version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
71 earlyConfig.Path, earlyConfig.SourceAddr, tfversion.String(),
72 ),
73 ))
74 }
75 }
76
77 for _, c := range earlyConfig.Children {
78 childDiags := CheckCoreVersionRequirements(c)
79 diags = diags.Append(childDiags)
80 }
81
82 return diags
83}