github.com/opentofu/opentofu@v1.7.1/internal/tofu/graph_builder_plan.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"log"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  	"github.com/opentofu/opentofu/internal/configs"
    13  	"github.com/opentofu/opentofu/internal/dag"
    14  	"github.com/opentofu/opentofu/internal/states"
    15  	"github.com/opentofu/opentofu/internal/tfdiags"
    16  )
    17  
    18  // PlanGraphBuilder is a GraphBuilder implementation that builds a graph for
    19  // planning and for other "plan-like" operations which don't require an
    20  // already-calculated plan as input.
    21  //
    22  // Unlike the apply graph builder, this graph builder:
    23  //
    24  //   - Makes its decisions primarily based on the given configuration, which
    25  //     represents the desired state.
    26  //
    27  //   - Ignores certain lifecycle concerns like create_before_destroy, because
    28  //     those are only important once we already know what action we're planning
    29  //     to take against a particular resource instance.
    30  type PlanGraphBuilder struct {
    31  	// Config is the configuration tree to build a plan from.
    32  	Config *configs.Config
    33  
    34  	// State is the current state
    35  	State *states.State
    36  
    37  	// RootVariableValues are the raw input values for root input variables
    38  	// given by the caller, which we'll resolve into final values as part
    39  	// of the plan walk.
    40  	RootVariableValues InputValues
    41  
    42  	// Plugins is a library of plug-in components (providers and
    43  	// provisioners) available for use.
    44  	Plugins *contextPlugins
    45  
    46  	// Targets are resources to target
    47  	Targets []addrs.Targetable
    48  
    49  	// ForceReplace are resource instances where if we would normally have
    50  	// generated a NoOp or Update action then we'll force generating a replace
    51  	// action instead. Create and Delete actions are not affected.
    52  	ForceReplace []addrs.AbsResourceInstance
    53  
    54  	// skipRefresh indicates that we should skip refreshing managed resources
    55  	skipRefresh bool
    56  
    57  	// preDestroyRefresh indicates that we are executing the refresh which
    58  	// happens immediately before a destroy plan, which happens to use the
    59  	// normal planing mode so skipPlanChanges cannot be set.
    60  	preDestroyRefresh bool
    61  
    62  	// skipPlanChanges indicates that we should skip the step of comparing
    63  	// prior state with configuration and generating planned changes to
    64  	// resource instances. (This is for the "refresh only" planning mode,
    65  	// where we _only_ do the refresh step.)
    66  	skipPlanChanges bool
    67  
    68  	ConcreteProvider                ConcreteProviderNodeFunc
    69  	ConcreteResource                ConcreteResourceNodeFunc
    70  	ConcreteResourceInstance        ConcreteResourceInstanceNodeFunc
    71  	ConcreteResourceOrphan          ConcreteResourceInstanceNodeFunc
    72  	ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc
    73  	ConcreteModule                  ConcreteModuleNodeFunc
    74  
    75  	// Plan Operation this graph will be used for.
    76  	Operation walkOperation
    77  
    78  	// ExternalReferences allows the external caller to pass in references to
    79  	// nodes that should not be pruned even if they are not referenced within
    80  	// the actual graph.
    81  	ExternalReferences []*addrs.Reference
    82  
    83  	// ImportTargets are the list of resources to import.
    84  	ImportTargets []*ImportTarget
    85  
    86  	// EndpointsToRemove are the list of resources and modules to forget from
    87  	// the state.
    88  	EndpointsToRemove []addrs.ConfigRemovable
    89  
    90  	// GenerateConfig tells OpenTofu where to write and generated config for
    91  	// any import targets that do not already have configuration.
    92  	//
    93  	// If empty, then config will not be generated.
    94  	GenerateConfigPath string
    95  }
    96  
    97  // See GraphBuilder
    98  func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
    99  	log.Printf("[TRACE] building graph for %s", b.Operation)
   100  	return (&BasicGraphBuilder{
   101  		Steps: b.Steps(),
   102  		Name:  "PlanGraphBuilder",
   103  	}).Build(path)
   104  }
   105  
   106  // See GraphBuilder
   107  func (b *PlanGraphBuilder) Steps() []GraphTransformer {
   108  	switch b.Operation {
   109  	case walkPlan:
   110  		b.initPlan()
   111  	case walkPlanDestroy:
   112  		b.initDestroy()
   113  	case walkValidate:
   114  		b.initValidate()
   115  	case walkImport:
   116  		b.initImport()
   117  	default:
   118  		panic("invalid plan operation: " + b.Operation.String())
   119  	}
   120  
   121  	steps := []GraphTransformer{
   122  		// Creates all the resources represented in the config
   123  		&ConfigTransformer{
   124  			Concrete: b.ConcreteResource,
   125  			Config:   b.Config,
   126  
   127  			// Resources are not added from the config on destroy.
   128  			skip: b.Operation == walkPlanDestroy,
   129  
   130  			importTargets: b.ImportTargets,
   131  
   132  			// We only want to generate config during a plan operation.
   133  			generateConfigPathForImportTargets: b.GenerateConfigPath,
   134  		},
   135  
   136  		// Add dynamic values
   137  		&RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues},
   138  		&ModuleVariableTransformer{Config: b.Config},
   139  		&LocalTransformer{Config: b.Config},
   140  		&OutputTransformer{
   141  			Config:      b.Config,
   142  			RefreshOnly: b.skipPlanChanges || b.preDestroyRefresh,
   143  			Destroying:  b.Operation == walkPlanDestroy,
   144  
   145  			// NOTE: We currently treat anything built with the plan graph
   146  			// builder as "planning" for our purposes here, because we share
   147  			// the same graph node implementation between all of the walk
   148  			// types and so the pre-planning walks still think they are
   149  			// producing a plan even though we immediately discard it.
   150  			Planning: true,
   151  		},
   152  
   153  		// Add nodes and edges for the check block assertions. Check block data
   154  		// sources were added earlier.
   155  		&checkTransformer{
   156  			Config:    b.Config,
   157  			Operation: b.Operation,
   158  		},
   159  
   160  		// Add orphan resources
   161  		&OrphanResourceInstanceTransformer{
   162  			Concrete: b.ConcreteResourceOrphan,
   163  			State:    b.State,
   164  			Config:   b.Config,
   165  			skip:     b.Operation == walkPlanDestroy,
   166  		},
   167  
   168  		// We also need nodes for any deposed instance objects present in the
   169  		// state, so we can plan to destroy them. (During plan this will
   170  		// intentionally skip creating nodes for _current_ objects, since
   171  		// ConfigTransformer created nodes that will do that during
   172  		// DynamicExpand.)
   173  		&StateTransformer{
   174  			ConcreteCurrent: b.ConcreteResourceInstance,
   175  			ConcreteDeposed: b.ConcreteResourceInstanceDeposed,
   176  			State:           b.State,
   177  		},
   178  
   179  		// Attach the state
   180  		&AttachStateTransformer{State: b.State},
   181  
   182  		// Create orphan output nodes
   183  		&OrphanOutputTransformer{
   184  			Config:   b.Config,
   185  			State:    b.State,
   186  			Planning: true,
   187  		},
   188  
   189  		// Attach the configuration to any resources
   190  		&AttachResourceConfigTransformer{Config: b.Config},
   191  
   192  		// add providers
   193  		transformProviders(b.ConcreteProvider, b.Config),
   194  
   195  		// Remove modules no longer present in the config
   196  		&RemovedModuleTransformer{Config: b.Config, State: b.State},
   197  
   198  		// Must attach schemas before ReferenceTransformer so that we can
   199  		// analyze the configuration to find references.
   200  		&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
   201  
   202  		// After schema transformer, we can add function references
   203  		&ProviderFunctionTransformer{Config: b.Config},
   204  
   205  		// Remove unused providers and proxies
   206  		&PruneProviderTransformer{},
   207  
   208  		// Create expansion nodes for all of the module calls. This must
   209  		// come after all other transformers that create nodes representing
   210  		// objects that can belong to modules.
   211  		&ModuleExpansionTransformer{Concrete: b.ConcreteModule, Config: b.Config},
   212  
   213  		// Plug in any external references.
   214  		&ExternalReferenceTransformer{
   215  			ExternalReferences: b.ExternalReferences,
   216  		},
   217  
   218  		&ReferenceTransformer{},
   219  
   220  		&AttachDependenciesTransformer{},
   221  
   222  		// Make sure data sources are aware of any depends_on from the
   223  		// configuration
   224  		&attachDataResourceDependsOnTransformer{},
   225  
   226  		// DestroyEdgeTransformer is only required during a plan so that the
   227  		// TargetsTransformer can determine which nodes to keep in the graph.
   228  		&DestroyEdgeTransformer{
   229  			Operation: b.Operation,
   230  		},
   231  
   232  		&pruneUnusedNodesTransformer{
   233  			skip: b.Operation != walkPlanDestroy,
   234  		},
   235  
   236  		// Target
   237  		&TargetsTransformer{Targets: b.Targets},
   238  
   239  		// Detect when create_before_destroy must be forced on for a particular
   240  		// node due to dependency edges, to avoid graph cycles during apply.
   241  		&ForcedCBDTransformer{},
   242  
   243  		// Close opened plugin connections
   244  		&CloseProviderTransformer{},
   245  
   246  		// Close the root module
   247  		&CloseRootModuleTransformer{},
   248  
   249  		// Perform the transitive reduction to make our graph a bit
   250  		// more understandable if possible (it usually is possible).
   251  		&TransitiveReductionTransformer{},
   252  	}
   253  
   254  	return steps
   255  }
   256  
   257  func (b *PlanGraphBuilder) initPlan() {
   258  	b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
   259  		return &NodeApplyableProvider{
   260  			NodeAbstractProvider: a,
   261  		}
   262  	}
   263  
   264  	b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
   265  		return &nodeExpandPlannableResource{
   266  			NodeAbstractResource: a,
   267  			skipRefresh:          b.skipRefresh,
   268  			skipPlanChanges:      b.skipPlanChanges,
   269  			preDestroyRefresh:    b.preDestroyRefresh,
   270  			forceReplace:         b.ForceReplace,
   271  		}
   272  	}
   273  
   274  	b.ConcreteResourceOrphan = func(a *NodeAbstractResourceInstance) dag.Vertex {
   275  		return &NodePlannableResourceInstanceOrphan{
   276  			NodeAbstractResourceInstance: a,
   277  			skipRefresh:                  b.skipRefresh,
   278  			skipPlanChanges:              b.skipPlanChanges,
   279  			EndpointsToRemove:            b.EndpointsToRemove,
   280  		}
   281  	}
   282  
   283  	b.ConcreteResourceInstanceDeposed = func(a *NodeAbstractResourceInstance, key states.DeposedKey) dag.Vertex {
   284  		return &NodePlanDeposedResourceInstanceObject{
   285  			NodeAbstractResourceInstance: a,
   286  			DeposedKey:                   key,
   287  
   288  			skipRefresh:       b.skipRefresh,
   289  			skipPlanChanges:   b.skipPlanChanges,
   290  			EndpointsToRemove: b.EndpointsToRemove,
   291  		}
   292  	}
   293  }
   294  
   295  func (b *PlanGraphBuilder) initDestroy() {
   296  	b.initPlan()
   297  
   298  	b.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
   299  		return &NodePlanDestroyableResourceInstance{
   300  			NodeAbstractResourceInstance: a,
   301  			skipRefresh:                  b.skipRefresh,
   302  		}
   303  	}
   304  }
   305  
   306  func (b *PlanGraphBuilder) initValidate() {
   307  	// Set the provider to the normal provider. This will ask for input.
   308  	b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
   309  		return &NodeApplyableProvider{
   310  			NodeAbstractProvider: a,
   311  		}
   312  	}
   313  
   314  	b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
   315  		return &NodeValidatableResource{
   316  			NodeAbstractResource: a,
   317  		}
   318  	}
   319  
   320  	b.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
   321  		return &nodeValidateModule{
   322  			nodeExpandModule: *n,
   323  		}
   324  	}
   325  }
   326  
   327  func (b *PlanGraphBuilder) initImport() {
   328  	b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
   329  		return &NodeApplyableProvider{
   330  			NodeAbstractProvider: a,
   331  		}
   332  	}
   333  
   334  	b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
   335  		return &nodeExpandPlannableResource{
   336  			NodeAbstractResource: a,
   337  
   338  			// For now we always skip planning changes for import, since we are
   339  			// not going to combine importing with other changes. This is
   340  			// temporary to try and maintain existing import behaviors, but
   341  			// planning will need to be allowed for more complex configurations.
   342  			skipPlanChanges: true,
   343  
   344  			// We also skip refresh for now, since the plan output is written
   345  			// as the new state, and users are not expecting the import process
   346  			// to update any other instances in state.
   347  			skipRefresh: true,
   348  		}
   349  	}
   350  }