github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/states/state.go (about) 1 package states 2 3 import ( 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. 22 type 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. 29 func 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. 40 func 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. 49 func (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. 66 func (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. 82 func (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). 91 func (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. 103 func (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. 114 func (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. 128 func (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. 138 func (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. 151 func (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. 161 func (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. 173 func (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. 215 func (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. 225 func (s *State) SyncWrapper() *SyncState { 226 return &SyncState{ 227 state: s, 228 } 229 }