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  }