11 getter "github.com/hashicorp/go-getter"
12 "github.com/hashicorp/terraform/registry"
13 "github.com/hashicorp/terraform/registry/regsrc"
14 "github.com/hashicorp/terraform/svchost/disco"
15 "github.com/mitchellh/cli"
18 const manifestName = "modules.json"
20 // moduleManifest is the serialization structure used to record the stored
22 type moduleManifest struct {
23 Modules []moduleRecord
26 // moduleRecords represents the stored module's metadata.
27 // This is compared for equality using '==', so all fields needs to remain
29 type moduleRecord struct {
30 // Source is the module source string from the config, minus any
34 // Key is the locally unique identifier for this module.
37 // Version is the exact version string for the stored module.
40 // Dir is the directory name returned by the FileStorage. This is what
41 // allows us to correlate a particular module version with the location on
45 // Root is the root directory containing the module. If the module is
46 // unpacked from an archive, and not located in the root directory, this is
47 // used to direct the loader to the correct subdirectory. This is
48 // independent from any subdirectory in the original source string, which
49 // may traverse further into the module tree.
52 // url is the location of the module source
55 // Registry is true if this module is sourced from a registry
59 // Storage implements methods to manage the storage of modules.
60 // This is used by Tree.Load to query registries, authenticate requests, and
61 // store modules locally.
63 // StorageDir is the full path to the directory where all modules will be
67 // Ui is an optional cli.Ui for user output
70 // Mode is the GetMode that will be used for various operations.
73 registry *registry.Client
76 // NewStorage returns a new initialized Storage object.
77 func NewStorage(dir string, services *disco.Disco) *Storage {
78 regClient := registry.NewClient(services, nil)
86 // loadManifest returns the moduleManifest file from the parent directory.
87 func (s Storage) loadManifest() (moduleManifest, error) {
88 manifest := moduleManifest{}
90 manifestPath := filepath.Join(s.StorageDir, manifestName)
91 data, err := ioutil.ReadFile(manifestPath)
92 if err != nil && !os.IsNotExist(err) {
100 if err := json.Unmarshal(data, &manifest); err != nil {
106 // Store the location of the module, along with the version used and the module
107 // root directory. The storage method loads the entire file and rewrites it
108 // each time. This is only done a few times during init, so efficiency is
110 func (s Storage) recordModule(rec moduleRecord) error {
111 manifest, err := s.loadManifest()
113 // if there was a problem with the file, we will attempt to write a new
114 // one. Any non-data related error should surface there.
115 log.Printf("[WARN] error reading module manifest: %s", err)
118 // do nothing if we already have the exact module
119 for i, stored := range manifest.Modules {
124 // they are not equal, but if the storage path is the same we need to
125 // remove this rec to be replaced.
126 if rec.Dir == stored.Dir {
127 manifest.Modules[i] = manifest.Modules[len(manifest.Modules)-1]
128 manifest.Modules = manifest.Modules[:len(manifest.Modules)-1]
133 manifest.Modules = append(manifest.Modules, rec)
135 js, err := json.Marshal(manifest)
140 manifestPath := filepath.Join(s.StorageDir, manifestName)
141 return ioutil.WriteFile(manifestPath, js, 0644)
144 // load the manifest from dir, and return all module versions matching the
145 // provided source. Records with no version info will be skipped, as they need
146 // to be uniquely identified by other means.
147 func (s Storage) moduleVersions(source string) ([]moduleRecord, error) {
148 manifest, err := s.loadManifest()
150 return manifest.Modules, err
153 var matching []moduleRecord
155 for _, m := range manifest.Modules {
156 if m.Source == source && m.Version != "" {
157 log.Printf("[DEBUG] found local version %q for module %s", m.Version, m.Source)
158 matching = append(matching, m)
165 func (s Storage) moduleDir(key string) (string, error) {
166 manifest, err := s.loadManifest()
171 for _, m := range manifest.Modules {
180 // return only the root directory of the module stored in dir.
181 func (s Storage) getModuleRoot(dir string) (string, error) {
182 manifest, err := s.loadManifest()
187 for _, mod := range manifest.Modules {
195 // record only the Root directory for the module stored at dir.
196 func (s Storage) recordModuleRoot(dir, root string) error {
202 return s.recordModule(rec)
205 func (s Storage) output(msg string) {
206 if s.Ui == nil || s.Mode == GetModeNone {
212 func (s Storage) getStorage(key string, src string) (string, bool, error) {
213 storage := &getter.FolderStorage{
214 StorageDir: s.StorageDir,
217 log.Printf("[DEBUG] fetching module from %s", src)
219 // Get the module with the level specified if we were told to.
220 if s.Mode > GetModeNone {
221 log.Printf("[DEBUG] fetching %q with key %q", src, key)
222 if err := storage.Get(key, src, s.Mode == GetModeUpdate); err != nil {
223 return "", false, err
227 // Get the directory where the module is.
228 dir, found, err := storage.Dir(key)
229 log.Printf("[DEBUG] found %q in %q: %t", src, dir, found)
230 return dir, found, err
233 // find a stored module that's not from a registry
234 func (s Storage) findModule(key string) (string, error) {
235 if s.Mode == GetModeUpdate {
239 return s.moduleDir(key)
242 // GetModule fetches a module source into the specified directory. This is used
243 // as a convenience function by the CLI to initialize a configuration.
244 func (s Storage) GetModule(dst, src string) error {
245 // reset this in case the caller was going to re-use it
247 s.Mode = GetModeUpdate
252 rec, err := s.findRegistryModule(src, anyVersion)
257 pwd, err := os.Getwd()
264 source, err = getter.Detect(src, pwd, getter.Detectors)
266 return fmt.Errorf("module %s: %s", src, err)
271 return fmt.Errorf("module %q not found", src)
274 return GetCopy(dst, source)
277 // find a registry module
278 func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, error) {
282 // detect if we have a registry source
283 mod, err := regsrc.ParseModuleSource(mSource)
287 case regsrc.ErrInvalidModuleSource:
294 log.Printf("[TRACE] %q is a registry module", mod.Display())
296 versions, err := s.moduleVersions(mod.String())
298 log.Printf("[ERROR] error looking up versions for %q: %s", mod.Display(), err)
302 match, err := newestRecord(versions, constraint)
304 log.Printf("[INFO] no matching version for %q<%s>, %s", mod.Display(), constraint, err)
306 log.Printf("[DEBUG] matched %q version %s for %s", mod, match.Version, constraint)
309 rec.Version = match.Version
310 found := rec.Dir != ""
312 // we need to lookup available versions
313 // Only on Get if it's not found, on unconditionally on Update
314 if (s.Mode == GetModeGet && !found) || (s.Mode == GetModeUpdate) {
315 resp, err := s.registry.ModuleVersions(mod)
320 if len(resp.Modules) == 0 {
321 return rec, fmt.Errorf("module %q not found in registry", mod.Display())
324 match, err := newestVersion(resp.Modules[0].Versions, constraint)
330 return rec, fmt.Errorf("no versions for %q found matching %q", mod.Display(), constraint)
333 rec.Version = match.Version
335 rec.url, err = s.registry.ModuleLocation(mod, rec.Version)
340 // we've already validated this by now
341 host, _ := mod.SvcHost()
342 s.output(fmt.Sprintf(" Found version %s of %s on %s", rec.Version, mod.Module(), host.ForDisplay()))