blob: 08cbc7736210bba70f47cbb0e452cfa1006b4ba0 [file] [log] [blame]
package config
import (
"bufio"
"fmt"
"io"
"os"
"github.com/hashicorp/errwrap"
)
// configurable is an interface that must be implemented by any configuration
// formats of Terraform in order to return a *Config.
type configurable interface {
Config() (*Config, error)
}
// importTree is the result of the first-pass load of the configuration
// files. It is a tree of raw configurables and then any children (their
// imports).
//
// An importTree can be turned into a configTree.
type importTree struct {
Path string
Raw configurable
Children []*importTree
}
// This is the function type that must be implemented by the configuration
// file loader to turn a single file into a configurable and any additional
// imports.
type fileLoaderFunc func(path string) (configurable, []string, error)
// Set this to a non-empty value at link time to enable the HCL2 experiment.
// This is not currently enabled for release builds.
//
// For example:
// go install -ldflags="-X github.com/hashicorp/terraform/config.enableHCL2Experiment=true" github.com/hashicorp/terraform
var enableHCL2Experiment = ""
// loadTree takes a single file and loads the entire importTree for that
// file. This function detects what kind of configuration file it is an
// executes the proper fileLoaderFunc.
func loadTree(root string) (*importTree, error) {
var f fileLoaderFunc
// HCL2 experiment is currently activated at build time via the linker.
// See the comment on this variable for more information.
if enableHCL2Experiment == "" {
// Main-line behavior: always use the original HCL parser
switch ext(root) {
case ".tf", ".tf.json":
f = loadFileHcl
default:
}
} else {
// Experimental behavior: use the HCL2 parser if the opt-in comment
// is present.
switch ext(root) {
case ".tf":
// We need to sniff the file for the opt-in comment line to decide
// if the file is participating in the HCL2 experiment.
cf, err := os.Open(root)
if err != nil {
return nil, err
}
sc := bufio.NewScanner(cf)
for sc.Scan() {
if sc.Text() == "#terraform:hcl2" {
f = globalHCL2Loader.loadFile
}
}
if f == nil {
f = loadFileHcl
}
case ".tf.json":
f = loadFileHcl
default:
}
}
if f == nil {
return nil, fmt.Errorf(
"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
root)
}
c, imps, err := f(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
}
// Close releases any resources we might be holding open for the importTree.
//
// This can safely be called even while ConfigTree results are alive. The
// importTree is not bound to these.
func (t *importTree) Close() error {
if c, ok := t.Raw.(io.Closer); ok {
c.Close()
}
for _, ct := range t.Children {
ct.Close()
}
return nil
}
// ConfigTree traverses the importTree and turns each node into a *Config
// object, ultimately returning a *configTree.
func (t *importTree) ConfigTree() (*configTree, error) {
config, err := t.Raw.Config()
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error loading %s: {{err}}", t.Path), err)
}
// Build our result
result := &configTree{
Path: t.Path,
Config: config,
}
// Build the config trees for the children
result.Children = make([]*configTree, len(t.Children))
for i, ct := range t.Children {
t, err := ct.ConfigTree()
if err != nil {
return nil, err
}
result.Children[i] = t
}
return result, nil
}