github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/config/loader_hcl.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 7 "github.com/hashicorp/hcl" 8 hclobj "github.com/hashicorp/hcl/hcl" 9 ) 10 11 // hclConfigurable is an implementation of configurable that knows 12 // how to turn HCL configuration into a *Config object. 13 type hclConfigurable struct { 14 File string 15 Object *hclobj.Object 16 } 17 18 func (t *hclConfigurable) Config() (*Config, error) { 19 validKeys := map[string]struct{}{ 20 "module": struct{}{}, 21 "output": struct{}{}, 22 "provider": struct{}{}, 23 "resource": struct{}{}, 24 "variable": struct{}{}, 25 } 26 27 type hclVariable struct { 28 Default interface{} 29 Description string 30 Fields []string `hcl:",decodedFields"` 31 } 32 33 var rawConfig struct { 34 Variable map[string]*hclVariable 35 } 36 37 if err := hcl.DecodeObject(&rawConfig, t.Object); err != nil { 38 return nil, err 39 } 40 41 // Start building up the actual configuration. We start with 42 // variables. 43 // TODO(mitchellh): Make function like loadVariablesHcl so that 44 // duplicates aren't overriden 45 config := new(Config) 46 if len(rawConfig.Variable) > 0 { 47 config.Variables = make([]*Variable, 0, len(rawConfig.Variable)) 48 for k, v := range rawConfig.Variable { 49 // Defaults turn into a slice of map[string]interface{} and 50 // we need to make sure to convert that down into the 51 // proper type for Config. 52 if ms, ok := v.Default.([]map[string]interface{}); ok { 53 def := make(map[string]interface{}) 54 for _, m := range ms { 55 for k, v := range m { 56 def[k] = v 57 } 58 } 59 60 v.Default = def 61 } 62 63 newVar := &Variable{ 64 Name: k, 65 Default: v.Default, 66 Description: v.Description, 67 } 68 69 config.Variables = append(config.Variables, newVar) 70 } 71 } 72 73 // Build the modules 74 if modules := t.Object.Get("module", false); modules != nil { 75 var err error 76 config.Modules, err = loadModulesHcl(modules) 77 if err != nil { 78 return nil, err 79 } 80 } 81 82 // Build the provider configs 83 if providers := t.Object.Get("provider", false); providers != nil { 84 var err error 85 config.ProviderConfigs, err = loadProvidersHcl(providers) 86 if err != nil { 87 return nil, err 88 } 89 } 90 91 // Build the resources 92 if resources := t.Object.Get("resource", false); resources != nil { 93 var err error 94 config.Resources, err = loadResourcesHcl(resources) 95 if err != nil { 96 return nil, err 97 } 98 } 99 100 // Build the outputs 101 if outputs := t.Object.Get("output", false); outputs != nil { 102 var err error 103 config.Outputs, err = loadOutputsHcl(outputs) 104 if err != nil { 105 return nil, err 106 } 107 } 108 109 // Check for invalid keys 110 for _, elem := range t.Object.Elem(true) { 111 k := elem.Key 112 if _, ok := validKeys[k]; ok { 113 continue 114 } 115 116 config.unknownKeys = append(config.unknownKeys, k) 117 } 118 119 return config, nil 120 } 121 122 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 123 // files and turn them into hclConfigurables. 124 func loadFileHcl(root string) (configurable, []string, error) { 125 var obj *hclobj.Object = nil 126 127 // Read the HCL file and prepare for parsing 128 d, err := ioutil.ReadFile(root) 129 if err != nil { 130 return nil, nil, fmt.Errorf( 131 "Error reading %s: %s", root, err) 132 } 133 134 // Parse it 135 obj, err = hcl.Parse(string(d)) 136 if err != nil { 137 return nil, nil, fmt.Errorf( 138 "Error parsing %s: %s", root, err) 139 } 140 141 // Start building the result 142 result := &hclConfigurable{ 143 File: root, 144 Object: obj, 145 } 146 147 // Dive in, find the imports. This is disabled for now since 148 // imports were removed prior to Terraform 0.1. The code is 149 // remaining here commented for historical purposes. 150 /* 151 imports := obj.Get("import") 152 if imports == nil { 153 result.Object.Ref() 154 return result, nil, nil 155 } 156 157 if imports.Type() != libucl.ObjectTypeString { 158 imports.Close() 159 160 return nil, nil, fmt.Errorf( 161 "Error in %s: all 'import' declarations should be in the format\n"+ 162 "`import \"foo\"` (Got type %s)", 163 root, 164 imports.Type()) 165 } 166 167 // Gather all the import paths 168 importPaths := make([]string, 0, imports.Len()) 169 iter := imports.Iterate(false) 170 for imp := iter.Next(); imp != nil; imp = iter.Next() { 171 path := imp.ToString() 172 if !filepath.IsAbs(path) { 173 // Relative paths are relative to the Terraform file itself 174 dir := filepath.Dir(root) 175 path = filepath.Join(dir, path) 176 } 177 178 importPaths = append(importPaths, path) 179 imp.Close() 180 } 181 iter.Close() 182 imports.Close() 183 184 result.Object.Ref() 185 */ 186 187 return result, nil, nil 188 } 189 190 // Given a handle to a HCL object, this recurses into the structure 191 // and pulls out a list of modules. 192 // 193 // The resulting modules may not be unique, but each module 194 // represents exactly one module definition in the HCL configuration. 195 // We leave it up to another pass to merge them together. 196 func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { 197 var allNames []*hclobj.Object 198 199 // See loadResourcesHcl for why this exists. Don't touch this. 200 for _, o1 := range os.Elem(false) { 201 // Iterate the inner to get the list of types 202 for _, o2 := range o1.Elem(true) { 203 // Iterate all of this type to get _all_ the types 204 for _, o3 := range o2.Elem(false) { 205 allNames = append(allNames, o3) 206 } 207 } 208 } 209 210 // Where all the results will go 211 var result []*Module 212 213 // Now go over all the types and their children in order to get 214 // all of the actual resources. 215 for _, obj := range allNames { 216 k := obj.Key 217 218 var config map[string]interface{} 219 if err := hcl.DecodeObject(&config, obj); err != nil { 220 return nil, fmt.Errorf( 221 "Error reading config for %s: %s", 222 k, 223 err) 224 } 225 226 // Remove the fields we handle specially 227 delete(config, "source") 228 229 rawConfig, err := NewRawConfig(config) 230 if err != nil { 231 return nil, fmt.Errorf( 232 "Error reading config for %s: %s", 233 k, 234 err) 235 } 236 237 // If we have a count, then figure it out 238 var source string 239 if o := obj.Get("source", false); o != nil { 240 err = hcl.DecodeObject(&source, o) 241 if err != nil { 242 return nil, fmt.Errorf( 243 "Error parsing source for %s: %s", 244 k, 245 err) 246 } 247 } 248 249 result = append(result, &Module{ 250 Name: k, 251 Source: source, 252 RawConfig: rawConfig, 253 }) 254 } 255 256 return result, nil 257 } 258 259 // LoadOutputsHcl recurses into the given HCL object and turns 260 // it into a mapping of outputs. 261 func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) { 262 objects := make(map[string]*hclobj.Object) 263 264 // Iterate over all the "output" blocks and get the keys along with 265 // their raw configuration objects. We'll parse those later. 266 for _, o1 := range os.Elem(false) { 267 for _, o2 := range o1.Elem(true) { 268 objects[o2.Key] = o2 269 } 270 } 271 272 if len(objects) == 0 { 273 return nil, nil 274 } 275 276 // Go through each object and turn it into an actual result. 277 result := make([]*Output, 0, len(objects)) 278 for n, o := range objects { 279 var config map[string]interface{} 280 281 if err := hcl.DecodeObject(&config, o); err != nil { 282 return nil, err 283 } 284 285 rawConfig, err := NewRawConfig(config) 286 if err != nil { 287 return nil, fmt.Errorf( 288 "Error reading config for output %s: %s", 289 n, 290 err) 291 } 292 293 result = append(result, &Output{ 294 Name: n, 295 RawConfig: rawConfig, 296 }) 297 } 298 299 return result, nil 300 } 301 302 // LoadProvidersHcl recurses into the given HCL object and turns 303 // it into a mapping of provider configs. 304 func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { 305 objects := make(map[string]*hclobj.Object) 306 307 // Iterate over all the "provider" blocks and get the keys along with 308 // their raw configuration objects. We'll parse those later. 309 for _, o1 := range os.Elem(false) { 310 for _, o2 := range o1.Elem(true) { 311 objects[o2.Key] = o2 312 } 313 } 314 315 if len(objects) == 0 { 316 return nil, nil 317 } 318 319 // Go through each object and turn it into an actual result. 320 result := make([]*ProviderConfig, 0, len(objects)) 321 for n, o := range objects { 322 var config map[string]interface{} 323 324 if err := hcl.DecodeObject(&config, o); err != nil { 325 return nil, err 326 } 327 328 rawConfig, err := NewRawConfig(config) 329 if err != nil { 330 return nil, fmt.Errorf( 331 "Error reading config for provider config %s: %s", 332 n, 333 err) 334 } 335 336 result = append(result, &ProviderConfig{ 337 Name: n, 338 RawConfig: rawConfig, 339 }) 340 } 341 342 return result, nil 343 } 344 345 // Given a handle to a HCL object, this recurses into the structure 346 // and pulls out a list of resources. 347 // 348 // The resulting resources may not be unique, but each resource 349 // represents exactly one resource definition in the HCL configuration. 350 // We leave it up to another pass to merge them together. 351 func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { 352 var allTypes []*hclobj.Object 353 354 // HCL object iteration is really nasty. Below is likely to make 355 // no sense to anyone approaching this code. Luckily, it is very heavily 356 // tested. If working on a bug fix or feature, we recommend writing a 357 // test first then doing whatever you want to the code below. If you 358 // break it, the tests will catch it. Likewise, if you change this, 359 // MAKE SURE you write a test for your change, because its fairly impossible 360 // to reason about this mess. 361 // 362 // Functionally, what the code does below is get the libucl.Objects 363 // for all the TYPES, such as "aws_security_group". 364 for _, o1 := range os.Elem(false) { 365 // Iterate the inner to get the list of types 366 for _, o2 := range o1.Elem(true) { 367 // Iterate all of this type to get _all_ the types 368 for _, o3 := range o2.Elem(false) { 369 allTypes = append(allTypes, o3) 370 } 371 } 372 } 373 374 // Where all the results will go 375 var result []*Resource 376 377 // Now go over all the types and their children in order to get 378 // all of the actual resources. 379 for _, t := range allTypes { 380 for _, obj := range t.Elem(true) { 381 k := obj.Key 382 383 var config map[string]interface{} 384 if err := hcl.DecodeObject(&config, obj); err != nil { 385 return nil, fmt.Errorf( 386 "Error reading config for %s[%s]: %s", 387 t.Key, 388 k, 389 err) 390 } 391 392 // Remove the fields we handle specially 393 delete(config, "connection") 394 delete(config, "count") 395 delete(config, "depends_on") 396 delete(config, "provisioner") 397 delete(config, "lifecycle") 398 399 rawConfig, err := NewRawConfig(config) 400 if err != nil { 401 return nil, fmt.Errorf( 402 "Error reading config for %s[%s]: %s", 403 t.Key, 404 k, 405 err) 406 } 407 408 // If we have a count, then figure it out 409 var count string = "1" 410 if o := obj.Get("count", false); o != nil { 411 err = hcl.DecodeObject(&count, o) 412 if err != nil { 413 return nil, fmt.Errorf( 414 "Error parsing count for %s[%s]: %s", 415 t.Key, 416 k, 417 err) 418 } 419 } 420 countConfig, err := NewRawConfig(map[string]interface{}{ 421 "count": count, 422 }) 423 if err != nil { 424 return nil, err 425 } 426 countConfig.Key = "count" 427 428 // If we have depends fields, then add those in 429 var dependsOn []string 430 if o := obj.Get("depends_on", false); o != nil { 431 err := hcl.DecodeObject(&dependsOn, o) 432 if err != nil { 433 return nil, fmt.Errorf( 434 "Error reading depends_on for %s[%s]: %s", 435 t.Key, 436 k, 437 err) 438 } 439 } 440 441 // If we have connection info, then parse those out 442 var connInfo map[string]interface{} 443 if o := obj.Get("connection", false); o != nil { 444 err := hcl.DecodeObject(&connInfo, o) 445 if err != nil { 446 return nil, fmt.Errorf( 447 "Error reading connection info for %s[%s]: %s", 448 t.Key, 449 k, 450 err) 451 } 452 } 453 454 // If we have provisioners, then parse those out 455 var provisioners []*Provisioner 456 if os := obj.Get("provisioner", false); os != nil { 457 var err error 458 provisioners, err = loadProvisionersHcl(os, connInfo) 459 if err != nil { 460 return nil, fmt.Errorf( 461 "Error reading provisioners for %s[%s]: %s", 462 t.Key, 463 k, 464 err) 465 } 466 } 467 468 // Check if the resource should be re-created before 469 // destroying the existing instance 470 var lifecycle ResourceLifecycle 471 if o := obj.Get("lifecycle", false); o != nil { 472 err = hcl.DecodeObject(&lifecycle, o) 473 if err != nil { 474 return nil, fmt.Errorf( 475 "Error parsing lifecycle for %s[%s]: %s", 476 t.Key, 477 k, 478 err) 479 } 480 } 481 482 result = append(result, &Resource{ 483 Name: k, 484 Type: t.Key, 485 RawCount: countConfig, 486 RawConfig: rawConfig, 487 Provisioners: provisioners, 488 DependsOn: dependsOn, 489 Lifecycle: lifecycle, 490 }) 491 } 492 } 493 494 return result, nil 495 } 496 497 func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([]*Provisioner, error) { 498 pos := make([]*hclobj.Object, 0, int(os.Len())) 499 500 // Accumulate all the actual provisioner configuration objects. We 501 // have to iterate twice here: 502 // 503 // 1. The first iteration is of the list of `provisioner` blocks. 504 // 2. The second iteration is of the dictionary within the 505 // provisioner which will have only one element which is the 506 // type of provisioner to use along with tis config. 507 // 508 // In JSON it looks kind of like this: 509 // 510 // [ 511 // { 512 // "shell": { 513 // ... 514 // } 515 // } 516 // ] 517 // 518 for _, o1 := range os.Elem(false) { 519 for _, o2 := range o1.Elem(true) { 520 pos = append(pos, o2) 521 } 522 } 523 524 // Short-circuit if there are no items 525 if len(pos) == 0 { 526 return nil, nil 527 } 528 529 result := make([]*Provisioner, 0, len(pos)) 530 for _, po := range pos { 531 var config map[string]interface{} 532 if err := hcl.DecodeObject(&config, po); err != nil { 533 return nil, err 534 } 535 536 // Delete the "connection" section, handle seperately 537 delete(config, "connection") 538 539 rawConfig, err := NewRawConfig(config) 540 if err != nil { 541 return nil, err 542 } 543 544 // Check if we have a provisioner-level connection 545 // block that overrides the resource-level 546 var subConnInfo map[string]interface{} 547 if o := po.Get("connection", false); o != nil { 548 err := hcl.DecodeObject(&subConnInfo, o) 549 if err != nil { 550 return nil, err 551 } 552 } 553 554 // Inherit from the resource connInfo any keys 555 // that are not explicitly overriden. 556 if connInfo != nil && subConnInfo != nil { 557 for k, v := range connInfo { 558 if _, ok := subConnInfo[k]; !ok { 559 subConnInfo[k] = v 560 } 561 } 562 } else if subConnInfo == nil { 563 subConnInfo = connInfo 564 } 565 566 // Parse the connInfo 567 connRaw, err := NewRawConfig(subConnInfo) 568 if err != nil { 569 return nil, err 570 } 571 572 result = append(result, &Provisioner{ 573 Type: po.Key, 574 RawConfig: rawConfig, 575 ConnInfo: connRaw, 576 }) 577 } 578 579 return result, nil 580 } 581 582 /* 583 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 584 objects := make(map[string][]*hclobj.Object) 585 586 for _, o := range os.Elem(false) { 587 for _, elem := range o.Elem(true) { 588 val, ok := objects[elem.Key] 589 if !ok { 590 val = make([]*hclobj.Object, 0, 1) 591 } 592 593 val = append(val, elem) 594 objects[elem.Key] = val 595 } 596 } 597 598 return objects 599 } 600 */