1package terraform
2
3import (
4	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
5	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
6	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
7	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
8	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
9	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
10)
11
12// ApplyGraphBuilder implements GraphBuilder and is responsible for building
13// a graph for applying a Terraform diff.
14//
15// Because the graph is built from the diff (vs. the config or state),
16// this helps ensure that the apply-time graph doesn't modify any resources
17// that aren't explicitly in the diff. There are other scenarios where the
18// diff can be deviated, so this is just one layer of protection.
19type ApplyGraphBuilder struct {
20	// Config is the configuration tree that the diff was built from.
21	Config *configs.Config
22
23	// Changes describes the changes that we need apply.
24	Changes *plans.Changes
25
26	// State is the current state
27	State *states.State
28
29	// Components is a factory for the plug-in components (providers and
30	// provisioners) available for use.
31	Components contextComponentFactory
32
33	// Schemas is the repository of schemas we will draw from to analyse
34	// the configuration.
35	Schemas *Schemas
36
37	// Targets are resources to target. This is only required to make sure
38	// unnecessary outputs aren't included in the apply graph. The plan
39	// builder successfully handles targeting resources. In the future,
40	// outputs should go into the diff so that this is unnecessary.
41	Targets []addrs.Targetable
42
43	// DisableReduce, if true, will not reduce the graph. Great for testing.
44	DisableReduce bool
45
46	// Destroy, if true, represents a pure destroy operation
47	Destroy bool
48
49	// Validate will do structural validation of the graph.
50	Validate bool
51}
52
53// See GraphBuilder
54func (b *ApplyGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
55	return (&BasicGraphBuilder{
56		Steps:    b.Steps(),
57		Validate: b.Validate,
58		Name:     "ApplyGraphBuilder",
59	}).Build(path)
60}
61
62// See GraphBuilder
63func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
64	// Custom factory for creating providers.
65	concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
66		return &NodeApplyableProvider{
67			NodeAbstractProvider: a,
68		}
69	}
70
71	concreteResource := func(a *NodeAbstractResource) dag.Vertex {
72		return &NodeApplyableResource{
73			NodeAbstractResource: a,
74		}
75	}
76
77	concreteOrphanResource := func(a *NodeAbstractResource) dag.Vertex {
78		return &NodeDestroyResource{
79			NodeAbstractResource: a,
80		}
81	}
82
83	concreteResourceInstance := func(a *NodeAbstractResourceInstance) dag.Vertex {
84		return &NodeApplyableResourceInstance{
85			NodeAbstractResourceInstance: a,
86		}
87	}
88
89	steps := []GraphTransformer{
90		// Creates all the resources represented in the config. During apply,
91		// we use this just to ensure that the whole-resource metadata is
92		// updated to reflect things such as whether the count argument is
93		// set in config, or which provider configuration manages each resource.
94		&ConfigTransformer{
95			Concrete: concreteResource,
96			Config:   b.Config,
97		},
98
99		// Creates all the resource instances represented in the diff, along
100		// with dependency edges against the whole-resource nodes added by
101		// ConfigTransformer above.
102		&DiffTransformer{
103			Concrete: concreteResourceInstance,
104			State:    b.State,
105			Changes:  b.Changes,
106		},
107
108		// Creates extra cleanup nodes for any entire resources that are
109		// no longer present in config, so we can make sure we clean up the
110		// leftover empty resource states after the instances have been
111		// destroyed.
112		// (We don't track this particular type of change in the plan because
113		// it's just cleanup of our own state object, and so doesn't effect
114		// any real remote objects or consumable outputs.)
115		&OrphanResourceTransformer{
116			Concrete: concreteOrphanResource,
117			Config:   b.Config,
118			State:    b.State,
119		},
120
121		// Create orphan output nodes
122		&OrphanOutputTransformer{Config: b.Config, State: b.State},
123
124		// Attach the configuration to any resources
125		&AttachResourceConfigTransformer{Config: b.Config},
126
127		// Attach the state
128		&AttachStateTransformer{State: b.State},
129
130		// Destruction ordering
131		&DestroyEdgeTransformer{
132			Config:  b.Config,
133			State:   b.State,
134			Schemas: b.Schemas,
135		},
136		GraphTransformIf(
137			func() bool { return !b.Destroy },
138			&CBDEdgeTransformer{
139				Config:  b.Config,
140				State:   b.State,
141				Schemas: b.Schemas,
142			},
143		),
144
145		// Provisioner-related transformations
146		&MissingProvisionerTransformer{Provisioners: b.Components.ResourceProvisioners()},
147		&ProvisionerTransformer{},
148
149		// Add root variables
150		&RootVariableTransformer{Config: b.Config},
151
152		// Add the local values
153		&LocalTransformer{Config: b.Config},
154
155		// Add the outputs
156		&OutputTransformer{Config: b.Config},
157
158		// Add module variables
159		&ModuleVariableTransformer{Config: b.Config},
160
161		// add providers
162		TransformProviders(b.Components.ResourceProviders(), concreteProvider, b.Config),
163
164		// Remove modules no longer present in the config
165		&RemovedModuleTransformer{Config: b.Config, State: b.State},
166
167		// Must attach schemas before ReferenceTransformer so that we can
168		// analyze the configuration to find references.
169		&AttachSchemaTransformer{Schemas: b.Schemas},
170
171		// Connect references so ordering is correct
172		&ReferenceTransformer{},
173
174		// Handle destroy time transformations for output and local values.
175		// Reverse the edges from outputs and locals, so that
176		// interpolations don't fail during destroy.
177		// Create a destroy node for outputs to remove them from the state.
178		// Prune unreferenced values, which may have interpolations that can't
179		// be resolved.
180		GraphTransformIf(
181			func() bool { return b.Destroy },
182			GraphTransformMulti(
183				&DestroyValueReferenceTransformer{},
184				&DestroyOutputTransformer{},
185				&PruneUnusedValuesTransformer{},
186			),
187		),
188
189		// Add the node to fix the state count boundaries
190		&CountBoundaryTransformer{
191			Config: b.Config,
192		},
193
194		// Target
195		&TargetsTransformer{Targets: b.Targets},
196
197		// Close opened plugin connections
198		&CloseProviderTransformer{},
199		&CloseProvisionerTransformer{},
200
201		// Single root
202		&RootTransformer{},
203	}
204
205	if !b.DisableReduce {
206		// Perform the transitive reduction to make our graph a bit
207		// more sane if possible (it usually is possible).
208		steps = append(steps, &TransitiveReductionTransformer{})
209	}
210
211	return steps
212}
213