2017-10-21 17:56:53 +02:00
|
|
|
package module
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2017-10-26 01:20:05 +02:00
|
|
|
"fmt"
|
2017-10-21 17:56:53 +02:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-10-23 17:34:50 +02:00
|
|
|
"reflect"
|
|
|
|
|
|
|
|
getter "github.com/hashicorp/go-getter"
|
2017-10-26 01:20:05 +02:00
|
|
|
"github.com/hashicorp/terraform/registry/regsrc"
|
2017-10-21 17:56:53 +02:00
|
|
|
)
|
|
|
|
|
2017-10-23 17:19:24 +02:00
|
|
|
const manifestName = "modules.json"
|
|
|
|
|
2017-10-21 17:56:53 +02:00
|
|
|
// 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 {
|
2017-10-24 00:07:49 +02:00
|
|
|
// Source is the module source string from the config, minus any
|
|
|
|
// subdirectory.
|
2017-10-21 17:56:53 +02:00
|
|
|
Source string
|
|
|
|
|
2017-10-24 00:07:49 +02:00
|
|
|
// Key is the locally unique identifier for this module.
|
|
|
|
Key string
|
|
|
|
|
|
|
|
// Version is the exact version string for the stored module.
|
2017-10-21 17:56:53 +02:00
|
|
|
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
|
2017-10-26 01:20:05 +02:00
|
|
|
|
|
|
|
// url is the location of the module source
|
|
|
|
url string
|
|
|
|
|
|
|
|
// Registry is true if this module is sourced from a registry
|
|
|
|
registry bool
|
2017-10-21 17:56:53 +02:00
|
|
|
}
|
|
|
|
|
2017-10-23 17:58:44 +02:00
|
|
|
// moduleStorage implements methods to record and fetch metadata about the
|
2017-10-23 17:19:24 +02:00
|
|
|
// modules that have been fetched and stored locally. The getter.Storgae
|
|
|
|
// abstraction doesn't provide the information needed to know which versions of
|
|
|
|
// a module have been stored, or their location.
|
|
|
|
type moduleStorage struct {
|
2017-10-23 17:34:50 +02:00
|
|
|
getter.Storage
|
2017-10-23 17:19:24 +02:00
|
|
|
storageDir string
|
2017-10-26 01:20:05 +02:00
|
|
|
mode GetMode
|
2017-10-21 17:56:53 +02:00
|
|
|
}
|
|
|
|
|
2017-10-26 01:20:05 +02:00
|
|
|
func newModuleStorage(s getter.Storage, mode GetMode) moduleStorage {
|
2017-10-23 17:34:50 +02:00
|
|
|
return moduleStorage{
|
|
|
|
Storage: s,
|
|
|
|
storageDir: storageDir(s),
|
2017-10-26 01:20:05 +02:00
|
|
|
mode: mode,
|
2017-10-23 17:34:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The Tree needs to know where to store the module manifest.
|
|
|
|
// Th Storage abstraction doesn't provide access to the storage root directory,
|
|
|
|
// so we extract it here.
|
|
|
|
func storageDir(s getter.Storage) string {
|
|
|
|
// get the StorageDir directly if possible
|
|
|
|
switch t := s.(type) {
|
|
|
|
case *getter.FolderStorage:
|
|
|
|
return t.StorageDir
|
|
|
|
case moduleStorage:
|
|
|
|
return t.storageDir
|
2017-10-26 01:20:05 +02:00
|
|
|
case nil:
|
|
|
|
return ""
|
2017-10-23 17:34:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// this should be our UI wrapper which is exported here, so we need to
|
|
|
|
// extract the FolderStorage via reflection.
|
|
|
|
fs := reflect.ValueOf(s).Elem().FieldByName("Storage").Interface()
|
|
|
|
return storageDir(fs.(getter.Storage))
|
|
|
|
}
|
|
|
|
|
2017-10-21 17:56:53 +02:00
|
|
|
// loadManifest returns the moduleManifest file from the parent directory.
|
2017-10-23 17:19:24 +02:00
|
|
|
func (m moduleStorage) loadManifest() (moduleManifest, error) {
|
2017-10-21 17:56:53 +02:00
|
|
|
manifest := moduleManifest{}
|
|
|
|
|
2017-10-23 17:19:24 +02:00
|
|
|
manifestPath := filepath.Join(m.storageDir, manifestName)
|
2017-10-21 17:56:53 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
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.
|
2017-10-23 17:19:24 +02:00
|
|
|
func (m moduleStorage) recordModule(rec moduleRecord) error {
|
|
|
|
manifest, err := m.loadManifest()
|
2017-10-21 17:56:53 +02:00
|
|
|
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.
|
2017-10-23 17:19:24 +02:00
|
|
|
log.Printf("[WARN] error reading module manifest: %s", err)
|
2017-10-21 17:56:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// do nothing if we already have the exact module
|
|
|
|
for i, stored := range manifest.Modules {
|
2017-10-23 17:19:24 +02:00
|
|
|
if rec == stored {
|
2017-10-21 17:56:53 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// they are not equal, but if the storage path is the same we need to
|
2017-10-23 17:19:24 +02:00
|
|
|
// remove this rec to be replaced.
|
|
|
|
if rec.Dir == stored.Dir {
|
2017-10-21 17:56:53 +02:00
|
|
|
manifest.Modules[i] = manifest.Modules[len(manifest.Modules)-1]
|
|
|
|
manifest.Modules = manifest.Modules[:len(manifest.Modules)-1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-23 17:19:24 +02:00
|
|
|
manifest.Modules = append(manifest.Modules, rec)
|
2017-10-21 17:56:53 +02:00
|
|
|
|
|
|
|
js, err := json.Marshal(manifest)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2017-10-23 17:19:24 +02:00
|
|
|
manifestPath := filepath.Join(m.storageDir, manifestName)
|
2017-10-21 17:56:53 +02:00
|
|
|
return ioutil.WriteFile(manifestPath, js, 0644)
|
|
|
|
}
|
|
|
|
|
2017-10-24 00:07:49 +02:00
|
|
|
// 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 (m moduleStorage) moduleVersions(source string) ([]moduleRecord, error) {
|
|
|
|
manifest, err := m.loadManifest()
|
|
|
|
if err != nil {
|
|
|
|
return manifest.Modules, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var matching []moduleRecord
|
|
|
|
|
|
|
|
for _, m := range manifest.Modules {
|
|
|
|
if m.Source == source && m.Version != "" {
|
|
|
|
matching = append(matching, m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return matching, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m moduleStorage) moduleDir(key string) (string, error) {
|
|
|
|
manifest, err := m.loadManifest()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range manifest.Modules {
|
|
|
|
if m.Key == key {
|
|
|
|
return m.Dir, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2017-10-21 17:56:53 +02:00
|
|
|
// return only the root directory of the module stored in dir.
|
2017-10-23 17:19:24 +02:00
|
|
|
func (m moduleStorage) getModuleRoot(dir string) (string, error) {
|
|
|
|
manifest, err := m.loadManifest()
|
2017-10-21 17:56:53 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2017-10-23 17:19:24 +02:00
|
|
|
for _, mod := range manifest.Modules {
|
|
|
|
if mod.Dir == dir {
|
|
|
|
return mod.Root, nil
|
2017-10-21 17:56:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// record only the Root directory for the module stored at dir.
|
|
|
|
// TODO: remove this compatibility function to store the full moduleRecord.
|
2017-10-23 17:19:24 +02:00
|
|
|
func (m moduleStorage) recordModuleRoot(dir, root string) error {
|
|
|
|
rec := moduleRecord{
|
2017-10-21 17:56:53 +02:00
|
|
|
Dir: dir,
|
|
|
|
Root: root,
|
|
|
|
}
|
|
|
|
|
2017-10-23 17:19:24 +02:00
|
|
|
return m.recordModule(rec)
|
2017-10-21 17:56:53 +02:00
|
|
|
}
|
2017-10-23 17:58:44 +02:00
|
|
|
|
2017-10-26 01:20:05 +02:00
|
|
|
func (m moduleStorage) getStorage(key string, src string) (string, bool, error) {
|
2017-10-23 17:58:44 +02:00
|
|
|
// Get the module with the level specified if we were told to.
|
2017-10-26 01:20:05 +02:00
|
|
|
if m.mode > GetModeNone {
|
2017-10-23 17:58:44 +02:00
|
|
|
log.Printf("[DEBUG] fetching %q with key %q", src, key)
|
2017-10-26 01:20:05 +02:00
|
|
|
if err := m.Storage.Get(key, src, m.mode == GetModeUpdate); err != nil {
|
2017-10-23 17:58:44 +02:00
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the directory where the module is.
|
|
|
|
dir, found, err := m.Storage.Dir(key)
|
|
|
|
log.Printf("[DEBUG] found %q in %q: %t", src, dir, found)
|
|
|
|
return dir, found, err
|
|
|
|
}
|
2017-10-26 01:20:05 +02:00
|
|
|
|
|
|
|
// find a stored module that's not from a registry
|
|
|
|
func (m moduleStorage) findModule(key string) (string, error) {
|
|
|
|
if m.mode == GetModeUpdate {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return m.moduleDir(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// find a registry module
|
|
|
|
func (m moduleStorage) 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.Module())
|
|
|
|
|
|
|
|
versions, err := m.moduleVersions(mod.String())
|
|
|
|
if err != nil {
|
2017-10-27 15:07:25 +02:00
|
|
|
log.Printf("[ERROR] error looking up versions for %q: %s", mod.Module(), err)
|
2017-10-26 01:20:05 +02:00
|
|
|
return rec, err
|
|
|
|
}
|
|
|
|
|
|
|
|
match, err := newestRecord(versions, constraint)
|
|
|
|
if err != nil {
|
|
|
|
// TODO: does this allow previously unversioned modules?
|
|
|
|
log.Printf("[INFO] no matching version for %q<%s>, %s", mod.Module(), constraint, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (m.mode == GetModeGet && !found) || (m.mode == GetModeUpdate) {
|
|
|
|
resp, err := lookupModuleVersions(nil, mod)
|
|
|
|
if err != nil {
|
|
|
|
return rec, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Modules) == 0 {
|
|
|
|
return rec, fmt.Errorf("module %q not found in registry", mod.Module())
|
|
|
|
}
|
|
|
|
|
|
|
|
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.Module(), constraint)
|
|
|
|
}
|
|
|
|
|
|
|
|
rec.Version = match.Version
|
|
|
|
|
|
|
|
rec.url, err = lookupModuleLocation(nil, mod, rec.Version)
|
|
|
|
if err != nil {
|
|
|
|
return rec, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rec, nil
|
|
|
|
}
|