12 "github.com/hashicorp/hcl"
15 // ErrNoConfigsFound is the error returned by LoadDir if no
16 // Terraform configuration files were found in the given directory.
17 type ErrNoConfigsFound struct {
21 func (e ErrNoConfigsFound) Error() string {
23 "No Terraform configuration files found in directory: %s",
27 // LoadJSON loads a single Terraform configuration from a given JSON document.
29 // The document must be a complete Terraform configuration. This function will
30 // NOT try to load any additional modules so only the given document is loaded.
31 func LoadJSON(raw json.RawMessage) (*Config, error) {
32 obj, err := hcl.Parse(string(raw))
34 return nil, fmt.Errorf(
35 "Error parsing JSON document as HCL: %s", err)
38 // Start building the result
39 hclConfig := &hclConfigurable{
43 return hclConfig.Config()
46 // LoadFile loads the Terraform configuration from a given file.
48 // This file can be any format that Terraform recognizes, and import any
49 // other format that Terraform recognizes.
50 func LoadFile(path string) (*Config, error) {
51 importTree, err := loadTree(path)
56 configTree, err := importTree.ConfigTree()
58 // Close the importTree now so that we can clear resources as quickly
66 return configTree.Flatten()
69 // LoadDir loads all the Terraform configuration files in a single
70 // directory and appends them together.
72 // Special files known as "override files" can also be present, which
73 // are merged into the loaded configuration. That is, the non-override
74 // files are loaded first to create the configuration. Then, the overrides
75 // are merged into the configuration to create the final configuration.
77 // Files are loaded in lexical order.
78 func LoadDir(root string) (*Config, error) {
79 files, overrides, err := dirFiles(root)
84 return nil, &ErrNoConfigsFound{Dir: root}
87 // Determine the absolute path to the directory.
88 rootAbs, err := filepath.Abs(root)
95 // Sort the files and overrides so we have a deterministic order
97 sort.Strings(overrides)
99 // Load all the regular files, append them to each other.
100 for _, f := range files {
101 c, err := LoadFile(f)
107 result, err = Append(result, c)
116 // Load all the overrides, and merge them into the config
117 for _, f := range overrides {
118 c, err := LoadFile(f)
123 result, err = Merge(result, c)
129 // Mark the directory
135 // IsEmptyDir returns true if the directory given has no Terraform
136 // configuration files.
137 func IsEmptyDir(root string) (bool, error) {
138 if _, err := os.Stat(root); err != nil && os.IsNotExist(err) {
142 fs, os, err := dirFiles(root)
147 return len(fs) == 0 && len(os) == 0, nil
150 // Ext returns the Terraform configuration extension of the given
151 // path, or a blank string if it is an invalid function.
152 func ext(path string) string {
153 if strings.HasSuffix(path, ".tf") {
155 } else if strings.HasSuffix(path, ".tf.json") {
162 func dirFiles(dir string) ([]string, []string, error) {
163 f, err := os.Open(dir)
174 return nil, nil, fmt.Errorf(
175 "configuration path must be a directory: %s",
179 var files, overrides []string
182 var fis []os.FileInfo
183 fis, err = f.Readdir(128)
184 if err != nil && err != io.EOF {
188 for _, fi := range fis {
189 // Ignore directories
194 // Only care about files that are valid to load
196 extValue := ext(name)
197 if extValue == "" || isIgnoredFile(name) {
201 // Determine if we're dealing with an override
202 nameNoExt := name[:len(name)-len(extValue)]
203 override := nameNoExt == "override" ||
204 strings.HasSuffix(nameNoExt, "_override")
206 path := filepath.Join(dir, name)
208 overrides = append(overrides, path)
210 files = append(files, path)
215 return files, overrides, nil
218 // isIgnoredFile returns true or false depending on whether the
219 // provided file name is a file that should be ignored.
220 func isIgnoredFile(name string) bool {
221 return strings.HasPrefix(name, ".") || // Unix-like hidden files
222 strings.HasSuffix(name, "~") || // vim
223 strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs