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