github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/plans/changes_sync.go (about)

     1  package plans
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
     9  )
    10  
    11  // ChangesSync is a wrapper around a Changes that provides a concurrency-safe
    12  // interface to insert new changes and retrieve copies of existing changes.
    13  //
    14  // Each ChangesSync is independent of all others, so all concurrent writers
    15  // to a particular Changes must share a single ChangesSync. Behavior is
    16  // undefined if any other caller makes changes to the underlying Changes
    17  // object or its nested objects concurrently with any of the methods of a
    18  // particular ChangesSync.
    19  type ChangesSync struct {
    20  	lock    sync.Mutex
    21  	changes *Changes
    22  }
    23  
    24  // AppendResourceInstanceChange records the given resource instance change in
    25  // the set of planned resource changes.
    26  //
    27  // The caller must ensure that there are no concurrent writes to the given
    28  // change while this method is running, but it is safe to resume mutating
    29  // it after this method returns without affecting the saved change.
    30  func (cs *ChangesSync) AppendResourceInstanceChange(changeSrc *ResourceInstanceChangeSrc) {
    31  	if cs == nil {
    32  		panic("AppendResourceInstanceChange on nil ChangesSync")
    33  	}
    34  	cs.lock.Lock()
    35  	defer cs.lock.Unlock()
    36  
    37  	s := changeSrc.DeepCopy()
    38  	cs.changes.Resources = append(cs.changes.Resources, s)
    39  }
    40  
    41  // GetResourceInstanceChange searches the set of resource instance changes for
    42  // one matching the given address and generation, returning it if it exists.
    43  //
    44  // If no such change exists, nil is returned.
    45  //
    46  // The returned object is a deep copy of the change recorded in the plan, so
    47  // callers may mutate it although it's generally better (less confusing) to
    48  // treat planned changes as immutable after they've been initially constructed.
    49  func (cs *ChangesSync) GetResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) *ResourceInstanceChangeSrc {
    50  	if cs == nil {
    51  		panic("GetResourceInstanceChange on nil ChangesSync")
    52  	}
    53  	cs.lock.Lock()
    54  	defer cs.lock.Unlock()
    55  
    56  	if gen == states.CurrentGen {
    57  		return cs.changes.ResourceInstance(addr).DeepCopy()
    58  	}
    59  	if dk, ok := gen.(states.DeposedKey); ok {
    60  		return cs.changes.ResourceInstanceDeposed(addr, dk).DeepCopy()
    61  	}
    62  	panic(fmt.Sprintf("unsupported generation value %#v", gen))
    63  }
    64  
    65  // RemoveResourceInstanceChange searches the set of resource instance changes
    66  // for one matching the given address and generation, and removes it from the
    67  // set if it exists.
    68  func (cs *ChangesSync) RemoveResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) {
    69  	if cs == nil {
    70  		panic("RemoveResourceInstanceChange on nil ChangesSync")
    71  	}
    72  	cs.lock.Lock()
    73  	defer cs.lock.Unlock()
    74  
    75  	dk := states.NotDeposed
    76  	if realDK, ok := gen.(states.DeposedKey); ok {
    77  		dk = realDK
    78  	}
    79  
    80  	addrStr := addr.String()
    81  	for i, r := range cs.changes.Resources {
    82  		if r.Addr.String() != addrStr || r.DeposedKey != dk {
    83  			continue
    84  		}
    85  		copy(cs.changes.Resources[i:], cs.changes.Resources[i+1:])
    86  		cs.changes.Resources = cs.changes.Resources[:len(cs.changes.Resources)-1]
    87  		return
    88  	}
    89  }
    90  
    91  // AppendOutputChange records the given output value change in the set of
    92  // planned value changes.
    93  //
    94  // The caller must ensure that there are no concurrent writes to the given
    95  // change while this method is running, but it is safe to resume mutating
    96  // it after this method returns without affecting the saved change.
    97  func (cs *ChangesSync) AppendOutputChange(changeSrc *OutputChangeSrc) {
    98  	if cs == nil {
    99  		panic("AppendOutputChange on nil ChangesSync")
   100  	}
   101  	cs.lock.Lock()
   102  	defer cs.lock.Unlock()
   103  
   104  	s := changeSrc.DeepCopy()
   105  	cs.changes.Outputs = append(cs.changes.Outputs, s)
   106  }
   107  
   108  // GetOutputChange searches the set of output value changes for one matching
   109  // the given address, returning it if it exists.
   110  //
   111  // If no such change exists, nil is returned.
   112  //
   113  // The returned object is a deep copy of the change recorded in the plan, so
   114  // callers may mutate it although it's generally better (less confusing) to
   115  // treat planned changes as immutable after they've been initially constructed.
   116  func (cs *ChangesSync) GetOutputChange(addr addrs.AbsOutputValue) *OutputChangeSrc {
   117  	if cs == nil {
   118  		panic("GetOutputChange on nil ChangesSync")
   119  	}
   120  	cs.lock.Lock()
   121  	defer cs.lock.Unlock()
   122  
   123  	return cs.changes.OutputValue(addr)
   124  }
   125  
   126  // RemoveOutputChange searches the set of output value changes for one matching
   127  // the given address, and removes it from the set if it exists.
   128  func (cs *ChangesSync) RemoveOutputChange(addr addrs.AbsOutputValue) {
   129  	if cs == nil {
   130  		panic("RemoveOutputChange on nil ChangesSync")
   131  	}
   132  	cs.lock.Lock()
   133  	defer cs.lock.Unlock()
   134  
   135  	addrStr := addr.String()
   136  	for i, o := range cs.changes.Outputs {
   137  		if o.Addr.String() != addrStr {
   138  			continue
   139  		}
   140  		copy(cs.changes.Outputs[i:], cs.changes.Outputs[i+1:])
   141  		cs.changes.Outputs = cs.changes.Outputs[:len(cs.changes.Outputs)-1]
   142  		return
   143  	}
   144  }