opentofu/terraform/graph_builder_plan.go
Chris Marchesi 2802d319d2 core: Move CountBoundaryTransformer to the plan graph builder
This fixes interpolation issues on grandchild data sources that have
multiple instances (ie: counts). For example, baz depends on bar, which
depends on foo.

In this instance, after an initial TF run is done and state is saved,
the next refresh/plan is not properly transformed, and instead of the
graph/state coming through as data.x.bar.0, it comes through as
data.x.bar.  This breaks interpolations that rely on splat operators -
ie: data.x.bar.*.out.
2017-04-19 16:56:54 -07:00

165 lines
4.2 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{},
// Target
&TargetsTransformer{Targets: b.Targets},
// Close opened plugin connections
&CloseProviderTransformer{},
&CloseProvisionerTransformer{},
// Add the node to fix the state count boundaries
&CountBoundaryTransformer{},
// 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,
}
}
}