github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/state.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package states
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/terramate-io/tf/addrs"
    13  	"github.com/terramate-io/tf/getproviders"
    14  )
    15  
    16  // State is the top-level type of a Terraform state.
    17  //
    18  // A state should be mutated only via its accessor methods, to ensure that
    19  // invariants are preserved.
    20  //
    21  // Access to State and the nested values within it is not concurrency-safe,
    22  // so when accessing a State object concurrently it is the caller's
    23  // responsibility to ensure that only one write is in progress at a time
    24  // and that reads only occur when no write is in progress. The most common
    25  // way to achieve this is to wrap the State in a SyncState and use the
    26  // higher-level atomic operations supported by that type.
    27  type State struct {
    28  	// Modules contains the state for each module. The keys in this map are
    29  	// an implementation detail and must not be used by outside callers.
    30  	Modules map[string]*Module
    31  
    32  	// CheckResults contains a snapshot of the statuses of checks at the
    33  	// end of the most recent update to the state. Callers might compare
    34  	// checks between runs to see if e.g. a previously-failing check has
    35  	// been fixed since the last run, or similar.
    36  	//
    37  	// CheckResults can be nil to indicate that there are no check results
    38  	// from the previous run at all, which is subtly different than the
    39  	// previous run having affirmatively recorded that there are no checks
    40  	// to run. For example, if this object was created from a state snapshot
    41  	// created by a version of Terraform that didn't yet support checks
    42  	// then this field will be nil.
    43  	CheckResults *CheckResults
    44  }
    45  
    46  // NewState constructs a minimal empty state, containing an empty root module.
    47  func NewState() *State {
    48  	modules := map[string]*Module{}
    49  	modules[addrs.RootModuleInstance.String()] = NewModule(addrs.RootModuleInstance)
    50  	return &State{
    51  		Modules: modules,
    52  	}
    53  }
    54  
    55  // BuildState is a helper -- primarily intended for tests -- to build a state
    56  // using imperative code against the StateSync type while still acting as
    57  // an expression of type *State to assign into a containing struct.
    58  func BuildState(cb func(*SyncState)) *State {
    59  	s := NewState()
    60  	cb(s.SyncWrapper())
    61  	return s
    62  }
    63  
    64  // Empty returns true if there are no resources or populated output values
    65  // in the receiver. In other words, if this state could be safely replaced
    66  // with the return value of NewState and be functionally equivalent.
    67  func (s *State) Empty() bool {
    68  	if s == nil {
    69  		return true
    70  	}
    71  	for _, ms := range s.Modules {
    72  		if len(ms.Resources) != 0 {
    73  			return false
    74  		}
    75  		if len(ms.OutputValues) != 0 {
    76  			return false
    77  		}
    78  	}
    79  	return true
    80  }
    81  
    82  // Module returns the state for the module with the given address, or nil if
    83  // the requested module is not tracked in the state.
    84  func (s *State) Module(addr addrs.ModuleInstance) *Module {
    85  	if s == nil {
    86  		panic("State.Module on nil *State")
    87  	}
    88  	return s.Modules[addr.String()]
    89  }
    90  
    91  // ModuleInstances returns the set of Module states that matches the given path.
    92  func (s *State) ModuleInstances(addr addrs.Module) []*Module {
    93  	var ms []*Module
    94  	for _, m := range s.Modules {
    95  		if m.Addr.Module().Equal(addr) {
    96  			ms = append(ms, m)
    97  		}
    98  	}
    99  	return ms
   100  }
   101  
   102  // ModuleOutputs returns all outputs for the given module call under the
   103  // parentAddr instance.
   104  func (s *State) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue {
   105  	var os []*OutputValue
   106  	for _, m := range s.Modules {
   107  		// can't get outputs from the root module
   108  		if m.Addr.IsRoot() {
   109  			continue
   110  		}
   111  
   112  		parent, call := m.Addr.Call()
   113  		// make sure this is a descendent in the correct path
   114  		if !parentAddr.Equal(parent) {
   115  			continue
   116  		}
   117  
   118  		// and check if this is the correct child
   119  		if call.Name != module.Name {
   120  			continue
   121  		}
   122  
   123  		for _, o := range m.OutputValues {
   124  			os = append(os, o)
   125  		}
   126  	}
   127  
   128  	return os
   129  }
   130  
   131  // RemoveModule removes the module with the given address from the state,
   132  // unless it is the root module. The root module cannot be deleted, and so
   133  // this method will panic if that is attempted.
   134  //
   135  // Removing a module implicitly discards all of the resources, outputs and
   136  // local values within it, and so this should usually be done only for empty
   137  // modules. For callers accessing the state through a SyncState wrapper, modules
   138  // are automatically pruned if they are empty after one of their contained
   139  // elements is removed.
   140  func (s *State) RemoveModule(addr addrs.ModuleInstance) {
   141  	if addr.IsRoot() {
   142  		panic("attempted to remove root module")
   143  	}
   144  
   145  	delete(s.Modules, addr.String())
   146  }
   147  
   148  // RootModule is a convenient alias for Module(addrs.RootModuleInstance).
   149  func (s *State) RootModule() *Module {
   150  	if s == nil {
   151  		panic("RootModule called on nil State")
   152  	}
   153  	return s.Modules[addrs.RootModuleInstance.String()]
   154  }
   155  
   156  // EnsureModule returns the state for the module with the given address,
   157  // creating and adding a new one if necessary.
   158  //
   159  // Since this might modify the state to add a new instance, it is considered
   160  // to be a write operation.
   161  func (s *State) EnsureModule(addr addrs.ModuleInstance) *Module {
   162  	ms := s.Module(addr)
   163  	if ms == nil {
   164  		ms = NewModule(addr)
   165  		s.Modules[addr.String()] = ms
   166  	}
   167  	return ms
   168  }
   169  
   170  // HasManagedResourceInstanceObjects returns true if there is at least one
   171  // resource instance object (current or deposed) associated with a managed
   172  // resource in the receiving state.
   173  //
   174  // A true result would suggest that just discarding this state without first
   175  // destroying these objects could leave "dangling" objects in remote systems,
   176  // no longer tracked by any Terraform state.
   177  func (s *State) HasManagedResourceInstanceObjects() bool {
   178  	if s == nil {
   179  		return false
   180  	}
   181  	for _, ms := range s.Modules {
   182  		for _, rs := range ms.Resources {
   183  			if rs.Addr.Resource.Mode != addrs.ManagedResourceMode {
   184  				continue
   185  			}
   186  			for _, is := range rs.Instances {
   187  				if is.Current != nil || len(is.Deposed) != 0 {
   188  					return true
   189  				}
   190  			}
   191  		}
   192  	}
   193  	return false
   194  }
   195  
   196  // Resource returns the state for the resource with the given address, or nil
   197  // if no such resource is tracked in the state.
   198  func (s *State) Resource(addr addrs.AbsResource) *Resource {
   199  	ms := s.Module(addr.Module)
   200  	if ms == nil {
   201  		return nil
   202  	}
   203  	return ms.Resource(addr.Resource)
   204  }
   205  
   206  // Resources returns the set of resources that match the given configuration path.
   207  func (s *State) Resources(addr addrs.ConfigResource) []*Resource {
   208  	var ret []*Resource
   209  	for _, m := range s.ModuleInstances(addr.Module) {
   210  		r := m.Resource(addr.Resource)
   211  		if r != nil {
   212  			ret = append(ret, r)
   213  		}
   214  	}
   215  	return ret
   216  }
   217  
   218  // AllManagedResourceInstanceObjectAddrs returns a set of addresses for all of
   219  // the leaf resource instance objects associated with managed resources that
   220  // are tracked in this state.
   221  //
   222  // This result is the set of objects that would be effectively "forgotten"
   223  // (like "terraform state rm") if this state were totally discarded, such as
   224  // by deleting a workspace. This function is intended only for reporting
   225  // context in error messages, such as when we reject deleting a "non-empty"
   226  // workspace as detected by s.HasManagedResourceInstanceObjects.
   227  //
   228  // The ordering of the result is meaningless but consistent. DeposedKey will
   229  // be NotDeposed (the zero value of DeposedKey) for any "current" objects.
   230  // This method is guaranteed to return at least one item if
   231  // s.HasManagedResourceInstanceObjects returns true for the same state, and
   232  // to return a zero-length slice if it returns false.
   233  func (s *State) AllResourceInstanceObjectAddrs() []struct {
   234  	Instance   addrs.AbsResourceInstance
   235  	DeposedKey DeposedKey
   236  } {
   237  	if s == nil {
   238  		return nil
   239  	}
   240  
   241  	// We use an unnamed return type here just because we currently have no
   242  	// general need to return pairs of instance address and deposed key aside
   243  	// from this method, and this method itself is only of marginal value
   244  	// when producing some error messages.
   245  	//
   246  	// If that need ends up arising more in future then it might make sense to
   247  	// name this as addrs.AbsResourceInstanceObject, although that would require
   248  	// moving DeposedKey into the addrs package too.
   249  	type ResourceInstanceObject = struct {
   250  		Instance   addrs.AbsResourceInstance
   251  		DeposedKey DeposedKey
   252  	}
   253  	var ret []ResourceInstanceObject
   254  
   255  	for _, ms := range s.Modules {
   256  		for _, rs := range ms.Resources {
   257  			if rs.Addr.Resource.Mode != addrs.ManagedResourceMode {
   258  				continue
   259  			}
   260  
   261  			for instKey, is := range rs.Instances {
   262  				instAddr := rs.Addr.Instance(instKey)
   263  				if is.Current != nil {
   264  					ret = append(ret, ResourceInstanceObject{instAddr, NotDeposed})
   265  				}
   266  				for deposedKey := range is.Deposed {
   267  					ret = append(ret, ResourceInstanceObject{instAddr, deposedKey})
   268  				}
   269  			}
   270  		}
   271  	}
   272  
   273  	sort.SliceStable(ret, func(i, j int) bool {
   274  		objI, objJ := ret[i], ret[j]
   275  		switch {
   276  		case !objI.Instance.Equal(objJ.Instance):
   277  			return objI.Instance.Less(objJ.Instance)
   278  		default:
   279  			return objI.DeposedKey < objJ.DeposedKey
   280  		}
   281  	})
   282  
   283  	return ret
   284  }
   285  
   286  // ResourceInstance returns the state for the resource instance with the given
   287  // address, or nil if no such resource is tracked in the state.
   288  func (s *State) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance {
   289  	if s == nil {
   290  		panic("State.ResourceInstance on nil *State")
   291  	}
   292  	ms := s.Module(addr.Module)
   293  	if ms == nil {
   294  		return nil
   295  	}
   296  	return ms.ResourceInstance(addr.Resource)
   297  }
   298  
   299  // OutputValue returns the state for the output value with the given address,
   300  // or nil if no such output value is tracked in the state.
   301  func (s *State) OutputValue(addr addrs.AbsOutputValue) *OutputValue {
   302  	ms := s.Module(addr.Module)
   303  	if ms == nil {
   304  		return nil
   305  	}
   306  	return ms.OutputValues[addr.OutputValue.Name]
   307  }
   308  
   309  // LocalValue returns the value of the named local value with the given address,
   310  // or cty.NilVal if no such value is tracked in the state.
   311  func (s *State) LocalValue(addr addrs.AbsLocalValue) cty.Value {
   312  	ms := s.Module(addr.Module)
   313  	if ms == nil {
   314  		return cty.NilVal
   315  	}
   316  	return ms.LocalValues[addr.LocalValue.Name]
   317  }
   318  
   319  // ProviderAddrs returns a list of all of the provider configuration addresses
   320  // referenced throughout the receiving state.
   321  //
   322  // The result is de-duplicated so that each distinct address appears only once.
   323  func (s *State) ProviderAddrs() []addrs.AbsProviderConfig {
   324  	if s == nil {
   325  		return nil
   326  	}
   327  
   328  	m := map[string]addrs.AbsProviderConfig{}
   329  	for _, ms := range s.Modules {
   330  		for _, rc := range ms.Resources {
   331  			m[rc.ProviderConfig.String()] = rc.ProviderConfig
   332  		}
   333  	}
   334  	if len(m) == 0 {
   335  		return nil
   336  	}
   337  
   338  	// This is mainly just so we'll get stable results for testing purposes.
   339  	keys := make([]string, 0, len(m))
   340  	for k := range m {
   341  		keys = append(keys, k)
   342  	}
   343  	sort.Strings(keys)
   344  
   345  	ret := make([]addrs.AbsProviderConfig, len(keys))
   346  	for i, key := range keys {
   347  		ret[i] = m[key]
   348  	}
   349  
   350  	return ret
   351  }
   352  
   353  // ProviderRequirements returns a description of all of the providers that
   354  // are required to work with the receiving state.
   355  //
   356  // Because the state does not track specific version information for providers,
   357  // the requirements returned by this method will always be unconstrained.
   358  // The result should usually be merged with a Requirements derived from the
   359  // current configuration in order to apply some constraints.
   360  func (s *State) ProviderRequirements() getproviders.Requirements {
   361  	configAddrs := s.ProviderAddrs()
   362  	ret := make(getproviders.Requirements, len(configAddrs))
   363  	for _, configAddr := range configAddrs {
   364  		ret[configAddr.Provider] = nil // unconstrained dependency
   365  	}
   366  	return ret
   367  }
   368  
   369  // PruneResourceHusks is a specialized method that will remove any Resource
   370  // objects that do not contain any instances, even if they have an EachMode.
   371  //
   372  // This should generally be used only after a "terraform destroy" operation,
   373  // to finalize the cleanup of the state. It is not correct to use this after
   374  // other operations because if a resource has "count = 0" or "for_each" over
   375  // an empty collection then we want to retain it in the state so that references
   376  // to it, particularly in "strange" contexts like "terraform console", can be
   377  // properly resolved.
   378  //
   379  // This method MUST NOT be called concurrently with other readers and writers
   380  // of the receiving state.
   381  func (s *State) PruneResourceHusks() {
   382  	for _, m := range s.Modules {
   383  		m.PruneResourceHusks()
   384  		if len(m.Resources) == 0 && !m.Addr.IsRoot() {
   385  			s.RemoveModule(m.Addr)
   386  		}
   387  	}
   388  }
   389  
   390  // SyncWrapper returns a SyncState object wrapping the receiver.
   391  func (s *State) SyncWrapper() *SyncState {
   392  	return &SyncState{
   393  		state: s,
   394  	}
   395  }
   396  
   397  // MoveAbsResource moves the given src AbsResource's current state to the new
   398  // dst address. This will panic if the src AbsResource does not exist in state,
   399  // or if there is already a resource at the dst address. It is the caller's
   400  // responsibility to verify the validity of the move (for example, that the src
   401  // and dst are compatible types).
   402  func (s *State) MoveAbsResource(src, dst addrs.AbsResource) {
   403  	// verify that the src address exists and the dst address does not
   404  	rs := s.Resource(src)
   405  	if rs == nil {
   406  		panic(fmt.Sprintf("no state for src address %s", src.String()))
   407  	}
   408  
   409  	ds := s.Resource(dst)
   410  	if ds != nil {
   411  		panic(fmt.Sprintf("dst resource %s already exists", dst.String()))
   412  	}
   413  
   414  	ms := s.Module(src.Module)
   415  	ms.RemoveResource(src.Resource)
   416  
   417  	// Remove the module if it is empty (and not root) after removing the
   418  	// resource.
   419  	if !ms.Addr.IsRoot() && ms.empty() {
   420  		s.RemoveModule(src.Module)
   421  	}
   422  
   423  	// Update the address before adding it to the state
   424  	rs.Addr = dst
   425  	s.EnsureModule(dst.Module).Resources[dst.Resource.String()] = rs
   426  }
   427  
   428  // MaybeMoveAbsResource moves the given src AbsResource's current state to the
   429  // new dst address. This function will succeed if both the src address does not
   430  // exist in state and the dst address does; the return value indicates whether
   431  // or not the move occurred. This function will panic if either the src does not
   432  // exist or the dst does exist (but not both).
   433  func (s *State) MaybeMoveAbsResource(src, dst addrs.AbsResource) bool {
   434  	// Get the source and destinatation addresses from state.
   435  	rs := s.Resource(src)
   436  	ds := s.Resource(dst)
   437  
   438  	// Normal case: the src exists in state, dst does not
   439  	if rs != nil && ds == nil {
   440  		s.MoveAbsResource(src, dst)
   441  		return true
   442  	}
   443  
   444  	if rs == nil && ds != nil {
   445  		// The source is not in state, the destination is. This is not
   446  		// guaranteed to be idempotent since we aren't tracking exact moves, but
   447  		// it's useful information for the caller.
   448  		return false
   449  	} else {
   450  		panic("invalid move")
   451  	}
   452  }
   453  
   454  // MoveAbsResourceInstance moves the given src AbsResourceInstance's current state to
   455  // the new dst address. This will panic if the src AbsResourceInstance does not
   456  // exist in state, or if there is already a resource at the dst address. It is
   457  // the caller's responsibility to verify the validity of the move (for example,
   458  // that the src and dst are compatible types).
   459  func (s *State) MoveAbsResourceInstance(src, dst addrs.AbsResourceInstance) {
   460  	srcInstanceState := s.ResourceInstance(src)
   461  	if srcInstanceState == nil {
   462  		panic(fmt.Sprintf("no state for src address %s", src.String()))
   463  	}
   464  
   465  	dstInstanceState := s.ResourceInstance(dst)
   466  	if dstInstanceState != nil {
   467  		panic(fmt.Sprintf("dst resource %s already exists", dst.String()))
   468  	}
   469  
   470  	srcResourceState := s.Resource(src.ContainingResource())
   471  	srcProviderAddr := srcResourceState.ProviderConfig
   472  	dstResourceAddr := dst.ContainingResource()
   473  
   474  	// Remove the source resource instance from the module's state, and then the
   475  	// module if empty.
   476  	ms := s.Module(src.Module)
   477  	ms.ForgetResourceInstanceAll(src.Resource)
   478  	if !ms.Addr.IsRoot() && ms.empty() {
   479  		s.RemoveModule(src.Module)
   480  	}
   481  
   482  	dstModule := s.EnsureModule(dst.Module)
   483  
   484  	// See if there is already a resource we can add this instance to.
   485  	dstResourceState := s.Resource(dstResourceAddr)
   486  	if dstResourceState == nil {
   487  		// If we're moving to an address without an index then that
   488  		// suggests the user's intent is to establish both the
   489  		// resource and the instance at the same time (since the
   490  		// address covers both). If there's an index in the
   491  		// target then allow creating the new instance here.
   492  		dstModule.SetResourceProvider(
   493  			dstResourceAddr.Resource,
   494  			srcProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource
   495  		)
   496  		dstResourceState = dstModule.Resource(dstResourceAddr.Resource)
   497  	}
   498  
   499  	dstResourceState.Instances[dst.Resource.Key] = srcInstanceState
   500  }
   501  
   502  // MaybeMoveAbsResourceInstance moves the given src AbsResourceInstance's
   503  // current state to the new dst address. This function will succeed if both the
   504  // src address does not exist in state and the dst address does; the return
   505  // value indicates whether or not the move occured. This function will panic if
   506  // either the src does not exist or the dst does exist (but not both).
   507  func (s *State) MaybeMoveAbsResourceInstance(src, dst addrs.AbsResourceInstance) bool {
   508  	// get the src and dst resource instances from state
   509  	rs := s.ResourceInstance(src)
   510  	ds := s.ResourceInstance(dst)
   511  
   512  	// Normal case: the src exists in state, dst does not
   513  	if rs != nil && ds == nil {
   514  		s.MoveAbsResourceInstance(src, dst)
   515  		return true
   516  	}
   517  
   518  	if rs == nil && ds != nil {
   519  		// The source is not in state, the destination is. This is not
   520  		// guaranteed to be idempotent since we aren't tracking exact moves, but
   521  		// it's useful information.
   522  		return false
   523  	} else {
   524  		panic("invalid move")
   525  	}
   526  }
   527  
   528  // MoveModuleInstance moves the given src ModuleInstance's current state to the
   529  // new dst address. This will panic if the src ModuleInstance does not
   530  // exist in state, or if there is already a resource at the dst address. It is
   531  // the caller's responsibility to verify the validity of the move.
   532  func (s *State) MoveModuleInstance(src, dst addrs.ModuleInstance) {
   533  	if src.IsRoot() || dst.IsRoot() {
   534  		panic("cannot move to or from root module")
   535  	}
   536  
   537  	srcMod := s.Module(src)
   538  	if srcMod == nil {
   539  		panic(fmt.Sprintf("no state for src module %s", src.String()))
   540  	}
   541  
   542  	dstMod := s.Module(dst)
   543  	if dstMod != nil {
   544  		panic(fmt.Sprintf("dst module %s already exists in state", dst.String()))
   545  	}
   546  
   547  	s.RemoveModule(src)
   548  
   549  	srcMod.Addr = dst
   550  	s.EnsureModule(dst)
   551  	s.Modules[dst.String()] = srcMod
   552  
   553  	// Update any Resource's addresses.
   554  	if srcMod.Resources != nil {
   555  		for _, r := range srcMod.Resources {
   556  			r.Addr.Module = dst
   557  		}
   558  	}
   559  
   560  	// Update any OutputValues's addresses.
   561  	if srcMod.OutputValues != nil {
   562  		for _, ov := range srcMod.OutputValues {
   563  			ov.Addr.Module = dst
   564  		}
   565  	}
   566  }
   567  
   568  // MaybeMoveModuleInstance moves the given src ModuleInstance's current state to
   569  // the new dst address. This function will succeed if both the src address does
   570  // not exist in state and the dst address does; the return value indicates
   571  // whether or not the move occured. This function will panic if either the src
   572  // does not exist or the dst does exist (but not both).
   573  func (s *State) MaybeMoveModuleInstance(src, dst addrs.ModuleInstance) bool {
   574  	if src.IsRoot() || dst.IsRoot() {
   575  		panic("cannot move to or from root module")
   576  	}
   577  
   578  	srcMod := s.Module(src)
   579  	dstMod := s.Module(dst)
   580  
   581  	// Normal case: the src exists in state, dst does not
   582  	if srcMod != nil && dstMod == nil {
   583  		s.MoveModuleInstance(src, dst)
   584  		return true
   585  	}
   586  
   587  	if srcMod == nil || src.IsRoot() && dstMod != nil {
   588  		// The source is not in state, the destination is. This is not
   589  		// guaranteed to be idempotent since we aren't tracking exact moves, but
   590  		// it's useful information.
   591  		return false
   592  	} else {
   593  		panic("invalid move")
   594  	}
   595  }
   596  
   597  // MoveModule takes a source and destination addrs.Module address, and moves all
   598  // state Modules which are contained by the src address to the new address.
   599  func (s *State) MoveModule(src, dst addrs.AbsModuleCall) {
   600  	if src.Module.IsRoot() || dst.Module.IsRoot() {
   601  		panic("cannot move to or from root module")
   602  	}
   603  
   604  	// Modules only exist as ModuleInstances in state, so we need to check each
   605  	// state Module and see if it is contained by the src address to get a full
   606  	// list of modules to move.
   607  	var srcMIs []*Module
   608  	for _, module := range s.Modules {
   609  		if !module.Addr.IsRoot() {
   610  			if src.Module.TargetContains(module.Addr) {
   611  				srcMIs = append(srcMIs, module)
   612  			}
   613  		}
   614  	}
   615  
   616  	if len(srcMIs) == 0 {
   617  		panic(fmt.Sprintf("no matching module instances found for src module %s", src.String()))
   618  	}
   619  
   620  	for _, ms := range srcMIs {
   621  		newInst := make(addrs.ModuleInstance, len(ms.Addr))
   622  		copy(newInst, ms.Addr)
   623  		if ms.Addr.IsDeclaredByCall(src) {
   624  			// Easy case: we just need to update the last step with the new name
   625  			newInst[len(newInst)-1].Name = dst.Call.Name
   626  		} else {
   627  			// Trickier: this Module is a submodule. we need to find and update
   628  			// only that appropriate step
   629  			for s := range newInst {
   630  				if newInst[s].Name == src.Call.Name {
   631  					newInst[s].Name = dst.Call.Name
   632  				}
   633  			}
   634  		}
   635  		s.MoveModuleInstance(ms.Addr, newInst)
   636  	}
   637  }