mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-02-25 23:00:56 -05:00
In earlier commits we started to make the installation codepath context-aware so that it could be canceled in the event of a SIGINT, but we didn't complete wiring that through the API of the getproviders package. Here we make the getproviders.Source interface methods, along with some other functions that can make network requests, take a context.Context argument and act appropriately if that context is cancelled. The main providercache.Installer.EnsureProviderVersions method now also has some context-awareness so that it can abort its work early if its context reports any sort of error. That avoids waiting for the process to wind through all of the remaining iterations of the various loops, logging each request failure separately, and instead returns just a single aggregate "canceled" error. We can then set things up in the "terraform init" and "terraform providers mirror" commands so that the context will be cancelled if we get an interrupt signal, allowing provider installation to abort early while still atomically completing any local-side effects that may have started.
129 lines
4.7 KiB
Go
129 lines
4.7 KiB
Go
package getproviders
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
)
|
|
|
|
// FilesystemMirrorSource is a source that reads providers and their metadata
|
|
// from a directory prefix in the local filesystem.
|
|
type FilesystemMirrorSource struct {
|
|
baseDir string
|
|
|
|
// allPackages caches the result of scanning the baseDir for all available
|
|
// packages on the first call that needs package availability information,
|
|
// to avoid re-scanning the filesystem on subsequent operations.
|
|
allPackages map[addrs.Provider]PackageMetaList
|
|
}
|
|
|
|
var _ Source = (*FilesystemMirrorSource)(nil)
|
|
|
|
// NewFilesystemMirrorSource constructs and returns a new filesystem-based
|
|
// mirror source with the given base directory.
|
|
func NewFilesystemMirrorSource(baseDir string) *FilesystemMirrorSource {
|
|
return &FilesystemMirrorSource{
|
|
baseDir: baseDir,
|
|
}
|
|
}
|
|
|
|
// AvailableVersions scans the directory structure under the source's base
|
|
// directory for locally-mirrored packages for the given provider, returning
|
|
// a list of version numbers for the providers it found.
|
|
func (s *FilesystemMirrorSource) AvailableVersions(ctx context.Context, provider addrs.Provider) (VersionList, Warnings, error) {
|
|
// s.allPackages is populated if scanAllVersions succeeds
|
|
err := s.scanAllVersions()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// There might be multiple packages for a given version in the filesystem,
|
|
// but the contract here is to return distinct versions so we'll dedupe
|
|
// them first, then sort them, and then return them.
|
|
versionsMap := make(map[Version]struct{})
|
|
for _, m := range s.allPackages[provider] {
|
|
versionsMap[m.Version] = struct{}{}
|
|
}
|
|
ret := make(VersionList, 0, len(versionsMap))
|
|
for v := range versionsMap {
|
|
ret = append(ret, v)
|
|
}
|
|
ret.Sort()
|
|
return ret, nil, nil
|
|
}
|
|
|
|
// PackageMeta checks to see if the source's base directory contains a
|
|
// local copy of the distribution package for the given provider version on
|
|
// the given target, and returns the metadata about it if so.
|
|
func (s *FilesystemMirrorSource) PackageMeta(ctx context.Context, provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
|
// s.allPackages is populated if scanAllVersions succeeds
|
|
err := s.scanAllVersions()
|
|
if err != nil {
|
|
return PackageMeta{}, err
|
|
}
|
|
|
|
relevantPkgs := s.allPackages[provider].FilterProviderPlatformExactVersion(provider, target, version)
|
|
if len(relevantPkgs) == 0 {
|
|
// This is the local equivalent of a "404 Not Found" when retrieving
|
|
// a particular version from a registry or network mirror. Because
|
|
// the caller should've selected a version already found by
|
|
// AvailableVersions, the only discriminator that should fail here
|
|
// is the target platform, and so our error result assumes that,
|
|
// causing the caller to return an error like "This provider version is
|
|
// not compatible with aros_riscv".
|
|
return PackageMeta{}, ErrPlatformNotSupported{
|
|
Provider: provider,
|
|
Version: version,
|
|
Platform: target,
|
|
}
|
|
}
|
|
|
|
// It's possible that there could be multiple copies of the same package
|
|
// available in the filesystem, if e.g. there's both a packed and an
|
|
// unpacked variant. For now we assume that the decision between them
|
|
// is arbitrary and just take the first one in the result.
|
|
return relevantPkgs[0], nil
|
|
}
|
|
|
|
// AllAvailablePackages scans the directory structure under the source's base
|
|
// directory for locally-mirrored packages for all providers, returning a map
|
|
// of the discovered packages with the fully-qualified provider names as
|
|
// keys.
|
|
//
|
|
// This is not an operation generally supported by all Source implementations,
|
|
// but the filesystem implementation offers it because we also use the
|
|
// filesystem mirror source directly to scan our auto-install plugin directory
|
|
// and in other automatic discovery situations.
|
|
func (s *FilesystemMirrorSource) AllAvailablePackages() (map[addrs.Provider]PackageMetaList, error) {
|
|
// s.allPackages is populated if scanAllVersions succeeds
|
|
err := s.scanAllVersions()
|
|
return s.allPackages, err
|
|
}
|
|
|
|
func (s *FilesystemMirrorSource) scanAllVersions() error {
|
|
if s.allPackages != nil {
|
|
// we're distinguishing nil-ness from emptiness here so we can
|
|
// recognize when we've scanned the directory without errors, even
|
|
// if we found nothing during the scan.
|
|
return nil
|
|
}
|
|
|
|
ret, err := SearchLocalDirectory(s.baseDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// As noted above, we use an explicit empty map so we can distinguish a
|
|
// successful-but-empty result from a failure on future calls, so we'll
|
|
// make sure that's what we have before we assign it here.
|
|
if ret == nil {
|
|
ret = make(map[addrs.Provider]PackageMetaList)
|
|
}
|
|
s.allPackages = ret
|
|
return nil
|
|
}
|
|
|
|
func (s *FilesystemMirrorSource) ForDisplay(provider addrs.Provider) string {
|
|
return s.baseDir
|
|
}
|