mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-29 10:21:01 -06:00
3ea704ef81
In order to complete the terraform destroy command, a refresh must first be done to update state and remove any instances which have already been deleted externally. This was being done with a refresh plan, which will avoid any condition evaluations and avoid planning new instances. That however can fail due to invalid references from resources that are already missing from the state. A new plan type to handle the concept of the pre-destroy-refresh is needed here, which should probably be incorporated directly into the destroy plan, just like the original refresh walk was incorporated into the normal planning process. That however is major refactoring that is not appropriate for a patch release. Instead we make two discrete changes here to prevent blocking a destroy plan. The first is to use a normal plan to refresh, which will enable evaluation because missing and inconsistent instances will be planned for creation and updates, allowing them to be evaluated. That is not optimal of course, but does revert to the method used by previous Terraform releases until a better method can be implemented. The second change is adding a preDestroyRefresh flag to the planning process. This is checked in any location which evalCheckRules is called, and lets us change the diagnosticSeverity of the output to only be warnings, matching the behavior of a normal refresh plan.
302 lines
9.4 KiB
Go
302 lines
9.4 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
|
|
|
|
// preDestroyRefresh indicates that we are executing the refresh which
|
|
// happens immediately before a destroy plan, which happens to use the
|
|
// normal planing mode so skipPlanChanges cannot be set.
|
|
preDestroyRefresh 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 || b.preDestroyRefresh,
|
|
PlanDestroy: 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,
|
|
preDestroyRefresh: b.preDestroyRefresh,
|
|
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,
|
|
}
|
|
}
|
|
}
|