github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/context.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "log" 8 "sync" 9 10 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/lang" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/plans" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/provisioners" 16 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 17 "github.com/hashicorp/terraform-plugin-sdk/internal/states/statefile" 18 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 19 "github.com/zclconf/go-cty/cty" 20 ) 21 22 // InputMode defines what sort of input will be asked for when Input 23 // is called on Context. 24 type InputMode byte 25 26 const ( 27 // InputModeVar asks for all variables 28 InputModeVar InputMode = 1 << iota 29 30 // InputModeVarUnset asks for variables which are not set yet. 31 // InputModeVar must be set for this to have an effect. 32 InputModeVarUnset 33 34 // InputModeProvider asks for provider variables 35 InputModeProvider 36 37 // InputModeStd is the standard operating mode and asks for both variables 38 // and providers. 39 InputModeStd = InputModeVar | InputModeProvider 40 ) 41 42 // ContextOpts are the user-configurable options to create a context with 43 // NewContext. 44 type ContextOpts struct { 45 Config *configs.Config 46 Changes *plans.Changes 47 State *states.State 48 Targets []addrs.Targetable 49 Variables InputValues 50 Meta *ContextMeta 51 Destroy bool 52 53 Hooks []Hook 54 Parallelism int 55 ProviderResolver providers.Resolver 56 Provisioners map[string]ProvisionerFactory 57 58 // If non-nil, will apply as additional constraints on the provider 59 // plugins that will be requested from the provider resolver. 60 ProviderSHA256s map[string][]byte 61 SkipProviderVerify bool 62 63 UIInput UIInput 64 } 65 66 // ContextMeta is metadata about the running context. This is information 67 // that this package or structure cannot determine on its own but exposes 68 // into Terraform in various ways. This must be provided by the Context 69 // initializer. 70 type ContextMeta struct { 71 Env string // Env is the state environment 72 } 73 74 // Context represents all the context that Terraform needs in order to 75 // perform operations on infrastructure. This structure is built using 76 // NewContext. 77 type Context struct { 78 config *configs.Config 79 changes *plans.Changes 80 state *states.State 81 targets []addrs.Targetable 82 variables InputValues 83 meta *ContextMeta 84 destroy bool 85 86 hooks []Hook 87 components contextComponentFactory 88 schemas *Schemas 89 sh *stopHook 90 uiInput UIInput 91 92 l sync.Mutex // Lock acquired during any task 93 parallelSem Semaphore 94 providerInputConfig map[string]map[string]cty.Value 95 providerSHA256s map[string][]byte 96 runCond *sync.Cond 97 runContext context.Context 98 runContextCancel context.CancelFunc 99 shadowErr error 100 } 101 102 // (additional methods on Context can be found in context_*.go files.) 103 104 // NewContext creates a new Context structure. 105 // 106 // Once a Context is created, the caller must not access or mutate any of 107 // the objects referenced (directly or indirectly) by the ContextOpts fields. 108 // 109 // If the returned diagnostics contains errors then the resulting context is 110 // invalid and must not be used. 111 func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) { 112 log.Printf("[TRACE] terraform.NewContext: starting") 113 diags := CheckCoreVersionRequirements(opts.Config) 114 // If version constraints are not met then we'll bail early since otherwise 115 // we're likely to just see a bunch of other errors related to 116 // incompatibilities, which could be overwhelming for the user. 117 if diags.HasErrors() { 118 return nil, diags 119 } 120 121 // Copy all the hooks and add our stop hook. We don't append directly 122 // to the Config so that we're not modifying that in-place. 123 sh := new(stopHook) 124 hooks := make([]Hook, len(opts.Hooks)+1) 125 copy(hooks, opts.Hooks) 126 hooks[len(opts.Hooks)] = sh 127 128 state := opts.State 129 if state == nil { 130 state = states.NewState() 131 } 132 133 // Determine parallelism, default to 10. We do this both to limit 134 // CPU pressure but also to have an extra guard against rate throttling 135 // from providers. 136 par := opts.Parallelism 137 if par == 0 { 138 par = 10 139 } 140 141 // Set up the variables in the following sequence: 142 // 0 - Take default values from the configuration 143 // 1 - Take values from TF_VAR_x environment variables 144 // 2 - Take values specified in -var flags, overriding values 145 // set by environment variables if necessary. This includes 146 // values taken from -var-file in addition. 147 var variables InputValues 148 if opts.Config != nil { 149 // Default variables from the configuration seed our map. 150 variables = DefaultVariableValues(opts.Config.Module.Variables) 151 } 152 // Variables provided by the caller (from CLI, environment, etc) can 153 // override the defaults. 154 variables = variables.Override(opts.Variables) 155 156 // Bind available provider plugins to the constraints in config 157 var providerFactories map[string]providers.Factory 158 if opts.ProviderResolver != nil { 159 deps := ConfigTreeDependencies(opts.Config, state) 160 reqd := deps.AllPluginRequirements() 161 if opts.ProviderSHA256s != nil && !opts.SkipProviderVerify { 162 reqd.LockExecutables(opts.ProviderSHA256s) 163 } 164 log.Printf("[TRACE] terraform.NewContext: resolving provider version selections") 165 166 var providerDiags tfdiags.Diagnostics 167 providerFactories, providerDiags = resourceProviderFactories(opts.ProviderResolver, reqd) 168 diags = diags.Append(providerDiags) 169 170 if diags.HasErrors() { 171 return nil, diags 172 } 173 } else { 174 providerFactories = make(map[string]providers.Factory) 175 } 176 177 components := &basicComponentFactory{ 178 providers: providerFactories, 179 provisioners: opts.Provisioners, 180 } 181 182 log.Printf("[TRACE] terraform.NewContext: loading provider schemas") 183 schemas, err := LoadSchemas(opts.Config, opts.State, components) 184 if err != nil { 185 diags = diags.Append(err) 186 return nil, diags 187 } 188 189 changes := opts.Changes 190 if changes == nil { 191 changes = plans.NewChanges() 192 } 193 194 config := opts.Config 195 if config == nil { 196 config = configs.NewEmptyConfig() 197 } 198 199 log.Printf("[TRACE] terraform.NewContext: complete") 200 201 return &Context{ 202 components: components, 203 schemas: schemas, 204 destroy: opts.Destroy, 205 changes: changes, 206 hooks: hooks, 207 meta: opts.Meta, 208 config: config, 209 state: state, 210 targets: opts.Targets, 211 uiInput: opts.UIInput, 212 variables: variables, 213 214 parallelSem: NewSemaphore(par), 215 providerInputConfig: make(map[string]map[string]cty.Value), 216 providerSHA256s: opts.ProviderSHA256s, 217 sh: sh, 218 }, nil 219 } 220 221 func (c *Context) Schemas() *Schemas { 222 return c.schemas 223 } 224 225 type ContextGraphOpts struct { 226 // If true, validates the graph structure (checks for cycles). 227 Validate bool 228 229 // Legacy graphs only: won't prune the graph 230 Verbose bool 231 } 232 233 // Graph returns the graph used for the given operation type. 234 // 235 // The most extensive or complex graph type is GraphTypePlan. 236 func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, tfdiags.Diagnostics) { 237 if opts == nil { 238 opts = &ContextGraphOpts{Validate: true} 239 } 240 241 log.Printf("[INFO] terraform: building graph: %s", typ) 242 switch typ { 243 case GraphTypeApply: 244 return (&ApplyGraphBuilder{ 245 Config: c.config, 246 Changes: c.changes, 247 State: c.state, 248 Components: c.components, 249 Schemas: c.schemas, 250 Targets: c.targets, 251 Destroy: c.destroy, 252 Validate: opts.Validate, 253 }).Build(addrs.RootModuleInstance) 254 255 case GraphTypeValidate: 256 // The validate graph is just a slightly modified plan graph 257 fallthrough 258 case GraphTypePlan: 259 // Create the plan graph builder 260 p := &PlanGraphBuilder{ 261 Config: c.config, 262 State: c.state, 263 Components: c.components, 264 Schemas: c.schemas, 265 Targets: c.targets, 266 Validate: opts.Validate, 267 } 268 269 // Some special cases for other graph types shared with plan currently 270 var b GraphBuilder = p 271 switch typ { 272 case GraphTypeValidate: 273 b = ValidateGraphBuilder(p) 274 } 275 276 return b.Build(addrs.RootModuleInstance) 277 278 case GraphTypePlanDestroy: 279 return (&DestroyPlanGraphBuilder{ 280 Config: c.config, 281 State: c.state, 282 Components: c.components, 283 Schemas: c.schemas, 284 Targets: c.targets, 285 Validate: opts.Validate, 286 }).Build(addrs.RootModuleInstance) 287 288 case GraphTypeRefresh: 289 return (&RefreshGraphBuilder{ 290 Config: c.config, 291 State: c.state, 292 Components: c.components, 293 Schemas: c.schemas, 294 Targets: c.targets, 295 Validate: opts.Validate, 296 }).Build(addrs.RootModuleInstance) 297 298 case GraphTypeEval: 299 return (&EvalGraphBuilder{ 300 Config: c.config, 301 State: c.state, 302 Components: c.components, 303 Schemas: c.schemas, 304 }).Build(addrs.RootModuleInstance) 305 306 default: 307 // Should never happen, because the above is exhaustive for all graph types. 308 panic(fmt.Errorf("unsupported graph type %s", typ)) 309 } 310 } 311 312 // ShadowError returns any errors caught during a shadow operation. 313 // 314 // A shadow operation is an operation run in parallel to a real operation 315 // that performs the same tasks using new logic on copied state. The results 316 // are compared to ensure that the new logic works the same as the old logic. 317 // The shadow never affects the real operation or return values. 318 // 319 // The result of the shadow operation are only available through this function 320 // call after a real operation is complete. 321 // 322 // For API consumers of Context, you can safely ignore this function 323 // completely if you have no interest in helping report experimental feature 324 // errors to Terraform maintainers. Otherwise, please call this function 325 // after every operation and report this to the user. 326 // 327 // IMPORTANT: Shadow errors are _never_ critical: they _never_ affect 328 // the real state or result of a real operation. They are purely informational 329 // to assist in future Terraform versions being more stable. Please message 330 // this effectively to the end user. 331 // 332 // This must be called only when no other operation is running (refresh, 333 // plan, etc.). The result can be used in parallel to any other operation 334 // running. 335 func (c *Context) ShadowError() error { 336 return c.shadowErr 337 } 338 339 // State returns a copy of the current state associated with this context. 340 // 341 // This cannot safely be called in parallel with any other Context function. 342 func (c *Context) State() *states.State { 343 return c.state.DeepCopy() 344 } 345 346 // Eval produces a scope in which expressions can be evaluated for 347 // the given module path. 348 // 349 // This method must first evaluate any ephemeral values (input variables, local 350 // values, and output values) in the configuration. These ephemeral values are 351 // not included in the persisted state, so they must be re-computed using other 352 // values in the state before they can be properly evaluated. The updated 353 // values are retained in the main state associated with the receiving context. 354 // 355 // This function takes no action against remote APIs but it does need access 356 // to all provider and provisioner instances in order to obtain their schemas 357 // for type checking. 358 // 359 // The result is an evaluation scope that can be used to resolve references 360 // against the root module. If the returned diagnostics contains errors then 361 // the returned scope may be nil. If it is not nil then it may still be used 362 // to attempt expression evaluation or other analysis, but some expressions 363 // may not behave as expected. 364 func (c *Context) Eval(path addrs.ModuleInstance) (*lang.Scope, tfdiags.Diagnostics) { 365 // This is intended for external callers such as the "terraform console" 366 // command. Internally, we create an evaluator in c.walk before walking 367 // the graph, and create scopes in ContextGraphWalker. 368 369 var diags tfdiags.Diagnostics 370 defer c.acquireRun("eval")() 371 372 // Start with a copy of state so that we don't affect any instances 373 // that other methods may have already returned. 374 c.state = c.state.DeepCopy() 375 var walker *ContextGraphWalker 376 377 graph, graphDiags := c.Graph(GraphTypeEval, nil) 378 diags = diags.Append(graphDiags) 379 if !diags.HasErrors() { 380 var walkDiags tfdiags.Diagnostics 381 walker, walkDiags = c.walk(graph, walkEval) 382 diags = diags.Append(walker.NonFatalDiagnostics) 383 diags = diags.Append(walkDiags) 384 } 385 386 if walker == nil { 387 // If we skipped walking the graph (due to errors) then we'll just 388 // use a placeholder graph walker here, which'll refer to the 389 // unmodified state. 390 walker = c.graphWalker(walkEval) 391 } 392 393 // This is a bit weird since we don't normally evaluate outside of 394 // the context of a walk, but we'll "re-enter" our desired path here 395 // just to get hold of an EvalContext for it. GraphContextBuiltin 396 // caches its contexts, so we should get hold of the context that was 397 // previously used for evaluation here, unless we skipped walking. 398 evalCtx := walker.EnterPath(path) 399 return evalCtx.EvaluationScope(nil, EvalDataForNoInstanceKey), diags 400 } 401 402 // Apply applies the changes represented by this context and returns 403 // the resulting state. 404 // 405 // Even in the case an error is returned, the state may be returned and will 406 // potentially be partially updated. In addition to returning the resulting 407 // state, this context is updated with the latest state. 408 // 409 // If the state is required after an error, the caller should call 410 // Context.State, rather than rely on the return value. 411 // 412 // TODO: Apply and Refresh should either always return a state, or rely on the 413 // State() method. Currently the helper/resource testing framework relies 414 // on the absence of a returned state to determine if Destroy can be 415 // called, so that will need to be refactored before this can be changed. 416 func (c *Context) Apply() (*states.State, tfdiags.Diagnostics) { 417 defer c.acquireRun("apply")() 418 419 // Copy our own state 420 c.state = c.state.DeepCopy() 421 422 // Build the graph. 423 graph, diags := c.Graph(GraphTypeApply, nil) 424 if diags.HasErrors() { 425 return nil, diags 426 } 427 428 // Determine the operation 429 operation := walkApply 430 if c.destroy { 431 operation = walkDestroy 432 } 433 434 // Walk the graph 435 walker, walkDiags := c.walk(graph, operation) 436 diags = diags.Append(walker.NonFatalDiagnostics) 437 diags = diags.Append(walkDiags) 438 439 if c.destroy && !diags.HasErrors() { 440 // If we know we were trying to destroy objects anyway, and we 441 // completed without any errors, then we'll also prune out any 442 // leftover empty resource husks (left after all of the instances 443 // of a resource with "count" or "for_each" are destroyed) to 444 // help ensure we end up with an _actually_ empty state, assuming 445 // we weren't destroying with -target here. 446 // 447 // (This doesn't actually take into account -target, but that should 448 // be okay because it doesn't throw away anything we can't recompute 449 // on a subsequent "terraform plan" run, if the resources are still 450 // present in the configuration. However, this _will_ cause "count = 0" 451 // resources to read as unknown during the next refresh walk, which 452 // may cause some additional churn if used in a data resource or 453 // provider block, until we remove refreshing as a separate walk and 454 // just do it as part of the plan walk.) 455 c.state.PruneResourceHusks() 456 } 457 458 if len(c.targets) > 0 { 459 diags = diags.Append(tfdiags.Sourceless( 460 tfdiags.Warning, 461 "Applied changes may be incomplete", 462 `The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending: 463 terraform plan 464 465 Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`, 466 )) 467 } 468 469 return c.state, diags 470 } 471 472 // Plan generates an execution plan for the given context. 473 // 474 // The execution plan encapsulates the context and can be stored 475 // in order to reinstantiate a context later for Apply. 476 // 477 // Plan also updates the diff of this context to be the diff generated 478 // by the plan, so Apply can be called after. 479 func (c *Context) Plan() (*plans.Plan, tfdiags.Diagnostics) { 480 defer c.acquireRun("plan")() 481 c.changes = plans.NewChanges() 482 483 var diags tfdiags.Diagnostics 484 485 if len(c.targets) > 0 { 486 diags = diags.Append(tfdiags.Sourceless( 487 tfdiags.Warning, 488 "Resource targeting is in effect", 489 `You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration. 490 491 The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`, 492 )) 493 } 494 495 varVals := make(map[string]plans.DynamicValue, len(c.variables)) 496 for k, iv := range c.variables { 497 // We use cty.DynamicPseudoType here so that we'll save both the 498 // value _and_ its dynamic type in the plan, so we can recover 499 // exactly the same value later. 500 dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType) 501 if err != nil { 502 diags = diags.Append(tfdiags.Sourceless( 503 tfdiags.Error, 504 "Failed to prepare variable value for plan", 505 fmt.Sprintf("The value for variable %q could not be serialized to store in the plan: %s.", k, err), 506 )) 507 continue 508 } 509 varVals[k] = dv 510 } 511 512 p := &plans.Plan{ 513 VariableValues: varVals, 514 TargetAddrs: c.targets, 515 ProviderSHA256s: c.providerSHA256s, 516 } 517 518 var operation walkOperation 519 if c.destroy { 520 operation = walkPlanDestroy 521 } else { 522 // Set our state to be something temporary. We do this so that 523 // the plan can update a fake state so that variables work, then 524 // we replace it back with our old state. 525 old := c.state 526 if old == nil { 527 c.state = states.NewState() 528 } else { 529 c.state = old.DeepCopy() 530 } 531 defer func() { 532 c.state = old 533 }() 534 535 operation = walkPlan 536 } 537 538 // Build the graph. 539 graphType := GraphTypePlan 540 if c.destroy { 541 graphType = GraphTypePlanDestroy 542 } 543 graph, graphDiags := c.Graph(graphType, nil) 544 diags = diags.Append(graphDiags) 545 if graphDiags.HasErrors() { 546 return nil, diags 547 } 548 549 // Do the walk 550 walker, walkDiags := c.walk(graph, operation) 551 diags = diags.Append(walker.NonFatalDiagnostics) 552 diags = diags.Append(walkDiags) 553 if walkDiags.HasErrors() { 554 return nil, diags 555 } 556 p.Changes = c.changes 557 558 return p, diags 559 } 560 561 // Refresh goes through all the resources in the state and refreshes them 562 // to their latest state. This will update the state that this context 563 // works with, along with returning it. 564 // 565 // Even in the case an error is returned, the state may be returned and 566 // will potentially be partially updated. 567 func (c *Context) Refresh() (*states.State, tfdiags.Diagnostics) { 568 defer c.acquireRun("refresh")() 569 570 // Copy our own state 571 c.state = c.state.DeepCopy() 572 573 // Refresh builds a partial changeset as part of its work because it must 574 // create placeholder stubs for any resource instances that'll be created 575 // in subsequent plan so that provider configurations and data resources 576 // can interpolate from them. This plan is always thrown away after 577 // the operation completes, restoring any existing changeset. 578 oldChanges := c.changes 579 defer func() { c.changes = oldChanges }() 580 c.changes = plans.NewChanges() 581 582 // Build the graph. 583 graph, diags := c.Graph(GraphTypeRefresh, nil) 584 if diags.HasErrors() { 585 return nil, diags 586 } 587 588 // Do the walk 589 _, walkDiags := c.walk(graph, walkRefresh) 590 diags = diags.Append(walkDiags) 591 if walkDiags.HasErrors() { 592 return nil, diags 593 } 594 595 // During our walk we will have created planned object placeholders in 596 // state for resource instances that are in configuration but not yet 597 // created. These were created only to allow expression evaluation to 598 // work properly in provider and data blocks during the walk and must 599 // now be discarded, since a subsequent plan walk is responsible for 600 // creating these "for real". 601 // TODO: Consolidate refresh and plan into a single walk, so that the 602 // refresh walk doesn't need to emulate various aspects of the plan 603 // walk in order to properly evaluate provider and data blocks. 604 c.state.SyncWrapper().RemovePlannedResourceInstanceObjects() 605 606 return c.state, diags 607 } 608 609 // Stop stops the running task. 610 // 611 // Stop will block until the task completes. 612 func (c *Context) Stop() { 613 log.Printf("[WARN] terraform: Stop called, initiating interrupt sequence") 614 615 c.l.Lock() 616 defer c.l.Unlock() 617 618 // If we're running, then stop 619 if c.runContextCancel != nil { 620 log.Printf("[WARN] terraform: run context exists, stopping") 621 622 // Tell the hook we want to stop 623 c.sh.Stop() 624 625 // Stop the context 626 c.runContextCancel() 627 c.runContextCancel = nil 628 } 629 630 // Grab the condition var before we exit 631 if cond := c.runCond; cond != nil { 632 log.Printf("[INFO] terraform: waiting for graceful stop to complete") 633 cond.Wait() 634 } 635 636 log.Printf("[WARN] terraform: stop complete") 637 } 638 639 // Validate performs semantic validation of the configuration, and returning 640 // any warnings or errors. 641 // 642 // Syntax and structural checks are performed by the configuration loader, 643 // and so are not repeated here. 644 func (c *Context) Validate() tfdiags.Diagnostics { 645 defer c.acquireRun("validate")() 646 647 var diags tfdiags.Diagnostics 648 649 // Validate input variables. We do this only for the values supplied 650 // by the root module, since child module calls are validated when we 651 // visit their graph nodes. 652 if c.config != nil { 653 varDiags := checkInputVariables(c.config.Module.Variables, c.variables) 654 diags = diags.Append(varDiags) 655 } 656 657 // If we have errors at this point then we probably won't be able to 658 // construct a graph without producing redundant errors, so we'll halt early. 659 if diags.HasErrors() { 660 return diags 661 } 662 663 // Build the graph so we can walk it and run Validate on nodes. 664 // We also validate the graph generated here, but this graph doesn't 665 // necessarily match the graph that Plan will generate, so we'll validate the 666 // graph again later after Planning. 667 graph, graphDiags := c.Graph(GraphTypeValidate, nil) 668 diags = diags.Append(graphDiags) 669 if graphDiags.HasErrors() { 670 return diags 671 } 672 673 // Walk 674 walker, walkDiags := c.walk(graph, walkValidate) 675 diags = diags.Append(walker.NonFatalDiagnostics) 676 diags = diags.Append(walkDiags) 677 if walkDiags.HasErrors() { 678 return diags 679 } 680 681 return diags 682 } 683 684 // Config returns the configuration tree associated with this context. 685 func (c *Context) Config() *configs.Config { 686 return c.config 687 } 688 689 // Variables will return the mapping of variables that were defined 690 // for this Context. If Input was called, this mapping may be different 691 // than what was given. 692 func (c *Context) Variables() InputValues { 693 return c.variables 694 } 695 696 // SetVariable sets a variable after a context has already been built. 697 func (c *Context) SetVariable(k string, v cty.Value) { 698 c.variables[k] = &InputValue{ 699 Value: v, 700 SourceType: ValueFromCaller, 701 } 702 } 703 704 func (c *Context) acquireRun(phase string) func() { 705 // With the run lock held, grab the context lock to make changes 706 // to the run context. 707 c.l.Lock() 708 defer c.l.Unlock() 709 710 // Wait until we're no longer running 711 for c.runCond != nil { 712 c.runCond.Wait() 713 } 714 715 // Build our lock 716 c.runCond = sync.NewCond(&c.l) 717 718 // Create a new run context 719 c.runContext, c.runContextCancel = context.WithCancel(context.Background()) 720 721 // Reset the stop hook so we're not stopped 722 c.sh.Reset() 723 724 // Reset the shadow errors 725 c.shadowErr = nil 726 727 return c.releaseRun 728 } 729 730 func (c *Context) releaseRun() { 731 // Grab the context lock so that we can make modifications to fields 732 c.l.Lock() 733 defer c.l.Unlock() 734 735 // End our run. We check if runContext is non-nil because it can be 736 // set to nil if it was cancelled via Stop() 737 if c.runContextCancel != nil { 738 c.runContextCancel() 739 } 740 741 // Unlock all waiting our condition 742 cond := c.runCond 743 c.runCond = nil 744 cond.Broadcast() 745 746 // Unset the context 747 c.runContext = nil 748 } 749 750 func (c *Context) walk(graph *Graph, operation walkOperation) (*ContextGraphWalker, tfdiags.Diagnostics) { 751 log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) 752 753 walker := c.graphWalker(operation) 754 755 // Watch for a stop so we can call the provider Stop() API. 756 watchStop, watchWait := c.watchStop(walker) 757 758 // Walk the real graph, this will block until it completes 759 diags := graph.Walk(walker) 760 761 // Close the channel so the watcher stops, and wait for it to return. 762 close(watchStop) 763 <-watchWait 764 765 return walker, diags 766 } 767 768 func (c *Context) graphWalker(operation walkOperation) *ContextGraphWalker { 769 return &ContextGraphWalker{ 770 Context: c, 771 State: c.state.SyncWrapper(), 772 Changes: c.changes.SyncWrapper(), 773 Operation: operation, 774 StopContext: c.runContext, 775 RootVariableValues: c.variables, 776 } 777 } 778 779 // watchStop immediately returns a `stop` and a `wait` chan after dispatching 780 // the watchStop goroutine. This will watch the runContext for cancellation and 781 // stop the providers accordingly. When the watch is no longer needed, the 782 // `stop` chan should be closed before waiting on the `wait` chan. 783 // The `wait` chan is important, because without synchronizing with the end of 784 // the watchStop goroutine, the runContext may also be closed during the select 785 // incorrectly causing providers to be stopped. Even if the graph walk is done 786 // at that point, stopping a provider permanently cancels its StopContext which 787 // can cause later actions to fail. 788 func (c *Context) watchStop(walker *ContextGraphWalker) (chan struct{}, <-chan struct{}) { 789 stop := make(chan struct{}) 790 wait := make(chan struct{}) 791 792 // get the runContext cancellation channel now, because releaseRun will 793 // write to the runContext field. 794 done := c.runContext.Done() 795 796 go func() { 797 defer close(wait) 798 // Wait for a stop or completion 799 select { 800 case <-done: 801 // done means the context was canceled, so we need to try and stop 802 // providers. 803 case <-stop: 804 // our own stop channel was closed. 805 return 806 } 807 808 // If we're here, we're stopped, trigger the call. 809 log.Printf("[TRACE] Context: requesting providers and provisioners to gracefully stop") 810 811 { 812 // Copy the providers so that a misbehaved blocking Stop doesn't 813 // completely hang Terraform. 814 walker.providerLock.Lock() 815 ps := make([]providers.Interface, 0, len(walker.providerCache)) 816 for _, p := range walker.providerCache { 817 ps = append(ps, p) 818 } 819 defer walker.providerLock.Unlock() 820 821 for _, p := range ps { 822 // We ignore the error for now since there isn't any reasonable 823 // action to take if there is an error here, since the stop is still 824 // advisory: Terraform will exit once the graph node completes. 825 p.Stop() 826 } 827 } 828 829 { 830 // Call stop on all the provisioners 831 walker.provisionerLock.Lock() 832 ps := make([]provisioners.Interface, 0, len(walker.provisionerCache)) 833 for _, p := range walker.provisionerCache { 834 ps = append(ps, p) 835 } 836 defer walker.provisionerLock.Unlock() 837 838 for _, p := range ps { 839 // We ignore the error for now since there isn't any reasonable 840 // action to take if there is an error here, since the stop is still 841 // advisory: Terraform will exit once the graph node completes. 842 p.Stop() 843 } 844 } 845 }() 846 847 return stop, wait 848 } 849 850 // ShimLegacyState is a helper that takes the legacy state type and 851 // converts it to the new state type. 852 // 853 // This is implemented as a state file upgrade, so it will not preserve 854 // parts of the state structure that are not included in a serialized state, 855 // such as the resolved results of any local values, outputs in non-root 856 // modules, etc. 857 func ShimLegacyState(legacy *State) (*states.State, error) { 858 if legacy == nil { 859 return nil, nil 860 } 861 var buf bytes.Buffer 862 err := WriteState(legacy, &buf) 863 if err != nil { 864 return nil, err 865 } 866 f, err := statefile.Read(&buf) 867 if err != nil { 868 return nil, err 869 } 870 return f.State, err 871 } 872 873 // MustShimLegacyState is a wrapper around ShimLegacyState that panics if 874 // the conversion does not succeed. This is primarily intended for tests where 875 // the given legacy state is an object constructed within the test. 876 func MustShimLegacyState(legacy *State) *states.State { 877 ret, err := ShimLegacyState(legacy) 878 if err != nil { 879 panic(err) 880 } 881 return ret 882 }