mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
fc20f419dd
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.
152 lines
3.6 KiB
Go
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
|
|
}
|