config/module: detectors, some more work on Tree
This commit is contained in:
parent
799ffbb3ac
commit
a35a9262d4
|
@ -0,0 +1,50 @@
|
||||||
|
package module
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Detector defines the interface that an invalid URL or a URL with a blank
|
||||||
|
// scheme is passed through in order to determine if its shorthand for
|
||||||
|
// something else well-known.
|
||||||
|
type Detector interface {
|
||||||
|
// Detect will detect whether the string matches a known pattern to
|
||||||
|
// turn it into a proper URL.
|
||||||
|
Detect(string, string) (string, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detectors is the list of detectors that are tried on an invalid URL.
|
||||||
|
// This is also the order they're tried (index 0 is first).
|
||||||
|
var Detectors []Detector
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Detectors = []Detector{
|
||||||
|
new(FileDetector),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect turns a source string into another source string if it is
|
||||||
|
// detected to be of a known pattern.
|
||||||
|
//
|
||||||
|
// This is safe to be called with an already valid source string: Detect
|
||||||
|
// will just return it.
|
||||||
|
func Detect(src string, pwd string) (string, error) {
|
||||||
|
u, err := url.Parse(src)
|
||||||
|
if err == nil && u.Scheme != "" {
|
||||||
|
// Valid URL
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range Detectors {
|
||||||
|
result, ok, err := d.Detect(src, pwd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("invalid source string: %s", src)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package module
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileDetector implements Detector to detect file paths.
|
||||||
|
type FileDetector struct{}
|
||||||
|
|
||||||
|
func (d *FileDetector) Detect(src, pwd string) (string, bool, error) {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we're using "/" even on Windows. URLs are "/"-based.
|
||||||
|
src = filepath.ToSlash(src)
|
||||||
|
if !filepath.IsAbs(src) {
|
||||||
|
src = filepath.Join(pwd, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we don't start with "/" since we add that below
|
||||||
|
if src[0] == '/' {
|
||||||
|
src = src[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("file:///%s", src), true, nil
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package module
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileDetector(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
{"./foo", "file:///pwd/foo"},
|
||||||
|
{"foo", "file:///pwd/foo"},
|
||||||
|
{"/foo", "file:///foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
pwd := "/pwd"
|
||||||
|
f := new(FileDetector)
|
||||||
|
for i, tc := range cases {
|
||||||
|
output, ok, err := f.Detect(tc.Input, pwd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != tc.Output {
|
||||||
|
t.Fatalf("%d: bad: %#v", i, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,4 +4,5 @@ package module
|
||||||
type Module struct {
|
type Module struct {
|
||||||
Name string
|
Name string
|
||||||
Source string
|
Source string
|
||||||
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,3 +45,7 @@ func testModule(n string) string {
|
||||||
url.Path = p
|
url.Path = p
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testStorage(t *testing.T) Storage {
|
||||||
|
return &FolderStorage{StorageDir: tempDir(t)}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package module
|
package module
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,8 +13,9 @@ import (
|
||||||
// all the modules without getting, flatten the tree into something
|
// all the modules without getting, flatten the tree into something
|
||||||
// Terraform can use, etc.
|
// Terraform can use, etc.
|
||||||
type Tree struct {
|
type Tree struct {
|
||||||
Config *config.Config
|
config *config.Config
|
||||||
Children []*Tree
|
children []*Tree
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMode is an enum that describes how modules are loaded.
|
// GetMode is an enum that describes how modules are loaded.
|
||||||
|
@ -35,7 +39,15 @@ const (
|
||||||
|
|
||||||
// NewTree returns a new Tree for the given config structure.
|
// NewTree returns a new Tree for the given config structure.
|
||||||
func NewTree(c *config.Config) *Tree {
|
func NewTree(c *config.Config) *Tree {
|
||||||
return &Tree{Config: c}
|
return &Tree{config: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children returns the children of this tree (the modules that are
|
||||||
|
// imported by this root).
|
||||||
|
//
|
||||||
|
// This will only return a non-nil value after Load is called.
|
||||||
|
func (t *Tree) Children() []*Tree {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten takes the entire module tree and flattens it into a single
|
// Flatten takes the entire module tree and flattens it into a single
|
||||||
|
@ -54,8 +66,8 @@ func (t *Tree) Flatten() (*config.Config, error) {
|
||||||
// This is only the imports of _this_ level of the tree. To retrieve the
|
// This is only the imports of _this_ level of the tree. To retrieve the
|
||||||
// full nested imports, you'll have to traverse the tree.
|
// full nested imports, you'll have to traverse the tree.
|
||||||
func (t *Tree) Modules() []*Module {
|
func (t *Tree) Modules() []*Module {
|
||||||
result := make([]*Module, len(t.Config.Modules))
|
result := make([]*Module, len(t.config.Modules))
|
||||||
for i, m := range t.Config.Modules {
|
for i, m := range t.config.Modules {
|
||||||
result[i] = &Module{
|
result[i] = &Module{
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
Source: m.Source,
|
Source: m.Source,
|
||||||
|
@ -70,11 +82,66 @@ func (t *Tree) Modules() []*Module {
|
||||||
// The parameters are used to tell the tree where to find modules and
|
// The parameters are used to tell the tree where to find modules and
|
||||||
// whether it can download/update modules along the way.
|
// whether it can download/update modules along the way.
|
||||||
//
|
//
|
||||||
|
// Calling this multiple times will reload the tree.
|
||||||
|
//
|
||||||
// Various semantic-like checks are made along the way of loading since
|
// Various semantic-like checks are made along the way of loading since
|
||||||
// module trees inherently require the configuration to be in a reasonably
|
// module trees inherently require the configuration to be in a reasonably
|
||||||
// sane state: no circular dependencies, proper module sources, etc. A full
|
// sane state: no circular dependencies, proper module sources, etc. A full
|
||||||
// suite of validations can be done by running Validate (after loading).
|
// suite of validations can be done by running Validate (after loading).
|
||||||
func (t *Tree) Load(s Storage, mode GetMode) error {
|
func (t *Tree) Load(s Storage, mode GetMode) error {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
// Reset the children if we have any
|
||||||
|
t.children = nil
|
||||||
|
|
||||||
|
modules := t.Modules()
|
||||||
|
children := make([]*Tree, len(modules))
|
||||||
|
|
||||||
|
// Go through all the modules and get the directory for them.
|
||||||
|
update := mode == GetModeUpdate
|
||||||
|
for i, m := range modules {
|
||||||
|
source, err := Detect(m.Source, m.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("module %s: %s", m.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode > GetModeNone {
|
||||||
|
// Get the module since we specified we should
|
||||||
|
if err := s.Get(source, update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the directory where this module is so we can load it
|
||||||
|
dir, ok, err := s.Dir(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"module %s: not found, may need to be downloaded", m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the configuration
|
||||||
|
c, err := config.LoadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"module %s: %s", m.Name, err)
|
||||||
|
}
|
||||||
|
children[i] = NewTree(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the children and load them.
|
||||||
|
for _, c := range children {
|
||||||
|
if err := c.Load(s, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set our tree up
|
||||||
|
t.children = children
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTree(t *testing.T) {
|
func TestTree_Load(t *testing.T) {
|
||||||
|
tree := NewTree(testConfig(t, "basic"))
|
||||||
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTree_Modules(t *testing.T) {
|
||||||
tree := NewTree(testConfig(t, "basic"))
|
tree := NewTree(testConfig(t, "basic"))
|
||||||
actual := tree.Modules()
|
actual := tree.Modules()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue