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