github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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 "module": struct{}{}, 24 "output": struct{}{}, 25 "provider": struct{}{}, 26 "resource": struct{}{}, 27 "variable": struct{}{}, 28 } 29 30 type hclVariable struct { 31 Default interface{} 32 Description string 33 DeclaredType string `hcl:"type"` 34 Fields []string `hcl:",decodedFields"` 35 } 36 37 var rawConfig struct { 38 Variable map[string]*hclVariable 39 } 40 41 // Top-level item should be the object list 42 list, ok := t.Root.Node.(*ast.ObjectList) 43 if !ok { 44 return nil, fmt.Errorf("error parsing: file doesn't contain a root object") 45 } 46 47 if err := hcl.DecodeObject(&rawConfig, list); err != nil { 48 return nil, err 49 } 50 51 // Start building up the actual configuration. We start with 52 // variables. 53 // TODO(mitchellh): Make function like loadVariablesHcl so that 54 // duplicates aren't overriden 55 config := new(Config) 56 if len(rawConfig.Variable) > 0 { 57 config.Variables = make([]*Variable, 0, len(rawConfig.Variable)) 58 for k, v := range rawConfig.Variable { 59 // Defaults turn into a slice of map[string]interface{} and 60 // we need to make sure to convert that down into the 61 // proper type for Config. 62 if ms, ok := v.Default.([]map[string]interface{}); ok { 63 def := make(map[string]interface{}) 64 for _, m := range ms { 65 for k, v := range m { 66 def[k] = v 67 } 68 } 69 70 v.Default = def 71 } 72 73 newVar := &Variable{ 74 Name: k, 75 DeclaredType: v.DeclaredType, 76 Default: v.Default, 77 Description: v.Description, 78 } 79 80 if err := newVar.ValidateTypeAndDefault(); err != nil { 81 return nil, err 82 } 83 84 config.Variables = append(config.Variables, newVar) 85 } 86 } 87 88 // Get Atlas configuration 89 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { 90 var err error 91 config.Atlas, err = loadAtlasHcl(atlas) 92 if err != nil { 93 return nil, err 94 } 95 } 96 97 // Build the modules 98 if modules := list.Filter("module"); len(modules.Items) > 0 { 99 var err error 100 config.Modules, err = loadModulesHcl(modules) 101 if err != nil { 102 return nil, err 103 } 104 } 105 106 // Build the provider configs 107 if providers := list.Filter("provider"); len(providers.Items) > 0 { 108 var err error 109 config.ProviderConfigs, err = loadProvidersHcl(providers) 110 if err != nil { 111 return nil, err 112 } 113 } 114 115 // Build the resources 116 if resources := list.Filter("resource"); len(resources.Items) > 0 { 117 var err error 118 config.Resources, err = loadResourcesHcl(resources) 119 if err != nil { 120 return nil, err 121 } 122 } 123 124 // Build the outputs 125 if outputs := list.Filter("output"); len(outputs.Items) > 0 { 126 var err error 127 config.Outputs, err = loadOutputsHcl(outputs) 128 if err != nil { 129 return nil, err 130 } 131 } 132 133 // Check for invalid keys 134 for _, item := range list.Items { 135 if len(item.Keys) == 0 { 136 // Not sure how this would happen, but let's avoid a panic 137 continue 138 } 139 140 k := item.Keys[0].Token.Value().(string) 141 if _, ok := validKeys[k]; ok { 142 continue 143 } 144 145 config.unknownKeys = append(config.unknownKeys, k) 146 } 147 148 return config, nil 149 } 150 151 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 152 // files and turn them into hclConfigurables. 153 func loadFileHcl(root string) (configurable, []string, error) { 154 // Read the HCL file and prepare for parsing 155 d, err := ioutil.ReadFile(root) 156 if err != nil { 157 return nil, nil, fmt.Errorf( 158 "Error reading %s: %s", root, err) 159 } 160 161 // Parse it 162 hclRoot, err := hcl.Parse(string(d)) 163 if err != nil { 164 return nil, nil, fmt.Errorf( 165 "Error parsing %s: %s", root, err) 166 } 167 168 // Start building the result 169 result := &hclConfigurable{ 170 File: root, 171 Root: hclRoot, 172 } 173 174 // Dive in, find the imports. This is disabled for now since 175 // imports were removed prior to Terraform 0.1. The code is 176 // remaining here commented for historical purposes. 177 /* 178 imports := obj.Get("import") 179 if imports == nil { 180 result.Object.Ref() 181 return result, nil, nil 182 } 183 184 if imports.Type() != libucl.ObjectTypeString { 185 imports.Close() 186 187 return nil, nil, fmt.Errorf( 188 "Error in %s: all 'import' declarations should be in the format\n"+ 189 "`import \"foo\"` (Got type %s)", 190 root, 191 imports.Type()) 192 } 193 194 // Gather all the import paths 195 importPaths := make([]string, 0, imports.Len()) 196 iter := imports.Iterate(false) 197 for imp := iter.Next(); imp != nil; imp = iter.Next() { 198 path := imp.ToString() 199 if !filepath.IsAbs(path) { 200 // Relative paths are relative to the Terraform file itself 201 dir := filepath.Dir(root) 202 path = filepath.Join(dir, path) 203 } 204 205 importPaths = append(importPaths, path) 206 imp.Close() 207 } 208 iter.Close() 209 imports.Close() 210 211 result.Object.Ref() 212 */ 213 214 return result, nil, nil 215 } 216 217 // Given a handle to a HCL object, this transforms it into the Atlas 218 // configuration. 219 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { 220 if len(list.Items) > 1 { 221 return nil, fmt.Errorf("only one 'atlas' block allowed") 222 } 223 224 // Get our one item 225 item := list.Items[0] 226 227 var config AtlasConfig 228 if err := hcl.DecodeObject(&config, item.Val); err != nil { 229 return nil, fmt.Errorf( 230 "Error reading atlas config: %s", 231 err) 232 } 233 234 return &config, nil 235 } 236 237 // Given a handle to a HCL object, this recurses into the structure 238 // and pulls out a list of modules. 239 // 240 // The resulting modules may not be unique, but each module 241 // represents exactly one module definition in the HCL configuration. 242 // We leave it up to another pass to merge them together. 243 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { 244 list = list.Children() 245 if len(list.Items) == 0 { 246 return nil, nil 247 } 248 249 // Where all the results will go 250 var result []*Module 251 252 // Now go over all the types and their children in order to get 253 // all of the actual resources. 254 for _, item := range list.Items { 255 k := item.Keys[0].Token.Value().(string) 256 257 var listVal *ast.ObjectList 258 if ot, ok := item.Val.(*ast.ObjectType); ok { 259 listVal = ot.List 260 } else { 261 return nil, fmt.Errorf("module '%s': should be an object", k) 262 } 263 264 var config map[string]interface{} 265 if err := hcl.DecodeObject(&config, item.Val); err != nil { 266 return nil, fmt.Errorf( 267 "Error reading config for %s: %s", 268 k, 269 err) 270 } 271 272 // Remove the fields we handle specially 273 delete(config, "source") 274 275 rawConfig, err := NewRawConfig(config) 276 if err != nil { 277 return nil, fmt.Errorf( 278 "Error reading config for %s: %s", 279 k, 280 err) 281 } 282 283 // If we have a count, then figure it out 284 var source string 285 if o := listVal.Filter("source"); len(o.Items) > 0 { 286 err = hcl.DecodeObject(&source, o.Items[0].Val) 287 if err != nil { 288 return nil, fmt.Errorf( 289 "Error parsing source for %s: %s", 290 k, 291 err) 292 } 293 } 294 295 result = append(result, &Module{ 296 Name: k, 297 Source: source, 298 RawConfig: rawConfig, 299 }) 300 } 301 302 return result, nil 303 } 304 305 // LoadOutputsHcl recurses into the given HCL object and turns 306 // it into a mapping of outputs. 307 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { 308 list = list.Children() 309 if len(list.Items) == 0 { 310 return nil, nil 311 } 312 313 // Go through each object and turn it into an actual result. 314 result := make([]*Output, 0, len(list.Items)) 315 for _, item := range list.Items { 316 n := item.Keys[0].Token.Value().(string) 317 318 var config map[string]interface{} 319 if err := hcl.DecodeObject(&config, item.Val); err != nil { 320 return nil, err 321 } 322 323 rawConfig, err := NewRawConfig(config) 324 if err != nil { 325 return nil, fmt.Errorf( 326 "Error reading config for output %s: %s", 327 n, 328 err) 329 } 330 331 result = append(result, &Output{ 332 Name: n, 333 RawConfig: rawConfig, 334 }) 335 } 336 337 return result, nil 338 } 339 340 // LoadProvidersHcl recurses into the given HCL object and turns 341 // it into a mapping of provider configs. 342 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 343 list = list.Children() 344 if len(list.Items) == 0 { 345 return nil, nil 346 } 347 348 // Go through each object and turn it into an actual result. 349 result := make([]*ProviderConfig, 0, len(list.Items)) 350 for _, item := range list.Items { 351 n := item.Keys[0].Token.Value().(string) 352 353 var listVal *ast.ObjectList 354 if ot, ok := item.Val.(*ast.ObjectType); ok { 355 listVal = ot.List 356 } else { 357 return nil, fmt.Errorf("module '%s': should be an object", n) 358 } 359 360 var config map[string]interface{} 361 if err := hcl.DecodeObject(&config, item.Val); err != nil { 362 return nil, err 363 } 364 365 delete(config, "alias") 366 367 rawConfig, err := NewRawConfig(config) 368 if err != nil { 369 return nil, fmt.Errorf( 370 "Error reading config for provider config %s: %s", 371 n, 372 err) 373 } 374 375 // If we have an alias field, then add those in 376 var alias string 377 if a := listVal.Filter("alias"); len(a.Items) > 0 { 378 err := hcl.DecodeObject(&alias, a.Items[0].Val) 379 if err != nil { 380 return nil, fmt.Errorf( 381 "Error reading alias for provider[%s]: %s", 382 n, 383 err) 384 } 385 } 386 387 result = append(result, &ProviderConfig{ 388 Name: n, 389 Alias: alias, 390 RawConfig: rawConfig, 391 }) 392 } 393 394 return result, nil 395 } 396 397 // Given a handle to a HCL object, this recurses into the structure 398 // and pulls out a list of resources. 399 // 400 // The resulting resources may not be unique, but each resource 401 // represents exactly one resource definition in the HCL configuration. 402 // We leave it up to another pass to merge them together. 403 func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 404 list = list.Children() 405 if len(list.Items) == 0 { 406 return nil, nil 407 } 408 409 // Where all the results will go 410 var result []*Resource 411 412 // Now go over all the types and their children in order to get 413 // all of the actual resources. 414 for _, item := range list.Items { 415 // GH-4385: We detect a pure provisioner resource and give the user 416 // an error about how to do it cleanly. 417 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 418 return nil, fmt.Errorf( 419 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 420 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 421 item.Pos()) 422 } 423 424 if len(item.Keys) != 2 { 425 return nil, fmt.Errorf( 426 "position %s: resource must be followed by exactly two strings, a type and a name", 427 item.Pos()) 428 } 429 430 t := item.Keys[0].Token.Value().(string) 431 k := item.Keys[1].Token.Value().(string) 432 433 var listVal *ast.ObjectList 434 if ot, ok := item.Val.(*ast.ObjectType); ok { 435 listVal = ot.List 436 } else { 437 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 438 } 439 440 var config map[string]interface{} 441 if err := hcl.DecodeObject(&config, item.Val); err != nil { 442 return nil, fmt.Errorf( 443 "Error reading config for %s[%s]: %s", 444 t, 445 k, 446 err) 447 } 448 449 // Remove the fields we handle specially 450 delete(config, "connection") 451 delete(config, "count") 452 delete(config, "depends_on") 453 delete(config, "provisioner") 454 delete(config, "provider") 455 delete(config, "lifecycle") 456 457 rawConfig, err := NewRawConfig(config) 458 if err != nil { 459 return nil, fmt.Errorf( 460 "Error reading config for %s[%s]: %s", 461 t, 462 k, 463 err) 464 } 465 466 // If we have a count, then figure it out 467 var count string = "1" 468 if o := listVal.Filter("count"); len(o.Items) > 0 { 469 err = hcl.DecodeObject(&count, o.Items[0].Val) 470 if err != nil { 471 return nil, fmt.Errorf( 472 "Error parsing count for %s[%s]: %s", 473 t, 474 k, 475 err) 476 } 477 } 478 countConfig, err := NewRawConfig(map[string]interface{}{ 479 "count": count, 480 }) 481 if err != nil { 482 return nil, err 483 } 484 countConfig.Key = "count" 485 486 // If we have depends fields, then add those in 487 var dependsOn []string 488 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 489 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 490 if err != nil { 491 return nil, fmt.Errorf( 492 "Error reading depends_on for %s[%s]: %s", 493 t, 494 k, 495 err) 496 } 497 } 498 499 // If we have connection info, then parse those out 500 var connInfo map[string]interface{} 501 if o := listVal.Filter("connection"); len(o.Items) > 0 { 502 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 503 if err != nil { 504 return nil, fmt.Errorf( 505 "Error reading connection info for %s[%s]: %s", 506 t, 507 k, 508 err) 509 } 510 } 511 512 // If we have provisioners, then parse those out 513 var provisioners []*Provisioner 514 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 515 var err error 516 provisioners, err = loadProvisionersHcl(os, connInfo) 517 if err != nil { 518 return nil, fmt.Errorf( 519 "Error reading provisioners for %s[%s]: %s", 520 t, 521 k, 522 err) 523 } 524 } 525 526 // If we have a provider, then parse it out 527 var provider string 528 if o := listVal.Filter("provider"); len(o.Items) > 0 { 529 err := hcl.DecodeObject(&provider, o.Items[0].Val) 530 if err != nil { 531 return nil, fmt.Errorf( 532 "Error reading provider for %s[%s]: %s", 533 t, 534 k, 535 err) 536 } 537 } 538 539 // Check if the resource should be re-created before 540 // destroying the existing instance 541 var lifecycle ResourceLifecycle 542 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 543 // Check for invalid keys 544 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 545 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 546 return nil, multierror.Prefix(err, fmt.Sprintf( 547 "%s[%s]:", t, k)) 548 } 549 550 var raw map[string]interface{} 551 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 552 return nil, fmt.Errorf( 553 "Error parsing lifecycle for %s[%s]: %s", 554 t, 555 k, 556 err) 557 } 558 559 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 560 return nil, fmt.Errorf( 561 "Error parsing lifecycle for %s[%s]: %s", 562 t, 563 k, 564 err) 565 } 566 } 567 568 result = append(result, &Resource{ 569 Name: k, 570 Type: t, 571 RawCount: countConfig, 572 RawConfig: rawConfig, 573 Provisioners: provisioners, 574 Provider: provider, 575 DependsOn: dependsOn, 576 Lifecycle: lifecycle, 577 }) 578 } 579 580 return result, nil 581 } 582 583 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 584 list = list.Children() 585 if len(list.Items) == 0 { 586 return nil, nil 587 } 588 589 // Go through each object and turn it into an actual result. 590 result := make([]*Provisioner, 0, len(list.Items)) 591 for _, item := range list.Items { 592 n := item.Keys[0].Token.Value().(string) 593 594 var listVal *ast.ObjectList 595 if ot, ok := item.Val.(*ast.ObjectType); ok { 596 listVal = ot.List 597 } else { 598 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 599 } 600 601 var config map[string]interface{} 602 if err := hcl.DecodeObject(&config, item.Val); err != nil { 603 return nil, err 604 } 605 606 // Delete the "connection" section, handle separately 607 delete(config, "connection") 608 609 rawConfig, err := NewRawConfig(config) 610 if err != nil { 611 return nil, err 612 } 613 614 // Check if we have a provisioner-level connection 615 // block that overrides the resource-level 616 var subConnInfo map[string]interface{} 617 if o := listVal.Filter("connection"); len(o.Items) > 0 { 618 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 619 if err != nil { 620 return nil, err 621 } 622 } 623 624 // Inherit from the resource connInfo any keys 625 // that are not explicitly overriden. 626 if connInfo != nil && subConnInfo != nil { 627 for k, v := range connInfo { 628 if _, ok := subConnInfo[k]; !ok { 629 subConnInfo[k] = v 630 } 631 } 632 } else if subConnInfo == nil { 633 subConnInfo = connInfo 634 } 635 636 // Parse the connInfo 637 connRaw, err := NewRawConfig(subConnInfo) 638 if err != nil { 639 return nil, err 640 } 641 642 result = append(result, &Provisioner{ 643 Type: n, 644 RawConfig: rawConfig, 645 ConnInfo: connRaw, 646 }) 647 } 648 649 return result, nil 650 } 651 652 /* 653 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 654 objects := make(map[string][]*hclobj.Object) 655 656 for _, o := range os.Elem(false) { 657 for _, elem := range o.Elem(true) { 658 val, ok := objects[elem.Key] 659 if !ok { 660 val = make([]*hclobj.Object, 0, 1) 661 } 662 663 val = append(val, elem) 664 objects[elem.Key] = val 665 } 666 } 667 668 return objects 669 } 670 */ 671 672 func checkHCLKeys(node ast.Node, valid []string) error { 673 var list *ast.ObjectList 674 switch n := node.(type) { 675 case *ast.ObjectList: 676 list = n 677 case *ast.ObjectType: 678 list = n.List 679 default: 680 return fmt.Errorf("cannot check HCL keys of type %T", n) 681 } 682 683 validMap := make(map[string]struct{}, len(valid)) 684 for _, v := range valid { 685 validMap[v] = struct{}{} 686 } 687 688 var result error 689 for _, item := range list.Items { 690 key := item.Keys[0].Token.Value().(string) 691 if _, ok := validMap[key]; !ok { 692 result = multierror.Append(result, fmt.Errorf( 693 "invalid key: %s", key)) 694 } 695 } 696 697 return result 698 }