]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/internal/initwd/from_module.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / internal / initwd / from_module.go
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 }