github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/plans/plan.go (about)

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