github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/plans/plan.go (about)

     1  package plans
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/hashicorp/terraform/internal/addrs"
     7  	"github.com/hashicorp/terraform/internal/configs/configschema"
     8  	"github.com/hashicorp/terraform/internal/lang/globalref"
     9  	"github.com/hashicorp/terraform/internal/states"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  // Plan is the top-level type representing a planned set of changes.
    14  //
    15  // A plan is a summary of the set of changes required to move from a current
    16  // state to a goal state derived from configuration. The described changes
    17  // are not applied directly, but contain an approximation of the final
    18  // result that will be completed during apply by resolving any values that
    19  // cannot be predicted.
    20  //
    21  // A plan must always be accompanied by the configuration it was built from,
    22  // since the plan does not itself include all of the information required to
    23  // make the changes indicated.
    24  type Plan struct {
    25  	// Mode is the mode under which this plan was created.
    26  	//
    27  	// This is only recorded to allow for UI differences when presenting plans
    28  	// to the end-user, and so it must not be used to influence apply-time
    29  	// behavior. The actions during apply must be described entirely by
    30  	// the Changes field, regardless of how the plan was created.
    31  	UIMode Mode
    32  
    33  	VariableValues    map[string]DynamicValue
    34  	Changes           *Changes
    35  	DriftedResources  []*ResourceInstanceChangeSrc
    36  	TargetAddrs       []addrs.Targetable
    37  	ForceReplaceAddrs []addrs.AbsResourceInstance
    38  	Backend           Backend
    39  
    40  	// Checks captures a snapshot of the (probably-incomplete) check results
    41  	// at the end of the planning process.
    42  	//
    43  	// If this plan is applyable (that is, if the planning process completed
    44  	// without errors) then the set of checks here should be complete even
    45  	// though some of them will likely have StatusUnknown where the check
    46  	// condition depends on values we won't know until the apply step.
    47  	Checks *states.CheckResults
    48  
    49  	// RelevantAttributes is a set of resource instance addresses and
    50  	// attributes that are either directly affected by proposed changes or may
    51  	// have indirectly contributed to them via references in expressions.
    52  	//
    53  	// This is the result of a heuristic and is intended only as a hint to
    54  	// the UI layer in case it wants to emphasize or de-emphasize certain
    55  	// resources. Don't use this to drive any non-cosmetic behavior, especially
    56  	// including anything that would be subject to compatibility constraints.
    57  	RelevantAttributes []globalref.ResourceAttr
    58  
    59  	// PrevRunState and PriorState both describe the situation that the plan
    60  	// was derived from:
    61  	//
    62  	// PrevRunState is a representation of the outcome of the previous
    63  	// Terraform operation, without any updates from the remote system but
    64  	// potentially including some changes that resulted from state upgrade
    65  	// actions.
    66  	//
    67  	// PriorState is a representation of the current state of remote objects,
    68  	// which will differ from PrevRunState if the "refresh" step returned
    69  	// different data, which might reflect drift.
    70  	//
    71  	// PriorState is the main snapshot we use for actions during apply.
    72  	// PrevRunState is only here so that we can diff PriorState against it in
    73  	// order to report to the user any out-of-band changes we've detected.
    74  	PrevRunState *states.State
    75  	PriorState   *states.State
    76  }
    77  
    78  // CanApply returns true if and only if the recieving plan includes content
    79  // that would make sense to apply. If it returns false, the plan operation
    80  // should indicate that there's nothing to do and Terraform should exit
    81  // without prompting the user to confirm the changes.
    82  //
    83  // This function represents our main business logic for making the decision
    84  // about whether a given plan represents meaningful "changes", and so its
    85  // exact definition may change over time; the intent is just to centralize the
    86  // rules for that rather than duplicating different versions of it at various
    87  // locations in the UI code.
    88  func (p *Plan) CanApply() bool {
    89  	switch {
    90  	case !p.Changes.Empty():
    91  		// "Empty" means that everything in the changes is a "NoOp", so if
    92  		// not empty then there's at least one non-NoOp change.
    93  		return true
    94  
    95  	case !p.PriorState.ManagedResourcesEqual(p.PrevRunState):
    96  		// If there are no changes planned but we detected some
    97  		// outside-Terraform changes while refreshing then we consider
    98  		// that applyable in isolation only if this was a refresh-only
    99  		// plan where we expect updating the state to include these
   100  		// changes was the intended goal.
   101  		//
   102  		// (We don't treat a "refresh only" plan as applyable in normal
   103  		// planning mode because historically the refresh result wasn't
   104  		// considered part of a plan at all, and so it would be
   105  		// a disruptive breaking change if refreshing alone suddenly
   106  		// became applyable in the normal case and an existing configuration
   107  		// was relying on ignore_changes in order to be convergent in spite
   108  		// of intentional out-of-band operations.)
   109  		return p.UIMode == RefreshOnlyMode
   110  
   111  	default:
   112  		// Otherwise, there are either no changes to apply or they are changes
   113  		// our cases above don't consider as worthy of applying in isolation.
   114  		return false
   115  	}
   116  }
   117  
   118  // ProviderAddrs returns a list of all of the provider configuration addresses
   119  // referenced throughout the receiving plan.
   120  //
   121  // The result is de-duplicated so that each distinct address appears only once.
   122  func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig {
   123  	if p == nil || p.Changes == nil {
   124  		return nil
   125  	}
   126  
   127  	m := map[string]addrs.AbsProviderConfig{}
   128  	for _, rc := range p.Changes.Resources {
   129  		m[rc.ProviderAddr.String()] = rc.ProviderAddr
   130  	}
   131  	if len(m) == 0 {
   132  		return nil
   133  	}
   134  
   135  	// This is mainly just so we'll get stable results for testing purposes.
   136  	keys := make([]string, 0, len(m))
   137  	for k := range m {
   138  		keys = append(keys, k)
   139  	}
   140  	sort.Strings(keys)
   141  
   142  	ret := make([]addrs.AbsProviderConfig, len(keys))
   143  	for i, key := range keys {
   144  		ret[i] = m[key]
   145  	}
   146  
   147  	return ret
   148  }
   149  
   150  // Backend represents the backend-related configuration and other data as it
   151  // existed when a plan was created.
   152  type Backend struct {
   153  	// Type is the type of backend that the plan will apply against.
   154  	Type string
   155  
   156  	// Config is the configuration of the backend, whose schema is decided by
   157  	// the backend Type.
   158  	Config DynamicValue
   159  
   160  	// Workspace is the name of the workspace that was active when the plan
   161  	// was created. It is illegal to apply a plan created for one workspace
   162  	// to the state of another workspace.
   163  	// (This constraint is already enforced by the statefile lineage mechanism,
   164  	// but storing this explicitly allows us to return a better error message
   165  	// in the situation where the user has the wrong workspace selected.)
   166  	Workspace string
   167  }
   168  
   169  func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) {
   170  	dv, err := NewDynamicValue(config, configSchema.ImpliedType())
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	return &Backend{
   176  		Type:      typeName,
   177  		Config:    dv,
   178  		Workspace: workspaceName,
   179  	}, nil
   180  }