mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-23 07:33:32 -06:00
fd775f0fe3
Signed-off-by: ollevche <ollevche@gmail.com> Signed-off-by: Christian Mesh <christianmesh1@gmail.com> Signed-off-by: Ronny Orot <ronny.orot@gmail.com> Signed-off-by: Martin Atkins <mart@degeneration.co.uk> Co-authored-by: ollevche <ollevche@gmail.com> Co-authored-by: Ronny Orot <ronny.orot@gmail.com> Co-authored-by: Martin Atkins <mart@degeneration.co.uk>
226 lines
7.3 KiB
Go
226 lines
7.3 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package states
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
)
|
|
|
|
// Resource represents the state of a resource.
|
|
type Resource struct {
|
|
// Addr is the absolute address for the resource this state object
|
|
// belongs to.
|
|
Addr addrs.AbsResource
|
|
|
|
// Instances contains the potentially-multiple instances associated with
|
|
// this resource. This map can contain a mixture of different key types,
|
|
// but only the ones of InstanceKeyType are considered current.
|
|
Instances map[addrs.InstanceKey]*ResourceInstance
|
|
|
|
// ProviderConfig is the absolute address for the provider configuration that
|
|
// most recently managed this resource. This is used to connect a resource
|
|
// with a provider configuration when the resource configuration block is
|
|
// not available, such as if it has been removed from configuration
|
|
// altogether.
|
|
ProviderConfig addrs.AbsProviderConfig
|
|
}
|
|
|
|
// Instance returns the state for the instance with the given key, or nil
|
|
// if no such instance is tracked within the state.
|
|
func (rs *Resource) Instance(key addrs.InstanceKey) *ResourceInstance {
|
|
return rs.Instances[key]
|
|
}
|
|
|
|
// CreateInstance creates an instance and adds it to the resource
|
|
func (rs *Resource) CreateInstance(key addrs.InstanceKey) *ResourceInstance {
|
|
is := NewResourceInstance()
|
|
rs.Instances[key] = is
|
|
return is
|
|
}
|
|
|
|
// EnsureInstance returns the state for the instance with the given key,
|
|
// creating a new empty state for it if one doesn't already exist.
|
|
//
|
|
// Because this may create and save a new state, it is considered to be
|
|
// a write operation.
|
|
func (rs *Resource) EnsureInstance(key addrs.InstanceKey) *ResourceInstance {
|
|
ret := rs.Instance(key)
|
|
if ret == nil {
|
|
ret = NewResourceInstance()
|
|
rs.Instances[key] = ret
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// ResourceInstance represents the state of a particular instance of a resource.
|
|
type ResourceInstance struct {
|
|
// Current, if non-nil, is the remote object that is currently represented
|
|
// by the corresponding resource instance.
|
|
Current *ResourceInstanceObjectSrc
|
|
|
|
// Deposed, if len > 0, contains any remote objects that were previously
|
|
// represented by the corresponding resource instance but have been
|
|
// replaced and are pending destruction due to the create_before_destroy
|
|
// lifecycle mode.
|
|
Deposed map[DeposedKey]*ResourceInstanceObjectSrc
|
|
|
|
// ProviderKey, in combination with Resource.ProviderConfig, represents
|
|
// the resource instance's provider configuration. This is only set
|
|
// when using provider iteration on resources or modules
|
|
ProviderKey addrs.InstanceKey
|
|
}
|
|
|
|
// NewResourceInstance constructs and returns a new ResourceInstance, ready to
|
|
// use.
|
|
func NewResourceInstance() *ResourceInstance {
|
|
return &ResourceInstance{
|
|
Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{},
|
|
}
|
|
}
|
|
|
|
// HasCurrent returns true if this resource instance has a "current"-generation
|
|
// object. Most instances do, but this can briefly be false during a
|
|
// create-before-destroy replace operation when the current has been deposed
|
|
// but its replacement has not yet been created.
|
|
func (i *ResourceInstance) HasCurrent() bool {
|
|
return i != nil && i.Current != nil
|
|
}
|
|
|
|
// HasDeposed returns true if this resource instance has a deposed object
|
|
// with the given key.
|
|
func (i *ResourceInstance) HasDeposed(key DeposedKey) bool {
|
|
return i != nil && i.Deposed[key] != nil
|
|
}
|
|
|
|
// HasAnyDeposed returns true if this resource instance has one or more
|
|
// deposed objects.
|
|
func (i *ResourceInstance) HasAnyDeposed() bool {
|
|
return i != nil && len(i.Deposed) > 0
|
|
}
|
|
|
|
// HasObjects returns true if this resource has any objects at all, whether
|
|
// current or deposed.
|
|
func (i *ResourceInstance) HasObjects() bool {
|
|
return i.Current != nil || len(i.Deposed) != 0
|
|
}
|
|
|
|
// deposeCurrentObject is part of the real implementation of
|
|
// SyncState.DeposeResourceInstanceObject. The exported method uses a lock
|
|
// to ensure that we can safely allocate an unused deposed key without
|
|
// collision.
|
|
func (i *ResourceInstance) deposeCurrentObject(forceKey DeposedKey) DeposedKey {
|
|
if !i.HasCurrent() {
|
|
return NotDeposed
|
|
}
|
|
|
|
key := forceKey
|
|
if key == NotDeposed {
|
|
key = i.findUnusedDeposedKey()
|
|
} else {
|
|
if _, exists := i.Deposed[key]; exists {
|
|
panic(fmt.Sprintf("forced key %s is already in use", forceKey))
|
|
}
|
|
}
|
|
i.Deposed[key] = i.Current
|
|
i.Current = nil
|
|
return key
|
|
}
|
|
|
|
// GetGeneration retrieves the object of the given generation from the
|
|
// ResourceInstance, or returns nil if there is no such object.
|
|
//
|
|
// If the given generation is nil or invalid, this method will panic.
|
|
func (i *ResourceInstance) GetGeneration(gen Generation) *ResourceInstanceObjectSrc {
|
|
if gen == CurrentGen {
|
|
return i.Current
|
|
}
|
|
if dk, ok := gen.(DeposedKey); ok {
|
|
return i.Deposed[dk]
|
|
}
|
|
if gen == nil {
|
|
panic("get with nil Generation")
|
|
}
|
|
// Should never fall out here, since the above covers all possible
|
|
// Generation values.
|
|
panic(fmt.Sprintf("get invalid Generation %#v", gen))
|
|
}
|
|
|
|
// FindUnusedDeposedKey generates a unique DeposedKey that is guaranteed not to
|
|
// already be in use for this instance at the time of the call.
|
|
//
|
|
// Note that the validity of this result may change if new deposed keys are
|
|
// allocated before it is used. To avoid this risk, instead use the
|
|
// DeposeResourceInstanceObject method on the SyncState wrapper type, which
|
|
// allocates a key and uses it atomically.
|
|
func (i *ResourceInstance) FindUnusedDeposedKey() DeposedKey {
|
|
return i.findUnusedDeposedKey()
|
|
}
|
|
|
|
// findUnusedDeposedKey generates a unique DeposedKey that is guaranteed not to
|
|
// already be in use for this instance.
|
|
func (i *ResourceInstance) findUnusedDeposedKey() DeposedKey {
|
|
for {
|
|
key := NewDeposedKey()
|
|
if _, exists := i.Deposed[key]; !exists {
|
|
return key
|
|
}
|
|
// Spin until we find a unique one. This shouldn't take long, because
|
|
// we have a 32-bit keyspace and there's rarely more than one deposed
|
|
// instance.
|
|
}
|
|
}
|
|
|
|
// DeposedKey is a 8-character hex string used to uniquely identify deposed
|
|
// instance objects in the state.
|
|
type DeposedKey string
|
|
|
|
// NotDeposed is a special invalid value of DeposedKey that is used to represent
|
|
// the absence of a deposed key. It must not be used as an actual deposed key.
|
|
const NotDeposed = DeposedKey("")
|
|
|
|
var deposedKeyRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
// NewDeposedKey generates a pseudo-random deposed key. Because of the short
|
|
// length of these keys, uniqueness is not a natural consequence and so the
|
|
// caller should test to see if the generated key is already in use and generate
|
|
// another if so, until a unique key is found.
|
|
func NewDeposedKey() DeposedKey {
|
|
v := deposedKeyRand.Uint32()
|
|
return DeposedKey(fmt.Sprintf("%08x", v))
|
|
}
|
|
|
|
func (k DeposedKey) String() string {
|
|
return string(k)
|
|
}
|
|
|
|
func (k DeposedKey) GoString() string {
|
|
ks := string(k)
|
|
switch {
|
|
case ks == "":
|
|
return "states.NotDeposed"
|
|
default:
|
|
return fmt.Sprintf("states.DeposedKey(%s)", ks)
|
|
}
|
|
}
|
|
|
|
// Generation is a helper method to convert a DeposedKey into a Generation.
|
|
// If the receiver is anything other than NotDeposed then the result is
|
|
// just the same value as a Generation. If the receiver is NotDeposed then
|
|
// the result is CurrentGen.
|
|
func (k DeposedKey) Generation() Generation {
|
|
if k == NotDeposed {
|
|
return CurrentGen
|
|
}
|
|
return k
|
|
}
|
|
|
|
// generation is an implementation of Generation.
|
|
func (k DeposedKey) generation() {}
|