github.com/chentex/terraform@v0.11.2-0.20171208003256-252e8145842e/config/import_tree.go (about)

     1  package config
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/hashicorp/errwrap"
    10  )
    11  
    12  // configurable is an interface that must be implemented by any configuration
    13  // formats of Terraform in order to return a *Config.
    14  type configurable interface {
    15  	Config() (*Config, error)
    16  }
    17  
    18  // importTree is the result of the first-pass load of the configuration
    19  // files. It is a tree of raw configurables and then any children (their
    20  // imports).
    21  //
    22  // An importTree can be turned into a configTree.
    23  type importTree struct {
    24  	Path     string
    25  	Raw      configurable
    26  	Children []*importTree
    27  }
    28  
    29  // This is the function type that must be implemented by the configuration
    30  // file loader to turn a single file into a configurable and any additional
    31  // imports.
    32  type fileLoaderFunc func(path string) (configurable, []string, error)
    33  
    34  // Set this to a non-empty value at link time to enable the HCL2 experiment.
    35  // This is not currently enabled for release builds.
    36  //
    37  // For example:
    38  //    go install -ldflags="-X github.com/hashicorp/terraform/config.enableHCL2Experiment=true" github.com/hashicorp/terraform
    39  var enableHCL2Experiment = ""
    40  
    41  // loadTree takes a single file and loads the entire importTree for that
    42  // file. This function detects what kind of configuration file it is an
    43  // executes the proper fileLoaderFunc.
    44  func loadTree(root string) (*importTree, error) {
    45  	var f fileLoaderFunc
    46  
    47  	// HCL2 experiment is currently activated at build time via the linker.
    48  	// See the comment on this variable for more information.
    49  	if enableHCL2Experiment == "" {
    50  		// Main-line behavior: always use the original HCL parser
    51  		switch ext(root) {
    52  		case ".tf", ".tf.json":
    53  			f = loadFileHcl
    54  		default:
    55  		}
    56  	} else {
    57  		// Experimental behavior: use the HCL2 parser if the opt-in comment
    58  		// is present.
    59  		switch ext(root) {
    60  		case ".tf":
    61  			// We need to sniff the file for the opt-in comment line to decide
    62  			// if the file is participating in the HCL2 experiment.
    63  			cf, err := os.Open(root)
    64  			if err != nil {
    65  				return nil, err
    66  			}
    67  			sc := bufio.NewScanner(cf)
    68  			for sc.Scan() {
    69  				if sc.Text() == "#terraform:hcl2" {
    70  					f = globalHCL2Loader.loadFile
    71  				}
    72  			}
    73  			if f == nil {
    74  				f = loadFileHcl
    75  			}
    76  		case ".tf.json":
    77  			f = loadFileHcl
    78  		default:
    79  		}
    80  	}
    81  
    82  	if f == nil {
    83  		return nil, fmt.Errorf(
    84  			"%s: unknown configuration format. Use '.tf' or '.tf.json' extension",
    85  			root)
    86  	}
    87  
    88  	c, imps, err := f(root)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	children := make([]*importTree, len(imps))
    94  	for i, imp := range imps {
    95  		t, err := loadTree(imp)
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  
   100  		children[i] = t
   101  	}
   102  
   103  	return &importTree{
   104  		Path:     root,
   105  		Raw:      c,
   106  		Children: children,
   107  	}, nil
   108  }
   109  
   110  // Close releases any resources we might be holding open for the importTree.
   111  //
   112  // This can safely be called even while ConfigTree results are alive. The
   113  // importTree is not bound to these.
   114  func (t *importTree) Close() error {
   115  	if c, ok := t.Raw.(io.Closer); ok {
   116  		c.Close()
   117  	}
   118  	for _, ct := range t.Children {
   119  		ct.Close()
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // ConfigTree traverses the importTree and turns each node into a *Config
   126  // object, ultimately returning a *configTree.
   127  func (t *importTree) ConfigTree() (*configTree, error) {
   128  	config, err := t.Raw.Config()
   129  	if err != nil {
   130  		return nil, errwrap.Wrapf(fmt.Sprintf("Error loading %s: {{err}}", t.Path), err)
   131  	}
   132  
   133  	// Build our result
   134  	result := &configTree{
   135  		Path:   t.Path,
   136  		Config: config,
   137  	}
   138  
   139  	// Build the config trees for the children
   140  	result.Children = make([]*configTree, len(t.Children))
   141  	for i, ct := range t.Children {
   142  		t, err := ct.ConfigTree()
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  
   147  		result.Children[i] = t
   148  	}
   149  
   150  	return result, nil
   151  }