github.com/opentofu/opentofu@v1.7.1/internal/providercache/installer.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 providercache 7 8 import ( 9 "context" 10 "fmt" 11 "log" 12 "sort" 13 "strings" 14 15 "github.com/apparentlymart/go-versions/versions" 16 17 "github.com/opentofu/opentofu/internal/addrs" 18 copydir "github.com/opentofu/opentofu/internal/copy" 19 "github.com/opentofu/opentofu/internal/depsfile" 20 "github.com/opentofu/opentofu/internal/getproviders" 21 ) 22 23 // Installer is the main type in this package, representing a provider installer 24 // with a particular configuration-specific cache directory and an optional 25 // global cache directory. 26 type Installer struct { 27 // targetDir is the cache directory we're ultimately aiming to get the 28 // requested providers installed into. 29 targetDir *Dir 30 31 // source is the provider source that the installer will use to discover 32 // what provider versions are available for installation and to 33 // find the source locations for any versions that are not already 34 // available via one of the cache directories. 35 source getproviders.Source 36 37 // globalCacheDir is an optional additional directory that will, if 38 // provided, be treated as a read-through cache when retrieving new 39 // provider versions. That is, new packages are fetched into this 40 // directory first and then linked into targetDir, which allows sharing 41 // both the disk space and the download time for a particular provider 42 // version between different configurations on the same system. 43 globalCacheDir *Dir 44 45 // globalCacheDirMayBreakDependencyLockFile allows a temporary exception to 46 // the rule that an entry in globalCacheDir can normally only be used if 47 // its validity is already confirmed by an entry in the dependency lock 48 // file. 49 globalCacheDirMayBreakDependencyLockFile bool 50 51 // builtInProviderTypes is an optional set of types that should be 52 // considered valid to appear in the special terraform.io/builtin/... 53 // namespace, which we use for providers that are built in to OpenTofu 54 // and thus do not need any separate installation step. 55 builtInProviderTypes []string 56 57 // unmanagedProviderTypes is a set of provider addresses that should be 58 // considered implemented, but that OpenTofu does not manage the 59 // lifecycle for, and therefore does not need to worry about the 60 // installation of. 61 unmanagedProviderTypes map[addrs.Provider]struct{} 62 } 63 64 // NewInstaller constructs and returns a new installer with the given target 65 // directory and provider source. 66 // 67 // A newly-created installer does not have a global cache directory configured, 68 // but a caller can make a follow-up call to SetGlobalCacheDir to provide 69 // one prior to taking any installation actions. 70 // 71 // The target directory MUST NOT also be an input consulted by the given source, 72 // or the result is undefined. 73 func NewInstaller(targetDir *Dir, source getproviders.Source) *Installer { 74 return &Installer{ 75 targetDir: targetDir, 76 source: source, 77 } 78 } 79 80 // Clone returns a new Installer which has the a new target directory but 81 // the same optional global cache directory, the same installation sources, 82 // and the same built-in/unmanaged providers. The result can be mutated further 83 // using the various setter methods without affecting the original. 84 func (i *Installer) Clone(targetDir *Dir) *Installer { 85 // For now all of our setter methods just overwrite field values in 86 // their entirety, rather than mutating things on the other side of 87 // the shared pointers, and so we can safely just shallow-copy the 88 // root. We might need to be more careful here if in future we add 89 // methods that allow deeper mutations through the stored pointers. 90 ret := *i 91 ret.targetDir = targetDir 92 return &ret 93 } 94 95 // ProviderSource returns the getproviders.Source that the installer would 96 // use for installing any new providers. 97 func (i *Installer) ProviderSource() getproviders.Source { 98 return i.source 99 } 100 101 // SetGlobalCacheDir activates a second tier of caching for the receiving 102 // installer, with the given directory used as a read-through cache for 103 // installation operations that need to retrieve new packages. 104 // 105 // The global cache directory for an installer must never be the same as its 106 // target directory, and must not be used as one of its provider sources. 107 // If these overlap then undefined behavior will result. 108 func (i *Installer) SetGlobalCacheDir(cacheDir *Dir) { 109 // A little safety check to catch straightforward mistakes where the 110 // directories overlap. Better to panic early than to do 111 // possibly-distructive actions on the cache directory downstream. 112 if same, err := copydir.SameFile(i.targetDir.baseDir, cacheDir.baseDir); err == nil && same { 113 panic(fmt.Sprintf("global cache directory %s must not match the installation target directory %s", cacheDir.baseDir, i.targetDir.baseDir)) 114 } 115 i.globalCacheDir = cacheDir 116 } 117 118 // SetGlobalCacheDirMayBreakDependencyLockFile activates or deactivates our 119 // temporary exception to the rule that the global cache directory can be used 120 // only when entries are confirmed by existing entries in the dependency lock 121 // file. 122 // 123 // If this is set then if we install a provider for the first time from the 124 // cache then the dependency lock file will include only the checksum from 125 // the package in the global cache, which means the lock file won't be portable 126 // to OpenTofu running on another operating system or CPU architecture. 127 func (i *Installer) SetGlobalCacheDirMayBreakDependencyLockFile(mayBreak bool) { 128 i.globalCacheDirMayBreakDependencyLockFile = mayBreak 129 } 130 131 // HasGlobalCacheDir returns true if someone has previously called 132 // SetGlobalCacheDir to configure a global cache directory for this installer. 133 func (i *Installer) HasGlobalCacheDir() bool { 134 return i.globalCacheDir != nil 135 } 136 137 // SetBuiltInProviderTypes tells the receiver to consider the type names in the 138 // given slice to be valid as providers in the special special 139 // terraform.io/builtin/... namespace that we use for providers that are 140 // built in to OpenTofu and thus do not need a separate installation step. 141 // 142 // If a caller requests installation of a provider in that namespace, the 143 // installer will treat it as a no-op if its name exists in this list, but 144 // will produce an error if it does not. 145 // 146 // The default, if this method isn't called, is for there to be no valid 147 // builtin providers. 148 // 149 // Do not modify the buffer under the given slice after passing it to this 150 // method. 151 func (i *Installer) SetBuiltInProviderTypes(types []string) { 152 i.builtInProviderTypes = types 153 } 154 155 // SetUnmanagedProviderTypes tells the receiver to consider the providers 156 // indicated by the passed addrs.Providers as unmanaged. OpenTofu does not 157 // need to control the lifecycle of these providers, and they are assumed to be 158 // running already when OpenTofu is started. Because these are essentially 159 // processes, not binaries, OpenTofu will not do any work to ensure presence 160 // or versioning of these binaries. 161 func (i *Installer) SetUnmanagedProviderTypes(types map[addrs.Provider]struct{}) { 162 i.unmanagedProviderTypes = types 163 } 164 165 // EnsureProviderVersions compares the given provider requirements with what 166 // is already available in the installer's target directory and then takes 167 // appropriate installation actions to ensure that suitable packages 168 // are available in the target cache directory. 169 // 170 // The given mode modifies how the operation will treat providers that already 171 // have acceptable versions available in the target cache directory. See the 172 // documentation for InstallMode and the InstallMode values for more 173 // information. 174 // 175 // The given context can be used to cancel the overall installation operation 176 // (causing any operations in progress to fail with an error), and can also 177 // include an InstallerEvents value for optional intermediate progress 178 // notifications. 179 // 180 // If a given InstallerEvents subscribes to notifications about installation 181 // failures then those notifications will be redundant with the ones included 182 // in the final returned error value so callers should show either one or the 183 // other, and not both. 184 func (i *Installer) EnsureProviderVersions(ctx context.Context, locks *depsfile.Locks, reqs getproviders.Requirements, mode InstallMode) (*depsfile.Locks, error) { 185 errs := map[addrs.Provider]error{} 186 evts := installerEventsForContext(ctx) 187 188 // We'll work with a copy of the given locks, so we can modify it and 189 // return the updated locks without affecting the caller's object. 190 // We'll add, replace, or remove locks in here during our work so that the 191 // final locks file reflects what the installer has selected. 192 locks = locks.DeepCopy() 193 194 if cb := evts.PendingProviders; cb != nil { 195 cb(reqs) 196 } 197 198 // Step 1: Which providers might we need to fetch a new version of? 199 // This produces the subset of requirements we need to ask the provider 200 // source about. If we're in the normal (non-upgrade) mode then we'll 201 // just ask the source to confirm the continued existence of what 202 // was locked, or otherwise we'll find the newest version matching the 203 // configured version constraint. 204 mightNeed := map[addrs.Provider]getproviders.VersionSet{} 205 locked := map[addrs.Provider]bool{} 206 for provider, versionConstraints := range reqs { 207 if provider.IsBuiltIn() { 208 // Built in providers do not require installation but we'll still 209 // verify that the requested provider name is valid. 210 valid := false 211 for _, name := range i.builtInProviderTypes { 212 if name == provider.Type { 213 valid = true 214 break 215 } 216 } 217 var err error 218 if valid { 219 if len(versionConstraints) == 0 { 220 // Other than reporting an event for the outcome of this 221 // provider, we'll do nothing else with it: it's just 222 // automatically available for use. 223 if cb := evts.BuiltInProviderAvailable; cb != nil { 224 cb(provider) 225 } 226 } else { 227 // A built-in provider is not permitted to have an explicit 228 // version constraint, because we can only use the version 229 // that is built in to the current OpenTofu release. 230 err = fmt.Errorf("built-in providers do not support explicit version constraints") 231 } 232 } else { 233 err = fmt.Errorf("this OpenTofu release has no built-in provider named %q", provider.Type) 234 } 235 if err != nil { 236 errs[provider] = err 237 if cb := evts.BuiltInProviderFailure; cb != nil { 238 cb(provider, err) 239 } 240 } 241 continue 242 } 243 if _, ok := i.unmanagedProviderTypes[provider]; ok { 244 // unmanaged providers do not require installation 245 continue 246 } 247 acceptableVersions := versions.MeetingConstraints(versionConstraints) 248 if !mode.forceQueryAllProviders() { 249 // If we're not forcing potential changes of version then an 250 // existing selection from the lock file takes priority over 251 // the currently-configured version constraints. 252 if lock := locks.Provider(provider); lock != nil { 253 if !acceptableVersions.Has(lock.Version()) { 254 err := fmt.Errorf( 255 "locked provider %s %s does not match configured version constraint %s; must use tofu init -upgrade to allow selection of new versions", 256 provider, lock.Version(), getproviders.VersionConstraintsString(versionConstraints), 257 ) 258 errs[provider] = err 259 // This is a funny case where we're returning an error 260 // before we do any querying at all. To keep the event 261 // stream consistent without introducing an extra event 262 // type, we'll emit an artificial QueryPackagesBegin for 263 // this provider before we indicate that it failed using 264 // QueryPackagesFailure. 265 if cb := evts.QueryPackagesBegin; cb != nil { 266 cb(provider, versionConstraints, true) 267 } 268 if cb := evts.QueryPackagesFailure; cb != nil { 269 cb(provider, err) 270 } 271 continue 272 } 273 acceptableVersions = versions.Only(lock.Version()) 274 locked[provider] = true 275 } 276 } 277 mightNeed[provider] = acceptableVersions 278 } 279 280 // Step 2: Query the provider source for each of the providers we selected 281 // in the first step and select the latest available version that is 282 // in the set of acceptable versions. 283 // 284 // This produces a set of packages to install to our cache in the next step. 285 need := map[addrs.Provider]getproviders.Version{} 286 NeedProvider: 287 for provider, acceptableVersions := range mightNeed { 288 if err := ctx.Err(); err != nil { 289 // If our context has been cancelled or reached a timeout then 290 // we'll abort early, because subsequent operations against 291 // that context will fail immediately anyway. 292 return nil, err 293 } 294 295 if cb := evts.QueryPackagesBegin; cb != nil { 296 cb(provider, reqs[provider], locked[provider]) 297 } 298 available, warnings, err := i.source.AvailableVersions(ctx, provider) 299 if err != nil { 300 errs[provider] = err 301 if cb := evts.QueryPackagesFailure; cb != nil { 302 cb(provider, err) 303 } 304 // We will take no further actions for this provider. 305 continue 306 } 307 if len(warnings) > 0 { 308 if cb := evts.QueryPackagesWarning; cb != nil { 309 cb(provider, warnings) 310 } 311 } 312 available.Sort() // put the versions in increasing order of precedence 313 for i := len(available) - 1; i >= 0; i-- { // walk backwards to consider newer versions first 314 if acceptableVersions.Has(available[i]) { 315 need[provider] = available[i] 316 if cb := evts.QueryPackagesSuccess; cb != nil { 317 cb(provider, available[i]) 318 } 319 continue NeedProvider 320 } 321 } 322 // If we get here then the source has no packages that meet the given 323 // version constraint, which we model as a query error. 324 if locked[provider] { 325 // This situation should be a rare one: it suggests that a 326 // version was previously available but was yanked for some 327 // reason. 328 lock := locks.Provider(provider) 329 err = fmt.Errorf("the previously-selected version %s is no longer available", lock.Version()) 330 } else { 331 err = fmt.Errorf("no available releases match the given constraints %s", getproviders.VersionConstraintsString(reqs[provider])) 332 log.Printf("[DEBUG] %s", err.Error()) 333 log.Printf("[DEBUG] Available releases: %s", available) 334 } 335 errs[provider] = err 336 if cb := evts.QueryPackagesFailure; cb != nil { 337 cb(provider, err) 338 } 339 } 340 341 // Step 3: For each provider version we've decided we need to install, 342 // install its package into our target cache (possibly via the global cache). 343 authResults := map[addrs.Provider]*getproviders.PackageAuthenticationResult{} // record auth results for all successfully fetched providers 344 targetPlatform := i.targetDir.targetPlatform // we inherit this to behave correctly in unit tests 345 for provider, version := range need { 346 if err := ctx.Err(); err != nil { 347 // If our context has been cancelled or reached a timeout then 348 // we'll abort early, because subsequent operations against 349 // that context will fail immediately anyway. 350 return nil, err 351 } 352 353 lock := locks.Provider(provider) 354 var preferredHashes []getproviders.Hash 355 if lock != nil && lock.Version() == version { // hash changes are expected if the version is also changing 356 preferredHashes = lock.PreferredHashes() 357 } 358 359 // If our target directory already has the provider version that fulfills the lock file, carry on 360 if installed := i.targetDir.ProviderVersion(provider, version); installed != nil { 361 if len(preferredHashes) > 0 { 362 if matches, _ := installed.MatchesAnyHash(preferredHashes); matches { 363 if cb := evts.ProviderAlreadyInstalled; cb != nil { 364 cb(provider, version) 365 } 366 continue 367 } 368 } 369 } 370 371 if i.globalCacheDir != nil { 372 // Step 3a: If our global cache already has this version available then 373 // we'll just link it in. 374 if cached := i.globalCacheDir.ProviderVersion(provider, version); cached != nil { 375 // An existing cache entry is only an acceptable choice 376 // if there is already a lock file entry for this provider 377 // and the cache entry matches its checksums. 378 // 379 // If there was no lock file entry at all then we need to 380 // install the package for real so that we can lock as complete 381 // as possible a set of checksums for all of this provider's 382 // packages. 383 // 384 // If there was a lock file entry but the cache doesn't match 385 // it then we assume that the lock file checksums were only 386 // partially populated (e.g. from a local mirror where we can 387 // only see one package to checksum it) and so we'll fetch 388 // from upstream to see if the origin can give us a package 389 // that _does_ match. This might still not work out, but if 390 // it does then it allows us to avoid returning a checksum 391 // mismatch error. 392 acceptablePackage := false 393 if len(preferredHashes) != 0 { 394 var err error 395 acceptablePackage, err = cached.MatchesAnyHash(preferredHashes) 396 if err != nil { 397 // If we can't calculate the checksum for the cached 398 // package then we'll just treat it as a checksum failure. 399 acceptablePackage = false 400 } 401 } 402 403 if !acceptablePackage && i.globalCacheDirMayBreakDependencyLockFile { 404 // The "may break dependency lock file" setting effectively 405 // means that we'll accept any matching package that's 406 // already in the cache, regardless of whether it matches 407 // what's in the dependency lock file. 408 // 409 // That means two less-ideal situations might occur: 410 // - If this provider is not currently tracked in the lock 411 // file at all then after installation the lock file will 412 // only accept the package that was already present in 413 // the cache as a valid checksum. That means the generated 414 // lock file won't be portable to other operating systems 415 // or CPU architectures. 416 // - If the provider _is_ currently tracked in the lock file 417 // but the checksums there don't match what was in the 418 // cache then the LinkFromOtherCache call below will 419 // fail with a checksum error, and the user will need to 420 // either manually remove the entry from the lock file 421 // or remove the mismatching item from the cache, 422 // depending on which of these they prefer to use as the 423 // source of truth for the expected contents of the 424 // package. 425 // 426 // If the lock file already includes this provider and the 427 // cache entry matches one of the locked checksums then 428 // there's no problem, but in that case we wouldn't enter 429 // this branch because acceptablePackage would already be 430 // true from the check above. 431 log.Printf( 432 "[WARN] plugin_cache_may_break_dependency_lock_file: Using global cache dir package for %s v%s even though it doesn't match this configuration's dependency lock file", 433 provider.String(), version.String(), 434 ) 435 acceptablePackage = true 436 } 437 438 // TODO: Should we emit an event through the events object 439 // for "there was an entry in the cache but we ignored it 440 // because the checksum didn't match"? We can't use 441 // LinkFromCacheFailure in that case because this isn't a 442 // failure. For now we'll just be quiet about it. 443 444 if acceptablePackage { 445 if cb := evts.LinkFromCacheBegin; cb != nil { 446 cb(provider, version, i.globalCacheDir.baseDir) 447 } 448 if _, err := cached.ExecutableFile(); err != nil { 449 err := fmt.Errorf("provider binary not found: %w", err) 450 errs[provider] = err 451 if cb := evts.LinkFromCacheFailure; cb != nil { 452 cb(provider, version, err) 453 } 454 continue 455 } 456 457 err := i.targetDir.LinkFromOtherCache(cached, preferredHashes) 458 if err != nil { 459 errs[provider] = err 460 if cb := evts.LinkFromCacheFailure; cb != nil { 461 cb(provider, version, err) 462 } 463 continue 464 } 465 // We'll fetch what we just linked to make sure it actually 466 // did show up there. 467 new := i.targetDir.ProviderVersion(provider, version) 468 if new == nil { 469 err := fmt.Errorf("after linking %s from provider cache at %s it is still not detected in the target directory; this is a bug in OpenTofu", provider, i.globalCacheDir.baseDir) 470 errs[provider] = err 471 if cb := evts.LinkFromCacheFailure; cb != nil { 472 cb(provider, version, err) 473 } 474 continue 475 } 476 477 // The LinkFromOtherCache call above should've verified that 478 // the package matches one of the hashes previously recorded, 479 // if any. We'll now augment those hashes with one freshly 480 // calculated from the package we just linked, which allows 481 // the lock file to gradually transition to recording newer hash 482 // schemes when they become available. 483 var priorHashes []getproviders.Hash 484 if lock != nil && lock.Version() == version { 485 // If the version we're installing is identical to the 486 // one we previously locked then we'll keep all of the 487 // hashes we saved previously and add to it. Otherwise 488 // we'll be starting fresh, because each version has its 489 // own set of packages and thus its own hashes. 490 priorHashes = append(priorHashes, preferredHashes...) 491 492 // NOTE: The behavior here is unfortunate when a particular 493 // provider version was already cached on the first time 494 // the current configuration requested it, because that 495 // means we don't currently get the opportunity to fetch 496 // and verify the checksums for the new package from 497 // upstream. That's currently unavoidable because upstream 498 // checksums are in the "ziphash" format and so we can't 499 // verify them against our cache directory's unpacked 500 // packages: we'd need to go fetch the package from the 501 // origin and compare against it, which would defeat the 502 // purpose of the global cache. 503 // 504 // If we fetch from upstream on the first encounter with 505 // a particular provider then we'll end up in the other 506 // codepath below where we're able to also include the 507 // checksums from the origin registry. 508 } 509 newHash, err := cached.Hash() 510 if err != nil { 511 err := fmt.Errorf("after linking %s from provider cache at %s, failed to compute a checksum for it: %w", provider, i.globalCacheDir.baseDir, err) 512 errs[provider] = err 513 if cb := evts.LinkFromCacheFailure; cb != nil { 514 cb(provider, version, err) 515 } 516 continue 517 } 518 // The hashes slice gets deduplicated in the lock file 519 // implementation, so we don't worry about potentially 520 // creating a duplicate here. 521 var newHashes []getproviders.Hash 522 newHashes = append(newHashes, priorHashes...) 523 newHashes = append(newHashes, newHash) 524 locks.SetProvider(provider, version, reqs[provider], newHashes) 525 if cb := evts.ProvidersLockUpdated; cb != nil { 526 // We want to ensure that newHash and priorHashes are 527 // sorted. newHash is a single value, so it's definitely 528 // sorted. priorHashes are pulled from the lock file, so 529 // are also already sorted. 530 cb(provider, version, []getproviders.Hash{newHash}, nil, priorHashes) 531 } 532 533 if cb := evts.LinkFromCacheSuccess; cb != nil { 534 cb(provider, version, new.PackageDir) 535 } 536 continue // Don't need to do full install, then. 537 } 538 } 539 } 540 541 // Step 3b: Get the package metadata for the selected version from our 542 // provider source. 543 // 544 // This is the step where we might detect and report that the provider 545 // isn't available for the current platform. 546 if cb := evts.FetchPackageMeta; cb != nil { 547 cb(provider, version) 548 } 549 meta, err := i.source.PackageMeta(ctx, provider, version, targetPlatform) 550 if err != nil { 551 errs[provider] = err 552 if cb := evts.FetchPackageFailure; cb != nil { 553 cb(provider, version, err) 554 } 555 continue 556 } 557 558 // Step 3c: Retrieve the package indicated by the metadata we received, 559 // either directly into our target directory or via the global cache 560 // directory. 561 if cb := evts.FetchPackageBegin; cb != nil { 562 cb(provider, version, meta.Location) 563 } 564 var installTo, linkTo *Dir 565 if i.globalCacheDir != nil { 566 installTo = i.globalCacheDir 567 linkTo = i.targetDir 568 } else { 569 installTo = i.targetDir 570 linkTo = nil // no linking needed 571 } 572 573 allowedHashes := preferredHashes 574 if mode.forceInstallChecksums() { 575 allowedHashes = []getproviders.Hash{} 576 } 577 578 authResult, err := installTo.InstallPackage(ctx, meta, allowedHashes) 579 if err != nil { 580 // TODO: Consider retrying for certain kinds of error that seem 581 // likely to be transient. For now, we just treat all errors equally. 582 errs[provider] = err 583 if cb := evts.FetchPackageFailure; cb != nil { 584 cb(provider, version, err) 585 } 586 continue 587 } 588 new := installTo.ProviderVersion(provider, version) 589 if new == nil { 590 err := fmt.Errorf("after installing %s it is still not detected in %s; this is a bug in OpenTofu", provider, installTo.BasePath()) 591 errs[provider] = err 592 if cb := evts.FetchPackageFailure; cb != nil { 593 cb(provider, version, err) 594 } 595 continue 596 } 597 if _, err := new.ExecutableFile(); err != nil { 598 err := fmt.Errorf("provider binary not found: %w", err) 599 errs[provider] = err 600 if cb := evts.FetchPackageFailure; cb != nil { 601 cb(provider, version, err) 602 } 603 continue 604 } 605 if linkTo != nil { 606 // We skip emitting the "LinkFromCache..." events here because 607 // it's simpler for the caller to treat them as mutually exclusive. 608 // We can just subsume the linking step under the "FetchPackage..." 609 // series here (and that's why we use FetchPackageFailure below). 610 // We also don't do a hash check here because we already did that 611 // as part of the installTo.InstallPackage call above. 612 err := linkTo.LinkFromOtherCache(new, nil) 613 if err != nil { 614 errs[provider] = err 615 if cb := evts.FetchPackageFailure; cb != nil { 616 cb(provider, version, err) 617 } 618 continue 619 } 620 621 // We should now also find the package in the linkTo dir, which 622 // gives us the final value of "new" where the path points in to 623 // the true target directory, rather than possibly the global 624 // cache directory. 625 new = linkTo.ProviderVersion(provider, version) 626 if new == nil { 627 err := fmt.Errorf("after installing %s it is still not detected in %s; this is a bug in OpenTofu", provider, linkTo.BasePath()) 628 errs[provider] = err 629 if cb := evts.FetchPackageFailure; cb != nil { 630 cb(provider, version, err) 631 } 632 continue 633 } 634 if _, err := new.ExecutableFile(); err != nil { 635 err := fmt.Errorf("provider binary not found: %w", err) 636 errs[provider] = err 637 if cb := evts.FetchPackageFailure; cb != nil { 638 cb(provider, version, err) 639 } 640 continue 641 } 642 } 643 authResults[provider] = authResult 644 645 // The InstallPackage call above should've verified that 646 // the package matches one of the hashes previously recorded, 647 // if any. We'll now augment those hashes with a new set populated 648 // with the hashes returned by the upstream source and from the 649 // package we've just installed, which allows the lock file to 650 // gradually transition to newer hash schemes when they become 651 // available. 652 // 653 // This is assuming that if a package matches both a hash we saw before 654 // _and_ a new hash then the new hash is a valid substitute for 655 // the previous hash. 656 // 657 // The hashes slice gets deduplicated in the lock file 658 // implementation, so we don't worry about potentially 659 // creating duplicates here. 660 var priorHashes []getproviders.Hash 661 if lock != nil && lock.Version() == version { 662 // If the version we're installing is identical to the 663 // one we previously locked then we'll keep all of the 664 // hashes we saved previously and add to it. Otherwise 665 // we'll be starting fresh, because each version has its 666 // own set of packages and thus its own hashes. 667 priorHashes = append(priorHashes, preferredHashes...) 668 } 669 newHash, err := new.Hash() 670 if err != nil { 671 err := fmt.Errorf("after installing %s, failed to compute a checksum for it: %w", provider, err) 672 errs[provider] = err 673 if cb := evts.FetchPackageFailure; cb != nil { 674 cb(provider, version, err) 675 } 676 continue 677 } 678 679 var signedHashes []getproviders.Hash 680 // For now, we will temporarily trust the hashes returned by the 681 // installation process that are "SigningSkipped" or "Signed". 682 // This is only intended to be temporary, see https://github.com/opentofu/opentofu/issues/266 for more information 683 if authResult.Signed() || authResult.SigningSkipped() { 684 // We'll trust new hashes from upstream only if they were verified 685 // as signed by a suitable key or if the signing validation was skipped. 686 // Otherwise, we'd record only 687 // a new hash we just calculated ourselves from the bytes on disk, 688 // and so the hashes would cover only the current platform. 689 signedHashes = append(signedHashes, meta.AcceptableHashes()...) 690 } 691 692 var newHashes []getproviders.Hash 693 newHashes = append(newHashes, newHash) 694 newHashes = append(newHashes, priorHashes...) 695 newHashes = append(newHashes, signedHashes...) 696 697 locks.SetProvider(provider, version, reqs[provider], newHashes) 698 if cb := evts.ProvidersLockUpdated; cb != nil { 699 // newHash and priorHashes are already sorted. 700 // But we do need to sort signedHashes so we can reason about it 701 // sensibly. 702 sort.Slice(signedHashes, func(i, j int) bool { 703 return string(signedHashes[i]) < string(signedHashes[j]) 704 }) 705 706 cb(provider, version, []getproviders.Hash{newHash}, signedHashes, priorHashes) 707 } 708 709 if cb := evts.FetchPackageSuccess; cb != nil { 710 cb(provider, version, new.PackageDir, authResult) 711 } 712 } 713 714 // Emit final event for fetching if any were successfully fetched 715 if cb := evts.ProvidersFetched; cb != nil && len(authResults) > 0 { 716 cb(authResults) 717 } 718 719 // Finally, if the lock structure contains locks for any providers that 720 // are no longer needed by this configuration, we'll remove them. This 721 // is important because we will not have installed those providers 722 // above and so a lock file still containing them would make the working 723 // directory invalid: not every provider in the lock file is available 724 // for use. 725 for providerAddr := range locks.AllProviders() { 726 if _, ok := reqs[providerAddr]; !ok { 727 locks.RemoveProvider(providerAddr) 728 } 729 } 730 731 if len(errs) > 0 { 732 return locks, InstallerError{ 733 ProviderErrors: errs, 734 } 735 } 736 return locks, nil 737 } 738 739 // InstallMode customizes the details of how an install operation treats 740 // providers that have versions already cached in the target directory. 741 type InstallMode rune 742 743 const ( 744 // InstallNewProvidersOnly is an InstallMode that causes the installer 745 // to accept any existing version of a requested provider that is already 746 // cached as long as it's in the given version sets, without checking 747 // whether new versions are available that are also in the given version 748 // sets. 749 InstallNewProvidersOnly InstallMode = 'N' 750 751 // InstallNewProvidersForce is an InstallMode that follows the same 752 // logic as InstallNewProvidersOnly except it does not verify existing 753 // checksums but force installs new checksums for all given providers. 754 InstallNewProvidersForce InstallMode = 'F' 755 756 // InstallUpgrades is an InstallMode that causes the installer to check 757 // all requested providers to see if new versions are available that 758 // are also in the given version sets, even if a suitable version of 759 // a given provider is already available. 760 InstallUpgrades InstallMode = 'U' 761 ) 762 763 func (m InstallMode) forceQueryAllProviders() bool { 764 return m == InstallUpgrades 765 } 766 767 func (m InstallMode) forceInstallChecksums() bool { 768 return m == InstallNewProvidersForce 769 } 770 771 // InstallerError is an error type that may be returned (but is not guaranteed) 772 // from Installer.EnsureProviderVersions to indicate potentially several 773 // separate failed installation outcomes for different providers included in 774 // the overall request. 775 type InstallerError struct { 776 ProviderErrors map[addrs.Provider]error 777 } 778 779 func (err InstallerError) Error() string { 780 addrs := make([]addrs.Provider, 0, len(err.ProviderErrors)) 781 for addr := range err.ProviderErrors { 782 addrs = append(addrs, addr) 783 } 784 sort.Slice(addrs, func(i, j int) bool { 785 return addrs[i].LessThan(addrs[j]) 786 }) 787 var b strings.Builder 788 b.WriteString("some providers could not be installed:\n") 789 for _, addr := range addrs { 790 providerErr := err.ProviderErrors[addr] 791 fmt.Fprintf(&b, "- %s: %s\n", addr, providerErr) 792 } 793 return strings.TrimSpace(b.String()) 794 }