github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/providercache/installer.go (about)

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