github.com/nov1n/terraform@v0.7.9-0.20161103151050-bf6852f38e28/terraform/context.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "sort" 7 "strings" 8 "sync" 9 10 "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/hcl" 12 "github.com/hashicorp/terraform/config" 13 "github.com/hashicorp/terraform/config/module" 14 "github.com/hashicorp/terraform/helper/experiment" 15 ) 16 17 // InputMode defines what sort of input will be asked for when Input 18 // is called on Context. 19 type InputMode byte 20 21 const ( 22 // InputModeVar asks for all variables 23 InputModeVar InputMode = 1 << iota 24 25 // InputModeVarUnset asks for variables which are not set yet 26 InputModeVarUnset 27 28 // InputModeProvider asks for provider variables 29 InputModeProvider 30 31 // InputModeStd is the standard operating mode and asks for both variables 32 // and providers. 33 InputModeStd = InputModeVar | InputModeProvider 34 ) 35 36 var ( 37 // contextFailOnShadowError will cause Context operations to return 38 // errors when shadow operations fail. This is only used for testing. 39 contextFailOnShadowError = false 40 41 // contextTestDeepCopyOnPlan will perform a Diff DeepCopy on every 42 // Plan operation, effectively testing the Diff DeepCopy whenever 43 // a Plan occurs. This is enabled for tests. 44 contextTestDeepCopyOnPlan = false 45 ) 46 47 // ContextOpts are the user-configurable options to create a context with 48 // NewContext. 49 type ContextOpts struct { 50 Destroy bool 51 Diff *Diff 52 Hooks []Hook 53 Module *module.Tree 54 Parallelism int 55 State *State 56 StateFutureAllowed bool 57 Providers map[string]ResourceProviderFactory 58 Provisioners map[string]ResourceProvisionerFactory 59 Shadow bool 60 Targets []string 61 Variables map[string]interface{} 62 63 UIInput UIInput 64 } 65 66 // Context represents all the context that Terraform needs in order to 67 // perform operations on infrastructure. This structure is built using 68 // NewContext. See the documentation for that. 69 // 70 // Extra functions on Context can be found in context_*.go files. 71 type Context struct { 72 // Maintainer note: Anytime this struct is changed, please verify 73 // that newShadowContext still does the right thing. Tests should 74 // fail regardless but putting this note here as well. 75 76 components contextComponentFactory 77 destroy bool 78 diff *Diff 79 diffLock sync.RWMutex 80 hooks []Hook 81 module *module.Tree 82 sh *stopHook 83 shadow bool 84 state *State 85 stateLock sync.RWMutex 86 targets []string 87 uiInput UIInput 88 variables map[string]interface{} 89 90 l sync.Mutex // Lock acquired during any task 91 parallelSem Semaphore 92 providerInputConfig map[string]map[string]interface{} 93 runCh <-chan struct{} 94 shadowErr error 95 } 96 97 // NewContext creates a new Context structure. 98 // 99 // Once a Context is creator, the pointer values within ContextOpts 100 // should not be mutated in any way, since the pointers are copied, not 101 // the values themselves. 102 func NewContext(opts *ContextOpts) (*Context, error) { 103 // Copy all the hooks and add our stop hook. We don't append directly 104 // to the Config so that we're not modifying that in-place. 105 sh := new(stopHook) 106 hooks := make([]Hook, len(opts.Hooks)+1) 107 copy(hooks, opts.Hooks) 108 hooks[len(opts.Hooks)] = sh 109 110 state := opts.State 111 if state == nil { 112 state = new(State) 113 state.init() 114 } 115 116 // If our state is from the future, then error. Callers can avoid 117 // this error by explicitly setting `StateFutureAllowed`. 118 if !opts.StateFutureAllowed && state.FromFutureTerraform() { 119 return nil, fmt.Errorf( 120 "Terraform doesn't allow running any operations against a state\n"+ 121 "that was written by a future Terraform version. The state is\n"+ 122 "reporting it is written by Terraform '%s'.\n\n"+ 123 "Please run at least that version of Terraform to continue.", 124 state.TFVersion) 125 } 126 127 // Explicitly reset our state version to our current version so that 128 // any operations we do will write out that our latest version 129 // has run. 130 state.TFVersion = Version 131 132 // Determine parallelism, default to 10. We do this both to limit 133 // CPU pressure but also to have an extra guard against rate throttling 134 // from providers. 135 par := opts.Parallelism 136 if par == 0 { 137 par = 10 138 } 139 140 // Set up the variables in the following sequence: 141 // 0 - Take default values from the configuration 142 // 1 - Take values from TF_VAR_x environment variables 143 // 2 - Take values specified in -var flags, overriding values 144 // set by environment variables if necessary. This includes 145 // values taken from -var-file in addition. 146 variables := make(map[string]interface{}) 147 148 if opts.Module != nil { 149 var err error 150 variables, err = Variables(opts.Module, opts.Variables) 151 if err != nil { 152 return nil, err 153 } 154 } 155 156 return &Context{ 157 components: &basicComponentFactory{ 158 providers: opts.Providers, 159 provisioners: opts.Provisioners, 160 }, 161 destroy: opts.Destroy, 162 diff: opts.Diff, 163 hooks: hooks, 164 module: opts.Module, 165 shadow: opts.Shadow, 166 state: state, 167 targets: opts.Targets, 168 uiInput: opts.UIInput, 169 variables: variables, 170 171 parallelSem: NewSemaphore(par), 172 providerInputConfig: make(map[string]map[string]interface{}), 173 sh: sh, 174 }, nil 175 } 176 177 type ContextGraphOpts struct { 178 Validate bool 179 Verbose bool 180 } 181 182 // Graph returns the graph for this config. 183 func (c *Context) Graph(g *ContextGraphOpts) (*Graph, error) { 184 return c.graphBuilder(g).Build(RootModulePath) 185 } 186 187 // GraphBuilder returns the GraphBuilder that will be used to create 188 // the graphs for this context. 189 func (c *Context) graphBuilder(g *ContextGraphOpts) GraphBuilder { 190 return &BuiltinGraphBuilder{ 191 Root: c.module, 192 Diff: c.diff, 193 Providers: c.components.ResourceProviders(), 194 Provisioners: c.components.ResourceProvisioners(), 195 State: c.state, 196 Targets: c.targets, 197 Destroy: c.destroy, 198 Validate: g.Validate, 199 Verbose: g.Verbose, 200 } 201 } 202 203 // ShadowError returns any errors caught during a shadow operation. 204 // 205 // A shadow operation is an operation run in parallel to a real operation 206 // that performs the same tasks using new logic on copied state. The results 207 // are compared to ensure that the new logic works the same as the old logic. 208 // The shadow never affects the real operation or return values. 209 // 210 // The result of the shadow operation are only available through this function 211 // call after a real operation is complete. 212 // 213 // For API consumers of Context, you can safely ignore this function 214 // completely if you have no interest in helping report experimental feature 215 // errors to Terraform maintainers. Otherwise, please call this function 216 // after every operation and report this to the user. 217 // 218 // IMPORTANT: Shadow errors are _never_ critical: they _never_ affect 219 // the real state or result of a real operation. They are purely informational 220 // to assist in future Terraform versions being more stable. Please message 221 // this effectively to the end user. 222 // 223 // This must be called only when no other operation is running (refresh, 224 // plan, etc.). The result can be used in parallel to any other operation 225 // running. 226 func (c *Context) ShadowError() error { 227 return c.shadowErr 228 } 229 230 // Input asks for input to fill variables and provider configurations. 231 // This modifies the configuration in-place, so asking for Input twice 232 // may result in different UI output showing different current values. 233 func (c *Context) Input(mode InputMode) error { 234 v := c.acquireRun() 235 defer c.releaseRun(v) 236 237 if mode&InputModeVar != 0 { 238 // Walk the variables first for the root module. We walk them in 239 // alphabetical order for UX reasons. 240 rootConf := c.module.Config() 241 names := make([]string, len(rootConf.Variables)) 242 m := make(map[string]*config.Variable) 243 for i, v := range rootConf.Variables { 244 names[i] = v.Name 245 m[v.Name] = v 246 } 247 sort.Strings(names) 248 for _, n := range names { 249 // If we only care about unset variables, then if the variable 250 // is set, continue on. 251 if mode&InputModeVarUnset != 0 { 252 if _, ok := c.variables[n]; ok { 253 continue 254 } 255 } 256 257 var valueType config.VariableType 258 259 v := m[n] 260 switch valueType = v.Type(); valueType { 261 case config.VariableTypeUnknown: 262 continue 263 case config.VariableTypeMap: 264 // OK 265 case config.VariableTypeList: 266 // OK 267 case config.VariableTypeString: 268 // OK 269 default: 270 panic(fmt.Sprintf("Unknown variable type: %#v", v.Type())) 271 } 272 273 // If the variable is not already set, and the variable defines a 274 // default, use that for the value. 275 if _, ok := c.variables[n]; !ok { 276 if v.Default != nil { 277 c.variables[n] = v.Default.(string) 278 continue 279 } 280 } 281 282 // this should only happen during tests 283 if c.uiInput == nil { 284 log.Println("[WARN] Content.uiInput is nil") 285 continue 286 } 287 288 // Ask the user for a value for this variable 289 var value string 290 retry := 0 291 for { 292 var err error 293 value, err = c.uiInput.Input(&InputOpts{ 294 Id: fmt.Sprintf("var.%s", n), 295 Query: fmt.Sprintf("var.%s", n), 296 Description: v.Description, 297 }) 298 if err != nil { 299 return fmt.Errorf( 300 "Error asking for %s: %s", n, err) 301 } 302 303 if value == "" && v.Required() { 304 // Redo if it is required, but abort if we keep getting 305 // blank entries 306 if retry > 2 { 307 return fmt.Errorf("missing required value for %q", n) 308 } 309 retry++ 310 continue 311 } 312 313 break 314 } 315 316 // no value provided, so don't set the variable at all 317 if value == "" { 318 continue 319 } 320 321 decoded, err := parseVariableAsHCL(n, value, valueType) 322 if err != nil { 323 return err 324 } 325 326 if decoded != nil { 327 c.variables[n] = decoded 328 } 329 } 330 } 331 332 if mode&InputModeProvider != 0 { 333 // Build the graph 334 graph, err := c.Graph(&ContextGraphOpts{Validate: true}) 335 if err != nil { 336 return err 337 } 338 339 // Do the walk 340 if _, err := c.walk(graph, nil, walkInput); err != nil { 341 return err 342 } 343 } 344 345 return nil 346 } 347 348 // Apply applies the changes represented by this context and returns 349 // the resulting state. 350 // 351 // In addition to returning the resulting state, this context is updated 352 // with the latest state. 353 func (c *Context) Apply() (*State, error) { 354 v := c.acquireRun() 355 defer c.releaseRun(v) 356 357 // Copy our own state 358 c.state = c.state.DeepCopy() 359 360 X_newApply := experiment.Enabled(experiment.X_newApply) 361 X_newDestroy := experiment.Enabled(experiment.X_newDestroy) 362 newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply) 363 364 // Build the original graph. This is before the new graph builders 365 // coming in 0.8. We do this for shadow graphing. 366 oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true}) 367 if err != nil && X_newApply { 368 // If we had an error graphing but we're using the new graph, 369 // just set it to nil and let it go. There are some features that 370 // may work with the new graph that don't with the old. 371 oldGraph = nil 372 err = nil 373 } 374 if err != nil { 375 return nil, err 376 } 377 378 // Build the new graph. We do this no matter what so we can shadow it. 379 newGraph, err := (&ApplyGraphBuilder{ 380 Module: c.module, 381 Diff: c.diff, 382 State: c.state, 383 Providers: c.components.ResourceProviders(), 384 Provisioners: c.components.ResourceProvisioners(), 385 Destroy: c.destroy, 386 }).Build(RootModulePath) 387 if err != nil && !newGraphEnabled { 388 // If we had an error graphing but we're not using this graph, just 389 // set it to nil and record it as a shadow error. 390 c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf( 391 "Error building new graph: %s", err)) 392 393 newGraph = nil 394 err = nil 395 } 396 if err != nil { 397 return nil, err 398 } 399 400 // Determine what is the real and what is the shadow. The logic here 401 // is straightforward though the if statements are not: 402 // 403 // * Destroy mode - always use original, shadow with nothing because 404 // we're only testing the new APPLY graph. 405 // * Apply with new apply - use new graph, shadow is new graph. We can't 406 // shadow with the old graph because the old graph does a lot more 407 // that it shouldn't. 408 // * Apply with old apply - use old graph, shadow with new graph. 409 // 410 real := oldGraph 411 shadow := newGraph 412 if newGraphEnabled { 413 log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment") 414 real = shadow 415 } else { 416 log.Printf("[WARN] terraform: real graph is original, shadow is experiment") 417 } 418 419 // Determine the operation 420 operation := walkApply 421 if c.destroy { 422 operation = walkDestroy 423 } 424 425 // This shouldn't happen, so assert it. This is before any state changes 426 // so it is safe to crash here. 427 if real == nil { 428 panic("nil real graph") 429 } 430 431 // Walk the graph 432 walker, err := c.walk(real, shadow, operation) 433 if len(walker.ValidationErrors) > 0 { 434 err = multierror.Append(err, walker.ValidationErrors...) 435 } 436 437 // Clean out any unused things 438 c.state.prune() 439 440 return c.state, err 441 } 442 443 // Plan generates an execution plan for the given context. 444 // 445 // The execution plan encapsulates the context and can be stored 446 // in order to reinstantiate a context later for Apply. 447 // 448 // Plan also updates the diff of this context to be the diff generated 449 // by the plan, so Apply can be called after. 450 func (c *Context) Plan() (*Plan, error) { 451 v := c.acquireRun() 452 defer c.releaseRun(v) 453 454 p := &Plan{ 455 Module: c.module, 456 Vars: c.variables, 457 State: c.state, 458 Targets: c.targets, 459 } 460 461 var operation walkOperation 462 if c.destroy { 463 operation = walkPlanDestroy 464 } else { 465 // Set our state to be something temporary. We do this so that 466 // the plan can update a fake state so that variables work, then 467 // we replace it back with our old state. 468 old := c.state 469 if old == nil { 470 c.state = &State{} 471 c.state.init() 472 } else { 473 c.state = old.DeepCopy() 474 } 475 defer func() { 476 c.state = old 477 }() 478 479 operation = walkPlan 480 } 481 482 // Setup our diff 483 c.diffLock.Lock() 484 c.diff = new(Diff) 485 c.diff.init() 486 c.diffLock.Unlock() 487 488 // Used throughout below 489 X_newDestroy := experiment.Enabled(experiment.X_newDestroy) 490 491 // Build the graph. We have a branch here since for the pure-destroy 492 // plan (c.destroy) we use a much simpler graph builder that simply 493 // walks the state and reverses edges. 494 var graph *Graph 495 var err error 496 if c.destroy && X_newDestroy { 497 graph, err = (&DestroyPlanGraphBuilder{ 498 Module: c.module, 499 State: c.state, 500 Targets: c.targets, 501 }).Build(RootModulePath) 502 } else { 503 graph, err = c.Graph(&ContextGraphOpts{Validate: true}) 504 } 505 if err != nil { 506 return nil, err 507 } 508 509 // Do the walk 510 walker, err := c.walk(graph, graph, operation) 511 if err != nil { 512 return nil, err 513 } 514 p.Diff = c.diff 515 516 // If this is true, it means we're running unit tests. In this case, 517 // we perform a deep copy just to ensure that all context tests also 518 // test that a diff is copy-able. This will panic if it fails. This 519 // is enabled during unit tests. 520 // 521 // This should never be true during production usage, but even if it is, 522 // it can't do any real harm. 523 if contextTestDeepCopyOnPlan { 524 p.Diff.DeepCopy() 525 } 526 527 // We don't do the reverification during the new destroy plan because 528 // it will use a different apply process. 529 if !(c.destroy && X_newDestroy) { 530 // Now that we have a diff, we can build the exact graph that Apply will use 531 // and catch any possible cycles during the Plan phase. 532 if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { 533 return nil, err 534 } 535 } 536 537 var errs error 538 if len(walker.ValidationErrors) > 0 { 539 errs = multierror.Append(errs, walker.ValidationErrors...) 540 } 541 return p, errs 542 } 543 544 // Refresh goes through all the resources in the state and refreshes them 545 // to their latest state. This will update the state that this context 546 // works with, along with returning it. 547 // 548 // Even in the case an error is returned, the state will be returned and 549 // will potentially be partially updated. 550 func (c *Context) Refresh() (*State, error) { 551 v := c.acquireRun() 552 defer c.releaseRun(v) 553 554 // Copy our own state 555 c.state = c.state.DeepCopy() 556 557 // Build the graph 558 graph, err := c.Graph(&ContextGraphOpts{Validate: true}) 559 if err != nil { 560 return nil, err 561 } 562 563 // Do the walk 564 if _, err := c.walk(graph, graph, walkRefresh); err != nil { 565 return nil, err 566 } 567 568 // Clean out any unused things 569 c.state.prune() 570 571 return c.state, nil 572 } 573 574 // Stop stops the running task. 575 // 576 // Stop will block until the task completes. 577 func (c *Context) Stop() { 578 c.l.Lock() 579 ch := c.runCh 580 581 // If we aren't running, then just return 582 if ch == nil { 583 c.l.Unlock() 584 return 585 } 586 587 // Tell the hook we want to stop 588 c.sh.Stop() 589 590 // Wait for us to stop 591 c.l.Unlock() 592 <-ch 593 } 594 595 // Validate validates the configuration and returns any warnings or errors. 596 func (c *Context) Validate() ([]string, []error) { 597 v := c.acquireRun() 598 defer c.releaseRun(v) 599 600 var errs error 601 602 // Validate the configuration itself 603 if err := c.module.Validate(); err != nil { 604 errs = multierror.Append(errs, err) 605 } 606 607 // This only needs to be done for the root module, since inter-module 608 // variables are validated in the module tree. 609 if config := c.module.Config(); config != nil { 610 // Validate the user variables 611 if err := smcUserVariables(config, c.variables); len(err) > 0 { 612 errs = multierror.Append(errs, err...) 613 } 614 } 615 616 // If we have errors at this point, the graphing has no chance, 617 // so just bail early. 618 if errs != nil { 619 return nil, []error{errs} 620 } 621 622 // Build the graph so we can walk it and run Validate on nodes. 623 // We also validate the graph generated here, but this graph doesn't 624 // necessarily match the graph that Plan will generate, so we'll validate the 625 // graph again later after Planning. 626 graph, err := c.Graph(&ContextGraphOpts{Validate: true}) 627 if err != nil { 628 return nil, []error{err} 629 } 630 631 // Walk 632 walker, err := c.walk(graph, graph, walkValidate) 633 if err != nil { 634 return nil, multierror.Append(errs, err).Errors 635 } 636 637 // Return the result 638 rerrs := multierror.Append(errs, walker.ValidationErrors...) 639 return walker.ValidationWarnings, rerrs.Errors 640 } 641 642 // Module returns the module tree associated with this context. 643 func (c *Context) Module() *module.Tree { 644 return c.module 645 } 646 647 // Variables will return the mapping of variables that were defined 648 // for this Context. If Input was called, this mapping may be different 649 // than what was given. 650 func (c *Context) Variables() map[string]interface{} { 651 return c.variables 652 } 653 654 // SetVariable sets a variable after a context has already been built. 655 func (c *Context) SetVariable(k string, v interface{}) { 656 c.variables[k] = v 657 } 658 659 func (c *Context) acquireRun() chan<- struct{} { 660 c.l.Lock() 661 defer c.l.Unlock() 662 663 // Wait for no channel to exist 664 for c.runCh != nil { 665 c.l.Unlock() 666 ch := c.runCh 667 <-ch 668 c.l.Lock() 669 } 670 671 // Create the new channel 672 ch := make(chan struct{}) 673 c.runCh = ch 674 675 // Reset the stop hook so we're not stopped 676 c.sh.Reset() 677 678 // Reset the shadow errors 679 c.shadowErr = nil 680 681 return ch 682 } 683 684 func (c *Context) releaseRun(ch chan<- struct{}) { 685 c.l.Lock() 686 defer c.l.Unlock() 687 688 close(ch) 689 c.runCh = nil 690 } 691 692 func (c *Context) walk( 693 graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) { 694 // Keep track of the "real" context which is the context that does 695 // the real work: talking to real providers, modifying real state, etc. 696 realCtx := c 697 698 // If we don't want shadowing, remove it 699 if !experiment.Enabled(experiment.X_shadow) { 700 shadow = nil 701 } 702 703 // If we have a shadow graph, walk that as well 704 var shadowCtx *Context 705 var shadowCloser Shadow 706 if c.shadow && shadow != nil { 707 // Build the shadow context. In the process, override the real context 708 // with the one that is wrapped so that the shadow context can verify 709 // the results of the real. 710 realCtx, shadowCtx, shadowCloser = newShadowContext(c) 711 } 712 713 // Just log this so we can see it in a debug log 714 if !c.shadow { 715 log.Printf("[WARN] terraform: shadow graph disabled") 716 } 717 718 // Build the real graph walker 719 log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) 720 walker := &ContextGraphWalker{Context: realCtx, Operation: operation} 721 722 // Walk the real graph, this will block until it completes 723 realErr := graph.Walk(walker) 724 725 // If we have a shadow graph and we interrupted the real graph, then 726 // we just close the shadow and never verify it. It is non-trivial to 727 // recreate the exact execution state up until an interruption so this 728 // isn't supported with shadows at the moment. 729 if shadowCloser != nil && c.sh.Stopped() { 730 // Ignore the error result, there is nothing we could care about 731 shadowCloser.CloseShadow() 732 733 // Set it to nil so we don't do anything 734 shadowCloser = nil 735 } 736 737 // If we have a shadow graph, wait for that to complete. 738 if shadowCloser != nil { 739 // Build the graph walker for the shadow. 740 shadowWalker := &ContextGraphWalker{ 741 Context: shadowCtx, 742 Operation: operation, 743 } 744 745 // Kick off the shadow walk. This will block on any operations 746 // on the real walk so it is fine to start first. 747 log.Printf("[INFO] Starting shadow graph walk: %s", operation.String()) 748 shadowCh := make(chan error) 749 go func() { 750 shadowCh <- shadow.Walk(shadowWalker) 751 }() 752 753 // Notify the shadow that we're done 754 if err := shadowCloser.CloseShadow(); err != nil { 755 c.shadowErr = multierror.Append(c.shadowErr, err) 756 } 757 758 // Wait for the walk to end 759 log.Printf("[DEBUG] Waiting for shadow graph to complete...") 760 shadowWalkErr := <-shadowCh 761 762 // Get any shadow errors 763 if err := shadowCloser.ShadowError(); err != nil { 764 c.shadowErr = multierror.Append(c.shadowErr, err) 765 } 766 767 // Verify the contexts (compare) 768 if err := shadowContextVerify(realCtx, shadowCtx); err != nil { 769 c.shadowErr = multierror.Append(c.shadowErr, err) 770 } 771 772 // At this point, if we're supposed to fail on error, then 773 // we PANIC. Some tests just verify that there is an error, 774 // so simply appending it to realErr and returning could hide 775 // shadow problems. 776 // 777 // This must be done BEFORE appending shadowWalkErr since the 778 // shadowWalkErr may include expected errors. 779 if c.shadowErr != nil && contextFailOnShadowError { 780 panic(multierror.Prefix(c.shadowErr, "shadow graph:")) 781 } 782 783 // Now, if we have a walk error, we append that through 784 if shadowWalkErr != nil { 785 c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr) 786 } 787 788 if c.shadowErr == nil { 789 log.Printf("[INFO] Shadow graph success!") 790 } else { 791 log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr) 792 793 // If we're supposed to fail on shadow errors, then report it 794 if contextFailOnShadowError { 795 realErr = multierror.Append(realErr, multierror.Prefix( 796 c.shadowErr, "shadow graph:")) 797 } 798 } 799 } 800 801 return walker, realErr 802 } 803 804 // parseVariableAsHCL parses the value of a single variable as would have been specified 805 // on the command line via -var or in an environment variable named TF_VAR_x, where x is 806 // the name of the variable. In order to get around the restriction of HCL requiring a 807 // top level object, we prepend a sentinel key, decode the user-specified value as its 808 // value and pull the value back out of the resulting map. 809 func parseVariableAsHCL(name string, input string, targetType config.VariableType) (interface{}, error) { 810 // expecting a string so don't decode anything, just strip quotes 811 if targetType == config.VariableTypeString { 812 return strings.Trim(input, `"`), nil 813 } 814 815 // return empty types 816 if strings.TrimSpace(input) == "" { 817 switch targetType { 818 case config.VariableTypeList: 819 return []interface{}{}, nil 820 case config.VariableTypeMap: 821 return make(map[string]interface{}), nil 822 } 823 } 824 825 const sentinelValue = "SENTINEL_TERRAFORM_VAR_OVERRIDE_KEY" 826 inputWithSentinal := fmt.Sprintf("%s = %s", sentinelValue, input) 827 828 var decoded map[string]interface{} 829 err := hcl.Decode(&decoded, inputWithSentinal) 830 if err != nil { 831 return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", name, input, err) 832 } 833 834 if len(decoded) != 1 { 835 return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", name, input) 836 } 837 838 parsedValue, ok := decoded[sentinelValue] 839 if !ok { 840 return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. One value must be specified.", name, input) 841 } 842 843 switch targetType { 844 case config.VariableTypeList: 845 return parsedValue, nil 846 case config.VariableTypeMap: 847 if list, ok := parsedValue.([]map[string]interface{}); ok { 848 return list[0], nil 849 } 850 851 return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. One value must be specified.", name, input) 852 default: 853 panic(fmt.Errorf("unknown type %s", targetType.Printable())) 854 } 855 }