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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package states
     5  
     6  import (
     7  	"log"
     8  	"sync"
     9  
    10  	"github.com/terramate-io/tf/addrs"
    11  	"github.com/terramate-io/tf/checks"
    12  	"github.com/zclconf/go-cty/cty"
    13  )
    14  
    15  // SyncState is a wrapper around State that provides concurrency-safe access to
    16  // various common operations that occur during a Terraform graph walk, or other
    17  // similar concurrent contexts.
    18  //
    19  // When a SyncState wrapper is in use, no concurrent direct access to the
    20  // underlying objects is permitted unless the caller first acquires an explicit
    21  // lock, using the Lock and Unlock methods. Most callers should _not_
    22  // explicitly lock, and should instead use the other methods of this type that
    23  // handle locking automatically.
    24  //
    25  // Since SyncState is able to safely consolidate multiple updates into a single
    26  // atomic operation, many of its methods are at a higher level than those
    27  // of the underlying types, and operate on the state as a whole rather than
    28  // on individual sub-structures of the state.
    29  //
    30  // SyncState can only protect against races within its own methods. It cannot
    31  // provide any guarantees about the order in which concurrent operations will
    32  // be processed, so callers may still need to employ higher-level techniques
    33  // for ensuring correct operation sequencing, such as building and walking
    34  // a dependency graph.
    35  type SyncState struct {
    36  	state *State
    37  	lock  sync.RWMutex
    38  }
    39  
    40  // Module returns a snapshot of the state of the module instance with the given
    41  // address, or nil if no such module is tracked.
    42  //
    43  // The return value is a pointer to a copy of the module state, which the
    44  // caller may then freely access and mutate. However, since the module state
    45  // tends to be a large data structure with many child objects, where possible
    46  // callers should prefer to use a more granular accessor to access a child
    47  // module directly, and thus reduce the amount of copying required.
    48  func (s *SyncState) Module(addr addrs.ModuleInstance) *Module {
    49  	s.lock.RLock()
    50  	ret := s.state.Module(addr).DeepCopy()
    51  	s.lock.RUnlock()
    52  	return ret
    53  }
    54  
    55  // ModuleOutputs returns the set of OutputValues that matches the given path.
    56  func (s *SyncState) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue {
    57  	s.lock.RLock()
    58  	defer s.lock.RUnlock()
    59  	var os []*OutputValue
    60  
    61  	for _, o := range s.state.ModuleOutputs(parentAddr, module) {
    62  		os = append(os, o.DeepCopy())
    63  	}
    64  	return os
    65  }
    66  
    67  // RemoveModule removes the entire state for the given module, taking with
    68  // it any resources associated with the module. This should generally be
    69  // called only for modules whose resources have all been destroyed, but
    70  // that is not enforced by this method.
    71  func (s *SyncState) RemoveModule(addr addrs.ModuleInstance) {
    72  	s.lock.Lock()
    73  	defer s.lock.Unlock()
    74  
    75  	s.state.RemoveModule(addr)
    76  }
    77  
    78  // OutputValue returns a snapshot of the state of the output value with the
    79  // given address, or nil if no such output value is tracked.
    80  //
    81  // The return value is a pointer to a copy of the output value state, which the
    82  // caller may then freely access and mutate.
    83  func (s *SyncState) OutputValue(addr addrs.AbsOutputValue) *OutputValue {
    84  	s.lock.RLock()
    85  	ret := s.state.OutputValue(addr).DeepCopy()
    86  	s.lock.RUnlock()
    87  	return ret
    88  }
    89  
    90  // SetOutputValue writes a given output value into the state, overwriting
    91  // any existing value of the same name.
    92  //
    93  // If the module containing the output is not yet tracked in state then it
    94  // be added as a side-effect.
    95  func (s *SyncState) SetOutputValue(addr addrs.AbsOutputValue, value cty.Value, sensitive bool) {
    96  	s.lock.Lock()
    97  	defer s.lock.Unlock()
    98  
    99  	ms := s.state.EnsureModule(addr.Module)
   100  	ms.SetOutputValue(addr.OutputValue.Name, value, sensitive)
   101  }
   102  
   103  // RemoveOutputValue removes the stored value for the output value with the
   104  // given address.
   105  //
   106  // If this results in its containing module being empty, the module will be
   107  // pruned from the state as a side-effect.
   108  func (s *SyncState) RemoveOutputValue(addr addrs.AbsOutputValue) {
   109  	s.lock.Lock()
   110  	defer s.lock.Unlock()
   111  
   112  	ms := s.state.Module(addr.Module)
   113  	if ms == nil {
   114  		return
   115  	}
   116  	ms.RemoveOutputValue(addr.OutputValue.Name)
   117  	s.maybePruneModule(addr.Module)
   118  }
   119  
   120  // LocalValue returns the current value associated with the given local value
   121  // address.
   122  func (s *SyncState) LocalValue(addr addrs.AbsLocalValue) cty.Value {
   123  	s.lock.RLock()
   124  	// cty.Value is immutable, so we don't need any extra copying here.
   125  	ret := s.state.LocalValue(addr)
   126  	s.lock.RUnlock()
   127  	return ret
   128  }
   129  
   130  // SetLocalValue writes a given output value into the state, overwriting
   131  // any existing value of the same name.
   132  //
   133  // If the module containing the local value is not yet tracked in state then it
   134  // will be added as a side-effect.
   135  func (s *SyncState) SetLocalValue(addr addrs.AbsLocalValue, value cty.Value) {
   136  	s.lock.Lock()
   137  	defer s.lock.Unlock()
   138  
   139  	ms := s.state.EnsureModule(addr.Module)
   140  	ms.SetLocalValue(addr.LocalValue.Name, value)
   141  }
   142  
   143  // RemoveLocalValue removes the stored value for the local value with the
   144  // given address.
   145  //
   146  // If this results in its containing module being empty, the module will be
   147  // pruned from the state as a side-effect.
   148  func (s *SyncState) RemoveLocalValue(addr addrs.AbsLocalValue) {
   149  	s.lock.Lock()
   150  	defer s.lock.Unlock()
   151  
   152  	ms := s.state.Module(addr.Module)
   153  	if ms == nil {
   154  		return
   155  	}
   156  	ms.RemoveLocalValue(addr.LocalValue.Name)
   157  	s.maybePruneModule(addr.Module)
   158  }
   159  
   160  // Resource returns a snapshot of the state of the resource with the given
   161  // address, or nil if no such resource is tracked.
   162  //
   163  // The return value is a pointer to a copy of the resource state, which the
   164  // caller may then freely access and mutate.
   165  func (s *SyncState) Resource(addr addrs.AbsResource) *Resource {
   166  	s.lock.RLock()
   167  	ret := s.state.Resource(addr).DeepCopy()
   168  	s.lock.RUnlock()
   169  	return ret
   170  }
   171  
   172  // ResourceInstance returns a snapshot of the state the resource instance with
   173  // the given address, or nil if no such instance is tracked.
   174  //
   175  // The return value is a pointer to a copy of the instance state, which the
   176  // caller may then freely access and mutate.
   177  func (s *SyncState) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance {
   178  	s.lock.RLock()
   179  	ret := s.state.ResourceInstance(addr).DeepCopy()
   180  	s.lock.RUnlock()
   181  	return ret
   182  }
   183  
   184  // ResourceInstanceObject returns a snapshot of the current instance object
   185  // of the given generation belonging to the instance with the given address,
   186  // or nil if no such object is tracked..
   187  //
   188  // The return value is a pointer to a copy of the object, which the caller may
   189  // then freely access and mutate.
   190  func (s *SyncState) ResourceInstanceObject(addr addrs.AbsResourceInstance, gen Generation) *ResourceInstanceObjectSrc {
   191  	s.lock.RLock()
   192  	defer s.lock.RUnlock()
   193  
   194  	inst := s.state.ResourceInstance(addr)
   195  	if inst == nil {
   196  		return nil
   197  	}
   198  	return inst.GetGeneration(gen).DeepCopy()
   199  }
   200  
   201  // SetResourceMeta updates the resource-level metadata for the resource at
   202  // the given address, creating the containing module state and resource state
   203  // as a side-effect if not already present.
   204  func (s *SyncState) SetResourceProvider(addr addrs.AbsResource, provider addrs.AbsProviderConfig) {
   205  	s.lock.Lock()
   206  	defer s.lock.Unlock()
   207  
   208  	ms := s.state.EnsureModule(addr.Module)
   209  	ms.SetResourceProvider(addr.Resource, provider)
   210  }
   211  
   212  // RemoveResource removes the entire state for the given resource, taking with
   213  // it any instances associated with the resource. This should generally be
   214  // called only for resource objects whose instances have all been destroyed,
   215  // but that is not enforced by this method. (Use RemoveResourceIfEmpty instead
   216  // to safely check first.)
   217  func (s *SyncState) RemoveResource(addr addrs.AbsResource) {
   218  	s.lock.Lock()
   219  	defer s.lock.Unlock()
   220  
   221  	ms := s.state.EnsureModule(addr.Module)
   222  	ms.RemoveResource(addr.Resource)
   223  	s.maybePruneModule(addr.Module)
   224  }
   225  
   226  // RemoveResourceIfEmpty is similar to RemoveResource but first checks to
   227  // make sure there are no instances or objects left in the resource.
   228  //
   229  // Returns true if the resource was removed, or false if remaining child
   230  // objects prevented its removal. Returns true also if the resource was
   231  // already absent, and thus no action needed to be taken.
   232  func (s *SyncState) RemoveResourceIfEmpty(addr addrs.AbsResource) bool {
   233  	s.lock.Lock()
   234  	defer s.lock.Unlock()
   235  
   236  	ms := s.state.Module(addr.Module)
   237  	if ms == nil {
   238  		return true // nothing to do
   239  	}
   240  	rs := ms.Resource(addr.Resource)
   241  	if rs == nil {
   242  		return true // nothing to do
   243  	}
   244  	if len(rs.Instances) != 0 {
   245  		// We don't check here for the possibility of instances that exist
   246  		// but don't have any objects because it's the responsibility of the
   247  		// instance-mutation methods to prune those away automatically.
   248  		return false
   249  	}
   250  	ms.RemoveResource(addr.Resource)
   251  	s.maybePruneModule(addr.Module)
   252  	return true
   253  }
   254  
   255  // SetResourceInstanceCurrent saves the given instance object as the current
   256  // generation of the resource instance with the given address, simultaneously
   257  // updating the recorded provider configuration address, dependencies, and
   258  // resource EachMode.
   259  //
   260  // Any existing current instance object for the given resource is overwritten.
   261  // Set obj to nil to remove the primary generation object altogether. If there
   262  // are no deposed objects then the instance as a whole will be removed, which
   263  // may in turn also remove the containing module if it becomes empty.
   264  //
   265  // The caller must ensure that the given ResourceInstanceObject is not
   266  // concurrently mutated during this call, but may be freely used again once
   267  // this function returns.
   268  //
   269  // The provider address is a resource-wide settings and is updated
   270  // for all other instances of the same resource as a side-effect of this call.
   271  //
   272  // If the containing module for this resource or the resource itself are not
   273  // already tracked in state then they will be added as a side-effect.
   274  func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
   275  	s.lock.Lock()
   276  	defer s.lock.Unlock()
   277  
   278  	ms := s.state.EnsureModule(addr.Module)
   279  	ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider)
   280  	s.maybePruneModule(addr.Module)
   281  }
   282  
   283  // SetResourceInstanceDeposed saves the given instance object as a deposed
   284  // generation of the resource instance with the given address and deposed key.
   285  //
   286  // Call this method only for pre-existing deposed objects that already have
   287  // a known DeposedKey. For example, this method is useful if reloading objects
   288  // that were persisted to a state file. To mark the current object as deposed,
   289  // use DeposeResourceInstanceObject instead.
   290  //
   291  // The caller must ensure that the given ResourceInstanceObject is not
   292  // concurrently mutated during this call, but may be freely used again once
   293  // this function returns.
   294  //
   295  // The resource that contains the given instance must already exist in the
   296  // state, or this method will panic. Use Resource to check first if its
   297  // presence is not already guaranteed.
   298  //
   299  // Any existing current instance object for the given resource and deposed key
   300  // is overwritten. Set obj to nil to remove the deposed object altogether. If
   301  // the instance is left with no objects after this operation then it will
   302  // be removed from its containing resource altogether.
   303  //
   304  // If the containing module for this resource or the resource itself are not
   305  // already tracked in state then they will be added as a side-effect.
   306  func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) {
   307  	s.lock.Lock()
   308  	defer s.lock.Unlock()
   309  
   310  	ms := s.state.EnsureModule(addr.Module)
   311  	ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy(), provider)
   312  	s.maybePruneModule(addr.Module)
   313  }
   314  
   315  // DeposeResourceInstanceObject moves the current instance object for the
   316  // given resource instance address into the deposed set, leaving the instance
   317  // without a current object.
   318  //
   319  // The return value is the newly-allocated deposed key, or NotDeposed if the
   320  // given instance is already lacking a current object.
   321  //
   322  // If the containing module for this resource or the resource itself are not
   323  // already tracked in state then there cannot be a current object for the
   324  // given instance, and so NotDeposed will be returned without modifying the
   325  // state at all.
   326  func (s *SyncState) DeposeResourceInstanceObject(addr addrs.AbsResourceInstance) DeposedKey {
   327  	s.lock.Lock()
   328  	defer s.lock.Unlock()
   329  
   330  	ms := s.state.Module(addr.Module)
   331  	if ms == nil {
   332  		return NotDeposed
   333  	}
   334  
   335  	return ms.deposeResourceInstanceObject(addr.Resource, NotDeposed)
   336  }
   337  
   338  // DeposeResourceInstanceObjectForceKey is like DeposeResourceInstanceObject
   339  // but uses a pre-allocated key. It's the caller's responsibility to ensure
   340  // that there aren't any races to use a particular key; this method will panic
   341  // if the given key is already in use.
   342  func (s *SyncState) DeposeResourceInstanceObjectForceKey(addr addrs.AbsResourceInstance, forcedKey DeposedKey) {
   343  	s.lock.Lock()
   344  	defer s.lock.Unlock()
   345  
   346  	if forcedKey == NotDeposed {
   347  		// Usage error: should use DeposeResourceInstanceObject in this case
   348  		panic("DeposeResourceInstanceObjectForceKey called without forced key")
   349  	}
   350  
   351  	ms := s.state.Module(addr.Module)
   352  	if ms == nil {
   353  		return // Nothing to do, since there can't be any current object either.
   354  	}
   355  
   356  	ms.deposeResourceInstanceObject(addr.Resource, forcedKey)
   357  }
   358  
   359  // ForgetResourceInstanceAll removes the record of all objects associated with
   360  // the specified resource instance, if present. If not present, this is a no-op.
   361  func (s *SyncState) ForgetResourceInstanceAll(addr addrs.AbsResourceInstance) {
   362  	s.lock.Lock()
   363  	defer s.lock.Unlock()
   364  
   365  	ms := s.state.Module(addr.Module)
   366  	if ms == nil {
   367  		return
   368  	}
   369  	ms.ForgetResourceInstanceAll(addr.Resource)
   370  	s.maybePruneModule(addr.Module)
   371  }
   372  
   373  // ForgetResourceInstanceDeposed removes the record of the deposed object with
   374  // the given address and key, if present. If not present, this is a no-op.
   375  func (s *SyncState) ForgetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) {
   376  	s.lock.Lock()
   377  	defer s.lock.Unlock()
   378  
   379  	ms := s.state.Module(addr.Module)
   380  	if ms == nil {
   381  		return
   382  	}
   383  	ms.ForgetResourceInstanceDeposed(addr.Resource, key)
   384  	s.maybePruneModule(addr.Module)
   385  }
   386  
   387  // MaybeRestoreResourceInstanceDeposed will restore the deposed object with the
   388  // given key on the specified resource as the current object for that instance
   389  // if and only if that would not cause us to forget an existing current
   390  // object for that instance.
   391  //
   392  // Returns true if the object was restored to current, or false if no change
   393  // was made at all.
   394  func (s *SyncState) MaybeRestoreResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) bool {
   395  	s.lock.Lock()
   396  	defer s.lock.Unlock()
   397  
   398  	if key == NotDeposed {
   399  		panic("MaybeRestoreResourceInstanceDeposed called without DeposedKey")
   400  	}
   401  
   402  	ms := s.state.Module(addr.Module)
   403  	if ms == nil {
   404  		// Nothing to do, since the specified deposed object cannot exist.
   405  		return false
   406  	}
   407  
   408  	return ms.maybeRestoreResourceInstanceDeposed(addr.Resource, key)
   409  }
   410  
   411  // RemovePlannedResourceInstanceObjects removes from the state any resource
   412  // instance objects that have the status ObjectPlanned, indiciating that they
   413  // are just transient placeholders created during planning.
   414  //
   415  // Note that this does not restore any "ready" or "tainted" object that might
   416  // have been present before the planned object was written. The only real use
   417  // for this method is in preparing the state created during a refresh walk,
   418  // where we run the planning step for certain instances just to create enough
   419  // information to allow correct expression evaluation within provider and
   420  // data resource blocks. Discarding planned instances in that case is okay
   421  // because the refresh phase only creates planned objects to stand in for
   422  // objects that don't exist yet, and thus the planned object must have been
   423  // absent before by definition.
   424  func (s *SyncState) RemovePlannedResourceInstanceObjects() {
   425  	// TODO: Merge together the refresh and plan phases into a single walk,
   426  	// so we can remove the need to create this "partial plan" during refresh
   427  	// that we then need to clean up before proceeding.
   428  
   429  	s.lock.Lock()
   430  	defer s.lock.Unlock()
   431  
   432  	for _, ms := range s.state.Modules {
   433  		moduleAddr := ms.Addr
   434  
   435  		for _, rs := range ms.Resources {
   436  			resAddr := rs.Addr.Resource
   437  
   438  			for ik, is := range rs.Instances {
   439  				instAddr := resAddr.Instance(ik)
   440  
   441  				if is.Current != nil && is.Current.Status == ObjectPlanned {
   442  					// Setting the current instance to nil removes it from the
   443  					// state altogether if there are not also deposed instances.
   444  					ms.SetResourceInstanceCurrent(instAddr, nil, rs.ProviderConfig)
   445  				}
   446  
   447  				for dk, obj := range is.Deposed {
   448  					// Deposed objects should never be "planned", but we'll
   449  					// do this anyway for the sake of completeness.
   450  					if obj.Status == ObjectPlanned {
   451  						ms.ForgetResourceInstanceDeposed(instAddr, dk)
   452  					}
   453  				}
   454  			}
   455  		}
   456  
   457  		// We may have deleted some objects, which means that we may have
   458  		// left a module empty, and so we must prune to preserve the invariant
   459  		// that only the root module is allowed to be empty.
   460  		s.maybePruneModule(moduleAddr)
   461  	}
   462  }
   463  
   464  // DiscardCheckResults discards any previously-recorded check results, with
   465  // the intent of preventing any references to them after they have become
   466  // stale due to starting (but possibly not completing) an update.
   467  func (s *SyncState) DiscardCheckResults() {
   468  	s.lock.Lock()
   469  	s.state.CheckResults = nil
   470  	s.lock.Unlock()
   471  }
   472  
   473  // RecordCheckResults replaces any check results already recorded in the state
   474  // with a new set taken from the given check state object.
   475  func (s *SyncState) RecordCheckResults(checkState *checks.State) {
   476  	newResults := NewCheckResults(checkState)
   477  	s.lock.Lock()
   478  	s.state.CheckResults = newResults
   479  	s.lock.Unlock()
   480  }
   481  
   482  // Lock acquires an explicit lock on the state, allowing direct read and write
   483  // access to the returned state object. The caller must call Unlock once
   484  // access is no longer needed, and then immediately discard the state pointer
   485  // pointer.
   486  //
   487  // Most callers should not use this. Instead, use the concurrency-safe
   488  // accessors and mutators provided directly on SyncState.
   489  func (s *SyncState) Lock() *State {
   490  	s.lock.Lock()
   491  	return s.state
   492  }
   493  
   494  // Unlock releases a lock previously acquired by Lock, at which point the
   495  // caller must cease all use of the state pointer that was returned.
   496  //
   497  // Do not call this method except to end an explicit lock acquired by
   498  // Lock. If a caller calls Unlock without first holding the lock, behavior
   499  // is undefined.
   500  func (s *SyncState) Unlock() {
   501  	s.lock.Unlock()
   502  }
   503  
   504  // Close extracts the underlying state from inside this wrapper, making the
   505  // wrapper invalid for any future operations.
   506  func (s *SyncState) Close() *State {
   507  	s.lock.Lock()
   508  	ret := s.state
   509  	s.state = nil // make sure future operations can't still modify it
   510  	s.lock.Unlock()
   511  	return ret
   512  }
   513  
   514  // maybePruneModule will remove a module from the state altogether if it is
   515  // empty, unless it's the root module which must always be present.
   516  //
   517  // This helper method is not concurrency-safe on its own, so must only be
   518  // called while the caller is already holding the lock for writing.
   519  func (s *SyncState) maybePruneModule(addr addrs.ModuleInstance) {
   520  	if addr.IsRoot() {
   521  		// We never prune the root.
   522  		return
   523  	}
   524  
   525  	ms := s.state.Module(addr)
   526  	if ms == nil {
   527  		return
   528  	}
   529  
   530  	if ms.empty() {
   531  		log.Printf("[TRACE] states.SyncState: pruning %s because it is empty", addr)
   532  		s.state.RemoveModule(addr)
   533  	}
   534  }
   535  
   536  func (s *SyncState) MoveAbsResource(src, dst addrs.AbsResource) {
   537  	s.lock.Lock()
   538  	defer s.lock.Unlock()
   539  
   540  	s.state.MoveAbsResource(src, dst)
   541  }
   542  
   543  func (s *SyncState) MaybeMoveAbsResource(src, dst addrs.AbsResource) bool {
   544  	s.lock.Lock()
   545  	defer s.lock.Unlock()
   546  
   547  	return s.state.MaybeMoveAbsResource(src, dst)
   548  }
   549  
   550  func (s *SyncState) MoveResourceInstance(src, dst addrs.AbsResourceInstance) {
   551  	s.lock.Lock()
   552  	defer s.lock.Unlock()
   553  
   554  	s.state.MoveAbsResourceInstance(src, dst)
   555  }
   556  
   557  func (s *SyncState) MaybeMoveResourceInstance(src, dst addrs.AbsResourceInstance) bool {
   558  	s.lock.Lock()
   559  	defer s.lock.Unlock()
   560  
   561  	return s.state.MaybeMoveAbsResourceInstance(src, dst)
   562  }
   563  
   564  func (s *SyncState) MoveModuleInstance(src, dst addrs.ModuleInstance) {
   565  	s.lock.Lock()
   566  	defer s.lock.Unlock()
   567  
   568  	s.state.MoveModuleInstance(src, dst)
   569  }
   570  
   571  func (s *SyncState) MaybeMoveModuleInstance(src, dst addrs.ModuleInstance) bool {
   572  	s.lock.Lock()
   573  	defer s.lock.Unlock()
   574  
   575  	return s.state.MaybeMoveModuleInstance(src, dst)
   576  }