opentofu/terraform/graph_builder_plan.go
Martin Atkins a8c58b081c core: -target option to also select resources in descendant modules
Previously the behavior for -target when given a module address was to
target only resources directly within that module, ignoring any resources
defined in child modules.

This behavior turned out to be counter-intuitive, since users expected
the -target address to be interpreted hierarchically.

We'll now use the new "Contains" function for addresses, which provides
a hierarchical "containment" concept that is more consistent with user
expectations. In particular, it allows module.foo to match
module.foo.module.bar.aws_instance.baz, where before that would not have
been true.

Since Contains isn't commutative (unlike Equals) this requires some
special handling for targeting specific indices. When given an argument
like -target=aws_instance.foo[0], the initial graph construction (for
both plan and refresh) is for the resource nodes from configuration, which
have not yet been expanded to separate indexed instances. Thus we need
to do the first pass of TargetsTransformer in mode where indices are
ignored, with the work then completed by the DynamicExpand method which
re-applies the TargetsTransformer in index-sensitive mode.

This is a breaking change for anyone depending on the previous behavior
of -target, since it will now select more resources than before. There is
no way provided to obtain the previous behavior. Eventually we may support
negative targeting, which could then combine with positive targets to
regain the previous behavior as an explicit choice.
2017-06-16 16:36:08 -07:00

173 lines
4.4 KiB
Go

package terraform
import (
"sync"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// PlanGraphBuilder implements GraphBuilder and is responsible for building
// a graph for planning (creating a Terraform Diff).
//
// The primary difference between this graph and others:
//
// * Based on the config since it represents the target state
//
// * Ignores lifecycle options since no lifecycle events occur here. This
// simplifies the graph significantly since complex transforms such as
// create-before-destroy can be completely ignored.
//
type PlanGraphBuilder struct {
// Module is the root module for the graph to build.
Module *module.Tree
// State is the current state
State *State
// Providers is the list of providers supported.
Providers []string
// Provisioners is the list of provisioners supported.
Provisioners []string
// Targets are resources to target
Targets []string
// DisableReduce, if true, will not reduce the graph. Great for testing.
DisableReduce bool
// Validate will do structural validation of the graph.
Validate bool
// CustomConcrete can be set to customize the node types created
// for various parts of the plan. This is useful in order to customize
// the plan behavior.
CustomConcrete bool
ConcreteProvider ConcreteProviderNodeFunc
ConcreteResource ConcreteResourceNodeFunc
ConcreteResourceOrphan ConcreteResourceNodeFunc
once sync.Once
}
// See GraphBuilder
func (b *PlanGraphBuilder) Build(path []string) (*Graph, error) {
return (&BasicGraphBuilder{
Steps: b.Steps(),
Validate: b.Validate,
Name: "PlanGraphBuilder",
}).Build(path)
}
// See GraphBuilder
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
b.once.Do(b.init)
steps := []GraphTransformer{
// Creates all the resources represented in the config
&ConfigTransformer{
Concrete: b.ConcreteResource,
Module: b.Module,
},
// Add the outputs
&OutputTransformer{Module: b.Module},
// Add orphan resources
&OrphanResourceTransformer{
Concrete: b.ConcreteResourceOrphan,
State: b.State,
Module: b.Module,
},
// Attach the configuration to any resources
&AttachResourceConfigTransformer{Module: b.Module},
// Attach the state
&AttachStateTransformer{State: b.State},
// Add root variables
&RootVariableTransformer{Module: b.Module},
// Create all the providers
&MissingProviderTransformer{Providers: b.Providers, Concrete: b.ConcreteProvider},
&ProviderTransformer{},
&DisableProviderTransformer{},
&ParentProviderTransformer{},
&AttachProviderConfigTransformer{Module: b.Module},
// Provisioner-related transformations. Only add these if requested.
GraphTransformIf(
func() bool { return b.Provisioners != nil },
GraphTransformMulti(
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
&ProvisionerTransformer{},
),
),
// Add module variables
&ModuleVariableTransformer{Module: b.Module},
// Connect so that the references are ready for targeting. We'll
// have to connect again later for providers and so on.
&ReferenceTransformer{},
// Add the node to fix the state count boundaries
&CountBoundaryTransformer{},
// Target
&TargetsTransformer{
Targets: b.Targets,
// Resource nodes from config have not yet been expanded for
// "count", so we must apply targeting without indices. Exact
// targeting will be dealt with later when these resources
// DynamicExpand.
IgnoreIndices: true,
},
// Close opened plugin connections
&CloseProviderTransformer{},
&CloseProvisionerTransformer{},
// Single root
&RootTransformer{},
}
if !b.DisableReduce {
// Perform the transitive reduction to make our graph a bit
// more sane if possible (it usually is possible).
steps = append(steps, &TransitiveReductionTransformer{})
}
return steps
}
func (b *PlanGraphBuilder) init() {
// Do nothing if the user requests customizing the fields
if b.CustomConcrete {
return
}
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResource{
NodeAbstractCountResource: &NodeAbstractCountResource{
NodeAbstractResource: a,
},
}
}
b.ConcreteResourceOrphan = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResourceOrphan{
NodeAbstractResource: a,
}
}
}