github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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 {
    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  
   116  	// Load all the overrides, and merge them into the config
   117  	for _, f := range overrides {
   118  		c, err := LoadFile(f)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  
   123  		result, err = Merge(result, c)
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  	}
   128  
   129  	// Mark the directory
   130  	result.Dir = rootAbs
   131  
   132  	return result, nil
   133  }
   134  
   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) {
   139  		return true, nil
   140  	}
   141  
   142  	fs, os, err := dirFiles(root)
   143  	if err != nil {
   144  		return false, err
   145  	}
   146  
   147  	return len(fs) == 0 && len(os) == 0, nil
   148  }
   149  
   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") {
   154  		return ".tf"
   155  	} else if strings.HasSuffix(path, ".tf.json") {
   156  		return ".tf.json"
   157  	} else {
   158  		return ""
   159  	}
   160  }
   161  
   162  func dirFiles(dir string) ([]string, []string, error) {
   163  	f, err := os.Open(dir)
   164  	if err != nil {
   165  		return nil, nil, err
   166  	}
   167  	defer f.Close()
   168  
   169  	fi, err := f.Stat()
   170  	if err != nil {
   171  		return nil, nil, err
   172  	}
   173  	if !fi.IsDir() {
   174  		return nil, nil, fmt.Errorf(
   175  			"configuration path must be a directory: %s",
   176  			dir)
   177  	}
   178  
   179  	var files, overrides []string
   180  	err = nil
   181  	for err != io.EOF {
   182  		var fis []os.FileInfo
   183  		fis, err = f.Readdir(128)
   184  		if err != nil && err != io.EOF {
   185  			return nil, nil, err
   186  		}
   187  
   188  		for _, fi := range fis {
   189  			// Ignore directories
   190  			if fi.IsDir() {
   191  				continue
   192  			}
   193  
   194  			// Only care about files that are valid to load
   195  			name := fi.Name()
   196  			extValue := ext(name)
   197  			if extValue == "" || isIgnoredFile(name) {
   198  				continue
   199  			}
   200  
   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")
   205  
   206  			path := filepath.Join(dir, name)
   207  			if override {
   208  				overrides = append(overrides, path)
   209  			} else {
   210  				files = append(files, path)
   211  			}
   212  		}
   213  	}
   214  
   215  	return files, overrides, nil
   216  }
   217  
   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
   224  }