diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go | 363 |
1 files changed, 363 insertions, 0 deletions
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 | } | ||