2018-06-08 02:27:57 +02:00
|
|
|
package states
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Module is a container for the states of objects within a particular module.
|
|
|
|
type Module struct {
|
|
|
|
Addr addrs.ModuleInstance
|
|
|
|
|
|
|
|
// Resources contains the state for each resource. The keys in this map are
|
|
|
|
// an implementation detail and must not be used by outside callers.
|
|
|
|
Resources map[string]*Resource
|
|
|
|
|
|
|
|
// OutputValues contains the state for each output value. The keys in this
|
|
|
|
// map are output value names.
|
|
|
|
OutputValues map[string]*OutputValue
|
|
|
|
|
|
|
|
// LocalValues contains the value for each named output value. The keys
|
|
|
|
// in this map are local value names.
|
|
|
|
LocalValues map[string]cty.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewModule constructs an empty module state for the given module address.
|
|
|
|
func NewModule(addr addrs.ModuleInstance) *Module {
|
|
|
|
return &Module{
|
|
|
|
Addr: addr,
|
|
|
|
Resources: map[string]*Resource{},
|
|
|
|
OutputValues: map[string]*OutputValue{},
|
|
|
|
LocalValues: map[string]cty.Value{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resource returns the state for the resource with the given address within
|
|
|
|
// the receiving module state, or nil if the requested resource is not tracked
|
|
|
|
// in the state.
|
|
|
|
func (ms *Module) Resource(addr addrs.Resource) *Resource {
|
|
|
|
return ms.Resources[addr.String()]
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResourceInstance returns the state for the resource instance with the given
|
|
|
|
// address within the receiving module state, or nil if the requested instance
|
|
|
|
// is not tracked in the state.
|
|
|
|
func (ms *Module) ResourceInstance(addr addrs.ResourceInstance) *ResourceInstance {
|
|
|
|
rs := ms.Resource(addr.Resource)
|
|
|
|
if rs == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return rs.Instance(addr.Key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetResourceMeta updates the resource-level metadata for the resource
|
|
|
|
// with the given address, creating the resource state for it if it doesn't
|
|
|
|
// already exist.
|
|
|
|
func (ms *Module) SetResourceMeta(addr addrs.Resource, eachMode EachMode, provider addrs.AbsProviderConfig) {
|
|
|
|
rs := ms.Resource(addr)
|
|
|
|
if rs == nil {
|
|
|
|
rs = &Resource{
|
|
|
|
Addr: addr,
|
|
|
|
Instances: map[addrs.InstanceKey]*ResourceInstance{},
|
|
|
|
}
|
|
|
|
ms.Resources[addr.String()] = rs
|
|
|
|
}
|
|
|
|
|
|
|
|
rs.EachMode = eachMode
|
|
|
|
rs.ProviderConfig = 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.
|
|
|
|
func (ms *Module) RemoveResource(addr addrs.Resource) {
|
|
|
|
delete(ms.Resources, addr.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 will be removed altogether.
|
|
|
|
//
|
|
|
|
// 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.
|
2018-07-21 02:15:29 +02:00
|
|
|
func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
|
2018-06-08 02:27:57 +02:00
|
|
|
rs := ms.Resource(addr.Resource)
|
2019-11-22 18:49:40 +01:00
|
|
|
// if the resource is nil and the object is nil, don't do anything!
|
2019-11-22 19:57:36 +01:00
|
|
|
// you'll probably just cause issues
|
2019-11-22 18:49:40 +01:00
|
|
|
if obj == nil && rs == nil {
|
|
|
|
return
|
2018-06-08 02:27:57 +02:00
|
|
|
}
|
2019-11-22 18:49:40 +01:00
|
|
|
if obj == nil && rs != nil {
|
|
|
|
// does the resource have any other objects?
|
|
|
|
// if not then delete the whole resource
|
|
|
|
if len(rs.Instances) == 0 {
|
|
|
|
delete(ms.Resources, addr.Resource.String())
|
|
|
|
return
|
|
|
|
}
|
2019-11-22 19:57:36 +01:00
|
|
|
// check for an existing resource
|
2019-11-22 18:49:40 +01:00
|
|
|
inst := rs.Instances[addr.Key]
|
|
|
|
if inst == nil {
|
2019-11-22 19:57:36 +01:00
|
|
|
// if there is no instance, but the resource exists and has other instances,
|
|
|
|
// be chill, just return
|
2019-11-22 18:49:40 +01:00
|
|
|
return
|
|
|
|
}
|
2019-11-22 19:57:36 +01:00
|
|
|
// if we have an instance, update the current
|
|
|
|
// TODO: this setting happens below as well, so possibly this can be removed,
|
|
|
|
// but not changing it right now as we might return in the block below
|
2019-11-22 18:49:40 +01:00
|
|
|
inst.Current = obj
|
|
|
|
if !inst.HasObjects() {
|
|
|
|
// If we have no objects at all then we'll clean up.
|
|
|
|
delete(rs.Instances, addr.Key)
|
|
|
|
if len(rs.Instances) == 0 {
|
|
|
|
delete(ms.Resources, addr.Resource.String())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
2018-06-08 02:27:57 +02:00
|
|
|
}
|
2019-11-22 18:49:40 +01:00
|
|
|
if rs == nil && obj != nil {
|
2019-11-22 19:57:36 +01:00
|
|
|
// We don't have have a resource
|
2019-11-22 18:49:40 +01:00
|
|
|
// make the resource! which happens in setResourceMeta, so okay
|
|
|
|
ms.SetResourceMeta(addr.Resource, eachModeForInstanceKey(addr.Key), provider)
|
2019-11-22 19:57:36 +01:00
|
|
|
// now we have a resource! so update the rs value
|
2019-11-22 18:49:40 +01:00
|
|
|
rs = ms.Resource(addr.Resource)
|
|
|
|
}
|
2019-11-22 19:57:36 +01:00
|
|
|
// Get our instance from the resource; it could be there or not at this point
|
2019-11-22 18:49:40 +01:00
|
|
|
inst := rs.Instances[addr.Key]
|
|
|
|
if inst == nil {
|
2019-11-22 19:57:36 +01:00
|
|
|
// if we don't have a resource, create one
|
|
|
|
rs.Instances[addr.Key] = NewResourceInstance()
|
|
|
|
// update the resource meta because we have a new instance, so EachMode may have changed
|
2019-11-22 18:49:40 +01:00
|
|
|
ms.SetResourceMeta(addr.Resource, eachModeForInstanceKey(addr.Key), provider)
|
|
|
|
|
|
|
|
}
|
|
|
|
is := rs.EnsureInstance(addr.Key)
|
|
|
|
is.Current = obj
|
2018-06-08 02:27:57 +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 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.
|
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 (ms *Module) SetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
|
|
|
|
ms.SetResourceMeta(addr.Resource, eachModeForInstanceKey(addr.Key), provider)
|
|
|
|
|
2018-06-08 02:27:57 +02:00
|
|
|
rs := ms.Resource(addr.Resource)
|
|
|
|
is := rs.EnsureInstance(addr.Key)
|
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
|
|
|
if obj != nil {
|
|
|
|
is.Deposed[key] = obj
|
|
|
|
} else {
|
|
|
|
delete(is.Deposed, key)
|
|
|
|
}
|
2018-06-08 02:27:57 +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
|
|
|
if !is.HasObjects() {
|
|
|
|
// If we have no objects at all then we'll clean up.
|
|
|
|
delete(rs.Instances, addr.Key)
|
|
|
|
}
|
|
|
|
if rs.EachMode == NoEach && len(rs.Instances) == 0 {
|
|
|
|
// Also clean up if we only expect to have one instance anyway
|
2018-10-15 17:38:06 +02:00
|
|
|
// and there are none. We leave the resource behind if an each mode
|
|
|
|
// is active because an empty list or map of instances is a valid state.
|
|
|
|
delete(ms.Resources, addr.Resource.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForgetResourceInstanceAll removes the record of all objects associated with
|
|
|
|
// the specified resource instance, if present. If not present, this is a no-op.
|
|
|
|
func (ms *Module) ForgetResourceInstanceAll(addr addrs.ResourceInstance) {
|
|
|
|
rs := ms.Resource(addr.Resource)
|
|
|
|
if rs == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(rs.Instances, addr.Key)
|
|
|
|
|
|
|
|
if rs.EachMode == NoEach && len(rs.Instances) == 0 {
|
|
|
|
// Also clean up if we only expect to have one instance anyway
|
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
|
|
|
// and there are none. We leave the resource behind if an each mode
|
|
|
|
// is active because an empty list or map of instances is a valid state.
|
|
|
|
delete(ms.Resources, addr.Resource.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 (ms *Module) ForgetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) {
|
|
|
|
rs := ms.Resource(addr.Resource)
|
|
|
|
if rs == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
is := rs.Instance(addr.Key)
|
|
|
|
if is == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(is.Deposed, key)
|
2018-06-08 02:27:57 +02:00
|
|
|
|
|
|
|
if !is.HasObjects() {
|
|
|
|
// If we have no objects at all then we'll clean up.
|
|
|
|
delete(rs.Instances, addr.Key)
|
|
|
|
}
|
|
|
|
if rs.EachMode == NoEach && len(rs.Instances) == 0 {
|
|
|
|
// Also clean up if we only expect to have one instance anyway
|
|
|
|
// and there are none. We leave the resource behind if an each mode
|
|
|
|
// is active because an empty list or map of instances is a valid state.
|
|
|
|
delete(ms.Resources, addr.Resource.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-12 02:44:12 +02:00
|
|
|
// deposeResourceInstanceObject is the real implementation of
|
|
|
|
// SyncState.DeposeResourceInstanceObject.
|
2018-09-20 21:30:52 +02:00
|
|
|
func (ms *Module) deposeResourceInstanceObject(addr addrs.ResourceInstance, forceKey DeposedKey) DeposedKey {
|
2018-06-08 02:27:57 +02:00
|
|
|
is := ms.ResourceInstance(addr)
|
|
|
|
if is == nil {
|
|
|
|
return NotDeposed
|
|
|
|
}
|
2018-09-20 21:30:52 +02:00
|
|
|
return is.deposeCurrentObject(forceKey)
|
2018-06-08 02:27:57 +02:00
|
|
|
}
|
|
|
|
|
2018-09-21 03:28:27 +02:00
|
|
|
// maybeRestoreResourceInstanceDeposed is the real implementation of
|
|
|
|
// SyncState.MaybeRestoreResourceInstanceDeposed.
|
|
|
|
func (ms *Module) maybeRestoreResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) bool {
|
|
|
|
rs := ms.Resource(addr.Resource)
|
|
|
|
if rs == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
is := rs.Instance(addr.Key)
|
|
|
|
if is == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if is.Current != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if len(is.Deposed) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
is.Current = is.Deposed[key]
|
|
|
|
delete(is.Deposed, key)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-06-08 02:27:57 +02:00
|
|
|
// SetOutputValue writes an output value into the state, overwriting any
|
|
|
|
// existing value of the same name.
|
|
|
|
func (ms *Module) SetOutputValue(name string, value cty.Value, sensitive bool) *OutputValue {
|
|
|
|
os := &OutputValue{
|
|
|
|
Value: value,
|
|
|
|
Sensitive: sensitive,
|
|
|
|
}
|
|
|
|
ms.OutputValues[name] = os
|
|
|
|
return os
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveOutputValue removes the output value of the given name from the state,
|
|
|
|
// if it exists. This method is a no-op if there is no value of the given
|
|
|
|
// name.
|
|
|
|
func (ms *Module) RemoveOutputValue(name string) {
|
|
|
|
delete(ms.OutputValues, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetLocalValue writes a local value into the state, overwriting any
|
|
|
|
// existing value of the same name.
|
|
|
|
func (ms *Module) SetLocalValue(name string, value cty.Value) {
|
|
|
|
ms.LocalValues[name] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveLocalValue removes the local value of the given name from the state,
|
|
|
|
// if it exists. This method is a no-op if there is no value of the given
|
|
|
|
// name.
|
|
|
|
func (ms *Module) RemoveLocalValue(name string) {
|
|
|
|
delete(ms.LocalValues, name)
|
|
|
|
}
|
2018-06-12 02:44:12 +02:00
|
|
|
|
2018-09-30 17:51:47 +02:00
|
|
|
// PruneResourceHusks is a specialized method that will remove any Resource
|
|
|
|
// objects that do not contain any instances, even if they have an EachMode.
|
|
|
|
//
|
|
|
|
// You probably shouldn't call this! See the method of the same name on
|
|
|
|
// type State for more information on what this is for and the rare situations
|
|
|
|
// where it is safe to use.
|
|
|
|
func (ms *Module) PruneResourceHusks() {
|
|
|
|
for _, rs := range ms.Resources {
|
|
|
|
if len(rs.Instances) == 0 {
|
|
|
|
ms.RemoveResource(rs.Addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-12 02:44:12 +02:00
|
|
|
// empty returns true if the receving module state is contributing nothing
|
|
|
|
// to the state. In other words, it returns true if the module could be
|
|
|
|
// removed from the state altogether without changing the meaning of the state.
|
|
|
|
//
|
|
|
|
// In practice a module containing no objects is the same as a non-existent
|
|
|
|
// module, and so we can opportunistically clean up once a module becomes
|
|
|
|
// empty on the assumption that it will be re-added if needed later.
|
|
|
|
func (ms *Module) empty() bool {
|
|
|
|
if ms == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// This must be updated to cover any new collections added to Module
|
|
|
|
// in future.
|
|
|
|
return (len(ms.Resources) == 0 &&
|
|
|
|
len(ms.OutputValues) == 0 &&
|
|
|
|
len(ms.LocalValues) == 0)
|
|
|
|
}
|