github.com/opentofu/opentofu@v1.7.1/internal/depsfile/locks.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 depsfile 7 8 import ( 9 "fmt" 10 "sort" 11 12 "github.com/opentofu/opentofu/internal/addrs" 13 "github.com/opentofu/opentofu/internal/getproviders" 14 ) 15 16 // Locks is the top-level type representing the information retained in a 17 // dependency lock file. 18 // 19 // Locks and the other types used within it are mutable via various setter 20 // methods, but they are not safe for concurrent modifications, so it's the 21 // caller's responsibility to prevent concurrent writes and writes concurrent 22 // with reads. 23 type Locks struct { 24 providers map[addrs.Provider]*ProviderLock 25 26 // overriddenProviders is a subset of providers which we might be tracking 27 // in field providers but whose lock information we're disregarding for 28 // this particular run due to some feature that forces OpenTofu to not 29 // use a normally-installed plugin for it. For example, the "provider dev 30 // overrides" feature means that we'll be using an arbitrary directory on 31 // disk as the package, regardless of what might be selected in "providers". 32 // 33 // overriddenProviders is an in-memory-only annotation, never stored as 34 // part of a lock file and thus not persistent between OpenTofu runs. 35 // The CLI layer is generally the one responsible for populating this, 36 // by calling SetProviderOverridden in response to CLI Configuration 37 // settings, environment variables, or whatever similar sources. 38 overriddenProviders map[addrs.Provider]struct{} 39 40 // TODO: In future we'll also have module locks, but the design of that 41 // still needs some more work and we're deferring that to get the 42 // provider locking capability out sooner, because it's more common to 43 // directly depend on providers maintained outside your organization than 44 // modules maintained outside your organization. 45 46 // sources is a copy of the map of source buffers produced by the HCL 47 // parser during loading, which we retain only so that the caller can 48 // use it to produce source code snippets in error messages. 49 sources map[string][]byte 50 } 51 52 // NewLocks constructs and returns a new Locks object that initially contains 53 // no locks at all. 54 func NewLocks() *Locks { 55 return &Locks{ 56 providers: make(map[addrs.Provider]*ProviderLock), 57 58 // no "sources" here, because that's only for locks objects loaded 59 // from files. 60 } 61 } 62 63 // Provider returns the stored lock for the given provider, or nil if that 64 // provider currently has no lock. 65 func (l *Locks) Provider(addr addrs.Provider) *ProviderLock { 66 return l.providers[addr] 67 } 68 69 // AllProviders returns a map describing all of the provider locks in the 70 // receiver. 71 func (l *Locks) AllProviders() map[addrs.Provider]*ProviderLock { 72 // We return a copy of our internal map so that future calls to 73 // SetProvider won't modify the map we're returning, or vice-versa. 74 ret := make(map[addrs.Provider]*ProviderLock, len(l.providers)) 75 for k, v := range l.providers { 76 ret[k] = v 77 } 78 return ret 79 } 80 81 // SetProvider creates a new lock or replaces the existing lock for the given 82 // provider. 83 // 84 // SetProvider returns the newly-created provider lock object, which 85 // invalidates any ProviderLock object previously returned from Provider or 86 // SetProvider for the given provider address. 87 // 88 // The ownership of the backing array for the slice of hashes passes to this 89 // function, and so the caller must not read or write that backing array after 90 // calling SetProvider. 91 // 92 // Only lockable providers can be passed to this method. If you pass a 93 // non-lockable provider address then this function will panic. Use 94 // function ProviderIsLockable to determine whether a particular provider 95 // should participate in the version locking mechanism. 96 func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock { 97 if !ProviderIsLockable(addr) { 98 panic(fmt.Sprintf("Locks.SetProvider with non-lockable provider %s", addr)) 99 } 100 101 new := NewProviderLock(addr, version, constraints, hashes) 102 l.providers[new.addr] = new 103 return new 104 } 105 106 // RemoveProvider removes any existing lock file entry for the given provider. 107 // 108 // If the given provider did not already have a lock entry, RemoveProvider is 109 // a no-op. 110 // 111 // Only lockable providers can be passed to this method. If you pass a 112 // non-lockable provider address then this function will panic. Use 113 // function ProviderIsLockable to determine whether a particular provider 114 // should participate in the version locking mechanism. 115 func (l *Locks) RemoveProvider(addr addrs.Provider) { 116 if !ProviderIsLockable(addr) { 117 panic(fmt.Sprintf("Locks.RemoveProvider with non-lockable provider %s", addr)) 118 } 119 120 delete(l.providers, addr) 121 } 122 123 // SetProviderOverridden records that this particular OpenTofu process will 124 // not pay attention to the recorded lock entry for the given provider, and 125 // will instead access that provider's functionality in some other special 126 // way that isn't sensitive to provider version selections or checksums. 127 // 128 // This is an in-memory-only annotation which lives only inside a particular 129 // Locks object, and is never persisted as part of a saved lock file on disk. 130 // It's valid to still use other methods of the reciever to access 131 // already-stored lock information and to update lock information for an 132 // overridden provider, but some callers may need to use ProviderIsOverridden 133 // to selectively disregard stored lock information for overridden providers, 134 // depending on what they intended to use the lock information for. 135 func (l *Locks) SetProviderOverridden(addr addrs.Provider) { 136 if l.overriddenProviders == nil { 137 l.overriddenProviders = make(map[addrs.Provider]struct{}) 138 } 139 l.overriddenProviders[addr] = struct{}{} 140 } 141 142 // ProviderIsOverridden returns true only if the given provider address was 143 // previously registered as overridden by calling SetProviderOverridden. 144 func (l *Locks) ProviderIsOverridden(addr addrs.Provider) bool { 145 _, ret := l.overriddenProviders[addr] 146 return ret 147 } 148 149 // SetSameOverriddenProviders updates the receiver to mark as overridden all 150 // of the same providers already marked as overridden in the other given locks. 151 // 152 // This allows propagating override information between different lock objects, 153 // as if calling SetProviderOverridden for each address already overridden 154 // in the other given locks. If the reciever already has overridden providers, 155 // SetSameOverriddenProviders will preserve them. 156 func (l *Locks) SetSameOverriddenProviders(other *Locks) { 157 if other == nil { 158 return 159 } 160 for addr := range other.overriddenProviders { 161 l.SetProviderOverridden(addr) 162 } 163 } 164 165 // NewProviderLock creates a new ProviderLock object that isn't associated 166 // with any Locks object. 167 // 168 // This is here primarily for testing. Most callers should use Locks.SetProvider 169 // to construct a new provider lock and insert it into a Locks object at the 170 // same time. 171 // 172 // The ownership of the backing array for the slice of hashes passes to this 173 // function, and so the caller must not read or write that backing array after 174 // calling NewProviderLock. 175 // 176 // Only lockable providers can be passed to this method. If you pass a 177 // non-lockable provider address then this function will panic. Use 178 // function ProviderIsLockable to determine whether a particular provider 179 // should participate in the version locking mechanism. 180 func NewProviderLock(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock { 181 if !ProviderIsLockable(addr) { 182 panic(fmt.Sprintf("Locks.NewProviderLock with non-lockable provider %s", addr)) 183 } 184 185 // Normalize the hashes into lexical order so that we can do straightforward 186 // equality tests between different locks for the same provider. The 187 // hashes are logically a set, so the given order is insignificant. 188 sort.Slice(hashes, func(i, j int) bool { 189 return string(hashes[i]) < string(hashes[j]) 190 }) 191 192 // This is a slightly-tricky in-place deduping to avoid unnecessarily 193 // allocating a new array in the common case where there are no duplicates: 194 // we iterate over "hashes" at the same time as appending to another slice 195 // with the same backing array, relying on the fact that deduping can only 196 // _skip_ elements from the input, and will never generate additional ones 197 // that would cause the writer to get ahead of the reader. This also 198 // assumes that we already sorted the items, which means that any duplicates 199 // will be consecutive in the sequence. 200 dedupeHashes := hashes[:0] 201 prevHash := getproviders.NilHash 202 for _, hash := range hashes { 203 if hash != prevHash { 204 dedupeHashes = append(dedupeHashes, hash) 205 prevHash = hash 206 } 207 } 208 209 return &ProviderLock{ 210 addr: addr, 211 version: version, 212 versionConstraints: constraints, 213 hashes: dedupeHashes, 214 } 215 } 216 217 // ProviderIsLockable returns true if the given provider is eligible for 218 // version locking. 219 // 220 // Currently, all providers except builtin and legacy providers are eligible 221 // for locking. 222 func ProviderIsLockable(addr addrs.Provider) bool { 223 return !(addr.IsBuiltIn() || addr.IsLegacy()) 224 } 225 226 // Sources returns the source code of the file the receiver was generated from, 227 // or an empty map if the receiver wasn't generated from a file. 228 // 229 // This return type matches the one expected by HCL diagnostics printers to 230 // produce source code snapshots, which is the only intended use for this 231 // method. 232 func (l *Locks) Sources() map[string][]byte { 233 return l.sources 234 } 235 236 // Equal returns true if the given Locks represents the same information as 237 // the receiver. 238 // 239 // Equal explicitly _does not_ consider the equality of version constraints 240 // in the saved locks, because those are saved only as hints to help the UI 241 // explain what's changed between runs, and are never used as part of 242 // dependency installation decisions. 243 func (l *Locks) Equal(other *Locks) bool { 244 if len(l.providers) != len(other.providers) { 245 return false 246 } 247 for addr, thisLock := range l.providers { 248 otherLock, ok := other.providers[addr] 249 if !ok { 250 return false 251 } 252 253 if thisLock.addr != otherLock.addr { 254 // It'd be weird to get here because we already looked these up 255 // by address above. 256 return false 257 } 258 if thisLock.version != otherLock.version { 259 // Equality rather than "Version.Same" because changes to the 260 // build metadata are significant for the purpose of this function: 261 // it's a different package even if it has the same precedence. 262 return false 263 } 264 265 // Although "hashes" is declared as a slice, it's logically an 266 // unordered set. However, we normalize the slice of hashes when 267 // recieving it in NewProviderLock, so we can just do a simple 268 // item-by-item equality test here. 269 if len(thisLock.hashes) != len(otherLock.hashes) { 270 return false 271 } 272 for i := range thisLock.hashes { 273 if thisLock.hashes[i] != otherLock.hashes[i] { 274 return false 275 } 276 } 277 } 278 // We don't need to worry about providers that are in "other" but not 279 // in the receiver, because we tested the lengths being equal above. 280 281 return true 282 } 283 284 // EqualProviderAddress returns true if the given Locks have the same provider 285 // address as the receiver. This doesn't check version and hashes. 286 func (l *Locks) EqualProviderAddress(other *Locks) bool { 287 if len(l.providers) != len(other.providers) { 288 return false 289 } 290 291 for addr := range l.providers { 292 _, ok := other.providers[addr] 293 if !ok { 294 return false 295 } 296 } 297 298 return true 299 } 300 301 // Empty returns true if the given Locks object contains no actual locks. 302 // 303 // UI code might wish to use this to distinguish a lock file being 304 // written for the first time from subsequent updates to that lock file. 305 func (l *Locks) Empty() bool { 306 return len(l.providers) == 0 307 } 308 309 // DeepCopy creates a new Locks that represents the same information as the 310 // receiver but does not share memory for any parts of the structure that. 311 // are mutable through methods on Locks. 312 // 313 // Note that this does _not_ create deep copies of parts of the structure 314 // that are technically mutable but are immutable by convention, such as the 315 // array underlying the slice of version constraints. Callers may mutate the 316 // resulting data structure only via the direct methods of Locks. 317 func (l *Locks) DeepCopy() *Locks { 318 ret := NewLocks() 319 for addr, lock := range l.providers { 320 var hashes []getproviders.Hash 321 if len(lock.hashes) > 0 { 322 hashes = make([]getproviders.Hash, len(lock.hashes)) 323 copy(hashes, lock.hashes) 324 } 325 ret.SetProvider(addr, lock.version, lock.versionConstraints, hashes) 326 } 327 return ret 328 } 329 330 // ProviderLock represents lock information for a specific provider. 331 type ProviderLock struct { 332 // addr is the address of the provider this lock applies to. 333 addr addrs.Provider 334 335 // version is the specific version that was previously selected, while 336 // versionConstraints is the constraint that was used to make that 337 // selection, which we can potentially use to hint to run 338 // e.g. tofu init -upgrade if a user has changed a version 339 // constraint but the previous selection still remains valid. 340 // "version" is therefore authoritative, while "versionConstraints" is 341 // just for a UI hint and not used to make any real decisions. 342 version getproviders.Version 343 versionConstraints getproviders.VersionConstraints 344 345 // hashes contains zero or more hashes of packages or package contents 346 // for the package associated with the selected version across all of 347 // the supported platforms. 348 // 349 // hashes can contain a mixture of hashes in different formats to support 350 // changes over time. The new-style hash format is to have a string 351 // starting with "h" followed by a version number and then a colon, like 352 // "h1:" for the first hash format version. Other hash versions following 353 // this scheme may come later. These versioned hash schemes are implemented 354 // in the getproviders package; for example, "h1:" is implemented in 355 // getproviders.HashV1 . 356 // 357 // There is also a legacy hash format which is just a lowercase-hex-encoded 358 // SHA256 hash of the official upstream .zip file for the selected version. 359 // We'll allow as that a stop-gap until we can upgrade Terraform Registry 360 // to support the new scheme, but is non-ideal because we can verify it only 361 // when we have the original .zip file exactly; we can't verify a local 362 // directory containing the unpacked contents of that .zip file. 363 // 364 // We ideally want to populate hashes for all available platforms at 365 // once, by referring to the signed checksums file in the upstream 366 // registry. In that ideal case it's possible to later work with the same 367 // configuration on a different platform while still verifying the hashes. 368 // However, installation from any method other than an origin registry 369 // means we can only populate the hash for the current platform, and so 370 // it won't be possible to verify a subsequent installation of the same 371 // provider on a different platform. 372 hashes []getproviders.Hash 373 } 374 375 // Provider returns the address of the provider this lock applies to. 376 func (l *ProviderLock) Provider() addrs.Provider { 377 return l.addr 378 } 379 380 // Version returns the currently-selected version for the corresponding provider. 381 func (l *ProviderLock) Version() getproviders.Version { 382 return l.version 383 } 384 385 // VersionConstraints returns the version constraints that were recorded as 386 // being used to choose the version returned by Version. 387 // 388 // These version constraints are not authoritative for future selections and 389 // are included only so OpenTofu can detect if the constraints in 390 // configuration have changed since a selection was made, and thus hint to the 391 // user that they may need to run tofu init -upgrade to apply the new 392 // constraints. 393 func (l *ProviderLock) VersionConstraints() getproviders.VersionConstraints { 394 return l.versionConstraints 395 } 396 397 // AllHashes returns all of the package hashes that were recorded when this 398 // lock was created. If no hashes were recorded for that platform, the result 399 // is a zero-length slice. 400 // 401 // If your intent is to verify a package against the recorded hashes, use 402 // PreferredHashes to get only the hashes which the current version 403 // of OpenTofu considers the strongest of the available hashing schemes, one 404 // of which must match in order for verification to be considered successful. 405 // 406 // Do not modify the backing array of the returned slice. 407 func (l *ProviderLock) AllHashes() []getproviders.Hash { 408 return l.hashes 409 } 410 411 // ContainsAll returns true if the hashes in this ProviderLock contains 412 // all the hashes in the target. 413 // 414 // This function assumes the hashes are in each ProviderLock are sorted. 415 // If the ProviderLock was created by the NewProviderLock constructor then 416 // the hashes are guaranteed to be sorted. 417 func (l *ProviderLock) ContainsAll(target *ProviderLock) bool { 418 if target == nil || len(target.hashes) == 0 { 419 return true 420 } 421 422 targetIndex := 0 423 for ix := 0; ix < len(l.hashes); ix++ { 424 if l.hashes[ix] == target.hashes[targetIndex] { 425 targetIndex++ 426 427 if targetIndex >= len(target.hashes) { 428 return true 429 } 430 } 431 } 432 return false 433 } 434 435 // PreferredHashes returns a filtered version of the AllHashes return value 436 // which includes only the strongest of the availabile hash schemes, in 437 // case legacy hash schemes are deprecated over time but still supported for 438 // upgrade purposes. 439 // 440 // At least one of the given hashes must match for a package to be considered 441 // valud. 442 func (l *ProviderLock) PreferredHashes() []getproviders.Hash { 443 return getproviders.PreferredHashes(l.hashes) 444 }