2018-06-12 02:44:12 +02:00
|
|
|
package states
|
|
|
|
|
|
|
|
import (
|
2018-09-12 01:57:27 +02:00
|
|
|
"log"
|
2018-06-12 02:44:12 +02:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// SyncState is a wrapper around State that provides concurrency-safe access to
|
|
|
|
// various common operations that occur during a Terraform graph walk, or other
|
|
|
|
// similar concurrent contexts.
|
|
|
|
//
|
|
|
|
// When a SyncState wrapper is in use, no concurrent direct access to the
|
|
|
|
// underlying objects is permitted unless the caller first acquires an explicit
|
|
|
|
// lock, using the Lock and Unlock methods. Most callers should _not_
|
|
|
|
// explicitly lock, and should instead use the other methods of this type that
|
|
|
|
// handle locking automatically.
|
|
|
|
//
|
|
|
|
// Since SyncState is able to safely consolidate multiple updates into a single
|
|
|
|
// atomic operation, many of its methods are at a higher level than those
|
|
|
|
// of the underlying types, and operate on the state as a whole rather than
|
|
|
|
// on individual sub-structures of the state.
|
|
|
|
//
|
|
|
|
// SyncState can only protect against races within its own methods. It cannot
|
|
|
|
// provide any guarantees about the order in which concurrent operations will
|
|
|
|
// be processed, so callers may still need to employ higher-level techniques
|
|
|
|
// for ensuring correct operation sequencing, such as building and walking
|
|
|
|
// a dependency graph.
|
|
|
|
type SyncState struct {
|
|
|
|
state *State
|
|
|
|
lock sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Module returns a snapshot of the state of the module instance with the given
|
|
|
|
// address, or nil if no such module is tracked.
|
|
|
|
//
|
|
|
|
// The return value is a pointer to a copy of the module state, which the
|
|
|
|
// caller may then freely access and mutate. However, since the module state
|
|
|
|
// tends to be a large data structure with many child objects, where possible
|
|
|
|
// callers should prefer to use a more granular accessor to access a child
|
|
|
|
// module directly, and thus reduce the amount of copying required.
|
|
|
|
func (s *SyncState) Module(addr addrs.ModuleInstance) *Module {
|
|
|
|
s.lock.RLock()
|
|
|
|
ret := s.state.Module(addr).DeepCopy()
|
|
|
|
s.lock.RUnlock()
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// OutputValue returns a snapshot of the state of the output value with the
|
|
|
|
// given address, or nil if no such output value is tracked.
|
|
|
|
//
|
|
|
|
// The return value is a pointer to a copy of the output value state, which the
|
|
|
|
// caller may then freely access and mutate.
|
|
|
|
func (s *SyncState) OutputValue(addr addrs.AbsOutputValue) *OutputValue {
|
|
|
|
s.lock.RLock()
|
|
|
|
ret := s.state.OutputValue(addr).DeepCopy()
|
|
|
|
s.lock.RUnlock()
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetOutputValue writes a given output value into the state, overwriting
|
|
|
|
// any existing value of the same name.
|
|
|
|
//
|
|
|
|
// If the module containing the output is not yet tracked in state then it
|
|
|
|
// be added as a side-effect.
|
|
|
|
func (s *SyncState) SetOutputValue(addr addrs.AbsOutputValue, value cty.Value, sensitive bool) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.EnsureModule(addr.Module)
|
|
|
|
ms.SetOutputValue(addr.OutputValue.Name, value, sensitive)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveOutputValue removes the stored value for the output value with the
|
|
|
|
// given address.
|
|
|
|
//
|
|
|
|
// If this results in its containing module being empty, the module will be
|
|
|
|
// pruned from the state as a side-effect.
|
|
|
|
func (s *SyncState) RemoveOutputValue(addr addrs.AbsOutputValue) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ms.RemoveOutputValue(addr.OutputValue.Name)
|
|
|
|
s.maybePruneModule(addr.Module)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalValue returns the current value associated with the given local value
|
|
|
|
// address.
|
|
|
|
func (s *SyncState) LocalValue(addr addrs.AbsLocalValue) cty.Value {
|
|
|
|
s.lock.RLock()
|
|
|
|
// cty.Value is immutable, so we don't need any extra copying here.
|
|
|
|
ret := s.state.LocalValue(addr)
|
|
|
|
s.lock.RUnlock()
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetLocalValue writes a given output value into the state, overwriting
|
|
|
|
// any existing value of the same name.
|
|
|
|
//
|
|
|
|
// If the module containing the local value is not yet tracked in state then it
|
|
|
|
// will be added as a side-effect.
|
|
|
|
func (s *SyncState) SetLocalValue(addr addrs.AbsLocalValue, value cty.Value) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.EnsureModule(addr.Module)
|
|
|
|
ms.SetLocalValue(addr.LocalValue.Name, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveLocalValue removes the stored value for the local value with the
|
|
|
|
// given address.
|
|
|
|
//
|
|
|
|
// If this results in its containing module being empty, the module will be
|
|
|
|
// pruned from the state as a side-effect.
|
|
|
|
func (s *SyncState) RemoveLocalValue(addr addrs.AbsLocalValue) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ms.RemoveLocalValue(addr.LocalValue.Name)
|
|
|
|
s.maybePruneModule(addr.Module)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resource returns a snapshot of the state of the resource with the given
|
|
|
|
// address, or nil if no such resource is tracked.
|
|
|
|
//
|
|
|
|
// The return value is a pointer to a copy of the resource state, which the
|
|
|
|
// caller may then freely access and mutate.
|
|
|
|
func (s *SyncState) Resource(addr addrs.AbsResource) *Resource {
|
|
|
|
s.lock.RLock()
|
|
|
|
ret := s.state.Resource(addr).DeepCopy()
|
|
|
|
s.lock.RUnlock()
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResourceInstance returns a snapshot of the state the resource instance with
|
|
|
|
// the given address, or nil if no such instance is tracked.
|
|
|
|
//
|
|
|
|
// The return value is a pointer to a copy of the instance state, which the
|
|
|
|
// caller may then freely access and mutate.
|
|
|
|
func (s *SyncState) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance {
|
|
|
|
s.lock.RLock()
|
|
|
|
ret := s.state.ResourceInstance(addr).DeepCopy()
|
|
|
|
s.lock.RUnlock()
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResourceInstanceObject returns a snapshot of the current instance object
|
|
|
|
// of the given generation belonging to the instance with the given address,
|
|
|
|
// or nil if no such object is tracked..
|
|
|
|
//
|
|
|
|
// The return value is a pointer to a copy of the object, which the caller may
|
|
|
|
// then freely access and mutate.
|
2018-07-21 02:15:29 +02:00
|
|
|
func (s *SyncState) ResourceInstanceObject(addr addrs.AbsResourceInstance, gen Generation) *ResourceInstanceObjectSrc {
|
2018-06-12 02:44:12 +02:00
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
inst := s.state.ResourceInstance(addr)
|
|
|
|
if inst == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return inst.GetGeneration(gen)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetResourceMeta updates the resource-level metadata for the resource at
|
|
|
|
// the given address, creating the containing module state and resource state
|
|
|
|
// as a side-effect if not already present.
|
|
|
|
func (s *SyncState) SetResourceMeta(addr addrs.AbsResource, eachMode EachMode, provider addrs.AbsProviderConfig) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.EnsureModule(addr.Module)
|
|
|
|
ms.SetResourceMeta(addr.Resource, eachMode, provider)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveResource removes the entire state for the given resource, taking with
|
|
|
|
// it any instances associated with the resource. This should generally be
|
|
|
|
// called only for resource objects whose instances have all been destroyed,
|
2018-09-27 02:52:34 +02:00
|
|
|
// but that is not enforced by this method. (Use RemoveResourceIfEmpty instead
|
|
|
|
// to safely check first.)
|
2018-06-12 02:44:12 +02:00
|
|
|
func (s *SyncState) RemoveResource(addr addrs.AbsResource) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.EnsureModule(addr.Module)
|
|
|
|
ms.RemoveResource(addr.Resource)
|
|
|
|
s.maybePruneModule(addr.Module)
|
|
|
|
}
|
|
|
|
|
2018-09-27 02:52:34 +02:00
|
|
|
// RemoveResourceIfEmpty is similar to RemoveResource but first checks to
|
|
|
|
// make sure there are no instances or objects left in the resource.
|
|
|
|
//
|
|
|
|
// Returns true if the resource was removed, or false if remaining child
|
|
|
|
// objects prevented its removal. Returns true also if the resource was
|
|
|
|
// already absent, and thus no action needed to be taken.
|
|
|
|
func (s *SyncState) RemoveResourceIfEmpty(addr addrs.AbsResource) bool {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return true // nothing to do
|
|
|
|
}
|
|
|
|
rs := ms.Resource(addr.Resource)
|
|
|
|
if rs == nil {
|
|
|
|
return true // nothing to do
|
|
|
|
}
|
|
|
|
if len(rs.Instances) != 0 {
|
|
|
|
// We don't check here for the possibility of instances that exist
|
|
|
|
// but don't have any objects because it's the responsibility of the
|
|
|
|
// instance-mutation methods to prune those away automatically.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ms.RemoveResource(addr.Resource)
|
|
|
|
s.maybePruneModule(addr.Module)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
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
|
|
|
// MaybeFixUpResourceInstanceAddressForCount deals with the situation where a
|
|
|
|
// resource has changed from having "count" set to not set, or vice-versa, and
|
|
|
|
// so we need to rename the zeroth instance key to no key at all, or vice-versa.
|
|
|
|
//
|
|
|
|
// Set countEnabled to true if the resource has count set in its new
|
|
|
|
// configuration, or false if it does not.
|
|
|
|
//
|
|
|
|
// The state is modified in-place if necessary, moving a resource instance
|
|
|
|
// between the two addresses. The return value is true if a change was made,
|
|
|
|
// and false otherwise.
|
|
|
|
func (s *SyncState) MaybeFixUpResourceInstanceAddressForCount(addr addrs.AbsResource, countEnabled bool) bool {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
relAddr := addr.Resource
|
|
|
|
rs := ms.Resource(relAddr)
|
|
|
|
if rs == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
huntKey := addrs.NoKey
|
|
|
|
replaceKey := addrs.InstanceKey(addrs.IntKey(0))
|
|
|
|
if !countEnabled {
|
|
|
|
huntKey, replaceKey = replaceKey, huntKey
|
|
|
|
}
|
|
|
|
|
|
|
|
is, exists := rs.Instances[huntKey]
|
|
|
|
if !exists {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := rs.Instances[replaceKey]; exists {
|
|
|
|
// If the replacement key also exists then we'll do nothing and keep both.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here then we need to "rename" from hunt to replace
|
|
|
|
rs.Instances[replaceKey] = is
|
|
|
|
delete(rs.Instances, huntKey)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-06-12 02:44:12 +02:00
|
|
|
// SetResourceInstanceCurrent saves the given instance object as the current
|
|
|
|
// generation of the resource instance with the given address, simulataneously
|
|
|
|
// updating the recorded provider configuration address, dependencies, and
|
|
|
|
// resource EachMode.
|
|
|
|
//
|
|
|
|
// Any existing current instance object for the given resource is overwritten.
|
|
|
|
// Set obj to nil to remove the primary generation object altogether. If there
|
|
|
|
// are no deposed objects then the instance as a whole will be removed, which
|
|
|
|
// may in turn also remove the containing module if it becomes empty.
|
|
|
|
//
|
|
|
|
// The caller must ensure that the given ResourceInstanceObject is not
|
|
|
|
// concurrently mutated during this call, but may be freely used again once
|
|
|
|
// this function returns.
|
|
|
|
//
|
|
|
|
// The provider address and "each mode" are resource-wide settings and so they
|
|
|
|
// are updated for all other instances of the same resource as a side-effect of
|
|
|
|
// this call.
|
|
|
|
//
|
|
|
|
// If the containing module for this resource or the resource itself are not
|
|
|
|
// already tracked in state then they will be added as a side-effect.
|
2018-07-21 02:15:29 +02:00
|
|
|
func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
|
2018-06-12 02:44:12 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.EnsureModule(addr.Module)
|
|
|
|
ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider)
|
2018-09-12 01:57:27 +02:00
|
|
|
s.maybePruneModule(addr.Module)
|
2018-06-12 02:44:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetResourceInstanceDeposed saves the given instance object as a deposed
|
|
|
|
// generation of the resource instance with the given address and deposed key.
|
|
|
|
//
|
|
|
|
// Call this method only for pre-existing deposed objects that already have
|
|
|
|
// a known DeposedKey. For example, this method is useful if reloading objects
|
|
|
|
// that were persisted to a state file. To mark the current object as deposed,
|
|
|
|
// use DeposeResourceInstanceObject instead.
|
|
|
|
//
|
|
|
|
// The caller must ensure that the given ResourceInstanceObject is not
|
|
|
|
// concurrently mutated during this call, but may be freely used again once
|
|
|
|
// this function returns.
|
|
|
|
//
|
|
|
|
// The resource that contains the given instance must already exist in the
|
|
|
|
// state, or this method will panic. Use Resource to check first if its
|
|
|
|
// presence is not already guaranteed.
|
|
|
|
//
|
|
|
|
// Any existing current instance object for the given resource and deposed key
|
|
|
|
// is overwritten. Set obj to nil to remove the deposed object altogether. If
|
|
|
|
// the instance is left with no objects after this operation then it will
|
|
|
|
// be removed from its containing resource altogether.
|
|
|
|
//
|
|
|
|
// If the containing module for this resource or the resource itself are not
|
|
|
|
// already tracked in state then they will be added as a side-effect.
|
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
|
|
|
func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
|
2018-06-12 02:44:12 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.EnsureModule(addr.Module)
|
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
|
|
|
ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy(), provider)
|
2018-09-12 01:57:27 +02:00
|
|
|
s.maybePruneModule(addr.Module)
|
2018-06-12 02:44:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeposeResourceInstanceObject moves the current instance object for the
|
|
|
|
// given resource instance address into the deposed set, leaving the instance
|
|
|
|
// without a current object.
|
|
|
|
//
|
|
|
|
// The return value is the newly-allocated deposed key, or NotDeposed if the
|
|
|
|
// given instance is already lacking a current object.
|
|
|
|
//
|
|
|
|
// If the containing module for this resource or the resource itself are not
|
|
|
|
// already tracked in state then there cannot be a current object for the
|
|
|
|
// given instance, and so NotDeposed will be returned without modifying the
|
|
|
|
// state at all.
|
|
|
|
func (s *SyncState) DeposeResourceInstanceObject(addr addrs.AbsResourceInstance) DeposedKey {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return NotDeposed
|
|
|
|
}
|
|
|
|
|
2018-09-20 21:30:52 +02:00
|
|
|
return ms.deposeResourceInstanceObject(addr.Resource, NotDeposed)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeposeResourceInstanceObjectForceKey is like DeposeResourceInstanceObject
|
|
|
|
// but uses a pre-allocated key. It's the caller's responsibility to ensure
|
|
|
|
// that there aren't any races to use a particular key; this method will panic
|
|
|
|
// if the given key is already in use.
|
|
|
|
func (s *SyncState) DeposeResourceInstanceObjectForceKey(addr addrs.AbsResourceInstance, forcedKey DeposedKey) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
if forcedKey == NotDeposed {
|
|
|
|
// Usage error: should use DeposeResourceInstanceObject in this case
|
|
|
|
panic("DeposeResourceInstanceObjectForceKey called without forced key")
|
|
|
|
}
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return // Nothing to do, since there can't be any current object either.
|
|
|
|
}
|
|
|
|
|
|
|
|
ms.deposeResourceInstanceObject(addr.Resource, forcedKey)
|
2018-06-12 02:44:12 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
// ForgetResourceInstanceDeposed removes the record of the deposed object with
|
|
|
|
// the given address and key, if present. If not present, this is a no-op.
|
|
|
|
func (s *SyncState) ForgetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ms.ForgetResourceInstanceDeposed(addr.Resource, key)
|
2018-09-12 01:57:27 +02:00
|
|
|
s.maybePruneModule(addr.Module)
|
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
|
|
|
}
|
|
|
|
|
2018-09-21 03:28:27 +02:00
|
|
|
// MaybeRestoreResourceInstanceDeposed will restore the deposed object with the
|
|
|
|
// given key on the specified resource as the current object for that instance
|
|
|
|
// if and only if that would not cause us to forget an existing current
|
|
|
|
// object for that instance.
|
|
|
|
//
|
|
|
|
// Returns true if the object was restored to current, or false if no change
|
|
|
|
// was made at all.
|
|
|
|
func (s *SyncState) MaybeRestoreResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) bool {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
if key == NotDeposed {
|
|
|
|
panic("MaybeRestoreResourceInstanceDeposed called without DeposedKey")
|
|
|
|
}
|
|
|
|
|
|
|
|
ms := s.state.Module(addr.Module)
|
|
|
|
if ms == nil {
|
|
|
|
// Nothing to do, since the specified deposed object cannot exist.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return ms.maybeRestoreResourceInstanceDeposed(addr.Resource, key)
|
|
|
|
}
|
|
|
|
|
core: Prune placeholder objects from state after refresh
Prior to our refactoring here, we were relying on a lucky coincidence for
correct behavior of the plan walk following a refresh in the same run:
- The refresh phase created placeholder objects in the state to represent
any resource instance pending creation, to allow the interpolator to
read attributes from them when evaluating "provider" and "data" blocks.
In effect, the refresh walk is creating a partial plan that only covers
creation actions, but was immediately discarding the actual diff entries
and storing only the planned new state.
- It happened that objects pending creation showed up in state with an
empty ID value, since that only gets assigned by the provider during
apply.
- The Refresh function concluded by calling terraform.State.Prune, which
deletes from the state any objects that have an empty ID value, which
therefore prevented these temporary objects from surviving into the
plan phase.
After refactoring, we no longer have this special ID field on instance
object state, and we instead rely on the Status field for tracking such
things. We also no longer have an explicit "prune" step on state, since
the state mutation methods themselves keep the structure pruned.
To address this, here we introduce a new instance object status "planned",
which is equivalent to having an empty ID value in the old world. We also
introduce a new method on states.SyncState that deletes from the state
any planned objects, which therefore replaces that portion of the old
State.prune operation just for this refresh use-case.
Finally, we are now expecting the expression evaluator to pull pending
objects from the planned changeset rather than from the state directly,
and so for correct results these placeholder resource creation changes
must also be reported in a throwaway changeset during the refresh walk.
The addition of states.ObjectPlanned also permits a previously-missing
safety check in the expression evaluator to prevent us from relying on the
incomplete value stored in state for a pending object, in the event that
some bug prevents the real pending object from being written into the
planned changeset.
2018-09-01 01:42:07 +02:00
|
|
|
// RemovePlannedResourceInstanceObjects removes from the state any resource
|
|
|
|
// instance objects that have the status ObjectPlanned, indiciating that they
|
|
|
|
// are just transient placeholders created during planning.
|
|
|
|
//
|
|
|
|
// Note that this does not restore any "ready" or "tainted" object that might
|
|
|
|
// have been present before the planned object was written. The only real use
|
|
|
|
// for this method is in preparing the state created during a refresh walk,
|
|
|
|
// where we run the planning step for certain instances just to create enough
|
|
|
|
// information to allow correct expression evaluation within provider and
|
|
|
|
// data resource blocks. Discarding planned instances in that case is okay
|
|
|
|
// because the refresh phase only creates planned objects to stand in for
|
|
|
|
// objects that don't exist yet, and thus the planned object must have been
|
|
|
|
// absent before by definition.
|
|
|
|
func (s *SyncState) RemovePlannedResourceInstanceObjects() {
|
|
|
|
// TODO: Merge together the refresh and plan phases into a single walk,
|
|
|
|
// so we can remove the need to create this "partial plan" during refresh
|
|
|
|
// that we then need to clean up before proceeding.
|
|
|
|
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
for _, ms := range s.state.Modules {
|
|
|
|
moduleAddr := ms.Addr
|
|
|
|
|
|
|
|
for _, rs := range ms.Resources {
|
|
|
|
resAddr := rs.Addr
|
|
|
|
|
|
|
|
for ik, is := range rs.Instances {
|
|
|
|
instAddr := resAddr.Instance(ik)
|
|
|
|
|
|
|
|
if is.Current != nil && is.Current.Status == ObjectPlanned {
|
|
|
|
// Setting the current instance to nil removes it from the
|
|
|
|
// state altogether if there are not also deposed instances.
|
|
|
|
ms.SetResourceInstanceCurrent(instAddr, nil, rs.ProviderConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
for dk, obj := range is.Deposed {
|
|
|
|
// Deposed objects should never be "planned", but we'll
|
|
|
|
// do this anyway for the sake of completeness.
|
|
|
|
if obj.Status == ObjectPlanned {
|
|
|
|
ms.ForgetResourceInstanceDeposed(instAddr, dk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We may have deleted some objects, which means that we may have
|
|
|
|
// left a module empty, and so we must prune to preserve the invariant
|
|
|
|
// that only the root module is allowed to be empty.
|
|
|
|
s.maybePruneModule(moduleAddr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-12 02:44:12 +02:00
|
|
|
// Lock acquires an explicit lock on the state, allowing direct read and write
|
|
|
|
// access to the returned state object. The caller must call Unlock once
|
|
|
|
// access is no longer needed, and then immediately discard the state pointer
|
|
|
|
// pointer.
|
|
|
|
//
|
|
|
|
// Most callers should not use this. Instead, use the concurrency-safe
|
|
|
|
// accessors and mutators provided directly on SyncState.
|
|
|
|
func (s *SyncState) Lock() *State {
|
|
|
|
s.lock.Lock()
|
|
|
|
return s.state
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlock releases a lock previously acquired by Lock, at which point the
|
|
|
|
// caller must cease all use of the state pointer that was returned.
|
|
|
|
//
|
|
|
|
// Do not call this method except to end an explicit lock acquired by
|
|
|
|
// Lock. If a caller calls Unlock without first holding the lock, behavior
|
|
|
|
// is undefined.
|
|
|
|
func (s *SyncState) Unlock() {
|
|
|
|
s.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// maybePruneModule will remove a module from the state altogether if it is
|
|
|
|
// empty, unless it's the root module which must always be present.
|
|
|
|
//
|
|
|
|
// This helper method is not concurrency-safe on its own, so must only be
|
|
|
|
// called while the caller is already holding the lock for writing.
|
|
|
|
func (s *SyncState) maybePruneModule(addr addrs.ModuleInstance) {
|
|
|
|
if addr.IsRoot() {
|
|
|
|
// We never prune the root.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ms := s.state.Module(addr)
|
|
|
|
if ms == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if ms.empty() {
|
2018-09-12 01:57:27 +02:00
|
|
|
log.Printf("[TRACE] states.SyncState: pruning %s because it is empty", addr)
|
2018-06-12 02:44:12 +02:00
|
|
|
s.state.RemoveModule(addr)
|
|
|
|
}
|
|
|
|
}
|