1package states
2
3import (
4	"github.com/zclconf/go-cty/cty"
5
6	"github.com/hashicorp/terraform/internal/addrs"
7)
8
9// Module is a container for the states of objects within a particular module.
10type Module struct {
11	Addr addrs.ModuleInstance
12
13	// Resources contains the state for each resource. The keys in this map are
14	// an implementation detail and must not be used by outside callers.
15	Resources map[string]*Resource
16
17	// OutputValues contains the state for each output value. The keys in this
18	// map are output value names.
19	OutputValues map[string]*OutputValue
20
21	// LocalValues contains the value for each named output value. The keys
22	// in this map are local value names.
23	LocalValues map[string]cty.Value
24}
25
26// NewModule constructs an empty module state for the given module address.
27func NewModule(addr addrs.ModuleInstance) *Module {
28	return &Module{
29		Addr:         addr,
30		Resources:    map[string]*Resource{},
31		OutputValues: map[string]*OutputValue{},
32		LocalValues:  map[string]cty.Value{},
33	}
34}
35
36// Resource returns the state for the resource with the given address within
37// the receiving module state, or nil if the requested resource is not tracked
38// in the state.
39func (ms *Module) Resource(addr addrs.Resource) *Resource {
40	return ms.Resources[addr.String()]
41}
42
43// ResourceInstance returns the state for the resource instance with the given
44// address within the receiving module state, or nil if the requested instance
45// is not tracked in the state.
46func (ms *Module) ResourceInstance(addr addrs.ResourceInstance) *ResourceInstance {
47	rs := ms.Resource(addr.Resource)
48	if rs == nil {
49		return nil
50	}
51	return rs.Instance(addr.Key)
52}
53
54// SetResourceProvider updates the resource-level metadata for the resource
55// with the given address, creating the resource state for it if it doesn't
56// already exist.
57func (ms *Module) SetResourceProvider(addr addrs.Resource, provider addrs.AbsProviderConfig) {
58	rs := ms.Resource(addr)
59	if rs == nil {
60		rs = &Resource{
61			Addr:      addr.Absolute(ms.Addr),
62			Instances: map[addrs.InstanceKey]*ResourceInstance{},
63		}
64		ms.Resources[addr.String()] = rs
65	}
66
67	rs.ProviderConfig = provider
68}
69
70// RemoveResource removes the entire state for the given resource, taking with
71// it any instances associated with the resource. This should generally be
72// called only for resource objects whose instances have all been destroyed.
73func (ms *Module) RemoveResource(addr addrs.Resource) {
74	delete(ms.Resources, addr.String())
75}
76
77// SetResourceInstanceCurrent saves the given instance object as the current
78// generation of the resource instance with the given address, simultaneously
79// updating the recorded provider configuration address and dependencies.
80//
81// Any existing current instance object for the given resource is overwritten.
82// Set obj to nil to remove the primary generation object altogether. If there
83// are no deposed objects then the instance will be removed altogether.
84//
85// The provider address is a resource-wide setting and is updated for all other
86// instances of the same resource as a side-effect of this call.
87func (ms *Module) SetResourceInstanceCurrent(addr addrs.ResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
88	rs := ms.Resource(addr.Resource)
89	// if the resource is nil and the object is nil, don't do anything!
90	// you'll probably just cause issues
91	if obj == nil && rs == nil {
92		return
93	}
94	if obj == nil && rs != nil {
95		// does the resource have any other objects?
96		// if not then delete the whole resource
97		if len(rs.Instances) == 0 {
98			delete(ms.Resources, addr.Resource.String())
99			return
100		}
101		// check for an existing resource, now that we've ensured that rs.Instances is more than 0/not nil
102		is := rs.Instance(addr.Key)
103		if is == nil {
104			// if there is no instance on the resource with this address and obj is nil, return and change nothing
105			return
106		}
107		// if we have an instance, update the current
108		is.Current = obj
109		if !is.HasObjects() {
110			// If we have no objects at all then we'll clean up.
111			delete(rs.Instances, addr.Key)
112			// Delete the resource if it has no instances, but only if NoEach
113			if len(rs.Instances) == 0 {
114				delete(ms.Resources, addr.Resource.String())
115				return
116			}
117		}
118		// Nothing more to do here, so return!
119		return
120	}
121	if rs == nil && obj != nil {
122		// We don't have have a resource so make one, which is a side effect of setResourceMeta
123		ms.SetResourceProvider(addr.Resource, provider)
124		// now we have a resource! so update the rs value to point to it
125		rs = ms.Resource(addr.Resource)
126	}
127	// Get our instance from the resource; it could be there or not at this point
128	is := rs.Instance(addr.Key)
129	if is == nil {
130		// if we don't have a resource, create one and add to the instances
131		is = rs.CreateInstance(addr.Key)
132		// update the resource meta because we have a new
133		ms.SetResourceProvider(addr.Resource, provider)
134	}
135	// Update the resource's ProviderConfig, in case the provider has updated
136	rs.ProviderConfig = provider
137	is.Current = obj
138}
139
140// SetResourceInstanceDeposed saves the given instance object as a deposed
141// generation of the resource instance with the given address and deposed key.
142//
143// Call this method only for pre-existing deposed objects that already have
144// a known DeposedKey. For example, this method is useful if reloading objects
145// that were persisted to a state file. To mark the current object as deposed,
146// use DeposeResourceInstanceObject instead.
147//
148// The resource that contains the given instance must already exist in the
149// state, or this method will panic. Use Resource to check first if its
150// presence is not already guaranteed.
151//
152// Any existing current instance object for the given resource and deposed key
153// is overwritten. Set obj to nil to remove the deposed object altogether. If
154// the instance is left with no objects after this operation then it will
155// be removed from its containing resource altogether.
156func (ms *Module) SetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
157	ms.SetResourceProvider(addr.Resource, provider)
158
159	rs := ms.Resource(addr.Resource)
160	is := rs.EnsureInstance(addr.Key)
161	if obj != nil {
162		is.Deposed[key] = obj
163	} else {
164		delete(is.Deposed, key)
165	}
166
167	if !is.HasObjects() {
168		// If we have no objects at all then we'll clean up.
169		delete(rs.Instances, addr.Key)
170	}
171	if len(rs.Instances) == 0 {
172		// Also clean up if we only expect to have one instance anyway
173		// and there are none. We leave the resource behind if an each mode
174		// is active because an empty list or map of instances is a valid state.
175		delete(ms.Resources, addr.Resource.String())
176	}
177}
178
179// ForgetResourceInstanceAll removes the record of all objects associated with
180// the specified resource instance, if present. If not present, this is a no-op.
181func (ms *Module) ForgetResourceInstanceAll(addr addrs.ResourceInstance) {
182	rs := ms.Resource(addr.Resource)
183	if rs == nil {
184		return
185	}
186	delete(rs.Instances, addr.Key)
187
188	if len(rs.Instances) == 0 {
189		// Also clean up if we only expect to have one instance anyway
190		// and there are none. We leave the resource behind if an each mode
191		// is active because an empty list or map of instances is a valid state.
192		delete(ms.Resources, addr.Resource.String())
193	}
194}
195
196// ForgetResourceInstanceDeposed removes the record of the deposed object with
197// the given address and key, if present. If not present, this is a no-op.
198func (ms *Module) ForgetResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) {
199	rs := ms.Resource(addr.Resource)
200	if rs == nil {
201		return
202	}
203	is := rs.Instance(addr.Key)
204	if is == nil {
205		return
206	}
207	delete(is.Deposed, key)
208
209	if !is.HasObjects() {
210		// If we have no objects at all then we'll clean up.
211		delete(rs.Instances, addr.Key)
212	}
213	if len(rs.Instances) == 0 {
214		// Also clean up if we only expect to have one instance anyway
215		// and there are none. We leave the resource behind if an each mode
216		// is active because an empty list or map of instances is a valid state.
217		delete(ms.Resources, addr.Resource.String())
218	}
219}
220
221// deposeResourceInstanceObject is the real implementation of
222// SyncState.DeposeResourceInstanceObject.
223func (ms *Module) deposeResourceInstanceObject(addr addrs.ResourceInstance, forceKey DeposedKey) DeposedKey {
224	is := ms.ResourceInstance(addr)
225	if is == nil {
226		return NotDeposed
227	}
228	return is.deposeCurrentObject(forceKey)
229}
230
231// maybeRestoreResourceInstanceDeposed is the real implementation of
232// SyncState.MaybeRestoreResourceInstanceDeposed.
233func (ms *Module) maybeRestoreResourceInstanceDeposed(addr addrs.ResourceInstance, key DeposedKey) bool {
234	rs := ms.Resource(addr.Resource)
235	if rs == nil {
236		return false
237	}
238	is := rs.Instance(addr.Key)
239	if is == nil {
240		return false
241	}
242	if is.Current != nil {
243		return false
244	}
245	if len(is.Deposed) == 0 {
246		return false
247	}
248	is.Current = is.Deposed[key]
249	delete(is.Deposed, key)
250	return true
251}
252
253// SetOutputValue writes an output value into the state, overwriting any
254// existing value of the same name.
255func (ms *Module) SetOutputValue(name string, value cty.Value, sensitive bool) *OutputValue {
256	os := &OutputValue{
257		Addr: addrs.AbsOutputValue{
258			Module: ms.Addr,
259			OutputValue: addrs.OutputValue{
260				Name: name,
261			},
262		},
263		Value:     value,
264		Sensitive: sensitive,
265	}
266	ms.OutputValues[name] = os
267	return os
268}
269
270// RemoveOutputValue removes the output value of the given name from the state,
271// if it exists. This method is a no-op if there is no value of the given
272// name.
273func (ms *Module) RemoveOutputValue(name string) {
274	delete(ms.OutputValues, name)
275}
276
277// SetLocalValue writes a local value into the state, overwriting any
278// existing value of the same name.
279func (ms *Module) SetLocalValue(name string, value cty.Value) {
280	ms.LocalValues[name] = value
281}
282
283// RemoveLocalValue removes the local value of the given name from the state,
284// if it exists. This method is a no-op if there is no value of the given
285// name.
286func (ms *Module) RemoveLocalValue(name string) {
287	delete(ms.LocalValues, name)
288}
289
290// PruneResourceHusks is a specialized method that will remove any Resource
291// objects that do not contain any instances, even if they have an EachMode.
292//
293// You probably shouldn't call this! See the method of the same name on
294// type State for more information on what this is for and the rare situations
295// where it is safe to use.
296func (ms *Module) PruneResourceHusks() {
297	for _, rs := range ms.Resources {
298		if len(rs.Instances) == 0 {
299			ms.RemoveResource(rs.Addr.Resource)
300		}
301	}
302}
303
304// empty returns true if the receving module state is contributing nothing
305// to the state. In other words, it returns true if the module could be
306// removed from the state altogether without changing the meaning of the state.
307//
308// In practice a module containing no objects is the same as a non-existent
309// module, and so we can opportunistically clean up once a module becomes
310// empty on the assumption that it will be re-added if needed later.
311func (ms *Module) empty() bool {
312	if ms == nil {
313		return true
314	}
315
316	// This must be updated to cover any new collections added to Module
317	// in future.
318	return (len(ms.Resources) == 0 &&
319		len(ms.OutputValues) == 0 &&
320		len(ms.LocalValues) == 0)
321}
322