mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-03 20:57:09 -06:00
6b290cf163
Once again we're caught out by sharing the same output value node type between the plan phase and the apply phase. To allow for some slight variation between plan and apply without drastic refactoring here we just add a new flag to nodeExpandOutput which is true only during the planning phase. This then allows us to register the checkable objects only during the planning phase and not incorrectly re-register them during the apply phase. It's incorrect to re-register during apply because we carry over the planned checkable objects from the plan phase into the apply phase so we can guarantee that the final state will have all of the same checkable objects that the plan did. This avoids a panic during the apply phase from the incorrect duplicate registration.
296 lines
9.1 KiB
Go
296 lines
9.1 KiB
Go
package terraform
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/dag"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// PlanGraphBuilder is a GraphBuilder implementation that builds a graph for
|
|
// planning and for other "plan-like" operations which don't require an
|
|
// already-calculated plan as input.
|
|
//
|
|
// Unlike the apply graph builder, this graph builder:
|
|
//
|
|
// - Makes its decisions primarily based on the given configuration, which
|
|
// represents the desired state.
|
|
//
|
|
// - Ignores certain lifecycle concerns like create_before_destroy, because
|
|
// those are only important once we already know what action we're planning
|
|
// to take against a particular resource instance.
|
|
type PlanGraphBuilder struct {
|
|
// Config is the configuration tree to build a plan from.
|
|
Config *configs.Config
|
|
|
|
// State is the current state
|
|
State *states.State
|
|
|
|
// RootVariableValues are the raw input values for root input variables
|
|
// given by the caller, which we'll resolve into final values as part
|
|
// of the plan walk.
|
|
RootVariableValues InputValues
|
|
|
|
// Plugins is a library of plug-in components (providers and
|
|
// provisioners) available for use.
|
|
Plugins *contextPlugins
|
|
|
|
// Targets are resources to target
|
|
Targets []addrs.Targetable
|
|
|
|
// ForceReplace are resource instances where if we would normally have
|
|
// generated a NoOp or Update action then we'll force generating a replace
|
|
// action instead. Create and Delete actions are not affected.
|
|
ForceReplace []addrs.AbsResourceInstance
|
|
|
|
// skipRefresh indicates that we should skip refreshing managed resources
|
|
skipRefresh bool
|
|
|
|
// skipPlanChanges indicates that we should skip the step of comparing
|
|
// prior state with configuration and generating planned changes to
|
|
// resource instances. (This is for the "refresh only" planning mode,
|
|
// where we _only_ do the refresh step.)
|
|
skipPlanChanges bool
|
|
|
|
ConcreteProvider ConcreteProviderNodeFunc
|
|
ConcreteResource ConcreteResourceNodeFunc
|
|
ConcreteResourceInstance ConcreteResourceInstanceNodeFunc
|
|
ConcreteResourceOrphan ConcreteResourceInstanceNodeFunc
|
|
ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc
|
|
ConcreteModule ConcreteModuleNodeFunc
|
|
|
|
// Plan Operation this graph will be used for.
|
|
Operation walkOperation
|
|
|
|
// ImportTargets are the list of resources to import.
|
|
ImportTargets []*ImportTarget
|
|
}
|
|
|
|
// See GraphBuilder
|
|
func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
|
log.Printf("[TRACE] building graph for %s", b.Operation)
|
|
return (&BasicGraphBuilder{
|
|
Steps: b.Steps(),
|
|
Name: "PlanGraphBuilder",
|
|
}).Build(path)
|
|
}
|
|
|
|
// See GraphBuilder
|
|
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|
switch b.Operation {
|
|
case walkPlan:
|
|
b.initPlan()
|
|
case walkPlanDestroy:
|
|
b.initDestroy()
|
|
case walkValidate:
|
|
b.initValidate()
|
|
case walkImport:
|
|
b.initImport()
|
|
default:
|
|
panic("invalid plan operation: " + b.Operation.String())
|
|
}
|
|
|
|
steps := []GraphTransformer{
|
|
// Creates all the resources represented in the config
|
|
&ConfigTransformer{
|
|
Concrete: b.ConcreteResource,
|
|
Config: b.Config,
|
|
|
|
// Resources are not added from the config on destroy.
|
|
skip: b.Operation == walkPlanDestroy,
|
|
|
|
importTargets: b.ImportTargets,
|
|
},
|
|
|
|
// Add dynamic values
|
|
&RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues},
|
|
&ModuleVariableTransformer{Config: b.Config},
|
|
&LocalTransformer{Config: b.Config},
|
|
&OutputTransformer{
|
|
Config: b.Config,
|
|
RefreshOnly: b.skipPlanChanges,
|
|
removeRootOutputs: b.Operation == walkPlanDestroy,
|
|
|
|
// NOTE: We currently treat anything built with the plan graph
|
|
// builder as "planning" for our purposes here, because we share
|
|
// the same graph node implementation between all of the walk
|
|
// types and so the pre-planning walks still think they are
|
|
// producing a plan even though we immediately discard it.
|
|
Planning: true,
|
|
},
|
|
|
|
// Add orphan resources
|
|
&OrphanResourceInstanceTransformer{
|
|
Concrete: b.ConcreteResourceOrphan,
|
|
State: b.State,
|
|
Config: b.Config,
|
|
skip: b.Operation == walkPlanDestroy,
|
|
},
|
|
|
|
// We also need nodes for any deposed instance objects present in the
|
|
// state, so we can plan to destroy them. (During plan this will
|
|
// intentionally skip creating nodes for _current_ objects, since
|
|
// ConfigTransformer created nodes that will do that during
|
|
// DynamicExpand.)
|
|
&StateTransformer{
|
|
ConcreteCurrent: b.ConcreteResourceInstance,
|
|
ConcreteDeposed: b.ConcreteResourceInstanceDeposed,
|
|
State: b.State,
|
|
},
|
|
|
|
// Attach the state
|
|
&AttachStateTransformer{State: b.State},
|
|
|
|
// Create orphan output nodes
|
|
&OrphanOutputTransformer{Config: b.Config, State: b.State},
|
|
|
|
// Attach the configuration to any resources
|
|
&AttachResourceConfigTransformer{Config: b.Config},
|
|
|
|
// add providers
|
|
transformProviders(b.ConcreteProvider, b.Config),
|
|
|
|
// Remove modules no longer present in the config
|
|
&RemovedModuleTransformer{Config: b.Config, State: b.State},
|
|
|
|
// Must attach schemas before ReferenceTransformer so that we can
|
|
// analyze the configuration to find references.
|
|
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
|
|
|
// Create expansion nodes for all of the module calls. This must
|
|
// come after all other transformers that create nodes representing
|
|
// objects that can belong to modules.
|
|
&ModuleExpansionTransformer{Concrete: b.ConcreteModule, Config: b.Config},
|
|
|
|
&ReferenceTransformer{},
|
|
|
|
&AttachDependenciesTransformer{},
|
|
|
|
// Make sure data sources are aware of any depends_on from the
|
|
// configuration
|
|
&attachDataResourceDependsOnTransformer{},
|
|
|
|
// DestroyEdgeTransformer is only required during a plan so that the
|
|
// TargetsTransformer can determine which nodes to keep in the graph.
|
|
&DestroyEdgeTransformer{},
|
|
|
|
&pruneUnusedNodesTransformer{
|
|
skip: b.Operation != walkPlanDestroy,
|
|
},
|
|
|
|
// Target
|
|
&TargetsTransformer{Targets: b.Targets},
|
|
|
|
// Detect when create_before_destroy must be forced on for a particular
|
|
// node due to dependency edges, to avoid graph cycles during apply.
|
|
&ForcedCBDTransformer{},
|
|
|
|
// Close opened plugin connections
|
|
&CloseProviderTransformer{},
|
|
|
|
// Close the root module
|
|
&CloseRootModuleTransformer{},
|
|
|
|
// Perform the transitive reduction to make our graph a bit
|
|
// more understandable if possible (it usually is possible).
|
|
&TransitiveReductionTransformer{},
|
|
}
|
|
|
|
return steps
|
|
}
|
|
|
|
func (b *PlanGraphBuilder) initPlan() {
|
|
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
|
return &NodeApplyableProvider{
|
|
NodeAbstractProvider: a,
|
|
}
|
|
}
|
|
|
|
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
|
return &nodeExpandPlannableResource{
|
|
NodeAbstractResource: a,
|
|
skipRefresh: b.skipRefresh,
|
|
skipPlanChanges: b.skipPlanChanges,
|
|
forceReplace: b.ForceReplace,
|
|
}
|
|
}
|
|
|
|
b.ConcreteResourceOrphan = func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
return &NodePlannableResourceInstanceOrphan{
|
|
NodeAbstractResourceInstance: a,
|
|
skipRefresh: b.skipRefresh,
|
|
skipPlanChanges: b.skipPlanChanges,
|
|
}
|
|
}
|
|
|
|
b.ConcreteResourceInstanceDeposed = func(a *NodeAbstractResourceInstance, key states.DeposedKey) dag.Vertex {
|
|
return &NodePlanDeposedResourceInstanceObject{
|
|
NodeAbstractResourceInstance: a,
|
|
DeposedKey: key,
|
|
|
|
skipRefresh: b.skipRefresh,
|
|
skipPlanChanges: b.skipPlanChanges,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *PlanGraphBuilder) initDestroy() {
|
|
b.initPlan()
|
|
|
|
b.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
return &NodePlanDestroyableResourceInstance{
|
|
NodeAbstractResourceInstance: a,
|
|
skipRefresh: b.skipRefresh,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *PlanGraphBuilder) initValidate() {
|
|
// Set the provider to the normal provider. This will ask for input.
|
|
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
|
return &NodeApplyableProvider{
|
|
NodeAbstractProvider: a,
|
|
}
|
|
}
|
|
|
|
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
|
return &NodeValidatableResource{
|
|
NodeAbstractResource: a,
|
|
}
|
|
}
|
|
|
|
b.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
|
|
return &nodeValidateModule{
|
|
nodeExpandModule: *n,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *PlanGraphBuilder) initImport() {
|
|
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
|
return &NodeApplyableProvider{
|
|
NodeAbstractProvider: a,
|
|
}
|
|
}
|
|
|
|
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
|
return &nodeExpandPlannableResource{
|
|
NodeAbstractResource: a,
|
|
|
|
// For now we always skip planning changes for import, since we are
|
|
// not going to combine importing with other changes. This is
|
|
// temporary to try and maintain existing import behaviors, but
|
|
// planning will need to be allowed for more complex configurations.
|
|
skipPlanChanges: true,
|
|
|
|
// We also skip refresh for now, since the plan output is written
|
|
// as the new state, and users are not expecting the import process
|
|
// to update any other instances in state.
|
|
skipRefresh: true,
|
|
}
|
|
}
|
|
}
|