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 }