| package config |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "github.com/hashicorp/hcl" |
| ) |
| |
| // ErrNoConfigsFound is the error returned by LoadDir if no |
| // Terraform configuration files were found in the given directory. |
| type ErrNoConfigsFound struct { |
| Dir string |
| } |
| |
| func (e ErrNoConfigsFound) Error() string { |
| return fmt.Sprintf( |
| "No Terraform configuration files found in directory: %s", |
| e.Dir) |
| } |
| |
| // LoadJSON loads a single Terraform configuration from a given JSON document. |
| // |
| // The document must be a complete Terraform configuration. This function will |
| // NOT try to load any additional modules so only the given document is loaded. |
| func LoadJSON(raw json.RawMessage) (*Config, error) { |
| obj, err := hcl.Parse(string(raw)) |
| if err != nil { |
| return nil, fmt.Errorf( |
| "Error parsing JSON document as HCL: %s", err) |
| } |
| |
| // Start building the result |
| hclConfig := &hclConfigurable{ |
| Root: obj, |
| } |
| |
| return hclConfig.Config() |
| } |
| |
| // LoadFile loads the Terraform configuration from a given file. |
| // |
| // This file can be any format that Terraform recognizes, and import any |
| // other format that Terraform recognizes. |
| func LoadFile(path string) (*Config, error) { |
| importTree, err := loadTree(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| configTree, err := importTree.ConfigTree() |
| |
| // Close the importTree now so that we can clear resources as quickly |
| // as possible. |
| importTree.Close() |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| return configTree.Flatten() |
| } |
| |
| // LoadDir loads all the Terraform configuration files in a single |
| // directory and appends them together. |
| // |
| // Special files known as "override files" can also be present, which |
| // are merged into the loaded configuration. That is, the non-override |
| // files are loaded first to create the configuration. Then, the overrides |
| // are merged into the configuration to create the final configuration. |
| // |
| // Files are loaded in lexical order. |
| func LoadDir(root string) (*Config, error) { |
| files, overrides, err := dirFiles(root) |
| if err != nil { |
| return nil, err |
| } |
| if len(files) == 0 && len(overrides) == 0 { |
| return nil, &ErrNoConfigsFound{Dir: root} |
| } |
| |
| // Determine the absolute path to the directory. |
| rootAbs, err := filepath.Abs(root) |
| if err != nil { |
| return nil, err |
| } |
| |
| var result *Config |
| |
| // Sort the files and overrides so we have a deterministic order |
| sort.Strings(files) |
| sort.Strings(overrides) |
| |
| // Load all the regular files, append them to each other. |
| for _, f := range files { |
| c, err := LoadFile(f) |
| if err != nil { |
| return nil, err |
| } |
| |
| if result != nil { |
| result, err = Append(result, c) |
| if err != nil { |
| return nil, err |
| } |
| } else { |
| result = c |
| } |
| } |
| if len(files) == 0 { |
| result = &Config{} |
| } |
| |
| // Load all the overrides, and merge them into the config |
| for _, f := range overrides { |
| c, err := LoadFile(f) |
| if err != nil { |
| return nil, err |
| } |
| |
| result, err = Merge(result, c) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| // Mark the directory |
| result.Dir = rootAbs |
| |
| return result, nil |
| } |
| |
| // IsEmptyDir returns true if the directory given has no Terraform |
| // configuration files. |
| func IsEmptyDir(root string) (bool, error) { |
| if _, err := os.Stat(root); err != nil && os.IsNotExist(err) { |
| return true, nil |
| } |
| |
| fs, os, err := dirFiles(root) |
| if err != nil { |
| return false, err |
| } |
| |
| return len(fs) == 0 && len(os) == 0, nil |
| } |
| |
| // Ext returns the Terraform configuration extension of the given |
| // path, or a blank string if it is an invalid function. |
| func ext(path string) string { |
| if strings.HasSuffix(path, ".tf") { |
| return ".tf" |
| } else if strings.HasSuffix(path, ".tf.json") { |
| return ".tf.json" |
| } else { |
| return "" |
| } |
| } |
| |
| func dirFiles(dir string) ([]string, []string, error) { |
| f, err := os.Open(dir) |
| if err != nil { |
| return nil, nil, err |
| } |
| defer f.Close() |
| |
| fi, err := f.Stat() |
| if err != nil { |
| return nil, nil, err |
| } |
| if !fi.IsDir() { |
| return nil, nil, fmt.Errorf( |
| "configuration path must be a directory: %s", |
| dir) |
| } |
| |
| var files, overrides []string |
| err = nil |
| for err != io.EOF { |
| var fis []os.FileInfo |
| fis, err = f.Readdir(128) |
| if err != nil && err != io.EOF { |
| return nil, nil, err |
| } |
| |
| for _, fi := range fis { |
| // Ignore directories |
| if fi.IsDir() { |
| continue |
| } |
| |
| // Only care about files that are valid to load |
| name := fi.Name() |
| extValue := ext(name) |
| if extValue == "" || IsIgnoredFile(name) { |
| continue |
| } |
| |
| // Determine if we're dealing with an override |
| nameNoExt := name[:len(name)-len(extValue)] |
| override := nameNoExt == "override" || |
| strings.HasSuffix(nameNoExt, "_override") |
| |
| path := filepath.Join(dir, name) |
| if override { |
| overrides = append(overrides, path) |
| } else { |
| files = append(files, path) |
| } |
| } |
| } |
| |
| return files, overrides, nil |
| } |
| |
| // IsIgnoredFile returns true or false depending on whether the |
| // provided file name is a file that should be ignored. |
| func IsIgnoredFile(name string) bool { |
| return strings.HasPrefix(name, ".") || // Unix-like hidden files |
| strings.HasSuffix(name, "~") || // vim |
| strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs |
| } |