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