aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/config/module/storage.go
blob: 58e3a10041462d48f57abbb3a176b9c7a723bbba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package module

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	getter "github.com/hashicorp/go-getter"
	"github.com/hashicorp/terraform/registry"
	"github.com/hashicorp/terraform/registry/regsrc"
	"github.com/hashicorp/terraform/svchost/disco"
	"github.com/mitchellh/cli"
)

const manifestName = "modules.json"

// moduleManifest is the serialization structure used to record the stored
// module's metadata.
type moduleManifest struct {
	Modules []moduleRecord
}

// moduleRecords represents the stored module's metadata.
// This is compared for equality using '==', so all fields needs to remain
// comparable.
type moduleRecord struct {
	// Source is the module source string from the config, minus any
	// subdirectory.
	Source string

	// Key is the locally unique identifier for this module.
	Key string

	// Version is the exact version string for the stored module.
	Version string

	// Dir is the directory name returned by the FileStorage. This is what
	// allows us to correlate a particular module version with the location on
	// disk.
	Dir string

	// Root is the root directory containing the module. If the module is
	// unpacked from an archive, and not located in the root directory, this is
	// used to direct the loader to the correct subdirectory. This is
	// independent from any subdirectory in the original source string, which
	// may traverse further into the module tree.
	Root string

	// url is the location of the module source
	url string

	// Registry is true if this module is sourced from a registry
	registry bool
}

// Storage implements methods to manage the storage of modules.
// This is used by Tree.Load to query registries, authenticate requests, and
// store modules locally.
type Storage struct {
	// StorageDir is the full path to the directory where all modules will be
	// stored.
	StorageDir string

	// Ui is an optional cli.Ui for user output
	Ui cli.Ui

	// Mode is the GetMode that will be used for various operations.
	Mode GetMode

	registry *registry.Client
}

// NewStorage returns a new initialized Storage object.
func NewStorage(dir string, services *disco.Disco) *Storage {
	regClient := registry.NewClient(services, nil)

	return &Storage{
		StorageDir: dir,
		registry:   regClient,
	}
}

// loadManifest returns the moduleManifest file from the parent directory.
func (s Storage) loadManifest() (moduleManifest, error) {
	manifest := moduleManifest{}

	manifestPath := filepath.Join(s.StorageDir, manifestName)
	data, err := ioutil.ReadFile(manifestPath)
	if err != nil && !os.IsNotExist(err) {
		return manifest, err
	}

	if len(data) == 0 {
		return manifest, nil
	}

	if err := json.Unmarshal(data, &manifest); err != nil {
		return manifest, err
	}

	for i, rec := range manifest.Modules {
		// If the path was recorded before we changed to always using a
		// slash as separator, we delete the record from the manifest so
		// it can be discovered again and will be recorded using a slash.
		if strings.Contains(rec.Dir, "\\") {
			manifest.Modules[i] = manifest.Modules[len(manifest.Modules)-1]
			manifest.Modules = manifest.Modules[:len(manifest.Modules)-1]
			continue
		}

		// Make sure we use the correct path separator.
		rec.Dir = filepath.FromSlash(rec.Dir)
	}

	return manifest, nil
}

// Store the location of the module, along with the version used and the module
// root directory. The storage method loads the entire file and rewrites it
// each time. This is only done a few times during init, so efficiency is
// not a concern.
func (s Storage) recordModule(rec moduleRecord) error {
	manifest, err := s.loadManifest()
	if err != nil {
		// if there was a problem with the file, we will attempt to write a new
		// one. Any non-data related error should surface there.
		log.Printf("[WARN] error reading module manifest: %s", err)
	}

	// do nothing if we already have the exact module
	for i, stored := range manifest.Modules {
		if rec == stored {
			return nil
		}

		// they are not equal, but if the storage path is the same we need to
		// remove this rec to be replaced.
		if rec.Dir == stored.Dir {
			manifest.Modules[i] = manifest.Modules[len(manifest.Modules)-1]
			manifest.Modules = manifest.Modules[:len(manifest.Modules)-1]
			break
		}
	}

	// Make sure we always use a slash separator.
	rec.Dir = filepath.ToSlash(rec.Dir)

	manifest.Modules = append(manifest.Modules, rec)

	js, err := json.Marshal(manifest)
	if err != nil {
		panic(err)
	}

	manifestPath := filepath.Join(s.StorageDir, manifestName)
	return ioutil.WriteFile(manifestPath, js, 0644)
}

// load the manifest from dir, and return all module versions matching the
// provided source. Records with no version info will be skipped, as they need
// to be uniquely identified by other means.
func (s Storage) moduleVersions(source string) ([]moduleRecord, error) {
	manifest, err := s.loadManifest()
	if err != nil {
		return manifest.Modules, err
	}

	var matching []moduleRecord

	for _, m := range manifest.Modules {
		if m.Source == source && m.Version != "" {
			log.Printf("[DEBUG] found local version %q for module %s", m.Version, m.Source)
			matching = append(matching, m)
		}
	}

	return matching, nil
}

func (s Storage) moduleDir(key string) (string, error) {
	manifest, err := s.loadManifest()
	if err != nil {
		return "", err
	}

	for _, m := range manifest.Modules {
		if m.Key == key {
			return m.Dir, nil
		}
	}

	return "", nil
}

// return only the root directory of the module stored in dir.
func (s Storage) getModuleRoot(dir string) (string, error) {
	manifest, err := s.loadManifest()
	if err != nil {
		return "", err
	}

	for _, mod := range manifest.Modules {
		if mod.Dir == dir {
			return mod.Root, nil
		}
	}
	return "", nil
}

// record only the Root directory for the module stored at dir.
func (s Storage) recordModuleRoot(dir, root string) error {
	rec := moduleRecord{
		Dir:  dir,
		Root: root,
	}

	return s.recordModule(rec)
}

func (s Storage) output(msg string) {
	if s.Ui == nil || s.Mode == GetModeNone {
		return
	}
	s.Ui.Output(msg)
}

func (s Storage) getStorage(key string, src string) (string, bool, error) {
	storage := &getter.FolderStorage{
		StorageDir: s.StorageDir,
	}

	log.Printf("[DEBUG] fetching module from %s", src)

	// Get the module with the level specified if we were told to.
	if s.Mode > GetModeNone {
		log.Printf("[DEBUG] fetching %q with key %q", src, key)
		if err := storage.Get(key, src, s.Mode == GetModeUpdate); err != nil {
			return "", false, err
		}
	}

	// Get the directory where the module is.
	dir, found, err := storage.Dir(key)
	log.Printf("[DEBUG] found %q in %q: %t", src, dir, found)
	return dir, found, err
}

// find a stored module that's not from a registry
func (s Storage) findModule(key string) (string, error) {
	if s.Mode == GetModeUpdate {
		return "", nil
	}

	return s.moduleDir(key)
}

// GetModule fetches a module source into the specified directory. This is used
// as a convenience function by the CLI to initialize a configuration.
func (s Storage) GetModule(dst, src string) error {
	// reset this in case the caller was going to re-use it
	mode := s.Mode
	s.Mode = GetModeUpdate
	defer func() {
		s.Mode = mode
	}()

	rec, err := s.findRegistryModule(src, anyVersion)
	if err != nil {
		return err
	}

	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	source := rec.url
	if source == "" {
		source, err = getter.Detect(src, pwd, getter.Detectors)
		if err != nil {
			return fmt.Errorf("module %s: %s", src, err)
		}
	}

	if source == "" {
		return fmt.Errorf("module %q not found", src)
	}

	return GetCopy(dst, source)
}

// find a registry module
func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, error) {
	rec := moduleRecord{
		Source: mSource,
	}
	// detect if we have a registry source
	mod, err := regsrc.ParseModuleSource(mSource)
	switch err {
	case nil:
		//ok
	case regsrc.ErrInvalidModuleSource:
		return rec, nil
	default:
		return rec, err
	}
	rec.registry = true

	log.Printf("[TRACE] %q is a registry module", mod.Display())

	versions, err := s.moduleVersions(mod.String())
	if err != nil {
		log.Printf("[ERROR] error looking up versions for %q: %s", mod.Display(), err)
		return rec, err
	}

	match, err := newestRecord(versions, constraint)
	if err != nil {
		log.Printf("[INFO] no matching version for %q<%s>, %s", mod.Display(), constraint, err)
	}
	log.Printf("[DEBUG] matched %q version %s for %s", mod, match.Version, constraint)

	rec.Dir = match.Dir
	rec.Version = match.Version
	found := rec.Dir != ""

	// we need to lookup available versions
	// Only on Get if it's not found, on unconditionally on Update
	if (s.Mode == GetModeGet && !found) || (s.Mode == GetModeUpdate) {
		resp, err := s.registry.Versions(mod)
		if err != nil {
			return rec, err
		}

		if len(resp.Modules) == 0 {
			return rec, fmt.Errorf("module %q not found in registry", mod.Display())
		}

		match, err := newestVersion(resp.Modules[0].Versions, constraint)
		if err != nil {
			return rec, err
		}

		if match == nil {
			return rec, fmt.Errorf("no versions for %q found matching %q", mod.Display(), constraint)
		}

		rec.Version = match.Version

		rec.url, err = s.registry.Location(mod, rec.Version)
		if err != nil {
			return rec, err
		}

		// we've already validated this by now
		host, _ := mod.SvcHost()
		s.output(fmt.Sprintf("  Found version %s of %s on %s", rec.Version, mod.Module(), host.ForDisplay()))

	}
	return rec, nil
}