github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/instance_object.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package states
     5  
     6  import (
     7  	"sort"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  	ctyjson "github.com/zclconf/go-cty/cty/json"
    11  
    12  	"github.com/terramate-io/tf/addrs"
    13  )
    14  
    15  // ResourceInstanceObject is the local representation of a specific remote
    16  // object associated with a resource instance. In practice not all remote
    17  // objects are actually remote in the sense of being accessed over the network,
    18  // but this is the most common case.
    19  //
    20  // It is not valid to mutate a ResourceInstanceObject once it has been created.
    21  // Instead, create a new object and replace the existing one.
    22  type ResourceInstanceObject struct {
    23  	// Value is the object-typed value representing the remote object within
    24  	// Terraform.
    25  	Value cty.Value
    26  
    27  	// Private is an opaque value set by the provider when this object was
    28  	// last created or updated. Terraform Core does not use this value in
    29  	// any way and it is not exposed anywhere in the user interface, so
    30  	// a provider can use it for retaining any necessary private state.
    31  	Private []byte
    32  
    33  	// Status represents the "readiness" of the object as of the last time
    34  	// it was updated.
    35  	Status ObjectStatus
    36  
    37  	// Dependencies is a set of absolute address to other resources this
    38  	// instance dependeded on when it was applied. This is used to construct
    39  	// the dependency relationships for an object whose configuration is no
    40  	// longer available, such as if it has been removed from configuration
    41  	// altogether, or is now deposed.
    42  	Dependencies []addrs.ConfigResource
    43  
    44  	// CreateBeforeDestroy reflects the status of the lifecycle
    45  	// create_before_destroy option when this instance was last updated.
    46  	// Because create_before_destroy also effects the overall ordering of the
    47  	// destroy operations, we need to record the status to ensure a resource
    48  	// removed from the config will still be destroyed in the same manner.
    49  	CreateBeforeDestroy bool
    50  }
    51  
    52  // ObjectStatus represents the status of a RemoteObject.
    53  type ObjectStatus rune
    54  
    55  //go:generate go run golang.org/x/tools/cmd/stringer -type ObjectStatus
    56  
    57  const (
    58  	// ObjectReady is an object status for an object that is ready to use.
    59  	ObjectReady ObjectStatus = 'R'
    60  
    61  	// ObjectTainted is an object status representing an object that is in
    62  	// an unrecoverable bad state due to a partial failure during a create,
    63  	// update, or delete operation. Since it cannot be moved into the
    64  	// ObjectRead state, a tainted object must be replaced.
    65  	ObjectTainted ObjectStatus = 'T'
    66  
    67  	// ObjectPlanned is a special object status used only for the transient
    68  	// placeholder objects we place into state during the refresh and plan
    69  	// walks to stand in for objects that will be created during apply.
    70  	//
    71  	// Any object of this status must have a corresponding change recorded
    72  	// in the current plan, whose value must then be used in preference to
    73  	// the value stored in state when evaluating expressions. A planned
    74  	// object stored in state will be incomplete if any of its attributes are
    75  	// not yet known, and the plan must be consulted in order to "see" those
    76  	// unknown values, because the state is not able to represent them.
    77  	ObjectPlanned ObjectStatus = 'P'
    78  )
    79  
    80  // Encode marshals the value within the receiver to produce a
    81  // ResourceInstanceObjectSrc ready to be written to a state file.
    82  //
    83  // The given type must be the implied type of the resource type schema, and
    84  // the given value must conform to it. It is important to pass the schema
    85  // type and not the object's own type so that dynamically-typed attributes
    86  // will be stored correctly. The caller must also provide the version number
    87  // of the schema that the given type was derived from, which will be recorded
    88  // in the source object so it can be used to detect when schema migration is
    89  // required on read.
    90  //
    91  // The returned object may share internal references with the receiver and
    92  // so the caller must not mutate the receiver any further once once this
    93  // method is called.
    94  func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*ResourceInstanceObjectSrc, error) {
    95  	// If it contains marks, remove these marks before traversing the
    96  	// structure with UnknownAsNull, and save the PathValueMarks
    97  	// so we can save them in state.
    98  	val, pvm := o.Value.UnmarkDeepWithPaths()
    99  
   100  	// Our state serialization can't represent unknown values, so we convert
   101  	// them to nulls here. This is lossy, but nobody should be writing unknown
   102  	// values here and expecting to get them out again later.
   103  	//
   104  	// We get unknown values here while we're building out a "planned state"
   105  	// during the plan phase, but the value stored in the plan takes precedence
   106  	// for expression evaluation. The apply step should never produce unknown
   107  	// values, but if it does it's the responsibility of the caller to detect
   108  	// and raise an error about that.
   109  	val = cty.UnknownAsNull(val)
   110  
   111  	src, err := ctyjson.Marshal(val, ty)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	// Dependencies are collected and merged in an unordered format (using map
   117  	// keys as a set), then later changed to a slice (in random ordering) to be
   118  	// stored in state as an array. To avoid pointless thrashing of state in
   119  	// refresh-only runs, we can either override comparison of dependency lists
   120  	// (more desirable, but tricky for Reasons) or just sort when encoding.
   121  	// Encoding of instances can happen concurrently, so we must copy the
   122  	// dependencies to avoid mutating what may be a shared array of values.
   123  	dependencies := make([]addrs.ConfigResource, len(o.Dependencies))
   124  	copy(dependencies, o.Dependencies)
   125  
   126  	sort.Slice(dependencies, func(i, j int) bool { return dependencies[i].String() < dependencies[j].String() })
   127  
   128  	return &ResourceInstanceObjectSrc{
   129  		SchemaVersion:       schemaVersion,
   130  		AttrsJSON:           src,
   131  		AttrSensitivePaths:  pvm,
   132  		Private:             o.Private,
   133  		Status:              o.Status,
   134  		Dependencies:        dependencies,
   135  		CreateBeforeDestroy: o.CreateBeforeDestroy,
   136  	}, nil
   137  }
   138  
   139  // AsTainted returns a deep copy of the receiver with the status updated to
   140  // ObjectTainted.
   141  func (o *ResourceInstanceObject) AsTainted() *ResourceInstanceObject {
   142  	if o == nil {
   143  		// A nil object can't be tainted, but we'll allow this anyway to
   144  		// avoid a crash, since we presumably intend to eventually record
   145  		// the object has having been deleted anyway.
   146  		return nil
   147  	}
   148  	ret := o.DeepCopy()
   149  	ret.Status = ObjectTainted
   150  	return ret
   151  }