diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/internal/initwd | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip |
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/internal/initwd')
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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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. | ||
12 | func 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. | ||
99 | func 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. | ||
7 | package 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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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 | |||
20 | const initFromModuleRootCallName = "root" | ||
21 | const 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. | ||
41 | func 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 | |||
337 | func 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. | ||
348 | type installHooksInitDir struct { | ||
349 | Wrapped ModuleInstallHooks | ||
350 | ModuleInstallHooksImpl | ||
351 | } | ||
352 | |||
353 | func (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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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 | |||
22 | var goGetterDetectors = []getter.Detector{ | ||
23 | new(getter.GitHubDetector), | ||
24 | new(getter.BitBucketDetector), | ||
25 | new(getter.S3Detector), | ||
26 | new(getter.FileDetector), | ||
27 | } | ||
28 | |||
29 | var goGetterNoDetectors = []getter.Detector{} | ||
30 | |||
31 | var 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 | |||
47 | var 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 | |||
56 | var getterHTTPClient = cleanhttp.DefaultClient() | ||
57 | |||
58 | var 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. | ||
71 | type 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. | ||
82 | func (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. | ||
169 | func splitAddrSubdir(addr string) (packageAddr, subDir string) { | ||
170 | return getter.SourceDirSubdir(addr) | ||
171 | } | ||
172 | |||
173 | var localSourcePrefixes = []string{ | ||
174 | "./", | ||
175 | "../", | ||
176 | ".\\", | ||
177 | "..\\", | ||
178 | } | ||
179 | |||
180 | func 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 | |||
189 | func isRegistrySourceAddr(addr string) bool { | ||
190 | _, err := regsrc.ParseModuleSource(addr) | ||
191 | return err == nil | ||
192 | } | ||
193 | |||
194 | type MaybeRelativePathErr struct { | ||
195 | Addr string | ||
196 | } | ||
197 | |||
198 | func (e *MaybeRelativePathErr) Error() string { | ||
199 | return fmt.Sprintf("Terraform cannot determine the module source for %s", e.Addr) | ||
200 | } | ||
201 | |||
202 | func 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 | |||
3 | package initwd | ||
4 | |||
5 | import ( | ||
6 | "fmt" | ||
7 | "os" | ||
8 | "syscall" | ||
9 | ) | ||
10 | |||
11 | // lookup the inode of a file on posix systems | ||
12 | func 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 | |||
3 | package initwd | ||
4 | |||
5 | import ( | ||
6 | "fmt" | ||
7 | "os" | ||
8 | "syscall" | ||
9 | ) | ||
10 | |||
11 | // lookup the inode of a file on posix systems | ||
12 | func 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 | |||
3 | package initwd | ||
4 | |||
5 | // no syscall.Stat_t on windows, return 0 for inodes | ||
6 | func 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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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. | ||
20 | func 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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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 | |||
20 | type ModuleInstaller struct { | ||
21 | modsDir string | ||
22 | reg *registry.Client | ||
23 | } | ||
24 | |||
25 | func 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. | ||
60 | func (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 | |||
85 | func (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 | |||
226 | func (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 | |||
286 | func (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 | |||
476 | func (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 | |||
556 | func (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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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. | ||
13 | type 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. | ||
27 | type ModuleInstallHooksImpl struct { | ||
28 | } | ||
29 | |||
30 | func (h ModuleInstallHooksImpl) Download(moduleAddr, packageAddr string, version *version.Version) { | ||
31 | } | ||
32 | |||
33 | func (h ModuleInstallHooksImpl) Install(moduleAddr string, version *version.Version, localPath string) { | ||
34 | } | ||
35 | |||
36 | var _ 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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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. | ||
30 | func 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. | ||
64 | func 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 @@ | |||
1 | package initwd | ||
2 | |||
3 | import ( | ||
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. | ||
19 | func 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 | } | ||