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 }