2018-06-08 02:27:57 +02:00
|
|
|
package states
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Resource represents the state of a resource.
|
|
|
|
type Resource struct {
|
|
|
|
// Addr is the module-relative address for the resource this state object
|
|
|
|
// belongs to.
|
|
|
|
Addr addrs.Resource
|
|
|
|
|
|
|
|
// EachMode is the multi-instance mode currently in use for this resource,
|
|
|
|
// or NoEach if this is a single-instance resource. This dictates what
|
|
|
|
// type of value is returned when accessing this resource via expressions
|
|
|
|
// in the Terraform language.
|
|
|
|
EachMode EachMode
|
|
|
|
|
|
|
|
// 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]
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2018-07-21 02:15:29 +02:00
|
|
|
Current *ResourceInstanceObjectSrc
|
2018-06-08 02:27:57 +02:00
|
|
|
|
|
|
|
// 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.
|
2018-07-21 02:15:29 +02:00
|
|
|
Deposed map[DeposedKey]*ResourceInstanceObjectSrc
|
2018-06-08 02:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewResourceInstance constructs and returns a new ResourceInstance, ready to
|
|
|
|
// use.
|
|
|
|
func NewResourceInstance() *ResourceInstance {
|
|
|
|
return &ResourceInstance{
|
2018-07-21 02:15:29 +02:00
|
|
|
Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{},
|
2018-06-08 02:27:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-06-12 02:44:12 +02:00
|
|
|
// 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.
|
2018-09-20 21:30:52 +02:00
|
|
|
func (i *ResourceInstance) deposeCurrentObject(forceKey DeposedKey) DeposedKey {
|
2018-06-08 02:27:57 +02:00
|
|
|
if !i.HasCurrent() {
|
|
|
|
return NotDeposed
|
|
|
|
}
|
|
|
|
|
2018-09-20 21:30:52 +02:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
2018-06-08 02:27:57 +02:00
|
|
|
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.
|
2018-07-21 02:15:29 +02:00
|
|
|
func (i *ResourceInstance) GetGeneration(gen Generation) *ResourceInstanceObjectSrc {
|
2018-06-08 02:27:57 +02:00
|
|
|
if gen == CurrentGen {
|
|
|
|
return i.Current
|
|
|
|
}
|
|
|
|
if dk, ok := gen.(DeposedKey); ok {
|
|
|
|
return i.Deposed[dk]
|
|
|
|
}
|
|
|
|
if gen == nil {
|
|
|
|
panic(fmt.Sprintf("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))
|
|
|
|
}
|
|
|
|
|
2018-09-20 21:30:52 +02:00
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
|
2018-06-08 02:27:57 +02:00
|
|
|
// 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.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// EachMode specifies the multi-instance mode for a resource.
|
|
|
|
type EachMode rune
|
|
|
|
|
|
|
|
const (
|
|
|
|
NoEach EachMode = 0
|
|
|
|
EachList EachMode = 'L'
|
|
|
|
EachMap EachMode = 'M'
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:generate stringer -type EachMode
|
|
|
|
|
|
|
|
func eachModeForInstanceKey(key addrs.InstanceKey) EachMode {
|
|
|
|
switch key.(type) {
|
|
|
|
case addrs.IntKey:
|
|
|
|
return EachList
|
|
|
|
case addrs.StringKey:
|
|
|
|
return EachMap
|
|
|
|
default:
|
|
|
|
if key == addrs.NoKey {
|
|
|
|
return NoEach
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("don't know an each mode for instance key %#v", key))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 absense 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
// Generation is a helper method to convert a DeposedKey into a Generation.
|
|
|
|
// If the reciever 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
|
|
|
|
}
|
|
|
|
|
2018-06-08 02:27:57 +02:00
|
|
|
// generation is an implementation of Generation.
|
|
|
|
func (k DeposedKey) generation() {}
|