github.com/archgrove/terraform@v0.9.5-0.20170502093151-adb789f0f8d2/config/loader_hcl.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 7 "github.com/hashicorp/go-multierror" 8 "github.com/hashicorp/hcl" 9 "github.com/hashicorp/hcl/hcl/ast" 10 "github.com/mitchellh/mapstructure" 11 ) 12 13 // hclConfigurable is an implementation of configurable that knows 14 // how to turn HCL configuration into a *Config object. 15 type hclConfigurable struct { 16 File string 17 Root *ast.File 18 } 19 20 func (t *hclConfigurable) Config() (*Config, error) { 21 validKeys := map[string]struct{}{ 22 "atlas": struct{}{}, 23 "data": struct{}{}, 24 "module": struct{}{}, 25 "output": struct{}{}, 26 "provider": struct{}{}, 27 "resource": struct{}{}, 28 "terraform": struct{}{}, 29 "variable": struct{}{}, 30 } 31 32 // Top-level item should be the object list 33 list, ok := t.Root.Node.(*ast.ObjectList) 34 if !ok { 35 return nil, fmt.Errorf("error parsing: file doesn't contain a root object") 36 } 37 38 // Start building up the actual configuration. 39 config := new(Config) 40 41 // Terraform config 42 if o := list.Filter("terraform"); len(o.Items) > 0 { 43 var err error 44 config.Terraform, err = loadTerraformHcl(o) 45 if err != nil { 46 return nil, err 47 } 48 } 49 50 // Build the variables 51 if vars := list.Filter("variable"); len(vars.Items) > 0 { 52 var err error 53 config.Variables, err = loadVariablesHcl(vars) 54 if err != nil { 55 return nil, err 56 } 57 } 58 59 // Get Atlas configuration 60 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { 61 var err error 62 config.Atlas, err = loadAtlasHcl(atlas) 63 if err != nil { 64 return nil, err 65 } 66 } 67 68 // Build the modules 69 if modules := list.Filter("module"); len(modules.Items) > 0 { 70 var err error 71 config.Modules, err = loadModulesHcl(modules) 72 if err != nil { 73 return nil, err 74 } 75 } 76 77 // Build the provider configs 78 if providers := list.Filter("provider"); len(providers.Items) > 0 { 79 var err error 80 config.ProviderConfigs, err = loadProvidersHcl(providers) 81 if err != nil { 82 return nil, err 83 } 84 } 85 86 // Build the resources 87 { 88 var err error 89 managedResourceConfigs := list.Filter("resource") 90 dataResourceConfigs := list.Filter("data") 91 92 config.Resources = make( 93 []*Resource, 0, 94 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items), 95 ) 96 97 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs) 98 if err != nil { 99 return nil, err 100 } 101 dataResources, err := loadDataResourcesHcl(dataResourceConfigs) 102 if err != nil { 103 return nil, err 104 } 105 106 config.Resources = append(config.Resources, dataResources...) 107 config.Resources = append(config.Resources, managedResources...) 108 } 109 110 // Build the outputs 111 if outputs := list.Filter("output"); len(outputs.Items) > 0 { 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 _, item := range list.Items { 121 if len(item.Keys) == 0 { 122 // Not sure how this would happen, but let's avoid a panic 123 continue 124 } 125 126 k := item.Keys[0].Token.Value().(string) 127 if _, ok := validKeys[k]; ok { 128 continue 129 } 130 131 config.unknownKeys = append(config.unknownKeys, k) 132 } 133 134 return config, nil 135 } 136 137 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 138 // files and turn them into hclConfigurables. 139 func loadFileHcl(root string) (configurable, []string, error) { 140 // Read the HCL file and prepare for parsing 141 d, err := ioutil.ReadFile(root) 142 if err != nil { 143 return nil, nil, fmt.Errorf( 144 "Error reading %s: %s", root, err) 145 } 146 147 // Parse it 148 hclRoot, err := hcl.Parse(string(d)) 149 if err != nil { 150 return nil, nil, fmt.Errorf( 151 "Error parsing %s: %s", root, err) 152 } 153 154 // Start building the result 155 result := &hclConfigurable{ 156 File: root, 157 Root: hclRoot, 158 } 159 160 // Dive in, find the imports. This is disabled for now since 161 // imports were removed prior to Terraform 0.1. The code is 162 // remaining here commented for historical purposes. 163 /* 164 imports := obj.Get("import") 165 if imports == nil { 166 result.Object.Ref() 167 return result, nil, nil 168 } 169 170 if imports.Type() != libucl.ObjectTypeString { 171 imports.Close() 172 173 return nil, nil, fmt.Errorf( 174 "Error in %s: all 'import' declarations should be in the format\n"+ 175 "`import \"foo\"` (Got type %s)", 176 root, 177 imports.Type()) 178 } 179 180 // Gather all the import paths 181 importPaths := make([]string, 0, imports.Len()) 182 iter := imports.Iterate(false) 183 for imp := iter.Next(); imp != nil; imp = iter.Next() { 184 path := imp.ToString() 185 if !filepath.IsAbs(path) { 186 // Relative paths are relative to the Terraform file itself 187 dir := filepath.Dir(root) 188 path = filepath.Join(dir, path) 189 } 190 191 importPaths = append(importPaths, path) 192 imp.Close() 193 } 194 iter.Close() 195 imports.Close() 196 197 result.Object.Ref() 198 */ 199 200 return result, nil, nil 201 } 202 203 // Given a handle to a HCL object, this transforms it into the Terraform config 204 func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) { 205 if len(list.Items) > 1 { 206 return nil, fmt.Errorf("only one 'terraform' block allowed per module") 207 } 208 209 // Get our one item 210 item := list.Items[0] 211 212 // This block should have an empty top level ObjectItem. If there are keys 213 // here, it's likely because we have a flattened JSON object, and we can 214 // lift this into a nested ObjectList to decode properly. 215 if len(item.Keys) > 0 { 216 item = &ast.ObjectItem{ 217 Val: &ast.ObjectType{ 218 List: &ast.ObjectList{ 219 Items: []*ast.ObjectItem{item}, 220 }, 221 }, 222 } 223 } 224 225 // We need the item value as an ObjectList 226 var listVal *ast.ObjectList 227 if ot, ok := item.Val.(*ast.ObjectType); ok { 228 listVal = ot.List 229 } else { 230 return nil, fmt.Errorf("terraform block: should be an object") 231 } 232 233 // NOTE: We purposely don't validate unknown HCL keys here so that 234 // we can potentially read _future_ Terraform version config (to 235 // still be able to validate the required version). 236 // 237 // We should still keep track of unknown keys to validate later, but 238 // HCL doesn't currently support that. 239 240 var config Terraform 241 if err := hcl.DecodeObject(&config, item.Val); err != nil { 242 return nil, fmt.Errorf( 243 "Error reading terraform config: %s", 244 err) 245 } 246 247 // If we have provisioners, then parse those out 248 if os := listVal.Filter("backend"); len(os.Items) > 0 { 249 var err error 250 config.Backend, err = loadTerraformBackendHcl(os) 251 if err != nil { 252 return nil, fmt.Errorf( 253 "Error reading backend config for terraform block: %s", 254 err) 255 } 256 } 257 258 return &config, nil 259 } 260 261 // Loads the Backend configuration from an object list. 262 func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) { 263 if len(list.Items) > 1 { 264 return nil, fmt.Errorf("only one 'backend' block allowed") 265 } 266 267 // Get our one item 268 item := list.Items[0] 269 270 // Verify the keys 271 if len(item.Keys) != 1 { 272 return nil, fmt.Errorf( 273 "position %s: 'backend' must be followed by exactly one string: a type", 274 item.Pos()) 275 } 276 277 typ := item.Keys[0].Token.Value().(string) 278 279 // Decode the raw config 280 var config map[string]interface{} 281 if err := hcl.DecodeObject(&config, item.Val); err != nil { 282 return nil, fmt.Errorf( 283 "Error reading backend config: %s", 284 err) 285 } 286 287 rawConfig, err := NewRawConfig(config) 288 if err != nil { 289 return nil, fmt.Errorf( 290 "Error reading backend config: %s", 291 err) 292 } 293 294 b := &Backend{ 295 Type: typ, 296 RawConfig: rawConfig, 297 } 298 b.Hash = b.Rehash() 299 300 return b, nil 301 } 302 303 // Given a handle to a HCL object, this transforms it into the Atlas 304 // configuration. 305 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { 306 if len(list.Items) > 1 { 307 return nil, fmt.Errorf("only one 'atlas' block allowed") 308 } 309 310 // Get our one item 311 item := list.Items[0] 312 313 var config AtlasConfig 314 if err := hcl.DecodeObject(&config, item.Val); err != nil { 315 return nil, fmt.Errorf( 316 "Error reading atlas config: %s", 317 err) 318 } 319 320 return &config, nil 321 } 322 323 // Given a handle to a HCL object, this recurses into the structure 324 // and pulls out a list of modules. 325 // 326 // The resulting modules may not be unique, but each module 327 // represents exactly one module definition in the HCL configuration. 328 // We leave it up to another pass to merge them together. 329 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { 330 list = list.Children() 331 if len(list.Items) == 0 { 332 return nil, nil 333 } 334 335 // Where all the results will go 336 var result []*Module 337 338 // Now go over all the types and their children in order to get 339 // all of the actual resources. 340 for _, item := range list.Items { 341 k := item.Keys[0].Token.Value().(string) 342 343 var listVal *ast.ObjectList 344 if ot, ok := item.Val.(*ast.ObjectType); ok { 345 listVal = ot.List 346 } else { 347 return nil, fmt.Errorf("module '%s': should be an object", k) 348 } 349 350 var config map[string]interface{} 351 if err := hcl.DecodeObject(&config, item.Val); err != nil { 352 return nil, fmt.Errorf( 353 "Error reading config for %s: %s", 354 k, 355 err) 356 } 357 358 // Remove the fields we handle specially 359 delete(config, "source") 360 361 rawConfig, err := NewRawConfig(config) 362 if err != nil { 363 return nil, fmt.Errorf( 364 "Error reading config for %s: %s", 365 k, 366 err) 367 } 368 369 // If we have a count, then figure it out 370 var source string 371 if o := listVal.Filter("source"); len(o.Items) > 0 { 372 err = hcl.DecodeObject(&source, o.Items[0].Val) 373 if err != nil { 374 return nil, fmt.Errorf( 375 "Error parsing source for %s: %s", 376 k, 377 err) 378 } 379 } 380 381 result = append(result, &Module{ 382 Name: k, 383 Source: source, 384 RawConfig: rawConfig, 385 }) 386 } 387 388 return result, nil 389 } 390 391 // LoadOutputsHcl recurses into the given HCL object and turns 392 // it into a mapping of outputs. 393 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { 394 list = list.Children() 395 if len(list.Items) == 0 { 396 return nil, fmt.Errorf( 397 "'output' must be followed by exactly one string: a name") 398 } 399 400 // Go through each object and turn it into an actual result. 401 result := make([]*Output, 0, len(list.Items)) 402 for _, item := range list.Items { 403 n := item.Keys[0].Token.Value().(string) 404 405 var listVal *ast.ObjectList 406 if ot, ok := item.Val.(*ast.ObjectType); ok { 407 listVal = ot.List 408 } else { 409 return nil, fmt.Errorf("output '%s': should be an object", n) 410 } 411 412 var config map[string]interface{} 413 if err := hcl.DecodeObject(&config, item.Val); err != nil { 414 return nil, err 415 } 416 417 // Delete special keys 418 delete(config, "depends_on") 419 420 rawConfig, err := NewRawConfig(config) 421 if err != nil { 422 return nil, fmt.Errorf( 423 "Error reading config for output %s: %s", 424 n, 425 err) 426 } 427 428 // If we have depends fields, then add those in 429 var dependsOn []string 430 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 431 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 432 if err != nil { 433 return nil, fmt.Errorf( 434 "Error reading depends_on for output %q: %s", 435 n, 436 err) 437 } 438 } 439 440 result = append(result, &Output{ 441 Name: n, 442 RawConfig: rawConfig, 443 DependsOn: dependsOn, 444 }) 445 } 446 447 return result, nil 448 } 449 450 // LoadVariablesHcl recurses into the given HCL object and turns 451 // it into a list of variables. 452 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { 453 list = list.Children() 454 if len(list.Items) == 0 { 455 return nil, fmt.Errorf( 456 "'variable' must be followed by exactly one strings: a name") 457 } 458 459 // hclVariable is the structure each variable is decoded into 460 type hclVariable struct { 461 DeclaredType string `hcl:"type"` 462 Default interface{} 463 Description string 464 Fields []string `hcl:",decodedFields"` 465 } 466 467 // Go through each object and turn it into an actual result. 468 result := make([]*Variable, 0, len(list.Items)) 469 for _, item := range list.Items { 470 // Clean up items from JSON 471 unwrapHCLObjectKeysFromJSON(item, 1) 472 473 // Verify the keys 474 if len(item.Keys) != 1 { 475 return nil, fmt.Errorf( 476 "position %s: 'variable' must be followed by exactly one strings: a name", 477 item.Pos()) 478 } 479 480 n := item.Keys[0].Token.Value().(string) 481 if !NameRegexp.MatchString(n) { 482 return nil, fmt.Errorf( 483 "position %s: 'variable' name must match regular expression: %s", 484 item.Pos(), NameRegexp) 485 } 486 487 // Check for invalid keys 488 valid := []string{"type", "default", "description"} 489 if err := checkHCLKeys(item.Val, valid); err != nil { 490 return nil, multierror.Prefix(err, fmt.Sprintf( 491 "variable[%s]:", n)) 492 } 493 494 // Decode into hclVariable to get typed values 495 var hclVar hclVariable 496 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil { 497 return nil, err 498 } 499 500 // Defaults turn into a slice of map[string]interface{} and 501 // we need to make sure to convert that down into the 502 // proper type for Config. 503 if ms, ok := hclVar.Default.([]map[string]interface{}); ok { 504 def := make(map[string]interface{}) 505 for _, m := range ms { 506 for k, v := range m { 507 def[k] = v 508 } 509 } 510 511 hclVar.Default = def 512 } 513 514 // Build the new variable and do some basic validation 515 newVar := &Variable{ 516 Name: n, 517 DeclaredType: hclVar.DeclaredType, 518 Default: hclVar.Default, 519 Description: hclVar.Description, 520 } 521 if err := newVar.ValidateTypeAndDefault(); err != nil { 522 return nil, err 523 } 524 525 result = append(result, newVar) 526 } 527 528 return result, nil 529 } 530 531 // LoadProvidersHcl recurses into the given HCL object and turns 532 // it into a mapping of provider configs. 533 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 534 list = list.Children() 535 if len(list.Items) == 0 { 536 return nil, nil 537 } 538 539 // Go through each object and turn it into an actual result. 540 result := make([]*ProviderConfig, 0, len(list.Items)) 541 for _, item := range list.Items { 542 n := item.Keys[0].Token.Value().(string) 543 544 var listVal *ast.ObjectList 545 if ot, ok := item.Val.(*ast.ObjectType); ok { 546 listVal = ot.List 547 } else { 548 return nil, fmt.Errorf("module '%s': should be an object", n) 549 } 550 551 var config map[string]interface{} 552 if err := hcl.DecodeObject(&config, item.Val); err != nil { 553 return nil, err 554 } 555 556 delete(config, "alias") 557 558 rawConfig, err := NewRawConfig(config) 559 if err != nil { 560 return nil, fmt.Errorf( 561 "Error reading config for provider config %s: %s", 562 n, 563 err) 564 } 565 566 // If we have an alias field, then add those in 567 var alias string 568 if a := listVal.Filter("alias"); len(a.Items) > 0 { 569 err := hcl.DecodeObject(&alias, a.Items[0].Val) 570 if err != nil { 571 return nil, fmt.Errorf( 572 "Error reading alias for provider[%s]: %s", 573 n, 574 err) 575 } 576 } 577 578 result = append(result, &ProviderConfig{ 579 Name: n, 580 Alias: alias, 581 RawConfig: rawConfig, 582 }) 583 } 584 585 return result, nil 586 } 587 588 // Given a handle to a HCL object, this recurses into the structure 589 // and pulls out a list of data sources. 590 // 591 // The resulting data sources may not be unique, but each one 592 // represents exactly one data definition in the HCL configuration. 593 // We leave it up to another pass to merge them together. 594 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 595 list = list.Children() 596 if len(list.Items) == 0 { 597 return nil, nil 598 } 599 600 // Where all the results will go 601 var result []*Resource 602 603 // Now go over all the types and their children in order to get 604 // all of the actual resources. 605 for _, item := range list.Items { 606 if len(item.Keys) != 2 { 607 return nil, fmt.Errorf( 608 "position %s: 'data' must be followed by exactly two strings: a type and a name", 609 item.Pos()) 610 } 611 612 t := item.Keys[0].Token.Value().(string) 613 k := item.Keys[1].Token.Value().(string) 614 615 var listVal *ast.ObjectList 616 if ot, ok := item.Val.(*ast.ObjectType); ok { 617 listVal = ot.List 618 } else { 619 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 620 } 621 622 var config map[string]interface{} 623 if err := hcl.DecodeObject(&config, item.Val); err != nil { 624 return nil, fmt.Errorf( 625 "Error reading config for %s[%s]: %s", 626 t, 627 k, 628 err) 629 } 630 631 // Remove the fields we handle specially 632 delete(config, "depends_on") 633 delete(config, "provider") 634 delete(config, "count") 635 636 rawConfig, err := NewRawConfig(config) 637 if err != nil { 638 return nil, fmt.Errorf( 639 "Error reading config for %s[%s]: %s", 640 t, 641 k, 642 err) 643 } 644 645 // If we have a count, then figure it out 646 var count string = "1" 647 if o := listVal.Filter("count"); len(o.Items) > 0 { 648 err = hcl.DecodeObject(&count, o.Items[0].Val) 649 if err != nil { 650 return nil, fmt.Errorf( 651 "Error parsing count for %s[%s]: %s", 652 t, 653 k, 654 err) 655 } 656 } 657 countConfig, err := NewRawConfig(map[string]interface{}{ 658 "count": count, 659 }) 660 if err != nil { 661 return nil, err 662 } 663 countConfig.Key = "count" 664 665 // If we have depends fields, then add those in 666 var dependsOn []string 667 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 668 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 669 if err != nil { 670 return nil, fmt.Errorf( 671 "Error reading depends_on for %s[%s]: %s", 672 t, 673 k, 674 err) 675 } 676 } 677 678 // If we have a provider, then parse it out 679 var provider string 680 if o := listVal.Filter("provider"); len(o.Items) > 0 { 681 err := hcl.DecodeObject(&provider, o.Items[0].Val) 682 if err != nil { 683 return nil, fmt.Errorf( 684 "Error reading provider for %s[%s]: %s", 685 t, 686 k, 687 err) 688 } 689 } 690 691 result = append(result, &Resource{ 692 Mode: DataResourceMode, 693 Name: k, 694 Type: t, 695 RawCount: countConfig, 696 RawConfig: rawConfig, 697 Provider: provider, 698 Provisioners: []*Provisioner{}, 699 DependsOn: dependsOn, 700 Lifecycle: ResourceLifecycle{}, 701 }) 702 } 703 704 return result, nil 705 } 706 707 // Given a handle to a HCL object, this recurses into the structure 708 // and pulls out a list of managed resources. 709 // 710 // The resulting resources may not be unique, but each resource 711 // represents exactly one "resource" block in the HCL configuration. 712 // We leave it up to another pass to merge them together. 713 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 714 list = list.Children() 715 if len(list.Items) == 0 { 716 return nil, nil 717 } 718 719 // Where all the results will go 720 var result []*Resource 721 722 // Now go over all the types and their children in order to get 723 // all of the actual resources. 724 for _, item := range list.Items { 725 // GH-4385: We detect a pure provisioner resource and give the user 726 // an error about how to do it cleanly. 727 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 728 return nil, fmt.Errorf( 729 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 730 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 731 item.Pos()) 732 } 733 734 // Fix up JSON input 735 unwrapHCLObjectKeysFromJSON(item, 2) 736 737 if len(item.Keys) != 2 { 738 return nil, fmt.Errorf( 739 "position %s: resource must be followed by exactly two strings, a type and a name", 740 item.Pos()) 741 } 742 743 t := item.Keys[0].Token.Value().(string) 744 k := item.Keys[1].Token.Value().(string) 745 746 var listVal *ast.ObjectList 747 if ot, ok := item.Val.(*ast.ObjectType); ok { 748 listVal = ot.List 749 } else { 750 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 751 } 752 753 var config map[string]interface{} 754 if err := hcl.DecodeObject(&config, item.Val); err != nil { 755 return nil, fmt.Errorf( 756 "Error reading config for %s[%s]: %s", 757 t, 758 k, 759 err) 760 } 761 762 // Remove the fields we handle specially 763 delete(config, "connection") 764 delete(config, "count") 765 delete(config, "depends_on") 766 delete(config, "provisioner") 767 delete(config, "provider") 768 delete(config, "lifecycle") 769 770 rawConfig, err := NewRawConfig(config) 771 if err != nil { 772 return nil, fmt.Errorf( 773 "Error reading config for %s[%s]: %s", 774 t, 775 k, 776 err) 777 } 778 779 // If we have a count, then figure it out 780 var count string = "1" 781 if o := listVal.Filter("count"); len(o.Items) > 0 { 782 err = hcl.DecodeObject(&count, o.Items[0].Val) 783 if err != nil { 784 return nil, fmt.Errorf( 785 "Error parsing count for %s[%s]: %s", 786 t, 787 k, 788 err) 789 } 790 } 791 countConfig, err := NewRawConfig(map[string]interface{}{ 792 "count": count, 793 }) 794 if err != nil { 795 return nil, err 796 } 797 countConfig.Key = "count" 798 799 // If we have depends fields, then add those in 800 var dependsOn []string 801 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 802 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 803 if err != nil { 804 return nil, fmt.Errorf( 805 "Error reading depends_on for %s[%s]: %s", 806 t, 807 k, 808 err) 809 } 810 } 811 812 // If we have connection info, then parse those out 813 var connInfo map[string]interface{} 814 if o := listVal.Filter("connection"); len(o.Items) > 0 { 815 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 816 if err != nil { 817 return nil, fmt.Errorf( 818 "Error reading connection info for %s[%s]: %s", 819 t, 820 k, 821 err) 822 } 823 } 824 825 // If we have provisioners, then parse those out 826 var provisioners []*Provisioner 827 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 828 var err error 829 provisioners, err = loadProvisionersHcl(os, connInfo) 830 if err != nil { 831 return nil, fmt.Errorf( 832 "Error reading provisioners for %s[%s]: %s", 833 t, 834 k, 835 err) 836 } 837 } 838 839 // If we have a provider, then parse it out 840 var provider string 841 if o := listVal.Filter("provider"); len(o.Items) > 0 { 842 err := hcl.DecodeObject(&provider, o.Items[0].Val) 843 if err != nil { 844 return nil, fmt.Errorf( 845 "Error reading provider for %s[%s]: %s", 846 t, 847 k, 848 err) 849 } 850 } 851 852 // Check if the resource should be re-created before 853 // destroying the existing instance 854 var lifecycle ResourceLifecycle 855 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 856 if len(o.Items) > 1 { 857 return nil, fmt.Errorf( 858 "%s[%s]: Multiple lifecycle blocks found, expected one", 859 t, k) 860 } 861 862 // Check for invalid keys 863 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 864 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 865 return nil, multierror.Prefix(err, fmt.Sprintf( 866 "%s[%s]:", t, k)) 867 } 868 869 var raw map[string]interface{} 870 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 871 return nil, fmt.Errorf( 872 "Error parsing lifecycle for %s[%s]: %s", 873 t, 874 k, 875 err) 876 } 877 878 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 879 return nil, fmt.Errorf( 880 "Error parsing lifecycle for %s[%s]: %s", 881 t, 882 k, 883 err) 884 } 885 } 886 887 result = append(result, &Resource{ 888 Mode: ManagedResourceMode, 889 Name: k, 890 Type: t, 891 RawCount: countConfig, 892 RawConfig: rawConfig, 893 Provisioners: provisioners, 894 Provider: provider, 895 DependsOn: dependsOn, 896 Lifecycle: lifecycle, 897 }) 898 } 899 900 return result, nil 901 } 902 903 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 904 list = list.Children() 905 if len(list.Items) == 0 { 906 return nil, nil 907 } 908 909 // Go through each object and turn it into an actual result. 910 result := make([]*Provisioner, 0, len(list.Items)) 911 for _, item := range list.Items { 912 n := item.Keys[0].Token.Value().(string) 913 914 var listVal *ast.ObjectList 915 if ot, ok := item.Val.(*ast.ObjectType); ok { 916 listVal = ot.List 917 } else { 918 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 919 } 920 921 var config map[string]interface{} 922 if err := hcl.DecodeObject(&config, item.Val); err != nil { 923 return nil, err 924 } 925 926 // Parse the "when" value 927 when := ProvisionerWhenCreate 928 if v, ok := config["when"]; ok { 929 switch v { 930 case "create": 931 when = ProvisionerWhenCreate 932 case "destroy": 933 when = ProvisionerWhenDestroy 934 default: 935 return nil, fmt.Errorf( 936 "position %s: 'provisioner' when must be 'create' or 'destroy'", 937 item.Pos()) 938 } 939 } 940 941 // Parse the "on_failure" value 942 onFailure := ProvisionerOnFailureFail 943 if v, ok := config["on_failure"]; ok { 944 switch v { 945 case "continue": 946 onFailure = ProvisionerOnFailureContinue 947 case "fail": 948 onFailure = ProvisionerOnFailureFail 949 default: 950 return nil, fmt.Errorf( 951 "position %s: 'provisioner' on_failure must be 'continue' or 'fail'", 952 item.Pos()) 953 } 954 } 955 956 // Delete fields we special case 957 delete(config, "connection") 958 delete(config, "when") 959 delete(config, "on_failure") 960 961 rawConfig, err := NewRawConfig(config) 962 if err != nil { 963 return nil, err 964 } 965 966 // Check if we have a provisioner-level connection 967 // block that overrides the resource-level 968 var subConnInfo map[string]interface{} 969 if o := listVal.Filter("connection"); len(o.Items) > 0 { 970 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 971 if err != nil { 972 return nil, err 973 } 974 } 975 976 // Inherit from the resource connInfo any keys 977 // that are not explicitly overriden. 978 if connInfo != nil && subConnInfo != nil { 979 for k, v := range connInfo { 980 if _, ok := subConnInfo[k]; !ok { 981 subConnInfo[k] = v 982 } 983 } 984 } else if subConnInfo == nil { 985 subConnInfo = connInfo 986 } 987 988 // Parse the connInfo 989 connRaw, err := NewRawConfig(subConnInfo) 990 if err != nil { 991 return nil, err 992 } 993 994 result = append(result, &Provisioner{ 995 Type: n, 996 RawConfig: rawConfig, 997 ConnInfo: connRaw, 998 When: when, 999 OnFailure: onFailure, 1000 }) 1001 } 1002 1003 return result, nil 1004 } 1005 1006 /* 1007 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 1008 objects := make(map[string][]*hclobj.Object) 1009 1010 for _, o := range os.Elem(false) { 1011 for _, elem := range o.Elem(true) { 1012 val, ok := objects[elem.Key] 1013 if !ok { 1014 val = make([]*hclobj.Object, 0, 1) 1015 } 1016 1017 val = append(val, elem) 1018 objects[elem.Key] = val 1019 } 1020 } 1021 1022 return objects 1023 } 1024 */ 1025 1026 func checkHCLKeys(node ast.Node, valid []string) error { 1027 var list *ast.ObjectList 1028 switch n := node.(type) { 1029 case *ast.ObjectList: 1030 list = n 1031 case *ast.ObjectType: 1032 list = n.List 1033 default: 1034 return fmt.Errorf("cannot check HCL keys of type %T", n) 1035 } 1036 1037 validMap := make(map[string]struct{}, len(valid)) 1038 for _, v := range valid { 1039 validMap[v] = struct{}{} 1040 } 1041 1042 var result error 1043 for _, item := range list.Items { 1044 key := item.Keys[0].Token.Value().(string) 1045 if _, ok := validMap[key]; !ok { 1046 result = multierror.Append(result, fmt.Errorf( 1047 "invalid key: %s", key)) 1048 } 1049 } 1050 1051 return result 1052 } 1053 1054 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when 1055 // parsing JSON as input: if we're parsing JSON then directly nested 1056 // items will show up as additional "keys". 1057 // 1058 // For objects that expect a fixed number of keys, this breaks the 1059 // decoding process. This function unwraps the object into what it would've 1060 // looked like if it came directly from HCL by specifying the number of keys 1061 // you expect. 1062 // 1063 // Example: 1064 // 1065 // { "foo": { "baz": {} } } 1066 // 1067 // Will show up with Keys being: []string{"foo", "baz"} 1068 // when we really just want the first two. This function will fix this. 1069 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { 1070 if len(item.Keys) > depth && item.Keys[0].Token.JSON { 1071 for len(item.Keys) > depth { 1072 // Pop off the last key 1073 n := len(item.Keys) 1074 key := item.Keys[n-1] 1075 item.Keys[n-1] = nil 1076 item.Keys = item.Keys[:n-1] 1077 1078 // Wrap our value in a list 1079 item.Val = &ast.ObjectType{ 1080 List: &ast.ObjectList{ 1081 Items: []*ast.ObjectItem{ 1082 &ast.ObjectItem{ 1083 Keys: []*ast.ObjectKey{key}, 1084 Val: item.Val, 1085 }, 1086 }, 1087 }, 1088 } 1089 } 1090 } 1091 }