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() {}