github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/context_plan.go (about)

     1  package durgaform
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/eliastor/durgaform/internal/addrs"
    13  	"github.com/eliastor/durgaform/internal/configs"
    14  	"github.com/eliastor/durgaform/internal/instances"
    15  	"github.com/eliastor/durgaform/internal/lang/globalref"
    16  	"github.com/eliastor/durgaform/internal/plans"
    17  	"github.com/eliastor/durgaform/internal/refactoring"
    18  	"github.com/eliastor/durgaform/internal/states"
    19  	"github.com/eliastor/durgaform/internal/tfdiags"
    20  )
    21  
    22  // PlanOpts are the various options that affect the details of how Durgaform
    23  // will build a plan.
    24  type PlanOpts struct {
    25  	// Mode defines what variety of plan the caller wishes to create.
    26  	// Refer to the documentation of the plans.Mode type and its values
    27  	// for more information.
    28  	Mode plans.Mode
    29  
    30  	// SkipRefresh specifies to trust that the current values for managed
    31  	// resource instances in the prior state are accurate and to therefore
    32  	// disable the usual step of fetching updated values for each resource
    33  	// instance using its corresponding provider.
    34  	SkipRefresh bool
    35  
    36  	// SetVariables are the raw values for root module variables as provided
    37  	// by the user who is requesting the run, prior to any normalization or
    38  	// substitution of defaults. See the documentation for the InputValue
    39  	// type for more information on how to correctly populate this.
    40  	SetVariables InputValues
    41  
    42  	// If Targets has a non-zero length then it activates targeted planning
    43  	// mode, where Durgaform will take actions only for resource instances
    44  	// mentioned in this set and any other objects those resource instances
    45  	// depend on.
    46  	//
    47  	// Targeted planning mode is intended for exceptional use only,
    48  	// and so populating this field will cause Durgaform to generate extra
    49  	// warnings as part of the planning result.
    50  	Targets []addrs.Targetable
    51  
    52  	// ForceReplace is a set of resource instance addresses whose corresponding
    53  	// objects should be forced planned for replacement if the provider's
    54  	// plan would otherwise have been to either update the object in-place or
    55  	// to take no action on it at all.
    56  	//
    57  	// A typical use of this argument is to ask Durgaform to replace an object
    58  	// which the user has determined is somehow degraded (via information from
    59  	// outside of Durgaform), thereby hopefully replacing it with a
    60  	// fully-functional new object.
    61  	ForceReplace []addrs.AbsResourceInstance
    62  }
    63  
    64  // Plan generates an execution plan for the given context, and returns the
    65  // refreshed state.
    66  //
    67  // The execution plan encapsulates the context and can be stored
    68  // in order to reinstantiate a context later for Apply.
    69  //
    70  // Plan also updates the diff of this context to be the diff generated
    71  // by the plan, so Apply can be called after.
    72  func (c *Context) Plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
    73  	defer c.acquireRun("plan")()
    74  	var diags tfdiags.Diagnostics
    75  
    76  	// Save the downstream functions from needing to deal with these broken situations.
    77  	// No real callers should rely on these, but we have a bunch of old and
    78  	// sloppy tests that don't always populate arguments properly.
    79  	if config == nil {
    80  		config = configs.NewEmptyConfig()
    81  	}
    82  	if prevRunState == nil {
    83  		prevRunState = states.NewState()
    84  	}
    85  	if opts == nil {
    86  		opts = &PlanOpts{
    87  			Mode: plans.NormalMode,
    88  		}
    89  	}
    90  
    91  	moreDiags := c.checkConfigDependencies(config)
    92  	diags = diags.Append(moreDiags)
    93  	// If required dependencies are not available then we'll bail early since
    94  	// otherwise we're likely to just see a bunch of other errors related to
    95  	// incompatibilities, which could be overwhelming for the user.
    96  	if diags.HasErrors() {
    97  		return nil, diags
    98  	}
    99  
   100  	switch opts.Mode {
   101  	case plans.NormalMode, plans.DestroyMode:
   102  		// OK
   103  	case plans.RefreshOnlyMode:
   104  		if opts.SkipRefresh {
   105  			// The CLI layer (and other similar callers) should prevent this
   106  			// combination of options.
   107  			diags = diags.Append(tfdiags.Sourceless(
   108  				tfdiags.Error,
   109  				"Incompatible plan options",
   110  				"Cannot skip refreshing in refresh-only mode. This is a bug in Durgaform.",
   111  			))
   112  			return nil, diags
   113  		}
   114  	default:
   115  		// The CLI layer (and other similar callers) should not try to
   116  		// create a context for a mode that Durgaform Core doesn't support.
   117  		diags = diags.Append(tfdiags.Sourceless(
   118  			tfdiags.Error,
   119  			"Unsupported plan mode",
   120  			fmt.Sprintf("Durgaform Core doesn't know how to handle plan mode %s. This is a bug in Terraform.", opts.Mode),
   121  		))
   122  		return nil, diags
   123  	}
   124  	if len(opts.ForceReplace) > 0 && opts.Mode != plans.NormalMode {
   125  		// The other modes don't generate no-op or update actions that we might
   126  		// upgrade to be "replace", so doesn't make sense to combine those.
   127  		diags = diags.Append(tfdiags.Sourceless(
   128  			tfdiags.Error,
   129  			"Unsupported plan mode",
   130  			"Forcing resource instance replacement (with -replace=...) is allowed only in normal planning mode.",
   131  		))
   132  		return nil, diags
   133  	}
   134  
   135  	// By the time we get here, we should have values defined for all of
   136  	// the root module variables, even if some of them are "unknown". It's the
   137  	// caller's responsibility to have already handled the decoding of these
   138  	// from the various ways the CLI allows them to be set and to produce
   139  	// user-friendly error messages if they are not all present, and so
   140  	// the error message from checkInputVariables should never be seen and
   141  	// includes language asking the user to report a bug.
   142  	varDiags := checkInputVariables(config.Module.Variables, opts.SetVariables)
   143  	diags = diags.Append(varDiags)
   144  
   145  	if len(opts.Targets) > 0 {
   146  		diags = diags.Append(tfdiags.Sourceless(
   147  			tfdiags.Warning,
   148  			"Resource targeting is in effect",
   149  			`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.
   150  
   151  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Durgaform specifically suggests to use it as part of an error message.`,
   152  		))
   153  	}
   154  
   155  	var plan *plans.Plan
   156  	var planDiags tfdiags.Diagnostics
   157  	switch opts.Mode {
   158  	case plans.NormalMode:
   159  		plan, planDiags = c.plan(config, prevRunState, opts)
   160  	case plans.DestroyMode:
   161  		plan, planDiags = c.destroyPlan(config, prevRunState, opts)
   162  	case plans.RefreshOnlyMode:
   163  		plan, planDiags = c.refreshOnlyPlan(config, prevRunState, opts)
   164  	default:
   165  		panic(fmt.Sprintf("unsupported plan mode %s", opts.Mode))
   166  	}
   167  	diags = diags.Append(planDiags)
   168  	if diags.HasErrors() {
   169  		return nil, diags
   170  	}
   171  
   172  	// convert the variables into the format expected for the plan
   173  	varVals := make(map[string]plans.DynamicValue, len(opts.SetVariables))
   174  	for k, iv := range opts.SetVariables {
   175  		if iv.Value == cty.NilVal {
   176  			continue // We only record values that the caller actually set
   177  		}
   178  
   179  		// We use cty.DynamicPseudoType here so that we'll save both the
   180  		// value _and_ its dynamic type in the plan, so we can recover
   181  		// exactly the same value later.
   182  		dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType)
   183  		if err != nil {
   184  			diags = diags.Append(tfdiags.Sourceless(
   185  				tfdiags.Error,
   186  				"Failed to prepare variable value for plan",
   187  				fmt.Sprintf("The value for variable %q could not be serialized to store in the plan: %s.", k, err),
   188  			))
   189  			continue
   190  		}
   191  		varVals[k] = dv
   192  	}
   193  
   194  	// insert the run-specific data from the context into the plan; variables,
   195  	// targets and provider SHAs.
   196  	if plan != nil {
   197  		plan.VariableValues = varVals
   198  		plan.TargetAddrs = opts.Targets
   199  	} else if !diags.HasErrors() {
   200  		panic("nil plan but no errors")
   201  	}
   202  
   203  	relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, plan)
   204  	diags = diags.Append(rDiags)
   205  
   206  	plan.RelevantAttributes = relevantAttrs
   207  	diags = diags.Append(c.checkApplyGraph(plan, config))
   208  
   209  	return plan, diags
   210  }
   211  
   212  // checkApplyGraph builds the apply graph out of the current plan to
   213  // check for any errors that may arise once the planned changes are added to
   214  // the graph. This allows durgaform to report errors (mostly cycles) during
   215  // plan that would otherwise only crop up during apply
   216  func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdiags.Diagnostics {
   217  	if plan.Changes.Empty() {
   218  		log.Println("[DEBUG] no planned changes, skipping apply graph check")
   219  		return nil
   220  	}
   221  	log.Println("[DEBUG] building apply graph to check for errors")
   222  	_, _, diags := c.applyGraph(plan, config, true)
   223  	return diags
   224  }
   225  
   226  var DefaultPlanOpts = &PlanOpts{
   227  	Mode: plans.NormalMode,
   228  }
   229  
   230  // SimplePlanOpts is a constructor to help with creating "simple" values of
   231  // PlanOpts which only specify a mode and input variables.
   232  //
   233  // This helper function is primarily intended for use in straightforward
   234  // tests that don't need any of the more "esoteric" planning options. For
   235  // handling real user requests to run Durgaform, it'd probably be better
   236  // to construct a *PlanOpts value directly and provide a way for the user
   237  // to set values for all of its fields.
   238  //
   239  // The "mode" and "setVariables" arguments become the values of the "Mode"
   240  // and "SetVariables" fields in the result. Refer to the PlanOpts type
   241  // documentation to learn about the meanings of those fields.
   242  func SimplePlanOpts(mode plans.Mode, setVariables InputValues) *PlanOpts {
   243  	return &PlanOpts{
   244  		Mode:         mode,
   245  		SetVariables: setVariables,
   246  	}
   247  }
   248  
   249  func (c *Context) plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   250  	var diags tfdiags.Diagnostics
   251  
   252  	if opts.Mode != plans.NormalMode {
   253  		panic(fmt.Sprintf("called Context.plan with %s", opts.Mode))
   254  	}
   255  
   256  	plan, walkDiags := c.planWalk(config, prevRunState, opts)
   257  	diags = diags.Append(walkDiags)
   258  	if diags.HasErrors() {
   259  		return nil, diags
   260  	}
   261  
   262  	// The refreshed state ends up with some placeholder objects in it for
   263  	// objects pending creation. We only really care about those being in
   264  	// the working state, since that's what we're going to use when applying,
   265  	// so we'll prune them all here.
   266  	plan.PriorState.SyncWrapper().RemovePlannedResourceInstanceObjects()
   267  
   268  	return plan, diags
   269  }
   270  
   271  func (c *Context) refreshOnlyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   272  	var diags tfdiags.Diagnostics
   273  
   274  	if opts.Mode != plans.RefreshOnlyMode {
   275  		panic(fmt.Sprintf("called Context.refreshOnlyPlan with %s", opts.Mode))
   276  	}
   277  
   278  	plan, walkDiags := c.planWalk(config, prevRunState, opts)
   279  	diags = diags.Append(walkDiags)
   280  	if diags.HasErrors() {
   281  		return nil, diags
   282  	}
   283  
   284  	// If the graph builder and graph nodes correctly obeyed our directive
   285  	// to refresh only, the set of resource changes should always be empty.
   286  	// We'll safety-check that here so we can return a clear message about it,
   287  	// rather than probably just generating confusing output at the UI layer.
   288  	if len(plan.Changes.Resources) != 0 {
   289  		// Some extra context in the logs in case the user reports this message
   290  		// as a bug, as a starting point for debugging.
   291  		for _, rc := range plan.Changes.Resources {
   292  			if depKey := rc.DeposedKey; depKey == states.NotDeposed {
   293  				log.Printf("[DEBUG] Refresh-only plan includes %s change for %s", rc.Action, rc.Addr)
   294  			} else {
   295  				log.Printf("[DEBUG] Refresh-only plan includes %s change for %s deposed object %s", rc.Action, rc.Addr, depKey)
   296  			}
   297  		}
   298  		diags = diags.Append(tfdiags.Sourceless(
   299  			tfdiags.Error,
   300  			"Invalid refresh-only plan",
   301  			"Durgaform generated planned resource changes in a refresh-only plan. This is a bug in Terraform.",
   302  		))
   303  	}
   304  
   305  	// Prune out any placeholder objects we put in the state to represent
   306  	// objects that would need to be created.
   307  	plan.PriorState.SyncWrapper().RemovePlannedResourceInstanceObjects()
   308  
   309  	// We don't populate RelevantResources for a refresh-only plan, because
   310  	// they never have any planned actions and so no resource can ever be
   311  	// "relevant" per the intended meaning of that field.
   312  
   313  	return plan, diags
   314  }
   315  
   316  func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   317  	var diags tfdiags.Diagnostics
   318  	pendingPlan := &plans.Plan{}
   319  
   320  	if opts.Mode != plans.DestroyMode {
   321  		panic(fmt.Sprintf("called Context.destroyPlan with %s", opts.Mode))
   322  	}
   323  
   324  	priorState := prevRunState
   325  
   326  	// A destroy plan starts by running Refresh to read any pending data
   327  	// sources, and remove missing managed resources. This is required because
   328  	// a "destroy plan" is only creating delete changes, and is essentially a
   329  	// local operation.
   330  	//
   331  	// NOTE: if skipRefresh _is_ set then we'll rely on the destroy-plan walk
   332  	// below to upgrade the prevRunState and priorState both to the latest
   333  	// resource type schemas, so NodePlanDestroyableResourceInstance.Execute
   334  	// must coordinate with this by taking that action only when c.skipRefresh
   335  	// _is_ set. This coupling between the two is unfortunate but necessary
   336  	// to work within our current structure.
   337  	if !opts.SkipRefresh {
   338  		log.Printf("[TRACE] Context.destroyPlan: calling Context.plan to get the effect of refreshing the prior state")
   339  		normalOpts := *opts
   340  		normalOpts.Mode = plans.NormalMode
   341  		refreshPlan, refreshDiags := c.plan(config, prevRunState, &normalOpts)
   342  		if refreshDiags.HasErrors() {
   343  			// NOTE: Normally we'd append diagnostics regardless of whether
   344  			// there are errors, just in case there are warnings we'd want to
   345  			// preserve, but we're intentionally _not_ doing that here because
   346  			// if the first plan succeeded then we'll be running another plan
   347  			// in DestroyMode below, and we don't want to double-up any
   348  			// warnings that both plan walks would generate.
   349  			// (This does mean we won't show any warnings that would've been
   350  			// unique to only this walk, but we're assuming here that if the
   351  			// warnings aren't also applicable to a destroy plan then we'd
   352  			// rather not show them here, because this non-destroy plan for
   353  			// refreshing is largely an implementation detail.)
   354  			diags = diags.Append(refreshDiags)
   355  			return nil, diags
   356  		}
   357  
   358  		// insert the refreshed state into the destroy plan result, and ignore
   359  		// the changes recorded from the refresh.
   360  		pendingPlan.PriorState = refreshPlan.PriorState.DeepCopy()
   361  		pendingPlan.PrevRunState = refreshPlan.PrevRunState.DeepCopy()
   362  		log.Printf("[TRACE] Context.destroyPlan: now _really_ creating a destroy plan")
   363  
   364  		// We'll use the refreshed state -- which is the  "prior state" from
   365  		// the perspective of this "pending plan" -- as the starting state
   366  		// for our destroy-plan walk, so it can take into account if we
   367  		// detected during refreshing that anything was already deleted outside
   368  		// of Durgaform.
   369  		priorState = pendingPlan.PriorState
   370  	}
   371  
   372  	destroyPlan, walkDiags := c.planWalk(config, priorState, opts)
   373  	diags = diags.Append(walkDiags)
   374  	if walkDiags.HasErrors() {
   375  		return nil, diags
   376  	}
   377  
   378  	if !opts.SkipRefresh {
   379  		// If we didn't skip refreshing then we want the previous run state
   380  		// prior state to be the one we originally fed into the c.plan call
   381  		// above, not the refreshed version we used for the destroy walk.
   382  		destroyPlan.PrevRunState = pendingPlan.PrevRunState
   383  	}
   384  
   385  	relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, destroyPlan)
   386  	diags = diags.Append(rDiags)
   387  
   388  	destroyPlan.RelevantAttributes = relevantAttrs
   389  	return destroyPlan, diags
   390  }
   391  
   392  func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState *states.State, targets []addrs.Targetable) ([]refactoring.MoveStatement, refactoring.MoveResults) {
   393  	explicitMoveStmts := refactoring.FindMoveStatements(config)
   394  	implicitMoveStmts := refactoring.ImpliedMoveStatements(config, prevRunState, explicitMoveStmts)
   395  	var moveStmts []refactoring.MoveStatement
   396  	if stmtsLen := len(explicitMoveStmts) + len(implicitMoveStmts); stmtsLen > 0 {
   397  		moveStmts = make([]refactoring.MoveStatement, 0, stmtsLen)
   398  		moveStmts = append(moveStmts, explicitMoveStmts...)
   399  		moveStmts = append(moveStmts, implicitMoveStmts...)
   400  	}
   401  	moveResults := refactoring.ApplyMoves(moveStmts, prevRunState)
   402  	return moveStmts, moveResults
   403  }
   404  
   405  func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults, targets []addrs.Targetable) tfdiags.Diagnostics {
   406  	if len(targets) < 1 {
   407  		return nil // the following only matters when targeting
   408  	}
   409  
   410  	var diags tfdiags.Diagnostics
   411  
   412  	var excluded []addrs.AbsResourceInstance
   413  	for _, result := range moveResults.Changes.Values() {
   414  		fromMatchesTarget := false
   415  		toMatchesTarget := false
   416  		for _, targetAddr := range targets {
   417  			if targetAddr.TargetContains(result.From) {
   418  				fromMatchesTarget = true
   419  			}
   420  			if targetAddr.TargetContains(result.To) {
   421  				toMatchesTarget = true
   422  			}
   423  		}
   424  		if !fromMatchesTarget {
   425  			excluded = append(excluded, result.From)
   426  		}
   427  		if !toMatchesTarget {
   428  			excluded = append(excluded, result.To)
   429  		}
   430  	}
   431  	if len(excluded) > 0 {
   432  		sort.Slice(excluded, func(i, j int) bool {
   433  			return excluded[i].Less(excluded[j])
   434  		})
   435  
   436  		var listBuf strings.Builder
   437  		var prevResourceAddr addrs.AbsResource
   438  		for _, instAddr := range excluded {
   439  			// Targeting generally ends up selecting whole resources rather
   440  			// than individual instances, because we don't factor in
   441  			// individual instances until DynamicExpand, so we're going to
   442  			// always show whole resource addresses here, excluding any
   443  			// instance keys. (This also neatly avoids dealing with the
   444  			// different quoting styles required for string instance keys
   445  			// on different shells, which is handy.)
   446  			//
   447  			// To avoid showing duplicates when we have multiple instances
   448  			// of the same resource, we'll remember the most recent
   449  			// resource we rendered in prevResource, which is sufficient
   450  			// because we sorted the list of instance addresses above, and
   451  			// our sort order always groups together instances of the same
   452  			// resource.
   453  			resourceAddr := instAddr.ContainingResource()
   454  			if resourceAddr.Equal(prevResourceAddr) {
   455  				continue
   456  			}
   457  			fmt.Fprintf(&listBuf, "\n  -target=%q", resourceAddr.String())
   458  			prevResourceAddr = resourceAddr
   459  		}
   460  		diags = diags.Append(tfdiags.Sourceless(
   461  			tfdiags.Error,
   462  			"Moved resource instances excluded by targeting",
   463  			fmt.Sprintf(
   464  				"Resource instances in your current state have moved to new addresses in the latest configuration. Durgaform must include those resource instances while planning in order to ensure a correct result, but your -target=... options to 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.",
   465  				listBuf.String(),
   466  			),
   467  		))
   468  	}
   469  
   470  	return diags
   471  }
   472  
   473  func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactoring.MoveStatement, allInsts instances.Set) tfdiags.Diagnostics {
   474  	return refactoring.ValidateMoves(stmts, config, allInsts)
   475  }
   476  
   477  func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
   478  	var diags tfdiags.Diagnostics
   479  	log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode)
   480  
   481  	prevRunState = prevRunState.DeepCopy() // don't modify the caller's object when we process the moves
   482  	moveStmts, moveResults := c.prePlanFindAndApplyMoves(config, prevRunState, opts.Targets)
   483  
   484  	// If resource targeting is in effect then it might conflict with the
   485  	// move result.
   486  	diags = diags.Append(c.prePlanVerifyTargetedMoves(moveResults, opts.Targets))
   487  	if diags.HasErrors() {
   488  		// We'll return early here, because if we have any moved resource
   489  		// instances excluded by targeting then planning is likely to encounter
   490  		// strange problems that may lead to confusing error messages.
   491  		return nil, diags
   492  	}
   493  
   494  	graph, walkOp, moreDiags := c.planGraph(config, prevRunState, opts)
   495  	diags = diags.Append(moreDiags)
   496  	if diags.HasErrors() {
   497  		return nil, diags
   498  	}
   499  
   500  	// If we get here then we should definitely have a non-nil "graph", which
   501  	// we can now walk.
   502  	changes := plans.NewChanges()
   503  	conditions := plans.NewConditions()
   504  	walker, walkDiags := c.walk(graph, walkOp, &graphWalkOpts{
   505  		Config:      config,
   506  		InputState:  prevRunState,
   507  		Changes:     changes,
   508  		Conditions:  conditions,
   509  		MoveResults: moveResults,
   510  	})
   511  	diags = diags.Append(walker.NonFatalDiagnostics)
   512  	diags = diags.Append(walkDiags)
   513  	moveValidateDiags := c.postPlanValidateMoves(config, moveStmts, walker.InstanceExpander.AllInstances())
   514  	if moveValidateDiags.HasErrors() {
   515  		// If any of the move statements are invalid then those errors take
   516  		// precedence over any other errors because an incomplete move graph
   517  		// is quite likely to be the _cause_ of various errors. This oddity
   518  		// comes from the fact that we need to apply the moves before we
   519  		// actually validate them, because validation depends on the result
   520  		// of first trying to plan.
   521  		return nil, moveValidateDiags
   522  	}
   523  	diags = diags.Append(moveValidateDiags) // might just contain warnings
   524  
   525  	if moveResults.Blocked.Len() > 0 && !diags.HasErrors() {
   526  		// If we had blocked moves and we're not going to be returning errors
   527  		// then we'll report the blockers as a warning. We do this only in the
   528  		// absense of errors because invalid move statements might well be
   529  		// the root cause of the blockers, and so better to give an actionable
   530  		// error message than a less-actionable warning.
   531  		diags = diags.Append(blockedMovesWarningDiag(moveResults))
   532  	}
   533  
   534  	prevRunState = walker.PrevRunState.Close()
   535  	priorState := walker.RefreshState.Close()
   536  	driftedResources, driftDiags := c.driftedResources(config, prevRunState, priorState, moveResults)
   537  	diags = diags.Append(driftDiags)
   538  
   539  	plan := &plans.Plan{
   540  		UIMode:           opts.Mode,
   541  		Changes:          changes,
   542  		Conditions:       conditions,
   543  		DriftedResources: driftedResources,
   544  		PrevRunState:     prevRunState,
   545  		PriorState:       priorState,
   546  
   547  		// Other fields get populated by Context.Plan after we return
   548  	}
   549  	return plan, diags
   550  }
   551  
   552  func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*Graph, walkOperation, tfdiags.Diagnostics) {
   553  	switch mode := opts.Mode; mode {
   554  	case plans.NormalMode:
   555  		graph, diags := (&PlanGraphBuilder{
   556  			Config:             config,
   557  			State:              prevRunState,
   558  			RootVariableValues: opts.SetVariables,
   559  			Plugins:            c.plugins,
   560  			Targets:            opts.Targets,
   561  			ForceReplace:       opts.ForceReplace,
   562  			skipRefresh:        opts.SkipRefresh,
   563  			Operation:          walkPlan,
   564  		}).Build(addrs.RootModuleInstance)
   565  		return graph, walkPlan, diags
   566  	case plans.RefreshOnlyMode:
   567  		graph, diags := (&PlanGraphBuilder{
   568  			Config:             config,
   569  			State:              prevRunState,
   570  			RootVariableValues: opts.SetVariables,
   571  			Plugins:            c.plugins,
   572  			Targets:            opts.Targets,
   573  			skipRefresh:        opts.SkipRefresh,
   574  			skipPlanChanges:    true, // this activates "refresh only" mode.
   575  			Operation:          walkPlan,
   576  		}).Build(addrs.RootModuleInstance)
   577  		return graph, walkPlan, diags
   578  	case plans.DestroyMode:
   579  		graph, diags := (&PlanGraphBuilder{
   580  			Config:             config,
   581  			State:              prevRunState,
   582  			RootVariableValues: opts.SetVariables,
   583  			Plugins:            c.plugins,
   584  			Targets:            opts.Targets,
   585  			skipRefresh:        opts.SkipRefresh,
   586  			Operation:          walkPlanDestroy,
   587  		}).Build(addrs.RootModuleInstance)
   588  		return graph, walkPlanDestroy, diags
   589  	default:
   590  		// The above should cover all plans.Mode values
   591  		panic(fmt.Sprintf("unsupported plan mode %s", mode))
   592  	}
   593  }
   594  
   595  func (c *Context) driftedResources(config *configs.Config, oldState, newState *states.State, moves refactoring.MoveResults) ([]*plans.ResourceInstanceChangeSrc, tfdiags.Diagnostics) {
   596  	var diags tfdiags.Diagnostics
   597  
   598  	if newState.ManagedResourcesEqual(oldState) && moves.Changes.Len() == 0 {
   599  		// Nothing to do, because we only detect and report drift for managed
   600  		// resource instances.
   601  		return nil, diags
   602  	}
   603  
   604  	schemas, schemaDiags := c.Schemas(config, newState)
   605  	diags = diags.Append(schemaDiags)
   606  	if diags.HasErrors() {
   607  		return nil, diags
   608  	}
   609  
   610  	var drs []*plans.ResourceInstanceChangeSrc
   611  
   612  	for _, ms := range oldState.Modules {
   613  		for _, rs := range ms.Resources {
   614  			if rs.Addr.Resource.Mode != addrs.ManagedResourceMode {
   615  				// Drift reporting is only for managed resources
   616  				continue
   617  			}
   618  
   619  			provider := rs.ProviderConfig.Provider
   620  			for key, oldIS := range rs.Instances {
   621  				if oldIS.Current == nil {
   622  					// Not interested in instances that only have deposed objects
   623  					continue
   624  				}
   625  				addr := rs.Addr.Instance(key)
   626  
   627  				// Previous run address defaults to the current address, but
   628  				// can differ if the resource moved before refreshing
   629  				prevRunAddr := addr
   630  				if move, ok := moves.Changes.GetOk(addr); ok {
   631  					prevRunAddr = move.From
   632  				}
   633  
   634  				newIS := newState.ResourceInstance(addr)
   635  
   636  				schema, _ := schemas.ResourceTypeConfig(
   637  					provider,
   638  					addr.Resource.Resource.Mode,
   639  					addr.Resource.Resource.Type,
   640  				)
   641  				if schema == nil {
   642  					// This should never happen, but just in case
   643  					return nil, diags.Append(tfdiags.Sourceless(
   644  						tfdiags.Error,
   645  						"Missing resource schema from provider",
   646  						fmt.Sprintf("No resource schema found for %s.", addr.Resource.Resource.Type),
   647  					))
   648  				}
   649  				ty := schema.ImpliedType()
   650  
   651  				oldObj, err := oldIS.Current.Decode(ty)
   652  				if err != nil {
   653  					// This should also never happen
   654  					return nil, diags.Append(tfdiags.Sourceless(
   655  						tfdiags.Error,
   656  						"Failed to decode resource from state",
   657  						fmt.Sprintf("Error decoding %q from previous state: %s", addr.String(), err),
   658  					))
   659  				}
   660  
   661  				var newObj *states.ResourceInstanceObject
   662  				if newIS != nil && newIS.Current != nil {
   663  					newObj, err = newIS.Current.Decode(ty)
   664  					if err != nil {
   665  						// This should also never happen
   666  						return nil, diags.Append(tfdiags.Sourceless(
   667  							tfdiags.Error,
   668  							"Failed to decode resource from state",
   669  							fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err),
   670  						))
   671  					}
   672  				}
   673  
   674  				var oldVal, newVal cty.Value
   675  				oldVal = oldObj.Value
   676  				if newObj != nil {
   677  					newVal = newObj.Value
   678  				} else {
   679  					newVal = cty.NullVal(ty)
   680  				}
   681  
   682  				if oldVal.RawEquals(newVal) && addr.Equal(prevRunAddr) {
   683  					// No drift if the two values are semantically equivalent
   684  					// and no move has happened
   685  					continue
   686  				}
   687  
   688  				// We can detect three types of changes after refreshing state,
   689  				// only two of which are easily understood as "drift":
   690  				//
   691  				// - Resources which were deleted outside of Durgaform;
   692  				// - Resources where the object value has changed outside of
   693  				//   Durgaform;
   694  				// - Resources which have been moved without other changes.
   695  				//
   696  				// All of these are returned as drift, to allow refresh-only plans
   697  				// to present a full set of changes which will be applied.
   698  				var action plans.Action
   699  				switch {
   700  				case newVal.IsNull():
   701  					action = plans.Delete
   702  				case !oldVal.RawEquals(newVal):
   703  					action = plans.Update
   704  				default:
   705  					action = plans.NoOp
   706  				}
   707  
   708  				change := &plans.ResourceInstanceChange{
   709  					Addr:         addr,
   710  					PrevRunAddr:  prevRunAddr,
   711  					ProviderAddr: rs.ProviderConfig,
   712  					Change: plans.Change{
   713  						Action: action,
   714  						Before: oldVal,
   715  						After:  newVal,
   716  					},
   717  				}
   718  
   719  				changeSrc, err := change.Encode(ty)
   720  				if err != nil {
   721  					diags = diags.Append(err)
   722  					return nil, diags
   723  				}
   724  
   725  				drs = append(drs, changeSrc)
   726  			}
   727  		}
   728  	}
   729  
   730  	return drs, diags
   731  }
   732  
   733  // PlanGraphForUI is a last vestage of graphs in the public interface of Context
   734  // (as opposed to graphs as an implementation detail) intended only for use
   735  // by the "durgaform graph" command when asked to render a plan-time graph.
   736  //
   737  // The result of this is intended only for rendering ot the user as a dot
   738  // graph, and so may change in future in order to make the result more useful
   739  // in that context, even if drifts away from the physical graph that Durgaform
   740  // Core currently uses as an implementation detail of planning.
   741  func (c *Context) PlanGraphForUI(config *configs.Config, prevRunState *states.State, mode plans.Mode) (*Graph, tfdiags.Diagnostics) {
   742  	// For now though, this really is just the internal graph, confusing
   743  	// implementation details and all.
   744  
   745  	var diags tfdiags.Diagnostics
   746  
   747  	opts := &PlanOpts{Mode: mode}
   748  
   749  	graph, _, moreDiags := c.planGraph(config, prevRunState, opts)
   750  	diags = diags.Append(moreDiags)
   751  	return graph, diags
   752  }
   753  
   754  func blockedMovesWarningDiag(results refactoring.MoveResults) tfdiags.Diagnostic {
   755  	if results.Blocked.Len() < 1 {
   756  		// Caller should check first
   757  		panic("request to render blocked moves warning without any blocked moves")
   758  	}
   759  
   760  	var itemsBuf bytes.Buffer
   761  	for _, blocked := range results.Blocked.Values() {
   762  		fmt.Fprintf(&itemsBuf, "\n  - %s could not move to %s", blocked.Actual, blocked.Wanted)
   763  	}
   764  
   765  	return tfdiags.Sourceless(
   766  		tfdiags.Warning,
   767  		"Unresolved resource instance address changes",
   768  		fmt.Sprintf(
   769  			"Durgaform 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\nTerraform has planned to destroy these objects. If Terraform's proposed changes aren't appropriate, you must first resolve the conflicts using the \"durgaform state\" subcommands and then create a new plan.",
   770  			itemsBuf.String(),
   771  		),
   772  	)
   773  }
   774  
   775  // referenceAnalyzer returns a globalref.Analyzer object to help with
   776  // global analysis of references within the configuration that's attached
   777  // to the receiving context.
   778  func (c *Context) referenceAnalyzer(config *configs.Config, state *states.State) (*globalref.Analyzer, tfdiags.Diagnostics) {
   779  	schemas, diags := c.Schemas(config, state)
   780  	if diags.HasErrors() {
   781  		return nil, diags
   782  	}
   783  	return globalref.NewAnalyzer(config, schemas.Providers), diags
   784  }
   785  
   786  // relevantResourcesForPlan implements the heuristic we use to populate the
   787  // RelevantResources field of returned plans.
   788  func (c *Context) relevantResourceAttrsForPlan(config *configs.Config, plan *plans.Plan) ([]globalref.ResourceAttr, tfdiags.Diagnostics) {
   789  	azr, diags := c.referenceAnalyzer(config, plan.PriorState)
   790  	if diags.HasErrors() {
   791  		return nil, diags
   792  	}
   793  
   794  	var refs []globalref.Reference
   795  	for _, change := range plan.Changes.Resources {
   796  		if change.Action == plans.NoOp {
   797  			continue
   798  		}
   799  
   800  		moreRefs := azr.ReferencesFromResourceInstance(change.Addr)
   801  		refs = append(refs, moreRefs...)
   802  	}
   803  
   804  	for _, change := range plan.Changes.Outputs {
   805  		if change.Action == plans.NoOp {
   806  			continue
   807  		}
   808  
   809  		moreRefs := azr.ReferencesFromOutputValue(change.Addr)
   810  		refs = append(refs, moreRefs...)
   811  	}
   812  
   813  	var contributors []globalref.ResourceAttr
   814  
   815  	for _, ref := range azr.ContributingResourceReferences(refs...) {
   816  		if res, ok := ref.ResourceAttr(); ok {
   817  			contributors = append(contributors, res)
   818  		}
   819  	}
   820  
   821  	return contributors, diags
   822  }