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  }