github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/resource.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package states 5 6 import ( 7 "fmt" 8 "math/rand" 9 "time" 10 11 "github.com/terramate-io/tf/addrs" 12 ) 13 14 // Resource represents the state of a resource. 15 type Resource struct { 16 // Addr is the absolute address for the resource this state object 17 // belongs to. 18 Addr addrs.AbsResource 19 20 // Instances contains the potentially-multiple instances associated with 21 // this resource. This map can contain a mixture of different key types, 22 // but only the ones of InstanceKeyType are considered current. 23 Instances map[addrs.InstanceKey]*ResourceInstance 24 25 // ProviderConfig is the absolute address for the provider configuration that 26 // most recently managed this resource. This is used to connect a resource 27 // with a provider configuration when the resource configuration block is 28 // not available, such as if it has been removed from configuration 29 // altogether. 30 ProviderConfig addrs.AbsProviderConfig 31 } 32 33 // Instance returns the state for the instance with the given key, or nil 34 // if no such instance is tracked within the state. 35 func (rs *Resource) Instance(key addrs.InstanceKey) *ResourceInstance { 36 return rs.Instances[key] 37 } 38 39 // CreateInstance creates an instance and adds it to the resource 40 func (rs *Resource) CreateInstance(key addrs.InstanceKey) *ResourceInstance { 41 is := NewResourceInstance() 42 rs.Instances[key] = is 43 return is 44 } 45 46 // EnsureInstance returns the state for the instance with the given key, 47 // creating a new empty state for it if one doesn't already exist. 48 // 49 // Because this may create and save a new state, it is considered to be 50 // a write operation. 51 func (rs *Resource) EnsureInstance(key addrs.InstanceKey) *ResourceInstance { 52 ret := rs.Instance(key) 53 if ret == nil { 54 ret = NewResourceInstance() 55 rs.Instances[key] = ret 56 } 57 return ret 58 } 59 60 // ResourceInstance represents the state of a particular instance of a resource. 61 type ResourceInstance struct { 62 // Current, if non-nil, is the remote object that is currently represented 63 // by the corresponding resource instance. 64 Current *ResourceInstanceObjectSrc 65 66 // Deposed, if len > 0, contains any remote objects that were previously 67 // represented by the corresponding resource instance but have been 68 // replaced and are pending destruction due to the create_before_destroy 69 // lifecycle mode. 70 Deposed map[DeposedKey]*ResourceInstanceObjectSrc 71 } 72 73 // NewResourceInstance constructs and returns a new ResourceInstance, ready to 74 // use. 75 func NewResourceInstance() *ResourceInstance { 76 return &ResourceInstance{ 77 Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{}, 78 } 79 } 80 81 // HasCurrent returns true if this resource instance has a "current"-generation 82 // object. Most instances do, but this can briefly be false during a 83 // create-before-destroy replace operation when the current has been deposed 84 // but its replacement has not yet been created. 85 func (i *ResourceInstance) HasCurrent() bool { 86 return i != nil && i.Current != nil 87 } 88 89 // HasDeposed returns true if this resource instance has a deposed object 90 // with the given key. 91 func (i *ResourceInstance) HasDeposed(key DeposedKey) bool { 92 return i != nil && i.Deposed[key] != nil 93 } 94 95 // HasAnyDeposed returns true if this resource instance has one or more 96 // deposed objects. 97 func (i *ResourceInstance) HasAnyDeposed() bool { 98 return i != nil && len(i.Deposed) > 0 99 } 100 101 // HasObjects returns true if this resource has any objects at all, whether 102 // current or deposed. 103 func (i *ResourceInstance) HasObjects() bool { 104 return i.Current != nil || len(i.Deposed) != 0 105 } 106 107 // deposeCurrentObject is part of the real implementation of 108 // SyncState.DeposeResourceInstanceObject. The exported method uses a lock 109 // to ensure that we can safely allocate an unused deposed key without 110 // collision. 111 func (i *ResourceInstance) deposeCurrentObject(forceKey DeposedKey) DeposedKey { 112 if !i.HasCurrent() { 113 return NotDeposed 114 } 115 116 key := forceKey 117 if key == NotDeposed { 118 key = i.findUnusedDeposedKey() 119 } else { 120 if _, exists := i.Deposed[key]; exists { 121 panic(fmt.Sprintf("forced key %s is already in use", forceKey)) 122 } 123 } 124 i.Deposed[key] = i.Current 125 i.Current = nil 126 return key 127 } 128 129 // GetGeneration retrieves the object of the given generation from the 130 // ResourceInstance, or returns nil if there is no such object. 131 // 132 // If the given generation is nil or invalid, this method will panic. 133 func (i *ResourceInstance) GetGeneration(gen Generation) *ResourceInstanceObjectSrc { 134 if gen == CurrentGen { 135 return i.Current 136 } 137 if dk, ok := gen.(DeposedKey); ok { 138 return i.Deposed[dk] 139 } 140 if gen == nil { 141 panic("get with nil Generation") 142 } 143 // Should never fall out here, since the above covers all possible 144 // Generation values. 145 panic(fmt.Sprintf("get invalid Generation %#v", gen)) 146 } 147 148 // FindUnusedDeposedKey generates a unique DeposedKey that is guaranteed not to 149 // already be in use for this instance at the time of the call. 150 // 151 // Note that the validity of this result may change if new deposed keys are 152 // allocated before it is used. To avoid this risk, instead use the 153 // DeposeResourceInstanceObject method on the SyncState wrapper type, which 154 // allocates a key and uses it atomically. 155 func (i *ResourceInstance) FindUnusedDeposedKey() DeposedKey { 156 return i.findUnusedDeposedKey() 157 } 158 159 // findUnusedDeposedKey generates a unique DeposedKey that is guaranteed not to 160 // already be in use for this instance. 161 func (i *ResourceInstance) findUnusedDeposedKey() DeposedKey { 162 for { 163 key := NewDeposedKey() 164 if _, exists := i.Deposed[key]; !exists { 165 return key 166 } 167 // Spin until we find a unique one. This shouldn't take long, because 168 // we have a 32-bit keyspace and there's rarely more than one deposed 169 // instance. 170 } 171 } 172 173 // DeposedKey is a 8-character hex string used to uniquely identify deposed 174 // instance objects in the state. 175 type DeposedKey string 176 177 // NotDeposed is a special invalid value of DeposedKey that is used to represent 178 // the absense of a deposed key. It must not be used as an actual deposed key. 179 const NotDeposed = DeposedKey("") 180 181 var deposedKeyRand = rand.New(rand.NewSource(time.Now().UnixNano())) 182 183 // NewDeposedKey generates a pseudo-random deposed key. Because of the short 184 // length of these keys, uniqueness is not a natural consequence and so the 185 // caller should test to see if the generated key is already in use and generate 186 // another if so, until a unique key is found. 187 func NewDeposedKey() DeposedKey { 188 v := deposedKeyRand.Uint32() 189 return DeposedKey(fmt.Sprintf("%08x", v)) 190 } 191 192 func (k DeposedKey) String() string { 193 return string(k) 194 } 195 196 func (k DeposedKey) GoString() string { 197 ks := string(k) 198 switch { 199 case ks == "": 200 return "states.NotDeposed" 201 default: 202 return fmt.Sprintf("states.DeposedKey(%s)", ks) 203 } 204 } 205 206 // Generation is a helper method to convert a DeposedKey into a Generation. 207 // If the reciever is anything other than NotDeposed then the result is 208 // just the same value as a Generation. If the receiver is NotDeposed then 209 // the result is CurrentGen. 210 func (k DeposedKey) Generation() Generation { 211 if k == NotDeposed { 212 return CurrentGen 213 } 214 return k 215 } 216 217 // generation is an implementation of Generation. 218 func (k DeposedKey) generation() {}