github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/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 // TODO: In future we'll also have module locks, but the design of that 22 // still needs some more work and we're deferring that to get the 23 // provider locking capability out sooner, because it's more common to 24 // directly depend on providers maintained outside your organization than 25 // modules maintained outside your organization. 26 27 // sources is a copy of the map of source buffers produced by the HCL 28 // parser during loading, which we retain only so that the caller can 29 // use it to produce source code snippets in error messages. 30 sources map[string][]byte 31 } 32 33 // NewLocks constructs and returns a new Locks object that initially contains 34 // no locks at all. 35 func NewLocks() *Locks { 36 return &Locks{ 37 providers: make(map[addrs.Provider]*ProviderLock), 38 39 // no "sources" here, because that's only for locks objects loaded 40 // from files. 41 } 42 } 43 44 // Provider returns the stored lock for the given provider, or nil if that 45 // provider currently has no lock. 46 func (l *Locks) Provider(addr addrs.Provider) *ProviderLock { 47 return l.providers[addr] 48 } 49 50 // AllProviders returns a map describing all of the provider locks in the 51 // receiver. 52 func (l *Locks) AllProviders() map[addrs.Provider]*ProviderLock { 53 // We return a copy of our internal map so that future calls to 54 // SetProvider won't modify the map we're returning, or vice-versa. 55 ret := make(map[addrs.Provider]*ProviderLock, len(l.providers)) 56 for k, v := range l.providers { 57 ret[k] = v 58 } 59 return ret 60 } 61 62 // SetProvider creates a new lock or replaces the existing lock for the given 63 // provider. 64 // 65 // SetProvider returns the newly-created provider lock object, which 66 // invalidates any ProviderLock object previously returned from Provider or 67 // SetProvider for the given provider address. 68 // 69 // The ownership of the backing array for the slice of hashes passes to this 70 // function, and so the caller must not read or write that backing array after 71 // calling SetProvider. 72 // 73 // Only lockable providers can be passed to this method. If you pass a 74 // non-lockable provider address then this function will panic. Use 75 // function ProviderIsLockable to determine whether a particular provider 76 // should participate in the version locking mechanism. 77 func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock { 78 if !ProviderIsLockable(addr) { 79 panic(fmt.Sprintf("Locks.SetProvider with non-lockable provider %s", addr)) 80 } 81 82 new := NewProviderLock(addr, version, constraints, hashes) 83 l.providers[new.addr] = new 84 return new 85 } 86 87 // NewProviderLock creates a new ProviderLock object that isn't associated 88 // with any Locks object. 89 // 90 // This is here primarily for testing. Most callers should use Locks.SetProvider 91 // to construct a new provider lock and insert it into a Locks object at the 92 // same time. 93 // 94 // The ownership of the backing array for the slice of hashes passes to this 95 // function, and so the caller must not read or write that backing array after 96 // calling NewProviderLock. 97 // 98 // Only lockable providers can be passed to this method. If you pass a 99 // non-lockable provider address then this function will panic. Use 100 // function ProviderIsLockable to determine whether a particular provider 101 // should participate in the version locking mechanism. 102 func NewProviderLock(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock { 103 if !ProviderIsLockable(addr) { 104 panic(fmt.Sprintf("Locks.NewProviderLock with non-lockable provider %s", addr)) 105 } 106 107 // Normalize the hashes into lexical order so that we can do straightforward 108 // equality tests between different locks for the same provider. The 109 // hashes are logically a set, so the given order is insignificant. 110 sort.Slice(hashes, func(i, j int) bool { 111 return string(hashes[i]) < string(hashes[j]) 112 }) 113 114 // This is a slightly-tricky in-place deduping to avoid unnecessarily 115 // allocating a new array in the common case where there are no duplicates: 116 // we iterate over "hashes" at the same time as appending to another slice 117 // with the same backing array, relying on the fact that deduping can only 118 // _skip_ elements from the input, and will never generate additional ones 119 // that would cause the writer to get ahead of the reader. This also 120 // assumes that we already sorted the items, which means that any duplicates 121 // will be consecutive in the sequence. 122 dedupeHashes := hashes[:0] 123 prevHash := getproviders.NilHash 124 for _, hash := range hashes { 125 if hash != prevHash { 126 dedupeHashes = append(dedupeHashes, hash) 127 prevHash = hash 128 } 129 } 130 131 return &ProviderLock{ 132 addr: addr, 133 version: version, 134 versionConstraints: constraints, 135 hashes: dedupeHashes, 136 } 137 } 138 139 // ProviderIsLockable returns true if the given provider is eligible for 140 // version locking. 141 // 142 // Currently, all providers except builtin and legacy providers are eligible 143 // for locking. 144 func ProviderIsLockable(addr addrs.Provider) bool { 145 return !(addr.IsBuiltIn() || addr.IsLegacy()) 146 } 147 148 // Sources returns the source code of the file the receiver was generated from, 149 // or an empty map if the receiver wasn't generated from a file. 150 // 151 // This return type matches the one expected by HCL diagnostics printers to 152 // produce source code snapshots, which is the only intended use for this 153 // method. 154 func (l *Locks) Sources() map[string][]byte { 155 return l.sources 156 } 157 158 // Equal returns true if the given Locks represents the same information as 159 // the receiver. 160 // 161 // Equal explicitly _does not_ consider the equality of version constraints 162 // in the saved locks, because those are saved only as hints to help the UI 163 // explain what's changed between runs, and are never used as part of 164 // dependency installation decisions. 165 func (l *Locks) Equal(other *Locks) bool { 166 if len(l.providers) != len(other.providers) { 167 return false 168 } 169 for addr, thisLock := range l.providers { 170 otherLock, ok := other.providers[addr] 171 if !ok { 172 return false 173 } 174 175 if thisLock.addr != otherLock.addr { 176 // It'd be weird to get here because we already looked these up 177 // by address above. 178 return false 179 } 180 if thisLock.version != otherLock.version { 181 // Equality rather than "Version.Same" because changes to the 182 // build metadata are significant for the purpose of this function: 183 // it's a different package even if it has the same precedence. 184 return false 185 } 186 187 // Although "hashes" is declared as a slice, it's logically an 188 // unordered set. However, we normalize the slice of hashes when 189 // recieving it in NewProviderLock, so we can just do a simple 190 // item-by-item equality test here. 191 if len(thisLock.hashes) != len(otherLock.hashes) { 192 return false 193 } 194 for i := range thisLock.hashes { 195 if thisLock.hashes[i] != otherLock.hashes[i] { 196 return false 197 } 198 } 199 } 200 // We don't need to worry about providers that are in "other" but not 201 // in the receiver, because we tested the lengths being equal above. 202 203 return true 204 } 205 206 // EqualProviderAddress returns true if the given Locks have the same provider 207 // address as the receiver. This doesn't check version and hashes. 208 func (l *Locks) EqualProviderAddress(other *Locks) bool { 209 if len(l.providers) != len(other.providers) { 210 return false 211 } 212 213 for addr := range l.providers { 214 _, ok := other.providers[addr] 215 if !ok { 216 return false 217 } 218 } 219 220 return true 221 } 222 223 // Empty returns true if the given Locks object contains no actual locks. 224 // 225 // UI code might wish to use this to distinguish a lock file being 226 // written for the first time from subsequent updates to that lock file. 227 func (l *Locks) Empty() bool { 228 return len(l.providers) == 0 229 } 230 231 // DeepCopy creates a new Locks that represents the same information as the 232 // receiver but does not share memory for any parts of the structure that. 233 // are mutable through methods on Locks. 234 // 235 // Note that this does _not_ create deep copies of parts of the structure 236 // that are technically mutable but are immutable by convention, such as the 237 // array underlying the slice of version constraints. Callers may mutate the 238 // resulting data structure only via the direct methods of Locks. 239 func (l *Locks) DeepCopy() *Locks { 240 ret := NewLocks() 241 for addr, lock := range l.providers { 242 var hashes []getproviders.Hash 243 if len(lock.hashes) > 0 { 244 hashes = make([]getproviders.Hash, len(lock.hashes)) 245 copy(hashes, lock.hashes) 246 } 247 ret.SetProvider(addr, lock.version, lock.versionConstraints, hashes) 248 } 249 return ret 250 } 251 252 // ProviderLock represents lock information for a specific provider. 253 type ProviderLock struct { 254 // addr is the address of the provider this lock applies to. 255 addr addrs.Provider 256 257 // version is the specific version that was previously selected, while 258 // versionConstraints is the constraint that was used to make that 259 // selection, which we can potentially use to hint to run 260 // e.g. terraform init -upgrade if a user has changed a version 261 // constraint but the previous selection still remains valid. 262 // "version" is therefore authoritative, while "versionConstraints" is 263 // just for a UI hint and not used to make any real decisions. 264 version getproviders.Version 265 versionConstraints getproviders.VersionConstraints 266 267 // hashes contains zero or more hashes of packages or package contents 268 // for the package associated with the selected version across all of 269 // the supported platforms. 270 // 271 // hashes can contain a mixture of hashes in different formats to support 272 // changes over time. The new-style hash format is to have a string 273 // starting with "h" followed by a version number and then a colon, like 274 // "h1:" for the first hash format version. Other hash versions following 275 // this scheme may come later. These versioned hash schemes are implemented 276 // in the getproviders package; for example, "h1:" is implemented in 277 // getproviders.HashV1 . 278 // 279 // There is also a legacy hash format which is just a lowercase-hex-encoded 280 // SHA256 hash of the official upstream .zip file for the selected version. 281 // We'll allow as that a stop-gap until we can upgrade Terraform Registry 282 // to support the new scheme, but is non-ideal because we can verify it only 283 // when we have the original .zip file exactly; we can't verify a local 284 // directory containing the unpacked contents of that .zip file. 285 // 286 // We ideally want to populate hashes for all available platforms at 287 // once, by referring to the signed checksums file in the upstream 288 // registry. In that ideal case it's possible to later work with the same 289 // configuration on a different platform while still verifying the hashes. 290 // However, installation from any method other than an origin registry 291 // means we can only populate the hash for the current platform, and so 292 // it won't be possible to verify a subsequent installation of the same 293 // provider on a different platform. 294 hashes []getproviders.Hash 295 } 296 297 // Provider returns the address of the provider this lock applies to. 298 func (l *ProviderLock) Provider() addrs.Provider { 299 return l.addr 300 } 301 302 // Version returns the currently-selected version for the corresponding provider. 303 func (l *ProviderLock) Version() getproviders.Version { 304 return l.version 305 } 306 307 // VersionConstraints returns the version constraints that were recorded as 308 // being used to choose the version returned by Version. 309 // 310 // These version constraints are not authoritative for future selections and 311 // are included only so Terraform can detect if the constraints in 312 // configuration have changed since a selection was made, and thus hint to the 313 // user that they may need to run terraform init -upgrade to apply the new 314 // constraints. 315 func (l *ProviderLock) VersionConstraints() getproviders.VersionConstraints { 316 return l.versionConstraints 317 } 318 319 // AllHashes returns all of the package hashes that were recorded when this 320 // lock was created. If no hashes were recorded for that platform, the result 321 // is a zero-length slice. 322 // 323 // If your intent is to verify a package against the recorded hashes, use 324 // PreferredHashes to get only the hashes which the current version 325 // of Terraform considers the strongest of the available hashing schemes, one 326 // of which must match in order for verification to be considered successful. 327 // 328 // Do not modify the backing array of the returned slice. 329 func (l *ProviderLock) AllHashes() []getproviders.Hash { 330 return l.hashes 331 } 332 333 // PreferredHashes returns a filtered version of the AllHashes return value 334 // which includes only the strongest of the availabile hash schemes, in 335 // case legacy hash schemes are deprecated over time but still supported for 336 // upgrade purposes. 337 // 338 // At least one of the given hashes must match for a package to be considered 339 // valud. 340 func (l *ProviderLock) PreferredHashes() []getproviders.Hash { 341 return getproviders.PreferredHashes(l.hashes) 342 }