aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/configs/configload/loader_snapshot.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/configload/loader_snapshot.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configload/loader_snapshot.go504
1 files changed, 504 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/configs/configload/loader_snapshot.go b/vendor/github.com/hashicorp/terraform/configs/configload/loader_snapshot.go
new file mode 100644
index 0000000..44c6439
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configload/loader_snapshot.go
@@ -0,0 +1,504 @@
1package configload
2
3import (
4 "fmt"
5 "io"
6 "os"
7 "path/filepath"
8 "sort"
9 "time"
10
11 version "github.com/hashicorp/go-version"
12 "github.com/hashicorp/hcl2/hcl"
13 "github.com/hashicorp/terraform/configs"
14 "github.com/hashicorp/terraform/internal/modsdir"
15 "github.com/spf13/afero"
16)
17
18// LoadConfigWithSnapshot is a variant of LoadConfig that also simultaneously
19// creates an in-memory snapshot of the configuration files used, which can
20// be later used to create a loader that may read only from this snapshot.
21func (l *Loader) LoadConfigWithSnapshot(rootDir string) (*configs.Config, *Snapshot, hcl.Diagnostics) {
22 rootMod, diags := l.parser.LoadConfigDir(rootDir)
23 if rootMod == nil {
24 return nil, nil, diags
25 }
26
27 snap := &Snapshot{
28 Modules: map[string]*SnapshotModule{},
29 }
30 walker := l.makeModuleWalkerSnapshot(snap)
31 cfg, cDiags := configs.BuildConfig(rootMod, walker)
32 diags = append(diags, cDiags...)
33
34 addDiags := l.addModuleToSnapshot(snap, "", rootDir, "", nil)
35 diags = append(diags, addDiags...)
36
37 return cfg, snap, diags
38}
39
40// NewLoaderFromSnapshot creates a Loader that reads files only from the
41// given snapshot.
42//
43// A snapshot-based loader cannot install modules, so calling InstallModules
44// on the return value will cause a panic.
45//
46// A snapshot-based loader also has access only to configuration files. Its
47// underlying parser does not have access to other files in the native
48// filesystem, such as values files. For those, either use a normal loader
49// (created by NewLoader) or use the configs.Parser API directly.
50func NewLoaderFromSnapshot(snap *Snapshot) *Loader {
51 fs := snapshotFS{snap}
52 parser := configs.NewParser(fs)
53
54 ret := &Loader{
55 parser: parser,
56 modules: moduleMgr{
57 FS: afero.Afero{Fs: fs},
58 CanInstall: false,
59 manifest: snap.moduleManifest(),
60 },
61 }
62
63 return ret
64}
65
66// Snapshot is an in-memory representation of the source files from a
67// configuration, which can be used as an alternative configurations source
68// for a loader with NewLoaderFromSnapshot.
69//
70// The primary purpose of a Snapshot is to build the configuration portion
71// of a plan file (see ../../plans/planfile) so that it can later be reloaded
72// and used to recover the exact configuration that the plan was built from.
73type Snapshot struct {
74 // Modules is a map from opaque module keys (suitable for use as directory
75 // names on all supported operating systems) to the snapshot information
76 // about each module.
77 Modules map[string]*SnapshotModule
78}
79
80// NewEmptySnapshot constructs and returns a snapshot containing only an empty
81// root module. This is not useful for anything except placeholders in tests.
82func NewEmptySnapshot() *Snapshot {
83 return &Snapshot{
84 Modules: map[string]*SnapshotModule{
85 "": &SnapshotModule{
86 Files: map[string][]byte{},
87 },
88 },
89 }
90}
91
92// SnapshotModule represents a single module within a Snapshot.
93type SnapshotModule struct {
94 // Dir is the path, relative to the root directory given when the
95 // snapshot was created, where the module appears in the snapshot's
96 // virtual filesystem.
97 Dir string
98
99 // Files is a map from each configuration file filename for the
100 // module to a raw byte representation of the source file contents.
101 Files map[string][]byte
102
103 // SourceAddr is the source address given for this module in configuration.
104 SourceAddr string `json:"Source"`
105
106 // Version is the version of the module that is installed, or nil if
107 // the module is installed from a source that does not support versions.
108 Version *version.Version `json:"-"`
109}
110
111// moduleManifest constructs a module manifest based on the contents of
112// the receiving snapshot.
113func (s *Snapshot) moduleManifest() modsdir.Manifest {
114 ret := make(modsdir.Manifest)
115
116 for k, modSnap := range s.Modules {
117 ret[k] = modsdir.Record{
118 Key: k,
119 Dir: modSnap.Dir,
120 SourceAddr: modSnap.SourceAddr,
121 Version: modSnap.Version,
122 }
123 }
124
125 return ret
126}
127
128// makeModuleWalkerSnapshot creates a configs.ModuleWalker that will exhibit
129// the same lookup behaviors as l.moduleWalkerLoad but will additionally write
130// source files from the referenced modules into the given snapshot.
131func (l *Loader) makeModuleWalkerSnapshot(snap *Snapshot) configs.ModuleWalker {
132 return configs.ModuleWalkerFunc(
133 func(req *configs.ModuleRequest) (*configs.Module, *version.Version, hcl.Diagnostics) {
134 mod, v, diags := l.moduleWalkerLoad(req)
135 if diags.HasErrors() {
136 return mod, v, diags
137 }
138
139 key := l.modules.manifest.ModuleKey(req.Path)
140 record, exists := l.modules.manifest[key]
141
142 if !exists {
143 // Should never happen, since otherwise moduleWalkerLoader would've
144 // returned an error and we would've returned already.
145 panic(fmt.Sprintf("module %s is not present in manifest", key))
146 }
147
148 addDiags := l.addModuleToSnapshot(snap, key, record.Dir, record.SourceAddr, record.Version)
149 diags = append(diags, addDiags...)
150
151 return mod, v, diags
152 },
153 )
154}
155
156func (l *Loader) addModuleToSnapshot(snap *Snapshot, key string, dir string, sourceAddr string, v *version.Version) hcl.Diagnostics {
157 var diags hcl.Diagnostics
158
159 primaryFiles, overrideFiles, moreDiags := l.parser.ConfigDirFiles(dir)
160 if moreDiags.HasErrors() {
161 // Any diagnostics we get here should be already present
162 // in diags, so it's weird if we get here but we'll allow it
163 // and return a general error message in that case.
164 diags = append(diags, &hcl.Diagnostic{
165 Severity: hcl.DiagError,
166 Summary: "Failed to read directory for module",
167 Detail: fmt.Sprintf("The source directory %s could not be read", dir),
168 })
169 return diags
170 }
171
172 snapMod := &SnapshotModule{
173 Dir: dir,
174 Files: map[string][]byte{},
175 SourceAddr: sourceAddr,
176 Version: v,
177 }
178
179 files := make([]string, 0, len(primaryFiles)+len(overrideFiles))
180 files = append(files, primaryFiles...)
181 files = append(files, overrideFiles...)
182 sources := l.Sources() // should be populated with all the files we need by now
183 for _, filePath := range files {
184 filename := filepath.Base(filePath)
185 src, exists := sources[filePath]
186 if !exists {
187 diags = append(diags, &hcl.Diagnostic{
188 Severity: hcl.DiagError,
189 Summary: "Missing source file for snapshot",
190 Detail: fmt.Sprintf("The source code for file %s could not be found to produce a configuration snapshot.", filePath),
191 })
192 continue
193 }
194 snapMod.Files[filepath.Clean(filename)] = src
195 }
196
197 snap.Modules[key] = snapMod
198
199 return diags
200}
201
202// snapshotFS is an implementation of afero.Fs that reads from a snapshot.
203//
204// This is not intended as a general-purpose filesystem implementation. Instead,
205// it just supports the minimal functionality required to support the
206// configuration loader and parser as an implementation detail of creating
207// a loader from a snapshot.
208type snapshotFS struct {
209 snap *Snapshot
210}
211
212var _ afero.Fs = snapshotFS{}
213
214func (fs snapshotFS) Create(name string) (afero.File, error) {
215 return nil, fmt.Errorf("cannot create file inside configuration snapshot")
216}
217
218func (fs snapshotFS) Mkdir(name string, perm os.FileMode) error {
219 return fmt.Errorf("cannot create directory inside configuration snapshot")
220}
221
222func (fs snapshotFS) MkdirAll(name string, perm os.FileMode) error {
223 return fmt.Errorf("cannot create directories inside configuration snapshot")
224}
225
226func (fs snapshotFS) Open(name string) (afero.File, error) {
227
228 // Our "filesystem" is sparsely populated only with the directories
229 // mentioned by modules in our snapshot, so the high-level process
230 // for opening a file is:
231 // - Find the module snapshot corresponding to the containing directory
232 // - Find the file within that snapshot
233 // - Wrap the resulting byte slice in a snapshotFile to return
234 //
235 // The other possibility handled here is if the given name is for the
236 // module directory itself, in which case we'll return a snapshotDir
237 // instead.
238 //
239 // This function doesn't try to be incredibly robust in supporting
240 // different permutations of paths, etc because in practice we only
241 // need to support the path forms that our own loader and parser will
242 // generate.
243
244 dir := filepath.Dir(name)
245 fn := filepath.Base(name)
246 directDir := filepath.Clean(name)
247
248 // First we'll check to see if this is an exact path for a module directory.
249 // We need to do this first (rather than as part of the next loop below)
250 // because a module in a child directory of another module can otherwise
251 // appear to be a file in that parent directory.
252 for _, candidate := range fs.snap.Modules {
253 modDir := filepath.Clean(candidate.Dir)
254 if modDir == directDir {
255 // We've matched the module directory itself
256 filenames := make([]string, 0, len(candidate.Files))
257 for n := range candidate.Files {
258 filenames = append(filenames, n)
259 }
260 sort.Strings(filenames)
261 return snapshotDir{
262 filenames: filenames,
263 }, nil
264 }
265 }
266
267 // If we get here then the given path isn't a module directory exactly, so
268 // we'll treat it as a file path and try to find a module directory it
269 // could be located in.
270 var modSnap *SnapshotModule
271 for _, candidate := range fs.snap.Modules {
272 modDir := filepath.Clean(candidate.Dir)
273 if modDir == dir {
274 modSnap = candidate
275 break
276 }
277 }
278 if modSnap == nil {
279 return nil, os.ErrNotExist
280 }
281
282 src, exists := modSnap.Files[fn]
283 if !exists {
284 return nil, os.ErrNotExist
285 }
286
287 return &snapshotFile{
288 src: src,
289 }, nil
290}
291
292func (fs snapshotFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
293 return fs.Open(name)
294}
295
296func (fs snapshotFS) Remove(name string) error {
297 return fmt.Errorf("cannot remove file inside configuration snapshot")
298}
299
300func (fs snapshotFS) RemoveAll(path string) error {
301 return fmt.Errorf("cannot remove files inside configuration snapshot")
302}
303
304func (fs snapshotFS) Rename(old, new string) error {
305 return fmt.Errorf("cannot rename file inside configuration snapshot")
306}
307
308func (fs snapshotFS) Stat(name string) (os.FileInfo, error) {
309 f, err := fs.Open(name)
310 if err != nil {
311 return nil, err
312 }
313 _, isDir := f.(snapshotDir)
314 return snapshotFileInfo{
315 name: filepath.Base(name),
316 isDir: isDir,
317 }, nil
318}
319
320func (fs snapshotFS) Name() string {
321 return "ConfigSnapshotFS"
322}
323
324func (fs snapshotFS) Chmod(name string, mode os.FileMode) error {
325 return fmt.Errorf("cannot set file mode inside configuration snapshot")
326}
327
328func (fs snapshotFS) Chtimes(name string, atime, mtime time.Time) error {
329 return fmt.Errorf("cannot set file times inside configuration snapshot")
330}
331
332type snapshotFile struct {
333 snapshotFileStub
334 src []byte
335 at int64
336}
337
338var _ afero.File = (*snapshotFile)(nil)
339
340func (f *snapshotFile) Read(p []byte) (n int, err error) {
341 if len(p) > 0 && f.at == int64(len(f.src)) {
342 return 0, io.EOF
343 }
344 if f.at > int64(len(f.src)) {
345 return 0, io.ErrUnexpectedEOF
346 }
347 if int64(len(f.src))-f.at >= int64(len(p)) {
348 n = len(p)
349 } else {
350 n = int(int64(len(f.src)) - f.at)
351 }
352 copy(p, f.src[f.at:f.at+int64(n)])
353 f.at += int64(n)
354 return
355}
356
357func (f *snapshotFile) ReadAt(p []byte, off int64) (n int, err error) {
358 f.at = off
359 return f.Read(p)
360}
361
362func (f *snapshotFile) Seek(offset int64, whence int) (int64, error) {
363 switch whence {
364 case 0:
365 f.at = offset
366 case 1:
367 f.at += offset
368 case 2:
369 f.at = int64(len(f.src)) + offset
370 }
371 return f.at, nil
372}
373
374type snapshotDir struct {
375 snapshotFileStub
376 filenames []string
377 at int
378}
379
380var _ afero.File = snapshotDir{}
381
382func (f snapshotDir) Readdir(count int) ([]os.FileInfo, error) {
383 names, err := f.Readdirnames(count)
384 if err != nil {
385 return nil, err
386 }
387 ret := make([]os.FileInfo, len(names))
388 for i, name := range names {
389 ret[i] = snapshotFileInfo{
390 name: name,
391 isDir: false,
392 }
393 }
394 return ret, nil
395}
396
397func (f snapshotDir) Readdirnames(count int) ([]string, error) {
398 var outLen int
399 names := f.filenames[f.at:]
400 if count > 0 {
401 if len(names) < count {
402 outLen = len(names)
403 } else {
404 outLen = count
405 }
406 if len(names) == 0 {
407 return nil, io.EOF
408 }
409 } else {
410 outLen = len(names)
411 }
412 f.at += outLen
413
414 return names[:outLen], nil
415}
416
417// snapshotFileInfo is a minimal implementation of os.FileInfo to support our
418// virtual filesystem from snapshots.
419type snapshotFileInfo struct {
420 name string
421 isDir bool
422}
423
424var _ os.FileInfo = snapshotFileInfo{}
425
426func (fi snapshotFileInfo) Name() string {
427 return fi.name
428}
429
430func (fi snapshotFileInfo) Size() int64 {
431 // In practice, our parser and loader never call Size
432 return -1
433}
434
435func (fi snapshotFileInfo) Mode() os.FileMode {
436 return os.ModePerm
437}
438
439func (fi snapshotFileInfo) ModTime() time.Time {
440 return time.Now()
441}
442
443func (fi snapshotFileInfo) IsDir() bool {
444 return fi.isDir
445}
446
447func (fi snapshotFileInfo) Sys() interface{} {
448 return nil
449}
450
451type snapshotFileStub struct{}
452
453func (f snapshotFileStub) Close() error {
454 return nil
455}
456
457func (f snapshotFileStub) Read(p []byte) (n int, err error) {
458 return 0, fmt.Errorf("cannot read")
459}
460
461func (f snapshotFileStub) ReadAt(p []byte, off int64) (n int, err error) {
462 return 0, fmt.Errorf("cannot read")
463}
464
465func (f snapshotFileStub) Seek(offset int64, whence int) (int64, error) {
466 return 0, fmt.Errorf("cannot seek")
467}
468
469func (f snapshotFileStub) Write(p []byte) (n int, err error) {
470 return f.WriteAt(p, 0)
471}
472
473func (f snapshotFileStub) WriteAt(p []byte, off int64) (n int, err error) {
474 return 0, fmt.Errorf("cannot write to file in snapshot")
475}
476
477func (f snapshotFileStub) WriteString(s string) (n int, err error) {
478 return 0, fmt.Errorf("cannot write to file in snapshot")
479}
480
481func (f snapshotFileStub) Name() string {
482 // in practice, the loader and parser never use this
483 return "<unimplemented>"
484}
485
486func (f snapshotFileStub) Readdir(count int) ([]os.FileInfo, error) {
487 return nil, fmt.Errorf("cannot use Readdir on a file")
488}
489
490func (f snapshotFileStub) Readdirnames(count int) ([]string, error) {
491 return nil, fmt.Errorf("cannot use Readdir on a file")
492}
493
494func (f snapshotFileStub) Stat() (os.FileInfo, error) {
495 return nil, fmt.Errorf("cannot stat")
496}
497
498func (f snapshotFileStub) Sync() error {
499 return nil
500}
501
502func (f snapshotFileStub) Truncate(size int64) error {
503 return fmt.Errorf("cannot write to file in snapshot")
504}