github.com/ezh/terraform@v0.11.12-beta1/config/module/storage.go (about)

     1  package module
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  
    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"
    16  )
    17  
    18  const manifestName = "modules.json"
    19  
    20  // moduleManifest is the serialization structure used to record the stored
    21  // module's metadata.
    22  type moduleManifest struct {
    23  	Modules []moduleRecord
    24  }
    25  
    26  // moduleRecords represents the stored module's metadata.
    27  // This is compared for equality using '==', so all fields needs to remain
    28  // comparable.
    29  type moduleRecord struct {
    30  	// Source is the module source string from the config, minus any
    31  	// subdirectory.
    32  	Source string
    33  
    34  	// Key is the locally unique identifier for this module.
    35  	Key string
    36  
    37  	// Version is the exact version string for the stored module.
    38  	Version string
    39  
    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
    42  	// disk.
    43  	Dir string
    44  
    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.
    50  	Root string
    51  
    52  	// url is the location of the module source
    53  	url string
    54  
    55  	// Registry is true if this module is sourced from a registry
    56  	registry bool
    57  }
    58  
    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.
    62  type Storage struct {
    63  	// StorageDir is the full path to the directory where all modules will be
    64  	// stored.
    65  	StorageDir string
    66  
    67  	// Ui is an optional cli.Ui for user output
    68  	Ui cli.Ui
    69  
    70  	// Mode is the GetMode that will be used for various operations.
    71  	Mode GetMode
    72  
    73  	registry *registry.Client
    74  }
    75  
    76  // NewStorage returns a new initialized Storage object.
    77  func NewStorage(dir string, services *disco.Disco) *Storage {
    78  	regClient := registry.NewClient(services, nil)
    79  
    80  	return &Storage{
    81  		StorageDir: dir,
    82  		registry:   regClient,
    83  	}
    84  }
    85  
    86  // loadManifest returns the moduleManifest file from the parent directory.
    87  func (s Storage) loadManifest() (moduleManifest, error) {
    88  	manifest := moduleManifest{}
    89  
    90  	manifestPath := filepath.Join(s.StorageDir, manifestName)
    91  	data, err := ioutil.ReadFile(manifestPath)
    92  	if err != nil && !os.IsNotExist(err) {
    93  		return manifest, err
    94  	}
    95  
    96  	if len(data) == 0 {
    97  		return manifest, nil
    98  	}
    99  
   100  	if err := json.Unmarshal(data, &manifest); err != nil {
   101  		return manifest, err
   102  	}
   103  	return manifest, nil
   104  }
   105  
   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
   109  // not a concern.
   110  func (s Storage) recordModule(rec moduleRecord) error {
   111  	manifest, err := s.loadManifest()
   112  	if err != nil {
   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)
   116  	}
   117  
   118  	// do nothing if we already have the exact module
   119  	for i, stored := range manifest.Modules {
   120  		if rec == stored {
   121  			return nil
   122  		}
   123  
   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]
   129  			break
   130  		}
   131  	}
   132  
   133  	manifest.Modules = append(manifest.Modules, rec)
   134  
   135  	js, err := json.Marshal(manifest)
   136  	if err != nil {
   137  		panic(err)
   138  	}
   139  
   140  	manifestPath := filepath.Join(s.StorageDir, manifestName)
   141  	return ioutil.WriteFile(manifestPath, js, 0644)
   142  }
   143  
   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()
   149  	if err != nil {
   150  		return manifest.Modules, err
   151  	}
   152  
   153  	var matching []moduleRecord
   154  
   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)
   159  		}
   160  	}
   161  
   162  	return matching, nil
   163  }
   164  
   165  func (s Storage) moduleDir(key string) (string, error) {
   166  	manifest, err := s.loadManifest()
   167  	if err != nil {
   168  		return "", err
   169  	}
   170  
   171  	for _, m := range manifest.Modules {
   172  		if m.Key == key {
   173  			return m.Dir, nil
   174  		}
   175  	}
   176  
   177  	return "", nil
   178  }
   179  
   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()
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	for _, mod := range manifest.Modules {
   188  		if mod.Dir == dir {
   189  			return mod.Root, nil
   190  		}
   191  	}
   192  	return "", nil
   193  }
   194  
   195  // record only the Root directory for the module stored at dir.
   196  func (s Storage) recordModuleRoot(dir, root string) error {
   197  	rec := moduleRecord{
   198  		Dir:  dir,
   199  		Root: root,
   200  	}
   201  
   202  	return s.recordModule(rec)
   203  }
   204  
   205  func (s Storage) output(msg string) {
   206  	if s.Ui == nil || s.Mode == GetModeNone {
   207  		return
   208  	}
   209  	s.Ui.Output(msg)
   210  }
   211  
   212  func (s Storage) getStorage(key string, src string) (string, bool, error) {
   213  	storage := &getter.FolderStorage{
   214  		StorageDir: s.StorageDir,
   215  	}
   216  
   217  	log.Printf("[DEBUG] fetching module from %s", src)
   218  
   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
   224  		}
   225  	}
   226  
   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
   231  }
   232  
   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 {
   236  		return "", nil
   237  	}
   238  
   239  	return s.moduleDir(key)
   240  }
   241  
   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
   246  	mode := s.Mode
   247  	s.Mode = GetModeUpdate
   248  	defer func() {
   249  		s.Mode = mode
   250  	}()
   251  
   252  	rec, err := s.findRegistryModule(src, anyVersion)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	pwd, err := os.Getwd()
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	source := rec.url
   263  	if source == "" {
   264  		source, err = getter.Detect(src, pwd, getter.Detectors)
   265  		if err != nil {
   266  			return fmt.Errorf("module %s: %s", src, err)
   267  		}
   268  	}
   269  
   270  	if source == "" {
   271  		return fmt.Errorf("module %q not found", src)
   272  	}
   273  
   274  	return GetCopy(dst, source)
   275  }
   276  
   277  // find a registry module
   278  func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, error) {
   279  	rec := moduleRecord{
   280  		Source: mSource,
   281  	}
   282  	// detect if we have a registry source
   283  	mod, err := regsrc.ParseModuleSource(mSource)
   284  	switch err {
   285  	case nil:
   286  		//ok
   287  	case regsrc.ErrInvalidModuleSource:
   288  		return rec, nil
   289  	default:
   290  		return rec, err
   291  	}
   292  	rec.registry = true
   293  
   294  	log.Printf("[TRACE] %q is a registry module", mod.Display())
   295  
   296  	versions, err := s.moduleVersions(mod.String())
   297  	if err != nil {
   298  		log.Printf("[ERROR] error looking up versions for %q: %s", mod.Display(), err)
   299  		return rec, err
   300  	}
   301  
   302  	match, err := newestRecord(versions, constraint)
   303  	if err != nil {
   304  		log.Printf("[INFO] no matching version for %q<%s>, %s", mod.Display(), constraint, err)
   305  	}
   306  	log.Printf("[DEBUG] matched %q version %s for %s", mod, match.Version, constraint)
   307  
   308  	rec.Dir = match.Dir
   309  	rec.Version = match.Version
   310  	found := rec.Dir != ""
   311  
   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.Versions(mod)
   316  		if err != nil {
   317  			return rec, err
   318  		}
   319  
   320  		if len(resp.Modules) == 0 {
   321  			return rec, fmt.Errorf("module %q not found in registry", mod.Display())
   322  		}
   323  
   324  		match, err := newestVersion(resp.Modules[0].Versions, constraint)
   325  		if err != nil {
   326  			return rec, err
   327  		}
   328  
   329  		if match == nil {
   330  			return rec, fmt.Errorf("no versions for %q found matching %q", mod.Display(), constraint)
   331  		}
   332  
   333  		rec.Version = match.Version
   334  
   335  		rec.url, err = s.registry.Location(mod, rec.Version)
   336  		if err != nil {
   337  			return rec, err
   338  		}
   339  
   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()))
   343  
   344  	}
   345  	return rec, nil
   346  }