// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package tofu import ( "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs" "github.com/opentofu/opentofu/internal/dag" "github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/states" "github.com/opentofu/opentofu/internal/tfdiags" ) // ApplyGraphBuilder implements GraphBuilder and is responsible for building // a graph for applying a OpenTofu diff. // // Because the graph is built from the diff (vs. the config or state), // this helps ensure that the apply-time graph doesn't modify any resources // that aren't explicitly in the diff. There are other scenarios where the // diff can be deviated, so this is just one layer of protection. type ApplyGraphBuilder struct { // Config is the configuration tree that the diff was built from. Config *configs.Config // Changes describes the changes that we need apply. Changes *plans.Changes // State is the current state State *states.State // RootVariableValues are the root module input variables captured as // part of the plan object, which we must reproduce in the apply step // to get a consistent result. RootVariableValues InputValues // Plugins is a library of the plug-in components (providers and // provisioners) available for use. Plugins *contextPlugins // Targets are resources to target. This is only required to make sure // unnecessary outputs aren't included in the apply graph. The plan // builder successfully handles targeting resources. In the future, // outputs should go into the diff so that this is unnecessary. Targets []addrs.Targetable // ForceReplace are the resource instance addresses that the user // requested to force replacement for when creating the plan, if any. // The apply step refers to these as part of verifying that the planned // actions remain consistent between plan and apply. ForceReplace []addrs.AbsResourceInstance // Plan Operation this graph will be used for. Operation walkOperation // ExternalReferences allows the external caller to pass in references to // nodes that should not be pruned even if they are not referenced within // the actual graph. ExternalReferences []*addrs.Reference } // See GraphBuilder func (b *ApplyGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) { return (&BasicGraphBuilder{ Steps: b.Steps(), Name: "ApplyGraphBuilder", }).Build(path) } // See GraphBuilder func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // Custom factory for creating providers. concreteProvider := func(a *NodeAbstractProvider) dag.Vertex { return &NodeApplyableProvider{ NodeAbstractProvider: a, } } concreteResource := func(a *NodeAbstractResource) dag.Vertex { return &nodeExpandApplyableResource{ NodeAbstractResource: a, } } concreteResourceInstance := func(a *NodeAbstractResourceInstance) dag.Vertex { return &NodeApplyableResourceInstance{ NodeAbstractResourceInstance: a, forceReplace: b.ForceReplace, } } steps := []GraphTransformer{ // Creates all the resources represented in the config. During apply, // we use this just to ensure that the whole-resource metadata is // updated to reflect things such as whether the count argument is // set in config, or which provider configuration manages each resource. &ConfigTransformer{ Concrete: concreteResource, Config: b.Config, }, // Add dynamic values &RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues}, &ModuleVariableTransformer{Config: b.Config}, &LocalTransformer{Config: b.Config}, &OutputTransformer{ Config: b.Config, Destroying: b.Operation == walkDestroy, }, // Creates all the resource instances represented in the diff, along // with dependency edges against the whole-resource nodes added by // ConfigTransformer above. &DiffTransformer{ Concrete: concreteResourceInstance, State: b.State, Changes: b.Changes, Config: b.Config, }, // Add nodes and edges for check block assertions. Check block data // sources were added earlier. &checkTransformer{ Config: b.Config, Operation: b.Operation, }, // 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(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}, // After schema transformer, we can add function references &ProviderFunctionTransformer{Config: b.Config}, // Remove unused providers and proxies &PruneProviderTransformer{}, // 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{Config: b.Config}, // Plug in any external references. &ExternalReferenceTransformer{ ExternalReferences: b.ExternalReferences, }, // Connect references so ordering is correct &ReferenceTransformer{}, &AttachDependenciesTransformer{}, // Nested data blocks should be loaded after every other resource has // done its thing. &checkStartTransformer{Config: b.Config, Operation: b.Operation}, // Detect when create_before_destroy must be forced on for a particular // node due to dependency edges, to avoid graph cycles during apply. &ForcedCBDTransformer{}, // Destruction ordering &DestroyEdgeTransformer{ Changes: b.Changes, Operation: b.Operation, }, &CBDEdgeTransformer{ Config: b.Config, State: b.State, }, // We need to remove configuration nodes that are not used at all, as // they may not be able to evaluate, especially during destroy. // These include variables, locals, and instance expanders. &pruneUnusedNodesTransformer{}, // Target &TargetsTransformer{Targets: b.Targets}, // Close opened plugin connections &CloseProviderTransformer{}, // close the root module &CloseRootModuleTransformer{ RootConfig: b.Config, }, // Perform the transitive reduction to make our graph a bit // more understandable if possible (it usually is possible). &TransitiveReductionTransformer{}, } return steps }