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