diff --git a/config/config_tree.go b/config/config_tree.go new file mode 100644 index 000000000..4025deece --- /dev/null +++ b/config/config_tree.go @@ -0,0 +1,15 @@ +package config + +// configTree represents a tree of configurations where the root is the +// first file and its children are the configurations it has imported. +type configTree struct { + Path string + Config *Config + Children []*configTree +} + +// Flatten flattens the entire tree down to a single merged Config +// structure. +func (t *configTree) Flatten() (*Config, error) { + return t.Config, nil +} diff --git a/config/loader.go b/config/loader.go new file mode 100644 index 000000000..06057f690 --- /dev/null +++ b/config/loader.go @@ -0,0 +1,76 @@ +package config + +import ( + "fmt" +) + +// Load loads the Terraform configuration from a given file. +func Load(path string) (*Config, error) { + importTree, err := loadTree(path) + if err != nil { + return nil, err + } + + configTree, err := importTree.ConfigTree() + if err != nil { + return nil, err + } + + return configTree.Flatten() +} + + +type configurable interface { + Config() (*Config, error) +} + +type importTree struct { + Path string + Raw configurable + Children []*importTree +} + +type fileLoaderFunc func(path string) (configurable, []string, error) + +func loadTree(root string) (*importTree, error) { + c, imps, err := loadFileLibucl(root) + if err != nil { + return nil, err + } + + children := make([]*importTree, len(imps)) + for i, imp := range imps { + t, err := loadTree(imp) + if err != nil { + return nil, err + } + + children[i] = t + } + + return &importTree{ + Path: root, + Raw: c, + Children: children, + }, nil +} + +func (t *importTree) ConfigTree() (*configTree, error) { + config, err := t.Raw.Config() + if err != nil { + return nil, fmt.Errorf( + "Error loading %s: %s", + t.Path, + err) + } + + // Build our result + result := &configTree{ + Path: t.Path, + Config: config, + } + + // TODO: Follow children and load them + + return result, nil +} diff --git a/config/loader_libucl.go b/config/loader_libucl.go index b020e8b89..895548b30 100644 --- a/config/loader_libucl.go +++ b/config/loader_libucl.go @@ -11,38 +11,39 @@ import ( // equally behaving parsing everywhere. const libuclParseFlags = libucl.ParserKeyLowercase -// libuclImportTree represents a tree structure of the imports from the -// configuration files along with the raw libucl objects from those files. -type libuclImportTree struct { - Path string - Object *libucl.Object - Children []*libuclImportTree +type libuclConfigurable struct { + Object *libucl.Object } -// libuclConfigTree represents a tree structure of the loaded configurations -// of all the Terraform files. -type libuclConfigTree struct { - Path string - Config *Config - Children []*libuclConfigTree -} +func (t *libuclConfigurable) Config() (*Config, error) { + var rawConfig struct { + Variable map[string]Variable + } -// Load loads the Terraform configuration from a given file. -func Load(path string) (*Config, error) { - importTree, err := loadTreeLibucl(path) - if err != nil { + if err := t.Object.Decode(&rawConfig); err != nil { return nil, err } - configTree, err := importTree.ConfigTree() - if err != nil { - return nil, err + // Start building up the actual configuration. We first + // copy the fields that can be directly assigned. + config := new(Config) + config.Variables = rawConfig.Variable + + // Build the resources + resources := t.Object.Get("resource") + if resources != nil { + var err error + config.Resources, err = loadResourcesLibucl(resources) + resources.Close() + if err != nil { + return nil, err + } } - return configTree.Config, nil + return config, nil } -func loadTreeLibucl(root string) (*libuclImportTree, error) { +func loadFileLibucl(root string) (configurable, []string, error) { var obj *libucl.Object = nil // Parse and store the object. We don't use a defer here so that @@ -58,12 +59,11 @@ func loadTreeLibucl(root string) (*libuclImportTree, error) { // If there was an error, return early if err != nil { - return nil, err + return nil, nil, err } // Start building the result - result := &libuclImportTree{ - Path: root, + result := &libuclConfigurable{ Object: obj, } @@ -71,13 +71,13 @@ func loadTreeLibucl(root string) (*libuclImportTree, error) { imports := obj.Get("import") if imports == nil { result.Object.Ref() - return result, nil + return result, nil, nil } if imports.Type() != libucl.ObjectTypeString { imports.Close() - return nil, fmt.Errorf( + return nil, nil, fmt.Errorf( "Error in %s: all 'import' declarations should be in the format\n"+ "`import \"foo\"` (Got type %s)", root, @@ -88,31 +88,21 @@ func loadTreeLibucl(root string) (*libuclImportTree, error) { importPaths := make([]string, 0, imports.Len()) iter := imports.Iterate(false) for imp := iter.Next(); imp != nil; imp = iter.Next() { - importPaths = append(importPaths, imp.ToString()) - imp.Close() - } - iter.Close() - imports.Close() - - // Load them all - result.Children = make([]*libuclImportTree, len(importPaths)) - for i, path := range importPaths { + path := imp.ToString() if !filepath.IsAbs(path) { // Relative paths are relative to the Terraform file itself dir := filepath.Dir(root) path = filepath.Join(dir, path) } - imp, err := loadTreeLibucl(path) - if err != nil { - return nil, err - } - - result.Children[i] = imp + importPaths = append(importPaths, path) + imp.Close() } + iter.Close() + imports.Close() result.Object.Ref() - return result, nil + return result, importPaths, nil } // Given a handle to a libucl object, this recurses into the structure @@ -188,60 +178,3 @@ func loadResourcesLibucl(o *libucl.Object) ([]Resource, error) { return result, nil } - -func (t *libuclImportTree) ConfigTree() (*libuclConfigTree, error) { - var rawConfig struct { - Variable map[string]Variable - } - - if err := t.Object.Decode(&rawConfig); err != nil { - return nil, fmt.Errorf( - "Error decoding %s: %s", - t.Path, - err) - } - - // Start building up the actual configuration. We first - // copy the fields that can be directly assigned. - config := new(Config) - config.Variables = rawConfig.Variable - - // Build the resources - resources := t.Object.Get("resource") - if resources != nil { - var err error - config.Resources, err = loadResourcesLibucl(resources) - resources.Close() - if err != nil { - return nil, err - } - } - - // Build our result - result := &libuclConfigTree{ - Path: t.Path, - Config: config, - } - - return result, nil -} - -// Helper for parsing a single libucl-formatted file into -// the given structure. -func parseFile(path string, result interface{}) error { - parser := libucl.NewParser(libuclParseFlags) - defer parser.Close() - - if err := parser.AddFile(path); err != nil { - return err - } - - root := parser.Object() - defer root.Close() - - if err := root.Decode(result); err != nil { - return err - } - - return nil -}