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

     1  package plans
     2  
     3  import (
     4  	"github.com/iaas-resource-provision/iaas-rpc/internal/addrs"
     5  	"github.com/iaas-resource-provision/iaas-rpc/internal/states"
     6  	"github.com/zclconf/go-cty/cty"
     7  )
     8  
     9  // Changes describes various actions that Terraform will attempt to take if
    10  // the corresponding plan is applied.
    11  //
    12  // A Changes object can be rendered into a visual diff (by the caller, using
    13  // code in another package) for display to the user.
    14  type Changes struct {
    15  	// Resources tracks planned changes to resource instance objects.
    16  	Resources []*ResourceInstanceChangeSrc
    17  
    18  	// Outputs tracks planned changes output values.
    19  	//
    20  	// Note that although an in-memory plan contains planned changes for
    21  	// outputs throughout the configuration, a plan serialized
    22  	// to disk retains only the root outputs because they are
    23  	// externally-visible, while other outputs are implementation details and
    24  	// can be easily re-calculated during the apply phase. Therefore only root
    25  	// module outputs will survive a round-trip through a plan file.
    26  	Outputs []*OutputChangeSrc
    27  }
    28  
    29  // NewChanges returns a valid Changes object that describes no changes.
    30  func NewChanges() *Changes {
    31  	return &Changes{}
    32  }
    33  
    34  func (c *Changes) Empty() bool {
    35  	for _, res := range c.Resources {
    36  		if res.Action != NoOp {
    37  			return false
    38  		}
    39  	}
    40  
    41  	for _, out := range c.Outputs {
    42  		if out.Addr.Module.IsRoot() && out.Action != NoOp {
    43  			return false
    44  		}
    45  	}
    46  
    47  	return true
    48  }
    49  
    50  // ResourceInstance returns the planned change for the current object of the
    51  // resource instance of the given address, if any. Returns nil if no change is
    52  // planned.
    53  func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc {
    54  	for _, rc := range c.Resources {
    55  		if rc.Addr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
    56  			return rc
    57  		}
    58  	}
    59  
    60  	return nil
    61  
    62  }
    63  
    64  // InstancesForConfigResource returns the planned change for the current objects
    65  // of the resource instances of the given address, if any. Returns nil if no
    66  // changes are planned.
    67  func (c *Changes) InstancesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc {
    68  	var changes []*ResourceInstanceChangeSrc
    69  	for _, rc := range c.Resources {
    70  		resAddr := rc.Addr.ContainingResource().Config()
    71  		if resAddr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
    72  			changes = append(changes, rc)
    73  		}
    74  	}
    75  
    76  	return changes
    77  }
    78  
    79  // ResourceInstanceDeposed returns the plan change of a deposed object of
    80  // the resource instance of the given address, if any. Returns nil if no change
    81  // is planned.
    82  func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc {
    83  	for _, rc := range c.Resources {
    84  		if rc.Addr.Equal(addr) && rc.DeposedKey == key {
    85  			return rc
    86  		}
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  // OutputValue returns the planned change for the output value with the
    93  //  given address, if any. Returns nil if no change is planned.
    94  func (c *Changes) OutputValue(addr addrs.AbsOutputValue) *OutputChangeSrc {
    95  	for _, oc := range c.Outputs {
    96  		if oc.Addr.Equal(addr) {
    97  			return oc
    98  		}
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // OutputValues returns planned changes for all outputs for all module
   105  // instances that reside in the parent path.  Returns nil if no changes are
   106  // planned.
   107  func (c *Changes) OutputValues(parent addrs.ModuleInstance, module addrs.ModuleCall) []*OutputChangeSrc {
   108  	var res []*OutputChangeSrc
   109  
   110  	for _, oc := range c.Outputs {
   111  		// we can't evaluate root module outputs
   112  		if oc.Addr.Module.Equal(addrs.RootModuleInstance) {
   113  			continue
   114  		}
   115  
   116  		changeMod, changeCall := oc.Addr.Module.Call()
   117  		// this does not reside on our parent instance path
   118  		if !changeMod.Equal(parent) {
   119  			continue
   120  		}
   121  
   122  		// this is not the module you're looking for
   123  		if changeCall.Name != module.Name {
   124  			continue
   125  		}
   126  
   127  		res = append(res, oc)
   128  
   129  	}
   130  
   131  	return res
   132  }
   133  
   134  // SyncWrapper returns a wrapper object around the receiver that can be used
   135  // to make certain changes to the receiver in a concurrency-safe way, as long
   136  // as all callers share the same wrapper object.
   137  func (c *Changes) SyncWrapper() *ChangesSync {
   138  	return &ChangesSync{
   139  		changes: c,
   140  	}
   141  }
   142  
   143  // ResourceInstanceChange describes a change to a particular resource instance
   144  // object.
   145  type ResourceInstanceChange struct {
   146  	// Addr is the absolute address of the resource instance that the change
   147  	// will apply to.
   148  	Addr addrs.AbsResourceInstance
   149  
   150  	// DeposedKey is the identifier for a deposed object associated with the
   151  	// given instance, or states.NotDeposed if this change applies to the
   152  	// current object.
   153  	//
   154  	// A Replace change for a resource with create_before_destroy set will
   155  	// create a new DeposedKey temporarily during replacement. In that case,
   156  	// DeposedKey in the plan is always states.NotDeposed, representing that
   157  	// the current object is being replaced with the deposed.
   158  	DeposedKey states.DeposedKey
   159  
   160  	// Provider is the address of the provider configuration that was used
   161  	// to plan this change, and thus the configuration that must also be
   162  	// used to apply it.
   163  	ProviderAddr addrs.AbsProviderConfig
   164  
   165  	// Change is an embedded description of the change.
   166  	Change
   167  
   168  	// ActionReason is an optional extra indication of why we chose the
   169  	// action recorded in Change.Action for this particular resource instance.
   170  	//
   171  	// This is an approximate mechanism only for the purpose of explaining the
   172  	// plan to end-users in the UI and is not to be used for any
   173  	// decision-making during the apply step; if apply behavior needs to vary
   174  	// depending on the "action reason" then the information for that decision
   175  	// must be recorded more precisely elsewhere for that purpose.
   176  	//
   177  	// Sometimes there might be more than one reason for choosing a particular
   178  	// action. In that case, it's up to the codepath making that decision to
   179  	// decide which value would provide the most relevant explanation to the
   180  	// end-user and return that. It's not a goal of this field to represent
   181  	// fine details about the planning process.
   182  	ActionReason ResourceInstanceChangeActionReason
   183  
   184  	// RequiredReplace is a set of paths that caused the change action to be
   185  	// Replace rather than Update. Always nil if the change action is not
   186  	// Replace.
   187  	//
   188  	// This is retained only for UI-plan-rendering purposes and so it does not
   189  	// currently survive a round-trip through a saved plan file.
   190  	RequiredReplace cty.PathSet
   191  
   192  	// Private allows a provider to stash any extra data that is opaque to
   193  	// Terraform that relates to this change. Terraform will save this
   194  	// byte-for-byte and return it to the provider in the apply call.
   195  	Private []byte
   196  }
   197  
   198  // Encode produces a variant of the reciever that has its change values
   199  // serialized so it can be written to a plan file. Pass the implied type of the
   200  // corresponding resource type schema for correct operation.
   201  func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSrc, error) {
   202  	cs, err := rc.Change.Encode(ty)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	return &ResourceInstanceChangeSrc{
   207  		Addr:            rc.Addr,
   208  		DeposedKey:      rc.DeposedKey,
   209  		ProviderAddr:    rc.ProviderAddr,
   210  		ChangeSrc:       *cs,
   211  		ActionReason:    rc.ActionReason,
   212  		RequiredReplace: rc.RequiredReplace,
   213  		Private:         rc.Private,
   214  	}, err
   215  }
   216  
   217  // Simplify will, where possible, produce a change with a simpler action than
   218  // the receiever given a flag indicating whether the caller is dealing with
   219  // a normal apply or a destroy. This flag deals with the fact that Terraform
   220  // Core uses a specialized graph node type for destroying; only that
   221  // specialized node should set "destroying" to true.
   222  //
   223  // The following table shows the simplification behavior:
   224  //
   225  //     Action    Destroying?   New Action
   226  //     --------+-------------+-----------
   227  //     Create    true          NoOp
   228  //     Delete    false         NoOp
   229  //     Replace   true          Delete
   230  //     Replace   false         Create
   231  //
   232  // For any combination not in the above table, the Simplify just returns the
   233  // receiver as-is.
   234  func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceChange {
   235  	if destroying {
   236  		switch rc.Action {
   237  		case Delete:
   238  			// We'll fall out and just return rc verbatim, then.
   239  		case CreateThenDelete, DeleteThenCreate:
   240  			return &ResourceInstanceChange{
   241  				Addr:         rc.Addr,
   242  				DeposedKey:   rc.DeposedKey,
   243  				Private:      rc.Private,
   244  				ProviderAddr: rc.ProviderAddr,
   245  				Change: Change{
   246  					Action: Delete,
   247  					Before: rc.Before,
   248  					After:  cty.NullVal(rc.Before.Type()),
   249  				},
   250  			}
   251  		default:
   252  			return &ResourceInstanceChange{
   253  				Addr:         rc.Addr,
   254  				DeposedKey:   rc.DeposedKey,
   255  				Private:      rc.Private,
   256  				ProviderAddr: rc.ProviderAddr,
   257  				Change: Change{
   258  					Action: NoOp,
   259  					Before: rc.Before,
   260  					After:  rc.Before,
   261  				},
   262  			}
   263  		}
   264  	} else {
   265  		switch rc.Action {
   266  		case Delete:
   267  			return &ResourceInstanceChange{
   268  				Addr:         rc.Addr,
   269  				DeposedKey:   rc.DeposedKey,
   270  				Private:      rc.Private,
   271  				ProviderAddr: rc.ProviderAddr,
   272  				Change: Change{
   273  					Action: NoOp,
   274  					Before: rc.Before,
   275  					After:  rc.Before,
   276  				},
   277  			}
   278  		case CreateThenDelete, DeleteThenCreate:
   279  			return &ResourceInstanceChange{
   280  				Addr:         rc.Addr,
   281  				DeposedKey:   rc.DeposedKey,
   282  				Private:      rc.Private,
   283  				ProviderAddr: rc.ProviderAddr,
   284  				Change: Change{
   285  					Action: Create,
   286  					Before: cty.NullVal(rc.After.Type()),
   287  					After:  rc.After,
   288  				},
   289  			}
   290  		}
   291  	}
   292  
   293  	// If we fall out here then our change is already simple enough.
   294  	return rc
   295  }
   296  
   297  // ResourceInstanceChangeActionReason allows for some extra user-facing
   298  // reasoning for why a particular change action was chosen for a particular
   299  // resource instance.
   300  //
   301  // This only represents sufficient detail to give a suitable explanation to
   302  // an end-user, and mustn't be used for any real decision-making during the
   303  // apply step.
   304  type ResourceInstanceChangeActionReason rune
   305  
   306  //go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceInstanceChangeActionReason changes.go
   307  
   308  const (
   309  	// In most cases there's no special reason for choosing a particular
   310  	// action, which is represented by ResourceInstanceChangeNoReason.
   311  	ResourceInstanceChangeNoReason ResourceInstanceChangeActionReason = 0
   312  
   313  	// ResourceInstanceReplaceBecauseTainted indicates that the resource
   314  	// instance must be replaced because its existing current object is
   315  	// marked as "tainted".
   316  	ResourceInstanceReplaceBecauseTainted ResourceInstanceChangeActionReason = 'T'
   317  
   318  	// ResourceInstanceReplaceByRequest indicates that the resource instance
   319  	// is planned to be replaced because a caller specifically asked for it
   320  	// to be using ReplaceAddrs. (On the command line, the -replace=...
   321  	// planning option.)
   322  	ResourceInstanceReplaceByRequest ResourceInstanceChangeActionReason = 'R'
   323  
   324  	// ResourceInstanceReplaceBecauseCannotUpdate indicates that the resource
   325  	// instance is planned to be replaced because the provider has indicated
   326  	// that a requested change cannot be applied as an update.
   327  	//
   328  	// In this case, the RequiredReplace field will typically be populated on
   329  	// the ResourceInstanceChange object to give information about specifically
   330  	// which arguments changed in a non-updatable way.
   331  	ResourceInstanceReplaceBecauseCannotUpdate ResourceInstanceChangeActionReason = 'F'
   332  )
   333  
   334  // OutputChange describes a change to an output value.
   335  type OutputChange struct {
   336  	// Addr is the absolute address of the output value that the change
   337  	// will apply to.
   338  	Addr addrs.AbsOutputValue
   339  
   340  	// Change is an embedded description of the change.
   341  	//
   342  	// For output value changes, the type constraint for the DynamicValue
   343  	// instances is always cty.DynamicPseudoType.
   344  	Change
   345  
   346  	// Sensitive, if true, indicates that either the old or new value in the
   347  	// change is sensitive and so a rendered version of the plan in the UI
   348  	// should elide the actual values while still indicating the action of the
   349  	// change.
   350  	Sensitive bool
   351  }
   352  
   353  // Encode produces a variant of the reciever that has its change values
   354  // serialized so it can be written to a plan file.
   355  func (oc *OutputChange) Encode() (*OutputChangeSrc, error) {
   356  	cs, err := oc.Change.Encode(cty.DynamicPseudoType)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  	return &OutputChangeSrc{
   361  		Addr:      oc.Addr,
   362  		ChangeSrc: *cs,
   363  		Sensitive: oc.Sensitive,
   364  	}, err
   365  }
   366  
   367  // Change describes a single change with a given action.
   368  type Change struct {
   369  	// Action defines what kind of change is being made.
   370  	Action Action
   371  
   372  	// Interpretation of Before and After depend on Action:
   373  	//
   374  	//     NoOp     Before and After are the same, unchanged value
   375  	//     Create   Before is nil, and After is the expected value after create.
   376  	//     Read     Before is any prior value (nil if no prior), and After is the
   377  	//              value that was or will be read.
   378  	//     Update   Before is the value prior to update, and After is the expected
   379  	//              value after update.
   380  	//     Replace  As with Update.
   381  	//     Delete   Before is the value prior to delete, and After is always nil.
   382  	//
   383  	// Unknown values may appear anywhere within the Before and After values,
   384  	// either as the values themselves or as nested elements within known
   385  	// collections/structures.
   386  	Before, After cty.Value
   387  }
   388  
   389  // Encode produces a variant of the reciever that has its change values
   390  // serialized so it can be written to a plan file. Pass the type constraint
   391  // that the values are expected to conform to; to properly decode the values
   392  // later an identical type constraint must be provided at that time.
   393  //
   394  // Where a Change is embedded in some other struct, it's generally better
   395  // to call the corresponding Encode method of that struct rather than working
   396  // directly with its embedded Change.
   397  func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) {
   398  	// Storing unmarked values so that we can encode unmarked values
   399  	// and save the PathValueMarks for re-marking the values later
   400  	var beforeVM, afterVM []cty.PathValueMarks
   401  	unmarkedBefore := c.Before
   402  	unmarkedAfter := c.After
   403  
   404  	if c.Before.ContainsMarked() {
   405  		unmarkedBefore, beforeVM = c.Before.UnmarkDeepWithPaths()
   406  	}
   407  	beforeDV, err := NewDynamicValue(unmarkedBefore, ty)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  
   412  	if c.After.ContainsMarked() {
   413  		unmarkedAfter, afterVM = c.After.UnmarkDeepWithPaths()
   414  	}
   415  	afterDV, err := NewDynamicValue(unmarkedAfter, ty)
   416  	if err != nil {
   417  		return nil, err
   418  	}
   419  
   420  	return &ChangeSrc{
   421  		Action:         c.Action,
   422  		Before:         beforeDV,
   423  		After:          afterDV,
   424  		BeforeValMarks: beforeVM,
   425  		AfterValMarks:  afterVM,
   426  	}, nil
   427  }