github.com/grange74/terraform@v0.7.0-rc3.0.20160722171430-8c8803864753/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 }