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 }