1package states
2
3import (
4	"sort"
5
6	"github.com/zclconf/go-cty/cty"
7
8	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
9)
10
11// State is the top-level type of a Terraform state.
12//
13// A state should be mutated only via its accessor methods, to ensure that
14// invariants are preserved.
15//
16// Access to State and the nested values within it is not concurrency-safe,
17// so when accessing a State object concurrently it is the caller's
18// responsibility to ensure that only one write is in progress at a time
19// and that reads only occur when no write is in progress. The most common
20// way to acheive this is to wrap the State in a SyncState and use the
21// higher-level atomic operations supported by that type.
22type State struct {
23	// Modules contains the state for each module. The keys in this map are
24	// an implementation detail and must not be used by outside callers.
25	Modules map[string]*Module
26}
27
28// NewState constructs a minimal empty state, containing an empty root module.
29func NewState() *State {
30	modules := map[string]*Module{}
31	modules[addrs.RootModuleInstance.String()] = NewModule(addrs.RootModuleInstance)
32	return &State{
33		Modules: modules,
34	}
35}
36
37// BuildState is a helper -- primarily intended for tests -- to build a state
38// using imperative code against the StateSync type while still acting as
39// an expression of type *State to assign into a containing struct.
40func BuildState(cb func(*SyncState)) *State {
41	s := NewState()
42	cb(s.SyncWrapper())
43	return s
44}
45
46// Empty returns true if there are no resources or populated output values
47// in the receiver. In other words, if this state could be safely replaced
48// with the return value of NewState and be functionally equivalent.
49func (s *State) Empty() bool {
50	if s == nil {
51		return true
52	}
53	for _, ms := range s.Modules {
54		if len(ms.Resources) != 0 {
55			return false
56		}
57		if len(ms.OutputValues) != 0 {
58			return false
59		}
60	}
61	return true
62}
63
64// Module returns the state for the module with the given address, or nil if
65// the requested module is not tracked in the state.
66func (s *State) Module(addr addrs.ModuleInstance) *Module {
67	if s == nil {
68		panic("State.Module on nil *State")
69	}
70	return s.Modules[addr.String()]
71}
72
73// RemoveModule removes the module with the given address from the state,
74// unless it is the root module. The root module cannot be deleted, and so
75// this method will panic if that is attempted.
76//
77// Removing a module implicitly discards all of the resources, outputs and
78// local values within it, and so this should usually be done only for empty
79// modules. For callers accessing the state through a SyncState wrapper, modules
80// are automatically pruned if they are empty after one of their contained
81// elements is removed.
82func (s *State) RemoveModule(addr addrs.ModuleInstance) {
83	if addr.IsRoot() {
84		panic("attempted to remove root module")
85	}
86
87	delete(s.Modules, addr.String())
88}
89
90// RootModule is a convenient alias for Module(addrs.RootModuleInstance).
91func (s *State) RootModule() *Module {
92	if s == nil {
93		panic("RootModule called on nil State")
94	}
95	return s.Modules[addrs.RootModuleInstance.String()]
96}
97
98// EnsureModule returns the state for the module with the given address,
99// creating and adding a new one if necessary.
100//
101// Since this might modify the state to add a new instance, it is considered
102// to be a write operation.
103func (s *State) EnsureModule(addr addrs.ModuleInstance) *Module {
104	ms := s.Module(addr)
105	if ms == nil {
106		ms = NewModule(addr)
107		s.Modules[addr.String()] = ms
108	}
109	return ms
110}
111
112// HasResources returns true if there is at least one resource (of any mode)
113// present in the receiving state.
114func (s *State) HasResources() bool {
115	if s == nil {
116		return false
117	}
118	for _, ms := range s.Modules {
119		if len(ms.Resources) > 0 {
120			return true
121		}
122	}
123	return false
124}
125
126// Resource returns the state for the resource with the given address, or nil
127// if no such resource is tracked in the state.
128func (s *State) Resource(addr addrs.AbsResource) *Resource {
129	ms := s.Module(addr.Module)
130	if ms == nil {
131		return nil
132	}
133	return ms.Resource(addr.Resource)
134}
135
136// ResourceInstance returns the state for the resource instance with the given
137// address, or nil if no such resource is tracked in the state.
138func (s *State) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance {
139	if s == nil {
140		panic("State.ResourceInstance on nil *State")
141	}
142	ms := s.Module(addr.Module)
143	if ms == nil {
144		return nil
145	}
146	return ms.ResourceInstance(addr.Resource)
147}
148
149// OutputValue returns the state for the output value with the given address,
150// or nil if no such output value is tracked in the state.
151func (s *State) OutputValue(addr addrs.AbsOutputValue) *OutputValue {
152	ms := s.Module(addr.Module)
153	if ms == nil {
154		return nil
155	}
156	return ms.OutputValues[addr.OutputValue.Name]
157}
158
159// LocalValue returns the value of the named local value with the given address,
160// or cty.NilVal if no such value is tracked in the state.
161func (s *State) LocalValue(addr addrs.AbsLocalValue) cty.Value {
162	ms := s.Module(addr.Module)
163	if ms == nil {
164		return cty.NilVal
165	}
166	return ms.LocalValues[addr.LocalValue.Name]
167}
168
169// ProviderAddrs returns a list of all of the provider configuration addresses
170// referenced throughout the receiving state.
171//
172// The result is de-duplicated so that each distinct address appears only once.
173func (s *State) ProviderAddrs() []addrs.AbsProviderConfig {
174	if s == nil {
175		return nil
176	}
177
178	m := map[string]addrs.AbsProviderConfig{}
179	for _, ms := range s.Modules {
180		for _, rc := range ms.Resources {
181			m[rc.ProviderConfig.String()] = rc.ProviderConfig
182		}
183	}
184	if len(m) == 0 {
185		return nil
186	}
187
188	// This is mainly just so we'll get stable results for testing purposes.
189	keys := make([]string, 0, len(m))
190	for k := range m {
191		keys = append(keys, k)
192	}
193	sort.Strings(keys)
194
195	ret := make([]addrs.AbsProviderConfig, len(keys))
196	for i, key := range keys {
197		ret[i] = m[key]
198	}
199
200	return ret
201}
202
203// PruneResourceHusks is a specialized method that will remove any Resource
204// objects that do not contain any instances, even if they have an EachMode.
205//
206// This should generally be used only after a "terraform destroy" operation,
207// to finalize the cleanup of the state. It is not correct to use this after
208// other operations because if a resource has "count = 0" or "for_each" over
209// an empty collection then we want to retain it in the state so that references
210// to it, particularly in "strange" contexts like "terraform console", can be
211// properly resolved.
212//
213// This method MUST NOT be called concurrently with other readers and writers
214// of the receiving state.
215func (s *State) PruneResourceHusks() {
216	for _, m := range s.Modules {
217		m.PruneResourceHusks()
218		if len(m.Resources) == 0 && !m.Addr.IsRoot() {
219			s.RemoveModule(m.Addr)
220		}
221	}
222}
223
224// SyncWrapper returns a SyncState object wrapping the receiver.
225func (s *State) SyncWrapper() *SyncState {
226	return &SyncState{
227		state: s,
228	}
229}
230