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