terraform/internal/terraform/context_walk.go

112 lines
3.6 KiB
Go

package terraform
import (
"log"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/refactoring"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// graphWalkOpts captures some transient values we use (and possibly mutate)
// during a graph walk.
//
// The way these options get used unfortunately varies between the different
// walkOperation types. This is a historical design wart that dates back to
// us using the same graph structure for all operations; hopefully we'll
// make the necessary differences between the walk types more explicit someday.
type graphWalkOpts struct {
InputState *states.State
Changes *plans.Changes
Config *configs.Config
MoveResults refactoring.MoveResults
}
func (c *Context) walk(graph *Graph, operation walkOperation, opts *graphWalkOpts) (*ContextGraphWalker, tfdiags.Diagnostics) {
log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
walker := c.graphWalker(operation, opts)
// Watch for a stop so we can call the provider Stop() API.
watchStop, watchWait := c.watchStop(walker)
// Walk the real graph, this will block until it completes
diags := graph.Walk(walker)
// Close the channel so the watcher stops, and wait for it to return.
close(watchStop)
<-watchWait
return walker, diags
}
func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *ContextGraphWalker {
var state *states.SyncState
var refreshState *states.SyncState
var prevRunState *states.SyncState
// NOTE: None of the SyncState objects must directly wrap opts.InputState,
// because we use those to mutate the state object and opts.InputState
// belongs to our caller and thus we must treat it as immutable.
//
// To account for that, most of our SyncState values created below end up
// wrapping a _deep copy_ of opts.InputState instead.
inputState := opts.InputState
if inputState == nil {
// Lots of callers use nil to represent the "empty" case where we've
// not run Apply yet, so we tolerate that.
inputState = states.NewState()
}
switch operation {
case walkValidate:
// validate should not use any state
state = states.NewState().SyncWrapper()
// validate currently uses the plan graph, so we have to populate the
// refreshState and the prevRunState.
refreshState = states.NewState().SyncWrapper()
prevRunState = states.NewState().SyncWrapper()
case walkPlan, walkPlanDestroy:
state = inputState.DeepCopy().SyncWrapper()
refreshState = inputState.DeepCopy().SyncWrapper()
prevRunState = inputState.DeepCopy().SyncWrapper()
default:
state = inputState.DeepCopy().SyncWrapper()
// Only plan-like walks use refreshState and prevRunState
}
changes := opts.Changes
if changes == nil {
// Several of our non-plan walks end up sharing codepaths with the
// plan walk and thus expect to generate planned changes even though
// we don't care about them. To avoid those crashing, we'll just
// insert a placeholder changes object which'll get discarded
// afterwards.
changes = plans.NewChanges()
}
if opts.Config == nil {
panic("Context.graphWalker call without Config")
}
return &ContextGraphWalker{
Context: c,
State: state,
Config: opts.Config,
RefreshState: refreshState,
PrevRunState: prevRunState,
Changes: changes.SyncWrapper(),
InstanceExpander: instances.NewExpander(),
MoveResults: opts.MoveResults,
Operation: operation,
StopContext: c.runContext,
}
}