config/module: start, lots of initial work
This commit is contained in:
parent
dd6f536fab
commit
bb22090040
|
@ -0,0 +1,65 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FolderStorage is an implementation of the Storage interface that manages
|
||||
// modules on the disk.
|
||||
type FolderStorage struct {
|
||||
// StorageDir is the directory where the modules will be stored.
|
||||
StorageDir string
|
||||
}
|
||||
|
||||
// Dir implements Storage.Dir
|
||||
func (s *FolderStorage) Dir(source string) (d string, e bool, err error) {
|
||||
d = s.dir(source)
|
||||
_, err = os.Stat(d)
|
||||
if err == nil {
|
||||
// Directory exists
|
||||
e = true
|
||||
return
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
// Directory doesn't exist
|
||||
d = ""
|
||||
e = false
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// An error
|
||||
d = ""
|
||||
e = false
|
||||
return
|
||||
}
|
||||
|
||||
// Get implements Storage.Get
|
||||
func (s *FolderStorage) Get(source string, update bool) error {
|
||||
dir := s.dir(source)
|
||||
if !update {
|
||||
if _, err := os.Stat(dir); err == nil {
|
||||
// If the directory already exists, then we're done since
|
||||
// we're not updating.
|
||||
return nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
// If the error we got wasn't a file-not-exist error, then
|
||||
// something went wrong and we should report it.
|
||||
return fmt.Errorf("Error reading module directory: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the source. This always forces an update.
|
||||
return Get(dir, source)
|
||||
}
|
||||
|
||||
// dir returns the directory name internally that we'll use to map to
|
||||
// internally.
|
||||
func (s *FolderStorage) dir(source string) string {
|
||||
sum := md5.Sum([]byte(source))
|
||||
return filepath.Join(s.StorageDir, hex.EncodeToString(sum[:]))
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFolderStorage_impl(t *testing.T) {
|
||||
var _ Storage = new(FolderStorage)
|
||||
}
|
||||
|
||||
func TestFolderStorage(t *testing.T) {
|
||||
s := &FolderStorage{StorageDir: tempDir(t)}
|
||||
|
||||
module := testModule("basic")
|
||||
|
||||
// A module shouldn't exist at first...
|
||||
_, ok, err := s.Dir(module)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatal("should not exist")
|
||||
}
|
||||
|
||||
// We can get it
|
||||
err = s.Get(module, false)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Now the module exists
|
||||
dir, ok, err := s.Dir(module)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("should exist")
|
||||
}
|
||||
|
||||
mainPath := filepath.Join(dir, "main.tf")
|
||||
if _, err := os.Stat(mainPath); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func tempDir(t *testing.T) string {
|
||||
dir, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Getter defines the interface that schemes must implement to download
|
||||
// and update modules.
|
||||
type Getter interface {
|
||||
// Get downloads the given URL into the given directory. This always
|
||||
// assumes that we're updating and gets the latest version that it can.
|
||||
//
|
||||
// The directory may already exist (if we're updating). If it is in a
|
||||
// format that isn't understood, an error should be returned. Get shouldn't
|
||||
// simply nuke the directory.
|
||||
Get(string, *url.URL) error
|
||||
}
|
||||
|
||||
// Getters is the mapping of scheme to the Getter implementation that will
|
||||
// be used to get a dependency.
|
||||
var Getters map[string]Getter
|
||||
|
||||
func init() {
|
||||
Getters = map[string]Getter{
|
||||
"file": new(FileGetter),
|
||||
}
|
||||
}
|
||||
|
||||
// Get downloads the module specified by src into the folder specified by
|
||||
// dst. If dst already exists, Get will attempt to update it.
|
||||
//
|
||||
// src is a URL, whereas dst is always just a file path to a folder. This
|
||||
// folder doesn't need to exist. It will be created if it doesn't exist.
|
||||
func Get(dst, src string) error {
|
||||
u, err := url.Parse(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g, ok := Getters[u.Scheme]
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
"module download not supported for scheme '%s'", u.Scheme)
|
||||
}
|
||||
|
||||
err = g.Get(dst, u)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error downloading module '%s': %s", src, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FileGetter is a Getter implementation that will download a module from
|
||||
// a file scheme.
|
||||
type FileGetter struct{}
|
||||
|
||||
func (g *FileGetter) Get(dst string, u *url.URL) error {
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the destination already exists, it must be a symlink
|
||||
if err == nil {
|
||||
mode := fi.Mode()
|
||||
if mode&os.ModeSymlink != 0 {
|
||||
return fmt.Errorf("destination exists and is not a symlink")
|
||||
}
|
||||
}
|
||||
|
||||
// The source path must exist and be a directory to be usable.
|
||||
if fi, err := os.Stat(u.Path); err != nil {
|
||||
return fmt.Errorf("source path error: %s", err)
|
||||
} else if !fi.IsDir() {
|
||||
return fmt.Errorf("source path must be a directory")
|
||||
}
|
||||
|
||||
// Create all the parent directories
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Symlink(u.Path, dst)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package module
|
||||
|
||||
// Module represents the metadata for a single module.
|
||||
type Module struct {
|
||||
Name string
|
||||
Source string
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
||||
func testModule(n string) string {
|
||||
p := filepath.Join(fixtureDir, n)
|
||||
p, err := filepath.Abs(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var url url.URL
|
||||
url.Scheme = "file"
|
||||
url.Path = p
|
||||
return url.String()
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package module
|
||||
|
||||
// Storage is an interface that knows how to lookup downloaded modules
|
||||
// as well as download and update modules from their sources into the
|
||||
// proper location.
|
||||
type Storage interface {
|
||||
// Dir returns the directory on local disk where the modulue source
|
||||
// can be loaded from.
|
||||
Dir(string) (string, bool, error)
|
||||
|
||||
// Get will download and optionally update the given module.
|
||||
Get(string, bool) error
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# Hello
|
|
@ -0,0 +1,71 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
// Tree represents the module import tree of configurations.
|
||||
//
|
||||
// This Tree structure can be used to get (download) new modules, load
|
||||
// all the modules without getting, flatten the tree into something
|
||||
// Terraform can use, etc.
|
||||
type Tree struct {
|
||||
Config *config.Config
|
||||
Children []*Tree
|
||||
}
|
||||
|
||||
// GetMode is an enum that describes how modules are loaded.
|
||||
//
|
||||
// GetModeLoad says that modules will not be downloaded or updated, they will
|
||||
// only be loaded from the storage.
|
||||
//
|
||||
// GetModeGet says that modules can be initially downloaded if they don't
|
||||
// exist, but otherwise to just load from the current version in storage.
|
||||
//
|
||||
// GetModeUpdate says that modules should be checked for updates and
|
||||
// downloaded prior to loading. If there are no updates, we load the version
|
||||
// from disk, otherwise we download first and then load.
|
||||
type GetMode byte
|
||||
|
||||
const (
|
||||
GetModeNone GetMode = iota
|
||||
GetModeGet
|
||||
GetModeUpdate
|
||||
)
|
||||
|
||||
// Flatten takes the entire module tree and flattens it into a single
|
||||
// namespace in *config.Config with no module imports.
|
||||
//
|
||||
// Validate is called here implicitly, since it is important that semantic
|
||||
// checks pass before flattening the configuration. Otherwise, encapsulation
|
||||
// breaks in horrible ways and the errors that come out the other side
|
||||
// will be surprising.
|
||||
func (t *Tree) Flatten() (*config.Config, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Modules returns the list of modules that this tree imports.
|
||||
func (t *Tree) Modules() []*Module {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads the configuration of the entire tree.
|
||||
//
|
||||
// The parameters are used to tell the tree where to find modules and
|
||||
// whether it can download/update modules along the way.
|
||||
//
|
||||
// Various semantic-like checks are made along the way of loading since
|
||||
// module trees inherently require the configuration to be in a reasonably
|
||||
// sane state: no circular dependencies, proper module sources, etc. A full
|
||||
// suite of validations can be done by running Validate (after loading).
|
||||
func (t *Tree) Load(s Storage, mode GetMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate does semantic checks on the entire tree of configurations.
|
||||
//
|
||||
// This will call the respective config.Config.Validate() functions as well
|
||||
// as verifying things such as parameters/outputs between the various modules.
|
||||
func (t *Tree) Validate() error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue