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/module_install.go | |
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/module_install.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/internal/initwd/module_install.go | 558 |
1 files changed, 558 insertions, 0 deletions
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 | } | ||