github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 }