github.com/opentofu/opentofu@v1.7.1/internal/plans/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 plans
     7  
     8  import (
     9  	"sort"
    10  	"time"
    11  
    12  	"github.com/zclconf/go-cty/cty"
    13  
    14  	"github.com/opentofu/opentofu/internal/addrs"
    15  	"github.com/opentofu/opentofu/internal/configs/configschema"
    16  	"github.com/opentofu/opentofu/internal/lang/globalref"
    17  	"github.com/opentofu/opentofu/internal/states"
    18  )
    19  
    20  // Plan is the top-level type representing a planned set of changes.
    21  //
    22  // A plan is a summary of the set of changes required to move from a current
    23  // state to a goal state derived from configuration. The described changes
    24  // are not applied directly, but contain an approximation of the final
    25  // result that will be completed during apply by resolving any values that
    26  // cannot be predicted.
    27  //
    28  // A plan must always be accompanied by the configuration it was built from,
    29  // since the plan does not itself include all of the information required to
    30  // make the changes indicated.
    31  type Plan struct {
    32  	// Mode is the mode under which this plan was created.
    33  	//
    34  	// This is only recorded to allow for UI differences when presenting plans
    35  	// to the end-user, and so it must not be used to influence apply-time
    36  	// behavior. The actions during apply must be described entirely by
    37  	// the Changes field, regardless of how the plan was created.
    38  	//
    39  	// FIXME: destroy operations still rely on DestroyMode being set, because
    40  	// there is no other source of this information in the plan. New behavior
    41  	// should not be added based on this flag, and changing the flag should be
    42  	// checked carefully against existing destroy behaviors.
    43  	UIMode Mode
    44  
    45  	VariableValues    map[string]DynamicValue
    46  	Changes           *Changes
    47  	DriftedResources  []*ResourceInstanceChangeSrc
    48  	TargetAddrs       []addrs.Targetable
    49  	ForceReplaceAddrs []addrs.AbsResourceInstance
    50  	Backend           Backend
    51  
    52  	// Errored is true if the Changes information is incomplete because
    53  	// the planning operation failed. An errored plan cannot be applied,
    54  	// but can be cautiously inspected for debugging purposes.
    55  	Errored bool
    56  
    57  	// Checks captures a snapshot of the (probably-incomplete) check results
    58  	// at the end of the planning process.
    59  	//
    60  	// If this plan is applyable (that is, if the planning process completed
    61  	// without errors) then the set of checks here should be complete even
    62  	// though some of them will likely have StatusUnknown where the check
    63  	// condition depends on values we won't know until the apply step.
    64  	Checks *states.CheckResults
    65  
    66  	// RelevantAttributes is a set of resource instance addresses and
    67  	// attributes that are either directly affected by proposed changes or may
    68  	// have indirectly contributed to them via references in expressions.
    69  	//
    70  	// This is the result of a heuristic and is intended only as a hint to
    71  	// the UI layer in case it wants to emphasize or de-emphasize certain
    72  	// resources. Don't use this to drive any non-cosmetic behavior, especially
    73  	// including anything that would be subject to compatibility constraints.
    74  	RelevantAttributes []globalref.ResourceAttr
    75  
    76  	// PrevRunState and PriorState both describe the situation that the plan
    77  	// was derived from:
    78  	//
    79  	// PrevRunState is a representation of the outcome of the previous
    80  	// OpenTofu operation, without any updates from the remote system but
    81  	// potentially including some changes that resulted from state upgrade
    82  	// actions.
    83  	//
    84  	// PriorState is a representation of the current state of remote objects,
    85  	// which will differ from PrevRunState if the "refresh" step returned
    86  	// different data, which might reflect drift.
    87  	//
    88  	// PriorState is the main snapshot we use for actions during apply.
    89  	// PrevRunState is only here so that we can diff PriorState against it in
    90  	// order to report to the user any out-of-band changes we've detected.
    91  	PrevRunState *states.State
    92  	PriorState   *states.State
    93  
    94  	// PlannedState is the temporary planned state that was created during the
    95  	// graph walk that generated this plan.
    96  	//
    97  	// This is required by the testing framework when evaluating run blocks
    98  	// executing in plan mode. The graph updates the state with certain values
    99  	// that are difficult to retrieve later, such as local values that reference
   100  	// updated resources. It is easier to build the testing scope with access
   101  	// to same temporary state the plan used/built.
   102  	//
   103  	// This is never recorded outside of OpenTofu. It is not written into the
   104  	// binary plan file, and it is not written into the JSON structured outputs.
   105  	// The testing framework never writes the plans out but holds everything in
   106  	// memory as it executes, so there is no need to add any kind of
   107  	// serialization for this field. This does mean that you shouldn't rely on
   108  	// this field existing unless you have just generated the plan.
   109  	PlannedState *states.State
   110  
   111  	// ExternalReferences are references that are being made to resources within
   112  	// the plan from external sources. As with PlannedState this is used by the
   113  	// OpenTofu testing framework, and so isn't written into any external
   114  	// representation of the plan.
   115  	ExternalReferences []*addrs.Reference
   116  
   117  	// Timestamp is the record of truth for when the plan happened.
   118  	Timestamp time.Time
   119  }
   120  
   121  // CanApply returns true if and only if the recieving plan includes content
   122  // that would make sense to apply. If it returns false, the plan operation
   123  // should indicate that there's nothing to do and OpenTofu should exit
   124  // without prompting the user to confirm the changes.
   125  //
   126  // This function represents our main business logic for making the decision
   127  // about whether a given plan represents meaningful "changes", and so its
   128  // exact definition may change over time; the intent is just to centralize the
   129  // rules for that rather than duplicating different versions of it at various
   130  // locations in the UI code.
   131  func (p *Plan) CanApply() bool {
   132  	switch {
   133  	case p.Errored:
   134  		// An errored plan can never be applied, because it is incomplete.
   135  		// Such a plan is only useful for describing the subset of actions
   136  		// planned so far in case they are useful for understanding the
   137  		// causes of the errors.
   138  		return false
   139  
   140  	case !p.Changes.Empty():
   141  		// "Empty" means that everything in the changes is a "NoOp", so if
   142  		// not empty then there's at least one non-NoOp change.
   143  		return true
   144  
   145  	case !p.PriorState.ManagedResourcesEqual(p.PrevRunState):
   146  		// If there are no changes planned but we detected some
   147  		// outside-OpenTofu changes while refreshing then we consider
   148  		// that applyable in isolation only if this was a refresh-only
   149  		// plan where we expect updating the state to include these
   150  		// changes was the intended goal.
   151  		//
   152  		// (We don't treat a "refresh only" plan as applyable in normal
   153  		// planning mode because historically the refresh result wasn't
   154  		// considered part of a plan at all, and so it would be
   155  		// a disruptive breaking change if refreshing alone suddenly
   156  		// became applyable in the normal case and an existing configuration
   157  		// was relying on ignore_changes in order to be convergent in spite
   158  		// of intentional out-of-band operations.)
   159  		return p.UIMode == RefreshOnlyMode
   160  
   161  	default:
   162  		// Otherwise, there are either no changes to apply or they are changes
   163  		// our cases above don't consider as worthy of applying in isolation.
   164  		return false
   165  	}
   166  }
   167  
   168  // ProviderAddrs returns a list of all of the provider configuration addresses
   169  // referenced throughout the receiving plan.
   170  //
   171  // The result is de-duplicated so that each distinct address appears only once.
   172  func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig {
   173  	if p == nil || p.Changes == nil {
   174  		return nil
   175  	}
   176  
   177  	m := map[string]addrs.AbsProviderConfig{}
   178  	for _, rc := range p.Changes.Resources {
   179  		m[rc.ProviderAddr.String()] = rc.ProviderAddr
   180  	}
   181  	if len(m) == 0 {
   182  		return nil
   183  	}
   184  
   185  	// This is mainly just so we'll get stable results for testing purposes.
   186  	keys := make([]string, 0, len(m))
   187  	for k := range m {
   188  		keys = append(keys, k)
   189  	}
   190  	sort.Strings(keys)
   191  
   192  	ret := make([]addrs.AbsProviderConfig, len(keys))
   193  	for i, key := range keys {
   194  		ret[i] = m[key]
   195  	}
   196  
   197  	return ret
   198  }
   199  
   200  // Backend represents the backend-related configuration and other data as it
   201  // existed when a plan was created.
   202  type Backend struct {
   203  	// Type is the type of backend that the plan will apply against.
   204  	Type string
   205  
   206  	// Config is the configuration of the backend, whose schema is decided by
   207  	// the backend Type.
   208  	Config DynamicValue
   209  
   210  	// Workspace is the name of the workspace that was active when the plan
   211  	// was created. It is illegal to apply a plan created for one workspace
   212  	// to the state of another workspace.
   213  	// (This constraint is already enforced by the statefile lineage mechanism,
   214  	// but storing this explicitly allows us to return a better error message
   215  	// in the situation where the user has the wrong workspace selected.)
   216  	Workspace string
   217  }
   218  
   219  func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) {
   220  	dv, err := NewDynamicValue(config, configSchema.ImpliedType())
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	return &Backend{
   226  		Type:      typeName,
   227  		Config:    dv,
   228  		Workspace: workspaceName,
   229  	}, nil
   230  }