github.com/davebizus/terraform-main@v0.11.12-beta1/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 }