github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_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  	"bytes"
    10  	"fmt"
    11  	"log"
    12  	"sort"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/hashicorp/hcl/v2"
    17  
    18  	"github.com/zclconf/go-cty/cty"
    19  
    20  	"github.com/opentofu/opentofu/internal/addrs"
    21  	"github.com/opentofu/opentofu/internal/configs"
    22  	"github.com/opentofu/opentofu/internal/instances"
    23  	"github.com/opentofu/opentofu/internal/lang/globalref"
    24  	"github.com/opentofu/opentofu/internal/plans"
    25  	"github.com/opentofu/opentofu/internal/refactoring"
    26  	"github.com/opentofu/opentofu/internal/states"
    27  	"github.com/opentofu/opentofu/internal/tfdiags"
    28  )
    29  
    30  // PlanOpts are the various options that affect the details of how OpenTofu
    31  // will build a plan.
    32  type PlanOpts struct {
    33  	// Mode defines what variety of plan the caller wishes to create.
    34  	// Refer to the documentation of the plans.Mode type and its values
    35  	// for more information.
    36  	Mode plans.Mode
    37  
    38  	// SkipRefresh specifies to trust that the current values for managed
    39  	// resource instances in the prior state are accurate and to therefore
    40  	// disable the usual step of fetching updated values for each resource
    41  	// instance using its corresponding provider.
    42  	SkipRefresh bool
    43  
    44  	// PreDestroyRefresh indicated that this is being passed to a plan used to
    45  	// refresh the state immediately before a destroy plan.
    46  	// FIXME: This is a temporary fix to allow the pre-destroy refresh to
    47  	// succeed. The refreshing operation during destroy must be a special case,
    48  	// which can allow for missing instances in the state, and avoid blocking
    49  	// on failing condition tests. The destroy plan itself should be
    50  	// responsible for this special case of refreshing, and the separate
    51  	// pre-destroy plan removed entirely.
    52  	PreDestroyRefresh bool
    53  
    54  	// SetVariables are the raw values for root module variables as provided
    55  	// by the user who is requesting the run, prior to any normalization or
    56  	// substitution of defaults. See the documentation for the InputValue
    57  	// type for more information on how to correctly populate this.
    58  	SetVariables InputValues
    59  
    60  	// If Targets has a non-zero length then it activates targeted planning
    61  	// mode, where OpenTofu will take actions only for resource instances
    62  	// mentioned in this set and any other objects those resource instances
    63  	// depend on.
    64  	//
    65  	// Targeted planning mode is intended for exceptional use only,
    66  	// and so populating this field will cause OpenTofu to generate extra
    67  	// warnings as part of the planning result.
    68  	Targets []addrs.Targetable
    69  
    70  	// ForceReplace is a set of resource instance addresses whose corresponding
    71  	// objects should be forced planned for replacement if the provider's
    72  	// plan would otherwise have been to either update the object in-place or
    73  	// to take no action on it at all.
    74  	//
    75  	// A typical use of this argument is to ask OpenTofu to replace an object
    76  	// which the user has determined is somehow degraded (via information from
    77  	// outside of OpenTofu), thereby hopefully replacing it with a
    78  	// fully-functional new object.
    79  	ForceReplace []addrs.AbsResourceInstance
    80  
    81  	// ExternalReferences allows the external caller to pass in references to
    82  	// nodes that should not be pruned even if they are not referenced within
    83  	// the actual graph.
    84  	ExternalReferences []*addrs.Reference
    85  
    86  	// ImportTargets is a list of target resources to import. These resources
    87  	// will be added to the plan graph.
    88  	ImportTargets []*ImportTarget
    89  
    90  	// EndpointsToRemove are the list of resources and modules to forget from
    91  	// the state.
    92  	EndpointsToRemove []addrs.ConfigRemovable
    93  
    94  	// GenerateConfig tells OpenTofu where to write any generated configuration
    95  	// for any ImportTargets that do not have configuration already.
    96  	//
    97  	// If empty, then no config will be generated.
    98  	GenerateConfigPath string
    99  }
   100  
   101  // Plan generates an execution plan by comparing the given configuration
   102  // with the given previous run state.
   103  //
   104  // The given planning options allow control of various other details of the
   105  // planning process that are not represented directly in the configuration.
   106  // You can use tofu.DefaultPlanOpts to generate a normal plan with no
   107  // special options.
   108  //
   109  // If the returned diagnostics contains no errors then the returned plan is
   110  // applyable, although OpenTofu cannot guarantee that applying it will fully
   111  // succeed. If the returned diagnostics contains errors but this method
   112  // still returns a non-nil Plan then the plan describes the subset of actions
   113  // planned so far, which is not safe to apply but could potentially be used
   114  // by the UI layer to give extra context to support understanding of the
   115  // returned error messages.
   116  func (c *Context) Plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   117  	defer c.acquireRun("plan")()
   118  	var diags tfdiags.Diagnostics
   119  
   120  	// Save the downstream functions from needing to deal with these broken situations.
   121  	// No real callers should rely on these, but we have a bunch of old and
   122  	// sloppy tests that don't always populate arguments properly.
   123  	if config == nil {
   124  		config = configs.NewEmptyConfig()
   125  	}
   126  	if prevRunState == nil {
   127  		prevRunState = states.NewState()
   128  	}
   129  	if opts == nil {
   130  		opts = &PlanOpts{
   131  			Mode: plans.NormalMode,
   132  		}
   133  	}
   134  
   135  	moreDiags := c.checkConfigDependencies(config)
   136  	diags = diags.Append(moreDiags)
   137  	// If required dependencies are not available then we'll bail early since
   138  	// otherwise we're likely to just see a bunch of other errors related to
   139  	// incompatibilities, which could be overwhelming for the user.
   140  	if diags.HasErrors() {
   141  		return nil, diags
   142  	}
   143  
   144  	switch opts.Mode {
   145  	case plans.NormalMode, plans.DestroyMode:
   146  		// OK
   147  	case plans.RefreshOnlyMode:
   148  		if opts.SkipRefresh {
   149  			// The CLI layer (and other similar callers) should prevent this
   150  			// combination of options.
   151  			diags = diags.Append(tfdiags.Sourceless(
   152  				tfdiags.Error,
   153  				"Incompatible plan options",
   154  				"Cannot skip refreshing in refresh-only mode. This is a bug in OpenTofu.",
   155  			))
   156  			return nil, diags
   157  		}
   158  	default:
   159  		// The CLI layer (and other similar callers) should not try to
   160  		// create a context for a mode that OpenTofu Core doesn't support.
   161  		diags = diags.Append(tfdiags.Sourceless(
   162  			tfdiags.Error,
   163  			"Unsupported plan mode",
   164  			fmt.Sprintf("OpenTofu Core doesn't know how to handle plan mode %s. This is a bug in OpenTofu.", opts.Mode),
   165  		))
   166  		return nil, diags
   167  	}
   168  	if len(opts.ForceReplace) > 0 && opts.Mode != plans.NormalMode {
   169  		// The other modes don't generate no-op or update actions that we might
   170  		// upgrade to be "replace", so doesn't make sense to combine those.
   171  		diags = diags.Append(tfdiags.Sourceless(
   172  			tfdiags.Error,
   173  			"Unsupported plan mode",
   174  			"Forcing resource instance replacement (with -replace=...) is allowed only in normal planning mode.",
   175  		))
   176  		return nil, diags
   177  	}
   178  
   179  	// By the time we get here, we should have values defined for all of
   180  	// the root module variables, even if some of them are "unknown". It's the
   181  	// caller's responsibility to have already handled the decoding of these
   182  	// from the various ways the CLI allows them to be set and to produce
   183  	// user-friendly error messages if they are not all present, and so
   184  	// the error message from checkInputVariables should never be seen and
   185  	// includes language asking the user to report a bug.
   186  	varDiags := checkInputVariables(config.Module.Variables, opts.SetVariables)
   187  	diags = diags.Append(varDiags)
   188  
   189  	if len(opts.Targets) > 0 {
   190  		diags = diags.Append(tfdiags.Sourceless(
   191  			tfdiags.Warning,
   192  			"Resource targeting is in effect",
   193  			`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
   194  
   195  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
   196  		))
   197  	}
   198  
   199  	var plan *plans.Plan
   200  	var planDiags tfdiags.Diagnostics
   201  	switch opts.Mode {
   202  	case plans.NormalMode:
   203  		plan, planDiags = c.plan(config, prevRunState, opts)
   204  	case plans.DestroyMode:
   205  		plan, planDiags = c.destroyPlan(config, prevRunState, opts)
   206  	case plans.RefreshOnlyMode:
   207  		plan, planDiags = c.refreshOnlyPlan(config, prevRunState, opts)
   208  	default:
   209  		panic(fmt.Sprintf("unsupported plan mode %s", opts.Mode))
   210  	}
   211  	diags = diags.Append(planDiags)
   212  	// NOTE: We're intentionally not returning early when diags.HasErrors
   213  	// here because we'll still populate other metadata below on a best-effort
   214  	// basis to try to give the UI some extra context to return alongside the
   215  	// error messages.
   216  
   217  	// convert the variables into the format expected for the plan
   218  	varVals := make(map[string]plans.DynamicValue, len(opts.SetVariables))
   219  	for k, iv := range opts.SetVariables {
   220  		if iv.Value == cty.NilVal {
   221  			continue // We only record values that the caller actually set
   222  		}
   223  
   224  		// We use cty.DynamicPseudoType here so that we'll save both the
   225  		// value _and_ its dynamic type in the plan, so we can recover
   226  		// exactly the same value later.
   227  		dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType)
   228  		if err != nil {
   229  			diags = diags.Append(tfdiags.Sourceless(
   230  				tfdiags.Error,
   231  				"Failed to prepare variable value for plan",
   232  				fmt.Sprintf("The value for variable %q could not be serialized to store in the plan: %s.", k, err),
   233  			))
   234  			continue
   235  		}
   236  		varVals[k] = dv
   237  	}
   238  
   239  	// insert the run-specific data from the context into the plan; variables,
   240  	// targets and provider SHAs.
   241  	if plan != nil {
   242  		plan.VariableValues = varVals
   243  		plan.TargetAddrs = opts.Targets
   244  	} else if !diags.HasErrors() {
   245  		panic("nil plan but no errors")
   246  	}
   247  
   248  	if plan != nil {
   249  		relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, plan)
   250  		diags = diags.Append(rDiags)
   251  		plan.RelevantAttributes = relevantAttrs
   252  	}
   253  
   254  	if diags.HasErrors() {
   255  		// We can't proceed further with an invalid plan, because an invalid
   256  		// plan isn't applyable by definition.
   257  		if plan != nil {
   258  			// We'll explicitly mark our plan as errored so that it can't
   259  			// be accidentally applied even though it's incomplete.
   260  			plan.Errored = true
   261  		}
   262  		return plan, diags
   263  	}
   264  
   265  	diags = diags.Append(c.checkApplyGraph(plan, config))
   266  
   267  	return plan, diags
   268  }
   269  
   270  // checkApplyGraph builds the apply graph out of the current plan to
   271  // check for any errors that may arise once the planned changes are added to
   272  // the graph. This allows tofu to report errors (mostly cycles) during
   273  // plan that would otherwise only crop up during apply
   274  func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdiags.Diagnostics {
   275  	if plan.Changes.Empty() {
   276  		log.Println("[DEBUG] no planned changes, skipping apply graph check")
   277  		return nil
   278  	}
   279  	log.Println("[DEBUG] building apply graph to check for errors")
   280  	_, _, diags := c.applyGraph(plan, config, true)
   281  	return diags
   282  }
   283  
   284  var DefaultPlanOpts = &PlanOpts{
   285  	Mode: plans.NormalMode,
   286  }
   287  
   288  // SimplePlanOpts is a constructor to help with creating "simple" values of
   289  // PlanOpts which only specify a mode and input variables.
   290  //
   291  // This helper function is primarily intended for use in straightforward
   292  // tests that don't need any of the more "esoteric" planning options. For
   293  // handling real user requests to run OpenTofu, it'd probably be better
   294  // to construct a *PlanOpts value directly and provide a way for the user
   295  // to set values for all of its fields.
   296  //
   297  // The "mode" and "setVariables" arguments become the values of the "Mode"
   298  // and "SetVariables" fields in the result. Refer to the PlanOpts type
   299  // documentation to learn about the meanings of those fields.
   300  func SimplePlanOpts(mode plans.Mode, setVariables InputValues) *PlanOpts {
   301  	return &PlanOpts{
   302  		Mode:         mode,
   303  		SetVariables: setVariables,
   304  	}
   305  }
   306  
   307  func (c *Context) plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   308  	var diags tfdiags.Diagnostics
   309  
   310  	if opts.Mode != plans.NormalMode {
   311  		panic(fmt.Sprintf("called Context.plan with %s", opts.Mode))
   312  	}
   313  
   314  	opts.ImportTargets = c.findImportTargets(config)
   315  	importTargetDiags := c.validateImportTargets(config, opts.ImportTargets, opts.GenerateConfigPath)
   316  	diags = diags.Append(importTargetDiags)
   317  	if diags.HasErrors() {
   318  		return nil, diags
   319  	}
   320  
   321  	var endpointsToRemoveDiags tfdiags.Diagnostics
   322  	opts.EndpointsToRemove, endpointsToRemoveDiags = refactoring.GetEndpointsToRemove(config)
   323  	diags = diags.Append(endpointsToRemoveDiags)
   324  
   325  	if diags.HasErrors() {
   326  		return nil, diags
   327  	}
   328  
   329  	plan, walkDiags := c.planWalk(config, prevRunState, opts)
   330  	diags = diags.Append(walkDiags)
   331  
   332  	return plan, diags
   333  }
   334  
   335  func (c *Context) refreshOnlyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   336  	var diags tfdiags.Diagnostics
   337  
   338  	if opts.Mode != plans.RefreshOnlyMode {
   339  		panic(fmt.Sprintf("called Context.refreshOnlyPlan with %s", opts.Mode))
   340  	}
   341  
   342  	plan, walkDiags := c.planWalk(config, prevRunState, opts)
   343  	diags = diags.Append(walkDiags)
   344  	if diags.HasErrors() {
   345  		// Non-nil plan along with errors indicates a non-applyable partial
   346  		// plan that's only suitable to be shown to the user as extra context
   347  		// to help understand the errors.
   348  		return plan, diags
   349  	}
   350  
   351  	// If the graph builder and graph nodes correctly obeyed our directive
   352  	// to refresh only, the set of resource changes should always be empty.
   353  	// We'll safety-check that here so we can return a clear message about it,
   354  	// rather than probably just generating confusing output at the UI layer.
   355  	if len(plan.Changes.Resources) != 0 {
   356  		// Some extra context in the logs in case the user reports this message
   357  		// as a bug, as a starting point for debugging.
   358  		for _, rc := range plan.Changes.Resources {
   359  			if depKey := rc.DeposedKey; depKey == states.NotDeposed {
   360  				log.Printf("[DEBUG] Refresh-only plan includes %s change for %s", rc.Action, rc.Addr)
   361  			} else {
   362  				log.Printf("[DEBUG] Refresh-only plan includes %s change for %s deposed object %s", rc.Action, rc.Addr, depKey)
   363  			}
   364  		}
   365  		diags = diags.Append(tfdiags.Sourceless(
   366  			tfdiags.Error,
   367  			"Invalid refresh-only plan",
   368  			"OpenTofu generated planned resource changes in a refresh-only plan. This is a bug in OpenTofu.",
   369  		))
   370  	}
   371  
   372  	// We don't populate RelevantResources for a refresh-only plan, because
   373  	// they never have any planned actions and so no resource can ever be
   374  	// "relevant" per the intended meaning of that field.
   375  
   376  	return plan, diags
   377  }
   378  
   379  func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   380  	var diags tfdiags.Diagnostics
   381  
   382  	if opts.Mode != plans.DestroyMode {
   383  		panic(fmt.Sprintf("called Context.destroyPlan with %s", opts.Mode))
   384  	}
   385  
   386  	priorState := prevRunState
   387  
   388  	// A destroy plan starts by running Refresh to read any pending data
   389  	// sources, and remove missing managed resources. This is required because
   390  	// a "destroy plan" is only creating delete changes, and is essentially a
   391  	// local operation.
   392  	//
   393  	// NOTE: if skipRefresh _is_ set then we'll rely on the destroy-plan walk
   394  	// below to upgrade the prevRunState and priorState both to the latest
   395  	// resource type schemas, so NodePlanDestroyableResourceInstance.Execute
   396  	// must coordinate with this by taking that action only when c.skipRefresh
   397  	// _is_ set. This coupling between the two is unfortunate but necessary
   398  	// to work within our current structure.
   399  	if !opts.SkipRefresh && !prevRunState.Empty() {
   400  		log.Printf("[TRACE] Context.destroyPlan: calling Context.plan to get the effect of refreshing the prior state")
   401  		refreshOpts := *opts
   402  		refreshOpts.Mode = plans.NormalMode
   403  		refreshOpts.PreDestroyRefresh = true
   404  
   405  		// FIXME: A normal plan is required here to refresh the state, because
   406  		// the state and configuration may not match during a destroy, and a
   407  		// normal refresh plan can fail with evaluation errors. In the future
   408  		// the destroy plan should take care of refreshing instances itself,
   409  		// where the special cases of evaluation and skipping condition checks
   410  		// can be done.
   411  		refreshPlan, refreshDiags := c.plan(config, prevRunState, &refreshOpts)
   412  		if refreshDiags.HasErrors() {
   413  			// NOTE: Normally we'd append diagnostics regardless of whether
   414  			// there are errors, just in case there are warnings we'd want to
   415  			// preserve, but we're intentionally _not_ doing that here because
   416  			// if the first plan succeeded then we'll be running another plan
   417  			// in DestroyMode below, and we don't want to double-up any
   418  			// warnings that both plan walks would generate.
   419  			// (This does mean we won't show any warnings that would've been
   420  			// unique to only this walk, but we're assuming here that if the
   421  			// warnings aren't also applicable to a destroy plan then we'd
   422  			// rather not show them here, because this non-destroy plan for
   423  			// refreshing is largely an implementation detail.)
   424  			diags = diags.Append(refreshDiags)
   425  			return nil, diags
   426  		}
   427  
   428  		// We'll use the refreshed state -- which is the  "prior state" from
   429  		// the perspective of this "destroy plan" -- as the starting state
   430  		// for our destroy-plan walk, so it can take into account if we
   431  		// detected during refreshing that anything was already deleted outside OpenTofu.
   432  		priorState = refreshPlan.PriorState.DeepCopy()
   433  
   434  		// The refresh plan may have upgraded state for some resources, make
   435  		// sure we store the new version.
   436  		prevRunState = refreshPlan.PrevRunState.DeepCopy()
   437  		log.Printf("[TRACE] Context.destroyPlan: now _really_ creating a destroy plan")
   438  	}
   439  
   440  	destroyPlan, walkDiags := c.planWalk(config, priorState, opts)
   441  	diags = diags.Append(walkDiags)
   442  	if walkDiags.HasErrors() {
   443  		// Non-nil plan along with errors indicates a non-applyable partial
   444  		// plan that's only suitable to be shown to the user as extra context
   445  		// to help understand the errors.
   446  		return destroyPlan, diags
   447  	}
   448  
   449  	if !opts.SkipRefresh {
   450  		// If we didn't skip refreshing then we want the previous run state to
   451  		// be the one we originally fed into the c.refreshOnlyPlan call above,
   452  		// not the refreshed version we used for the destroy planWalk.
   453  		destroyPlan.PrevRunState = prevRunState
   454  	}
   455  
   456  	relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, destroyPlan)
   457  	diags = diags.Append(rDiags)
   458  
   459  	destroyPlan.RelevantAttributes = relevantAttrs
   460  	return destroyPlan, diags
   461  }
   462  
   463  func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState *states.State, targets []addrs.Targetable) ([]refactoring.MoveStatement, refactoring.MoveResults) {
   464  	explicitMoveStmts := refactoring.FindMoveStatements(config)
   465  	implicitMoveStmts := refactoring.ImpliedMoveStatements(config, prevRunState, explicitMoveStmts)
   466  	var moveStmts []refactoring.MoveStatement
   467  	if stmtsLen := len(explicitMoveStmts) + len(implicitMoveStmts); stmtsLen > 0 {
   468  		moveStmts = make([]refactoring.MoveStatement, 0, stmtsLen)
   469  		moveStmts = append(moveStmts, explicitMoveStmts...)
   470  		moveStmts = append(moveStmts, implicitMoveStmts...)
   471  	}
   472  	moveResults := refactoring.ApplyMoves(moveStmts, prevRunState)
   473  	return moveStmts, moveResults
   474  }
   475  
   476  func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults, targets []addrs.Targetable) tfdiags.Diagnostics {
   477  	if len(targets) < 1 {
   478  		return nil // the following only matters when targeting
   479  	}
   480  
   481  	var diags tfdiags.Diagnostics
   482  
   483  	var excluded []addrs.AbsResourceInstance
   484  	for _, result := range moveResults.Changes.Values() {
   485  		fromMatchesTarget := false
   486  		toMatchesTarget := false
   487  		for _, targetAddr := range targets {
   488  			if targetAddr.TargetContains(result.From) {
   489  				fromMatchesTarget = true
   490  			}
   491  			if targetAddr.TargetContains(result.To) {
   492  				toMatchesTarget = true
   493  			}
   494  		}
   495  		if !fromMatchesTarget {
   496  			excluded = append(excluded, result.From)
   497  		}
   498  		if !toMatchesTarget {
   499  			excluded = append(excluded, result.To)
   500  		}
   501  	}
   502  	if len(excluded) > 0 {
   503  		sort.Slice(excluded, func(i, j int) bool {
   504  			return excluded[i].Less(excluded[j])
   505  		})
   506  
   507  		var listBuf strings.Builder
   508  		var prevResourceAddr addrs.AbsResource
   509  		for _, instAddr := range excluded {
   510  			// Targeting generally ends up selecting whole resources rather
   511  			// than individual instances, because we don't factor in
   512  			// individual instances until DynamicExpand, so we're going to
   513  			// always show whole resource addresses here, excluding any
   514  			// instance keys. (This also neatly avoids dealing with the
   515  			// different quoting styles required for string instance keys
   516  			// on different shells, which is handy.)
   517  			//
   518  			// To avoid showing duplicates when we have multiple instances
   519  			// of the same resource, we'll remember the most recent
   520  			// resource we rendered in prevResource, which is sufficient
   521  			// because we sorted the list of instance addresses above, and
   522  			// our sort order always groups together instances of the same
   523  			// resource.
   524  			resourceAddr := instAddr.ContainingResource()
   525  			if resourceAddr.Equal(prevResourceAddr) {
   526  				continue
   527  			}
   528  			fmt.Fprintf(&listBuf, "\n  -target=%q", resourceAddr.String())
   529  			prevResourceAddr = resourceAddr
   530  		}
   531  		diags = diags.Append(tfdiags.Sourceless(
   532  			tfdiags.Error,
   533  			"Moved resource instances excluded by targeting",
   534  			fmt.Sprintf(
   535  				"Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -target=... options do not fully cover all of those resource instances.\n\nTo create a valid plan, either remove your -target=... options altogether or add the following additional target options:%s\n\nNote that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.",
   536  				listBuf.String(),
   537  			),
   538  		))
   539  	}
   540  
   541  	return diags
   542  }
   543  
   544  func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactoring.MoveStatement, allInsts instances.Set) tfdiags.Diagnostics {
   545  	return refactoring.ValidateMoves(stmts, config, allInsts)
   546  }
   547  
   548  // All import target addresses with a key must already exist in config.
   549  // When we are able to generate config for expanded resources, this rule can be
   550  // relaxed.
   551  func (c *Context) postPlanValidateImports(importResolver *ImportResolver, allInst instances.Set) tfdiags.Diagnostics {
   552  	var diags tfdiags.Diagnostics
   553  	for _, importTarget := range importResolver.GetAllImports() {
   554  		if !allInst.HasResourceInstance(importTarget.Addr) {
   555  			diags = diags.Append(importResourceWithoutConfigDiags(importTarget.Addr.String(), nil))
   556  		}
   557  	}
   558  	return diags
   559  }
   560  
   561  // findImportTargets builds a list of import targets by going over the import
   562  // blocks in the config.
   563  func (c *Context) findImportTargets(config *configs.Config) []*ImportTarget {
   564  	var importTargets []*ImportTarget
   565  	for _, ic := range config.Module.Import {
   566  		importTargets = append(importTargets, &ImportTarget{
   567  			Config: ic,
   568  		})
   569  	}
   570  	return importTargets
   571  }
   572  
   573  // validateImportTargets makes sure all import targets are not breaking the following rules:
   574  //  1. Imports are attempted into resources that do not exist (if config generation is not enabled).
   575  //  2. Config generation is not attempted for resources inside sub-modules
   576  //  3. Config generation is not attempted for resources with indexes (for_each/count) - This will always include
   577  //     resources for which we could not yet resolve the address
   578  func (c *Context) validateImportTargets(config *configs.Config, importTargets []*ImportTarget, generateConfigPath string) (diags tfdiags.Diagnostics) {
   579  	configGeneration := len(generateConfigPath) > 0
   580  	for _, imp := range importTargets {
   581  		staticAddress := imp.StaticAddr()
   582  		descendantConfig := config.Descendent(staticAddress.Module)
   583  
   584  		// If import target's module does not exist
   585  		if descendantConfig == nil {
   586  			if configGeneration {
   587  				// Attempted config generation for resource in non-existing module. So error because resource generation
   588  				// is not allowed in a sub-module
   589  				diags = diags.Append(importConfigGenerationInModuleDiags(staticAddress.String(), imp.Config))
   590  			} else {
   591  				diags = diags.Append(importResourceWithoutConfigDiags(staticAddress.String(), imp.Config))
   592  			}
   593  			continue
   594  		}
   595  
   596  		if _, exists := descendantConfig.Module.ManagedResources[staticAddress.Resource.String()]; !exists {
   597  			if configGeneration {
   598  				if imp.ResolvedAddr() == nil {
   599  					// If we could not resolve the address of the import target, the address must have contained indexes
   600  					diags = diags.Append(importConfigGenerationWithIndexDiags(staticAddress.String(), imp.Config))
   601  					continue
   602  				} else if !imp.ResolvedAddr().Module.IsRoot() {
   603  					diags = diags.Append(importConfigGenerationInModuleDiags(imp.ResolvedAddr().String(), imp.Config))
   604  					continue
   605  				} else if imp.ResolvedAddr().Resource.Key != addrs.NoKey {
   606  					diags = diags.Append(importConfigGenerationWithIndexDiags(imp.ResolvedAddr().String(), imp.Config))
   607  					continue
   608  				}
   609  			} else {
   610  				diags = diags.Append(importResourceWithoutConfigDiags(staticAddress.String(), imp.Config))
   611  				continue
   612  			}
   613  		}
   614  	}
   615  	return
   616  }
   617  
   618  func importConfigGenerationInModuleDiags(addressStr string, config *configs.Import) *hcl.Diagnostic {
   619  	diag := hcl.Diagnostic{
   620  		Severity: hcl.DiagError,
   621  		Summary:  "Cannot generate configuration for resource inside sub-module",
   622  		Detail:   fmt.Sprintf("The configuration for the given import %s does not exist. Configuration generation is only possible for resources in the root module, and not possible for resources in sub-modules.", addressStr),
   623  	}
   624  
   625  	if config != nil {
   626  		diag.Subject = config.DeclRange.Ptr()
   627  	}
   628  
   629  	return &diag
   630  }
   631  
   632  func importConfigGenerationWithIndexDiags(addressStr string, config *configs.Import) *hcl.Diagnostic {
   633  	diag := hcl.Diagnostic{
   634  		Severity: hcl.DiagError,
   635  		Summary:  "Configuration generation for count and for_each resources not supported",
   636  		Detail:   fmt.Sprintf("The configuration for the given import %s does not exist. Configuration generation is only possible for resources that do not use count or for_each", addressStr),
   637  	}
   638  
   639  	if config != nil {
   640  		diag.Subject = config.DeclRange.Ptr()
   641  	}
   642  
   643  	return &diag
   644  }
   645  
   646  func importResourceWithoutConfigDiags(addressStr string, config *configs.Import) *hcl.Diagnostic {
   647  	diag := hcl.Diagnostic{
   648  		Severity: hcl.DiagError,
   649  		Summary:  "Configuration for import target does not exist",
   650  		Detail:   fmt.Sprintf("The configuration for the given import %s does not exist. All target instances must have an associated configuration to be imported.", addressStr),
   651  	}
   652  
   653  	if config != nil {
   654  		diag.Subject = config.DeclRange.Ptr()
   655  	}
   656  
   657  	return &diag
   658  }
   659  
   660  func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   661  	var diags tfdiags.Diagnostics
   662  	log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode)
   663  
   664  	prevRunState = prevRunState.DeepCopy() // don't modify the caller's object when we process the moves
   665  	moveStmts, moveResults := c.prePlanFindAndApplyMoves(config, prevRunState, opts.Targets)
   666  
   667  	// If resource targeting is in effect then it might conflict with the
   668  	// move result.
   669  	diags = diags.Append(c.prePlanVerifyTargetedMoves(moveResults, opts.Targets))
   670  	if diags.HasErrors() {
   671  		// We'll return early here, because if we have any moved resource
   672  		// instances excluded by targeting then planning is likely to encounter
   673  		// strange problems that may lead to confusing error messages.
   674  		return nil, diags
   675  	}
   676  
   677  	graph, walkOp, moreDiags := c.planGraph(config, prevRunState, opts)
   678  	diags = diags.Append(moreDiags)
   679  	if diags.HasErrors() {
   680  		return nil, diags
   681  	}
   682  
   683  	timestamp := time.Now().UTC()
   684  
   685  	// If we get here then we should definitely have a non-nil "graph", which
   686  	// we can now walk.
   687  	changes := plans.NewChanges()
   688  	walker, walkDiags := c.walk(graph, walkOp, &graphWalkOpts{
   689  		Config:            config,
   690  		InputState:        prevRunState,
   691  		Changes:           changes,
   692  		MoveResults:       moveResults,
   693  		PlanTimeTimestamp: timestamp,
   694  	})
   695  	diags = diags.Append(walker.NonFatalDiagnostics)
   696  	diags = diags.Append(walkDiags)
   697  
   698  	allInsts := walker.InstanceExpander.AllInstances()
   699  
   700  	importValidateDiags := c.postPlanValidateImports(walker.ImportResolver, allInsts)
   701  	if importValidateDiags.HasErrors() {
   702  		return nil, importValidateDiags
   703  	}
   704  
   705  	moveValidateDiags := c.postPlanValidateMoves(config, moveStmts, allInsts)
   706  	if moveValidateDiags.HasErrors() {
   707  		// If any of the move statements are invalid then those errors take
   708  		// precedence over any other errors because an incomplete move graph
   709  		// is quite likely to be the _cause_ of various errors. This oddity
   710  		// comes from the fact that we need to apply the moves before we
   711  		// actually validate them, because validation depends on the result
   712  		// of first trying to plan.
   713  		return nil, moveValidateDiags
   714  	}
   715  	diags = diags.Append(moveValidateDiags) // might just contain warnings
   716  
   717  	if moveResults.Blocked.Len() > 0 && !diags.HasErrors() {
   718  		// If we had blocked moves and we're not going to be returning errors
   719  		// then we'll report the blockers as a warning. We do this only in the
   720  		// absense of errors because invalid move statements might well be
   721  		// the root cause of the blockers, and so better to give an actionable
   722  		// error message than a less-actionable warning.
   723  		diags = diags.Append(blockedMovesWarningDiag(moveResults))
   724  	}
   725  
   726  	// If we reach this point with error diagnostics then "changes" is a
   727  	// representation of the subset of changes we were able to plan before
   728  	// we encountered errors, which we'll return as part of a non-nil plan
   729  	// so that e.g. the UI can show what was planned so far in case that extra
   730  	// context helps the user to understand the error messages we're returning.
   731  	prevRunState = walker.PrevRunState.Close()
   732  
   733  	// The refreshed state may have data resource objects which were deferred
   734  	// to apply and cannot be serialized.
   735  	walker.RefreshState.RemovePlannedResourceInstanceObjects()
   736  	priorState := walker.RefreshState.Close()
   737  
   738  	driftedResources, driftDiags := c.driftedResources(config, prevRunState, priorState, moveResults)
   739  	diags = diags.Append(driftDiags)
   740  
   741  	plan := &plans.Plan{
   742  		UIMode:             opts.Mode,
   743  		Changes:            changes,
   744  		DriftedResources:   driftedResources,
   745  		PrevRunState:       prevRunState,
   746  		PriorState:         priorState,
   747  		PlannedState:       walker.State.Close(),
   748  		ExternalReferences: opts.ExternalReferences,
   749  		Checks:             states.NewCheckResults(walker.Checks),
   750  		Timestamp:          timestamp,
   751  
   752  		// Other fields get populated by Context.Plan after we return
   753  	}
   754  	return plan, diags
   755  }
   756  
   757  func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*Graph, walkOperation, tfdiags.Diagnostics) {
   758  	switch mode := opts.Mode; mode {
   759  	case plans.NormalMode:
   760  		graph, diags := (&PlanGraphBuilder{
   761  			Config:             config,
   762  			State:              prevRunState,
   763  			RootVariableValues: opts.SetVariables,
   764  			Plugins:            c.plugins,
   765  			Targets:            opts.Targets,
   766  			ForceReplace:       opts.ForceReplace,
   767  			skipRefresh:        opts.SkipRefresh,
   768  			preDestroyRefresh:  opts.PreDestroyRefresh,
   769  			Operation:          walkPlan,
   770  			ExternalReferences: opts.ExternalReferences,
   771  			ImportTargets:      opts.ImportTargets,
   772  			GenerateConfigPath: opts.GenerateConfigPath,
   773  			EndpointsToRemove:  opts.EndpointsToRemove,
   774  		}).Build(addrs.RootModuleInstance)
   775  		return graph, walkPlan, diags
   776  	case plans.RefreshOnlyMode:
   777  		graph, diags := (&PlanGraphBuilder{
   778  			Config:             config,
   779  			State:              prevRunState,
   780  			RootVariableValues: opts.SetVariables,
   781  			Plugins:            c.plugins,
   782  			Targets:            opts.Targets,
   783  			skipRefresh:        opts.SkipRefresh,
   784  			skipPlanChanges:    true, // this activates "refresh only" mode.
   785  			Operation:          walkPlan,
   786  			ExternalReferences: opts.ExternalReferences,
   787  		}).Build(addrs.RootModuleInstance)
   788  		return graph, walkPlan, diags
   789  	case plans.DestroyMode:
   790  		graph, diags := (&PlanGraphBuilder{
   791  			Config:             config,
   792  			State:              prevRunState,
   793  			RootVariableValues: opts.SetVariables,
   794  			Plugins:            c.plugins,
   795  			Targets:            opts.Targets,
   796  			skipRefresh:        opts.SkipRefresh,
   797  			Operation:          walkPlanDestroy,
   798  		}).Build(addrs.RootModuleInstance)
   799  		return graph, walkPlanDestroy, diags
   800  	default:
   801  		// The above should cover all plans.Mode values
   802  		panic(fmt.Sprintf("unsupported plan mode %s", mode))
   803  	}
   804  }
   805  
   806  // driftedResources is a best-effort attempt to compare the current and prior
   807  // state. If we cannot decode the prior state for some reason, this should only
   808  // return warnings to help the user correlate any missing resources in the
   809  // report. This is known to happen when targeting a subset of resources,
   810  // because the excluded instances will have been removed from the plan and
   811  // not upgraded.
   812  func (c *Context) driftedResources(config *configs.Config, oldState, newState *states.State, moves refactoring.MoveResults) ([]*plans.ResourceInstanceChangeSrc, tfdiags.Diagnostics) {
   813  	var diags tfdiags.Diagnostics
   814  
   815  	if newState.ManagedResourcesEqual(oldState) && moves.Changes.Len() == 0 {
   816  		// Nothing to do, because we only detect and report drift for managed
   817  		// resource instances.
   818  		return nil, diags
   819  	}
   820  
   821  	schemas, schemaDiags := c.Schemas(config, newState)
   822  	diags = diags.Append(schemaDiags)
   823  	if diags.HasErrors() {
   824  		return nil, diags
   825  	}
   826  
   827  	var drs []*plans.ResourceInstanceChangeSrc
   828  
   829  	for _, ms := range oldState.Modules {
   830  		for _, rs := range ms.Resources {
   831  			if rs.Addr.Resource.Mode != addrs.ManagedResourceMode {
   832  				// Drift reporting is only for managed resources
   833  				continue
   834  			}
   835  
   836  			provider := rs.ProviderConfig.Provider
   837  			for key, oldIS := range rs.Instances {
   838  				if oldIS.Current == nil {
   839  					// Not interested in instances that only have deposed objects
   840  					continue
   841  				}
   842  				addr := rs.Addr.Instance(key)
   843  
   844  				// Previous run address defaults to the current address, but
   845  				// can differ if the resource moved before refreshing
   846  				prevRunAddr := addr
   847  				if move, ok := moves.Changes.GetOk(addr); ok {
   848  					prevRunAddr = move.From
   849  				}
   850  
   851  				newIS := newState.ResourceInstance(addr)
   852  
   853  				schema, _ := schemas.ResourceTypeConfig(
   854  					provider,
   855  					addr.Resource.Resource.Mode,
   856  					addr.Resource.Resource.Type,
   857  				)
   858  				if schema == nil {
   859  					diags = diags.Append(tfdiags.Sourceless(
   860  						tfdiags.Warning,
   861  						"Missing resource schema from provider",
   862  						fmt.Sprintf("No resource schema found for %s when decoding prior state", addr.Resource.Resource.Type),
   863  					))
   864  					continue
   865  				}
   866  				ty := schema.ImpliedType()
   867  
   868  				oldObj, err := oldIS.Current.Decode(ty)
   869  				if err != nil {
   870  					diags = diags.Append(tfdiags.Sourceless(
   871  						tfdiags.Warning,
   872  						"Failed to decode resource from state",
   873  						fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err),
   874  					))
   875  					continue
   876  				}
   877  
   878  				var newObj *states.ResourceInstanceObject
   879  				if newIS != nil && newIS.Current != nil {
   880  					newObj, err = newIS.Current.Decode(ty)
   881  					if err != nil {
   882  						diags = diags.Append(tfdiags.Sourceless(
   883  							tfdiags.Warning,
   884  							"Failed to decode resource from state",
   885  							fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err),
   886  						))
   887  						continue
   888  					}
   889  				}
   890  
   891  				var oldVal, newVal cty.Value
   892  				oldVal = oldObj.Value
   893  				if newObj != nil {
   894  					newVal = newObj.Value
   895  				} else {
   896  					newVal = cty.NullVal(ty)
   897  				}
   898  
   899  				if oldVal.RawEquals(newVal) && addr.Equal(prevRunAddr) {
   900  					// No drift if the two values are semantically equivalent
   901  					// and no move has happened
   902  					continue
   903  				}
   904  
   905  				// We can detect three types of changes after refreshing state,
   906  				// only two of which are easily understood as "drift":
   907  				//
   908  				// - Resources which were deleted outside OpenTofu;
   909  				// - Resources where the object value has changed outside OpenTofu;
   910  				// - Resources which have been moved without other changes.
   911  				//
   912  				// All of these are returned as drift, to allow refresh-only plans
   913  				// to present a full set of changes which will be applied.
   914  				var action plans.Action
   915  				switch {
   916  				case newVal.IsNull():
   917  					action = plans.Delete
   918  				case !oldVal.RawEquals(newVal):
   919  					action = plans.Update
   920  				default:
   921  					action = plans.NoOp
   922  				}
   923  
   924  				change := &plans.ResourceInstanceChange{
   925  					Addr:         addr,
   926  					PrevRunAddr:  prevRunAddr,
   927  					ProviderAddr: rs.ProviderConfig,
   928  					Change: plans.Change{
   929  						Action: action,
   930  						Before: oldVal,
   931  						After:  newVal,
   932  					},
   933  				}
   934  
   935  				changeSrc, err := change.Encode(ty)
   936  				if err != nil {
   937  					diags = diags.Append(err)
   938  					return nil, diags
   939  				}
   940  
   941  				drs = append(drs, changeSrc)
   942  			}
   943  		}
   944  	}
   945  
   946  	return drs, diags
   947  }
   948  
   949  // PlanGraphForUI is a last vestage of graphs in the public interface of Context
   950  // (as opposed to graphs as an implementation detail) intended only for use
   951  // by the "tofu graph" command when asked to render a plan-time graph.
   952  //
   953  // The result of this is intended only for rendering to the user as a dot
   954  // graph, and so may change in future in order to make the result more useful
   955  // in that context, even if drifts away from the physical graph that OpenTofu
   956  // Core currently uses as an implementation detail of planning.
   957  func (c *Context) PlanGraphForUI(config *configs.Config, prevRunState *states.State, mode plans.Mode) (*Graph, tfdiags.Diagnostics) {
   958  	// For now though, this really is just the internal graph, confusing
   959  	// implementation details and all.
   960  
   961  	var diags tfdiags.Diagnostics
   962  
   963  	opts := &PlanOpts{Mode: mode}
   964  
   965  	graph, _, moreDiags := c.planGraph(config, prevRunState, opts)
   966  	diags = diags.Append(moreDiags)
   967  	return graph, diags
   968  }
   969  
   970  func blockedMovesWarningDiag(results refactoring.MoveResults) tfdiags.Diagnostic {
   971  	if results.Blocked.Len() < 1 {
   972  		// Caller should check first
   973  		panic("request to render blocked moves warning without any blocked moves")
   974  	}
   975  
   976  	var itemsBuf bytes.Buffer
   977  	for _, blocked := range results.Blocked.Values() {
   978  		fmt.Fprintf(&itemsBuf, "\n  - %s could not move to %s", blocked.Actual, blocked.Wanted)
   979  	}
   980  
   981  	return tfdiags.Sourceless(
   982  		tfdiags.Warning,
   983  		"Unresolved resource instance address changes",
   984  		fmt.Sprintf(
   985  			"OpenTofu tried to adjust resource instance addresses in the prior state based on change information recorded in the configuration, but some adjustments did not succeed due to existing objects already at the intended addresses:%s\n\nOpenTofu has planned to destroy these objects. If OpenTofu's proposed changes aren't appropriate, you must first resolve the conflicts using the \"tofu state\" subcommands and then create a new plan.",
   986  			itemsBuf.String(),
   987  		),
   988  	)
   989  }
   990  
   991  // referenceAnalyzer returns a globalref.Analyzer object to help with
   992  // global analysis of references within the configuration that's attached
   993  // to the receiving context.
   994  func (c *Context) referenceAnalyzer(config *configs.Config, state *states.State) (*globalref.Analyzer, tfdiags.Diagnostics) {
   995  	schemas, diags := c.Schemas(config, state)
   996  	if diags.HasErrors() {
   997  		return nil, diags
   998  	}
   999  	return globalref.NewAnalyzer(config, schemas.Providers), diags
  1000  }
  1001  
  1002  // relevantResourcesForPlan implements the heuristic we use to populate the
  1003  // RelevantResources field of returned plans.
  1004  func (c *Context) relevantResourceAttrsForPlan(config *configs.Config, plan *plans.Plan) ([]globalref.ResourceAttr, tfdiags.Diagnostics) {
  1005  	azr, diags := c.referenceAnalyzer(config, plan.PriorState)
  1006  	if diags.HasErrors() {
  1007  		return nil, diags
  1008  	}
  1009  
  1010  	var refs []globalref.Reference
  1011  	for _, change := range plan.Changes.Resources {
  1012  		if change.Action == plans.NoOp {
  1013  			continue
  1014  		}
  1015  
  1016  		moreRefs := azr.ReferencesFromResourceInstance(change.Addr)
  1017  		refs = append(refs, moreRefs...)
  1018  	}
  1019  
  1020  	for _, change := range plan.Changes.Outputs {
  1021  		if change.Action == plans.NoOp {
  1022  			continue
  1023  		}
  1024  
  1025  		moreRefs := azr.ReferencesFromOutputValue(change.Addr)
  1026  		refs = append(refs, moreRefs...)
  1027  	}
  1028  
  1029  	var contributors []globalref.ResourceAttr
  1030  
  1031  	for _, ref := range azr.ContributingResourceReferences(refs...) {
  1032  		if res, ok := ref.ResourceAttr(); ok {
  1033  			contributors = append(contributors, res)
  1034  		}
  1035  	}
  1036  
  1037  	return contributors, diags
  1038  }