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

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