2014-05-24 00:35:18 +02:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
2017-09-29 00:15:14 +02:00
|
|
|
"bufio"
|
2014-05-24 00:35:18 +02:00
|
|
|
"fmt"
|
2014-05-24 21:51:31 +02:00
|
|
|
"io"
|
2017-09-29 00:15:14 +02:00
|
|
|
"os"
|
2017-10-05 02:15:23 +02:00
|
|
|
|
|
|
|
"github.com/hashicorp/errwrap"
|
2014-05-24 00:35:18 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
2017-09-29 00:15:14 +02:00
|
|
|
// 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 = ""
|
|
|
|
|
2014-05-24 00:35:18 +02:00
|
|
|
// 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) {
|
2014-05-24 00:42:29 +02:00
|
|
|
var f fileLoaderFunc
|
2017-09-29 00:15:14 +02:00
|
|
|
|
|
|
|
// 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:
|
|
|
|
}
|
2014-07-21 02:52:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if f == nil {
|
2014-05-24 00:42:29 +02:00
|
|
|
return nil, fmt.Errorf(
|
2014-07-19 01:54:52 +02:00
|
|
|
"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
|
2014-05-24 00:42:29 +02:00
|
|
|
root)
|
|
|
|
}
|
|
|
|
|
|
|
|
c, imps, err := f(root)
|
2014-05-24 00:35:18 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-05-24 01:30:28 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-05-24 00:35:18 +02:00
|
|
|
// 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 {
|
2017-10-05 02:15:23 +02:00
|
|
|
return nil, errwrap.Wrapf(fmt.Sprintf("Error loading %s: {{err}}", t.Path), err)
|
2014-05-24 00:35:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build our result
|
|
|
|
result := &configTree{
|
|
|
|
Path: t.Path,
|
|
|
|
Config: config,
|
|
|
|
}
|
|
|
|
|
2014-05-24 01:09:41 +02:00
|
|
|
// 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
|
|
|
|
}
|
2014-05-24 00:35:18 +02:00
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|