opentofu/config/import_tree.go
Martin Atkins fc20f419dd config and command: use errwrap to propagate config load errors
Previously we were using fmt.Sprintf and thus forcing the stringification
of the wrapped error.

Using errwrap allows us to unpack the original error at the top of the
stack, which is useful when the wrapped error is really a hcl.Diagnostics
containing potentially-multiple errors and possibly warnings.
2017-10-06 11:46:07 -07:00

152 lines
3.6 KiB
Go

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
}