github.com/opentofu/opentofu@v1.7.1/internal/plans/changes_sync.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package plans
     7  
     8  import (
     9  	"fmt"
    10  	"sync"
    11  
    12  	"github.com/opentofu/opentofu/internal/addrs"
    13  	"github.com/opentofu/opentofu/internal/states"
    14  )
    15  
    16  // ChangesSync is a wrapper around a Changes that provides a concurrency-safe
    17  // interface to insert new changes and retrieve copies of existing changes.
    18  //
    19  // Each ChangesSync is independent of all others, so all concurrent writers
    20  // to a particular Changes must share a single ChangesSync. Behavior is
    21  // undefined if any other caller makes changes to the underlying Changes
    22  // object or its nested objects concurrently with any of the methods of a
    23  // particular ChangesSync.
    24  type ChangesSync struct {
    25  	lock    sync.Mutex
    26  	changes *Changes
    27  }
    28  
    29  // AppendResourceInstanceChange records the given resource instance change in
    30  // the set of planned resource changes.
    31  //
    32  // The caller must ensure that there are no concurrent writes to the given
    33  // change while this method is running, but it is safe to resume mutating
    34  // it after this method returns without affecting the saved change.
    35  func (cs *ChangesSync) AppendResourceInstanceChange(changeSrc *ResourceInstanceChangeSrc) {
    36  	if cs == nil {
    37  		panic("AppendResourceInstanceChange on nil ChangesSync")
    38  	}
    39  	cs.lock.Lock()
    40  	defer cs.lock.Unlock()
    41  
    42  	s := changeSrc.DeepCopy()
    43  	cs.changes.Resources = append(cs.changes.Resources, s)
    44  }
    45  
    46  // GetResourceInstanceChange searches the set of resource instance changes for
    47  // one matching the given address and generation, returning it if it exists.
    48  //
    49  // If no such change exists, nil is returned.
    50  //
    51  // The returned object is a deep copy of the change recorded in the plan, so
    52  // callers may mutate it although it's generally better (less confusing) to
    53  // treat planned changes as immutable after they've been initially constructed.
    54  func (cs *ChangesSync) GetResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) *ResourceInstanceChangeSrc {
    55  	if cs == nil {
    56  		panic("GetResourceInstanceChange on nil ChangesSync")
    57  	}
    58  	cs.lock.Lock()
    59  	defer cs.lock.Unlock()
    60  
    61  	if gen == states.CurrentGen {
    62  		return cs.changes.ResourceInstance(addr).DeepCopy()
    63  	}
    64  	if dk, ok := gen.(states.DeposedKey); ok {
    65  		return cs.changes.ResourceInstanceDeposed(addr, dk).DeepCopy()
    66  	}
    67  	panic(fmt.Sprintf("unsupported generation value %#v", gen))
    68  }
    69  
    70  // GetChangesForConfigResource searches the set of resource instance
    71  // changes and returns all changes related to a given configuration address.
    72  // This is be used to find possible changes related to a configuration
    73  // reference.
    74  //
    75  // If no such changes exist, nil is returned.
    76  //
    77  // The returned objects are a deep copy of the change recorded in the plan, so
    78  // callers may mutate them although it's generally better (less confusing) to
    79  // treat planned changes as immutable after they've been initially constructed.
    80  func (cs *ChangesSync) GetChangesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc {
    81  	if cs == nil {
    82  		panic("GetChangesForConfigResource on nil ChangesSync")
    83  	}
    84  	cs.lock.Lock()
    85  	defer cs.lock.Unlock()
    86  	var changes []*ResourceInstanceChangeSrc
    87  	for _, c := range cs.changes.InstancesForConfigResource(addr) {
    88  		changes = append(changes, c.DeepCopy())
    89  	}
    90  	return changes
    91  }
    92  
    93  // GetChangesForAbsResource searches the set of resource instance
    94  // changes and returns all changes related to a given configuration address.
    95  //
    96  // If no such changes exist, nil is returned.
    97  //
    98  // The returned objects are a deep copy of the change recorded in the plan, so
    99  // callers may mutate them although it's generally better (less confusing) to
   100  // treat planned changes as immutable after they've been initially constructed.
   101  func (cs *ChangesSync) GetChangesForAbsResource(addr addrs.AbsResource) []*ResourceInstanceChangeSrc {
   102  	if cs == nil {
   103  		panic("GetChangesForAbsResource on nil ChangesSync")
   104  	}
   105  	cs.lock.Lock()
   106  	defer cs.lock.Unlock()
   107  	var changes []*ResourceInstanceChangeSrc
   108  	for _, c := range cs.changes.InstancesForAbsResource(addr) {
   109  		changes = append(changes, c.DeepCopy())
   110  	}
   111  	return changes
   112  }
   113  
   114  // RemoveResourceInstanceChange searches the set of resource instance changes
   115  // for one matching the given address and generation, and removes it from the
   116  // set if it exists.
   117  func (cs *ChangesSync) RemoveResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) {
   118  	if cs == nil {
   119  		panic("RemoveResourceInstanceChange on nil ChangesSync")
   120  	}
   121  	cs.lock.Lock()
   122  	defer cs.lock.Unlock()
   123  
   124  	dk := states.NotDeposed
   125  	if realDK, ok := gen.(states.DeposedKey); ok {
   126  		dk = realDK
   127  	}
   128  
   129  	addrStr := addr.String()
   130  	for i, r := range cs.changes.Resources {
   131  		if r.Addr.String() != addrStr || r.DeposedKey != dk {
   132  			continue
   133  		}
   134  		copy(cs.changes.Resources[i:], cs.changes.Resources[i+1:])
   135  		cs.changes.Resources = cs.changes.Resources[:len(cs.changes.Resources)-1]
   136  		return
   137  	}
   138  }
   139  
   140  // AppendOutputChange records the given output value change in the set of
   141  // planned value changes.
   142  //
   143  // The caller must ensure that there are no concurrent writes to the given
   144  // change while this method is running, but it is safe to resume mutating
   145  // it after this method returns without affecting the saved change.
   146  func (cs *ChangesSync) AppendOutputChange(changeSrc *OutputChangeSrc) {
   147  	if cs == nil {
   148  		panic("AppendOutputChange on nil ChangesSync")
   149  	}
   150  	cs.lock.Lock()
   151  	defer cs.lock.Unlock()
   152  
   153  	s := changeSrc.DeepCopy()
   154  	cs.changes.Outputs = append(cs.changes.Outputs, s)
   155  }
   156  
   157  // GetOutputChange searches the set of output value changes for one matching
   158  // the given address, returning it if it exists.
   159  //
   160  // If no such change exists, nil is returned.
   161  //
   162  // The returned object is a deep copy of the change recorded in the plan, so
   163  // callers may mutate it although it's generally better (less confusing) to
   164  // treat planned changes as immutable after they've been initially constructed.
   165  func (cs *ChangesSync) GetOutputChange(addr addrs.AbsOutputValue) *OutputChangeSrc {
   166  	if cs == nil {
   167  		panic("GetOutputChange on nil ChangesSync")
   168  	}
   169  	cs.lock.Lock()
   170  	defer cs.lock.Unlock()
   171  
   172  	return cs.changes.OutputValue(addr)
   173  }
   174  
   175  // GetRootOutputChanges searches the set of output changes for any that reside
   176  // the root module. If no such changes exist, nil is returned.
   177  //
   178  // The returned objects are a deep copy of the change recorded in the plan, so
   179  // callers may mutate them although it's generally better (less confusing) to
   180  // treat planned changes as immutable after they've been initially constructed.
   181  func (cs *ChangesSync) GetRootOutputChanges() []*OutputChangeSrc {
   182  	if cs == nil {
   183  		panic("GetRootOutputChanges on nil ChangesSync")
   184  	}
   185  	cs.lock.Lock()
   186  	defer cs.lock.Unlock()
   187  
   188  	return cs.changes.RootOutputValues()
   189  }
   190  
   191  // GetOutputChanges searches the set of output changes for any that reside in
   192  // module instances beneath the given module. If no changes exist, nil
   193  // is returned.
   194  //
   195  // The returned objects are a deep copy of the change recorded in the plan, so
   196  // callers may mutate them although it's generally better (less confusing) to
   197  // treat planned changes as immutable after they've been initially constructed.
   198  func (cs *ChangesSync) GetOutputChanges(parent addrs.ModuleInstance, module addrs.ModuleCall) []*OutputChangeSrc {
   199  	if cs == nil {
   200  		panic("GetOutputChange on nil ChangesSync")
   201  	}
   202  	cs.lock.Lock()
   203  	defer cs.lock.Unlock()
   204  
   205  	return cs.changes.OutputValues(parent, module)
   206  }
   207  
   208  // RemoveOutputChange searches the set of output value changes for one matching
   209  // the given address, and removes it from the set if it exists.
   210  func (cs *ChangesSync) RemoveOutputChange(addr addrs.AbsOutputValue) {
   211  	if cs == nil {
   212  		panic("RemoveOutputChange on nil ChangesSync")
   213  	}
   214  	cs.lock.Lock()
   215  	defer cs.lock.Unlock()
   216  
   217  	addrStr := addr.String()
   218  
   219  	for i, o := range cs.changes.Outputs {
   220  		if o.Addr.String() != addrStr {
   221  			continue
   222  		}
   223  		copy(cs.changes.Outputs[i:], cs.changes.Outputs[i+1:])
   224  		cs.changes.Outputs = cs.changes.Outputs[:len(cs.changes.Outputs)-1]
   225  		return
   226  	}
   227  }