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 }