github.com/KevinKlinger/open_terraform@v0.11.12-beta1/config/loader.go (about)

     1  package config
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/hcl"
    13  )
    14  
    15  // ErrNoConfigsFound is the error returned by LoadDir if no
    16  // Terraform configuration files were found in the given directory.
    17  type ErrNoConfigsFound struct {
    18  	Dir string
    19  }
    20  
    21  func (e ErrNoConfigsFound) Error() string {
    22  	return fmt.Sprintf(
    23  		"No Terraform configuration files found in directory: %s",
    24  		e.Dir)
    25  }
    26  
    27  // LoadJSON loads a single Terraform configuration from a given JSON document.
    28  //
    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))
    33  	if err != nil {
    34  		return nil, fmt.Errorf(
    35  			"Error parsing JSON document as HCL: %s", err)
    36  	}
    37  
    38  	// Start building the result
    39  	hclConfig := &hclConfigurable{
    40  		Root: obj,
    41  	}
    42  
    43  	return hclConfig.Config()
    44  }
    45  
    46  // LoadFile loads the Terraform configuration from a given file.
    47  //
    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)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	configTree, err := importTree.ConfigTree()
    57  
    58  	// Close the importTree now so that we can clear resources as quickly
    59  	// as possible.
    60  	importTree.Close()
    61  
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	return configTree.Flatten()
    67  }
    68  
    69  // LoadDir loads all the Terraform configuration files in a single
    70  // directory and appends them together.
    71  //
    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.
    76  //
    77  // Files are loaded in lexical order.
    78  func LoadDir(root string) (*Config, error) {
    79  	files, overrides, err := dirFiles(root)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	if len(files) == 0 && len(overrides) == 0 {
    84  		return nil, &ErrNoConfigsFound{Dir: root}
    85  	}
    86  
    87  	// Determine the absolute path to the directory.
    88  	rootAbs, err := filepath.Abs(root)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	var result *Config
    94  
    95  	// Sort the files and overrides so we have a deterministic order
    96  	sort.Strings(files)
    97  	sort.Strings(overrides)
    98  
    99  	// Load all the regular files, append them to each other.
   100  	for _, f := range files {
   101  		c, err := LoadFile(f)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  
   106  		if result != nil {
   107  			result, err = Append(result, c)
   108  			if err != nil {
   109  				return nil, err
   110  			}
   111  		} else {
   112  			result = c
   113  		}
   114  	}
   115  	if len(files) == 0 {
   116  		result = &Config{}
   117  	}
   118  
   119  	// Load all the overrides, and merge them into the config
   120  	for _, f := range overrides {
   121  		c, err := LoadFile(f)
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  
   126  		result, err = Merge(result, c)
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  	}
   131  
   132  	// Mark the directory
   133  	result.Dir = rootAbs
   134  
   135  	return result, nil
   136  }
   137  
   138  // IsEmptyDir returns true if the directory given has no Terraform
   139  // configuration files.
   140  func IsEmptyDir(root string) (bool, error) {
   141  	if _, err := os.Stat(root); err != nil && os.IsNotExist(err) {
   142  		return true, nil
   143  	}
   144  
   145  	fs, os, err := dirFiles(root)
   146  	if err != nil {
   147  		return false, err
   148  	}
   149  
   150  	return len(fs) == 0 && len(os) == 0, nil
   151  }
   152  
   153  // Ext returns the Terraform configuration extension of the given
   154  // path, or a blank string if it is an invalid function.
   155  func ext(path string) string {
   156  	if strings.HasSuffix(path, ".tf") {
   157  		return ".tf"
   158  	} else if strings.HasSuffix(path, ".tf.json") {
   159  		return ".tf.json"
   160  	} else {
   161  		return ""
   162  	}
   163  }
   164  
   165  func dirFiles(dir string) ([]string, []string, error) {
   166  	f, err := os.Open(dir)
   167  	if err != nil {
   168  		return nil, nil, err
   169  	}
   170  	defer f.Close()
   171  
   172  	fi, err := f.Stat()
   173  	if err != nil {
   174  		return nil, nil, err
   175  	}
   176  	if !fi.IsDir() {
   177  		return nil, nil, fmt.Errorf(
   178  			"configuration path must be a directory: %s",
   179  			dir)
   180  	}
   181  
   182  	var files, overrides []string
   183  	err = nil
   184  	for err != io.EOF {
   185  		var fis []os.FileInfo
   186  		fis, err = f.Readdir(128)
   187  		if err != nil && err != io.EOF {
   188  			return nil, nil, err
   189  		}
   190  
   191  		for _, fi := range fis {
   192  			// Ignore directories
   193  			if fi.IsDir() {
   194  				continue
   195  			}
   196  
   197  			// Only care about files that are valid to load
   198  			name := fi.Name()
   199  			extValue := ext(name)
   200  			if extValue == "" || IsIgnoredFile(name) {
   201  				continue
   202  			}
   203  
   204  			// Determine if we're dealing with an override
   205  			nameNoExt := name[:len(name)-len(extValue)]
   206  			override := nameNoExt == "override" ||
   207  				strings.HasSuffix(nameNoExt, "_override")
   208  
   209  			path := filepath.Join(dir, name)
   210  			if override {
   211  				overrides = append(overrides, path)
   212  			} else {
   213  				files = append(files, path)
   214  			}
   215  		}
   216  	}
   217  
   218  	return files, overrides, nil
   219  }
   220  
   221  // IsIgnoredFile returns true or false depending on whether the
   222  // provided file name is a file that should be ignored.
   223  func IsIgnoredFile(name string) bool {
   224  	return strings.HasPrefix(name, ".") || // Unix-like hidden files
   225  		strings.HasSuffix(name, "~") || // vim
   226  		strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs
   227  }