mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-13 09:32:24 -06:00
40ec62c139
Earlier work to make "terraform init" interruptible made the getproviders package context-aware in order to allow provider installation to be cancelled. Here we make a similar change for module installation, which is now also cancellable with SIGINT. This involves plumbing context through initwd and getmodules. Functions which can make network requests now include a context parameter whose cancellation cancels those requests. Since the module installation code is shared, "terraform get" is now also interruptible during module installation.
164 lines
6.2 KiB
Go
164 lines
6.2 KiB
Go
package getmodules
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
|
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
|
getter "github.com/hashicorp/go-getter"
|
|
"github.com/hashicorp/terraform/internal/copy"
|
|
)
|
|
|
|
// We configure our own go-getter detector and getter sets here, because
|
|
// the set of sources we support is part of Terraform's documentation and
|
|
// so we don't want any new sources introduced in go-getter to sneak in here
|
|
// and work even though they aren't documented. This also insulates us from
|
|
// any meddling that might be done by other go-getter callers linked into our
|
|
// executable.
|
|
//
|
|
// Note that over time we've found go-getter's design to be not wholly fit
|
|
// for Terraform's purposes in various ways, and so we're continuing to use
|
|
// it here because our backward compatibility with earlier versions depends
|
|
// on it, but we use go-getter very carefully and always only indirectly via
|
|
// the public API of this package so that we can get the subset of the
|
|
// go-getter functionality we need while working around some of the less
|
|
// helpful parts of its design. See the comments in various other functions
|
|
// in this package which call into go-getter for more information on what
|
|
// tradeoffs we're making here.
|
|
|
|
var goGetterDetectors = []getter.Detector{
|
|
new(getter.GitHubDetector),
|
|
new(getter.GitDetector),
|
|
|
|
// Because historically BitBucket supported both Git and Mercurial
|
|
// repositories but used the same repository URL syntax for both,
|
|
// this detector takes the unusual step of actually reaching out
|
|
// to the BitBucket API to recognize the repository type. That
|
|
// means there's the possibility of an outgoing network request
|
|
// inside what is otherwise normally just a local string manipulation
|
|
// operation, but we continue to accept this for now.
|
|
//
|
|
// Perhaps a future version of go-getter will remove the check now
|
|
// that BitBucket only supports Git anyway. Aside from this historical
|
|
// exception, we should avoid adding any new detectors that make network
|
|
// requests in here, and limit ourselves only to ones that can operate
|
|
// entirely through local string manipulation.
|
|
new(getter.BitBucketDetector),
|
|
|
|
new(getter.GCSDetector),
|
|
new(getter.S3Detector),
|
|
new(fileDetector),
|
|
}
|
|
|
|
var goGetterNoDetectors = []getter.Detector{}
|
|
|
|
var goGetterDecompressors = map[string]getter.Decompressor{
|
|
"bz2": new(getter.Bzip2Decompressor),
|
|
"gz": new(getter.GzipDecompressor),
|
|
"xz": new(getter.XzDecompressor),
|
|
"zip": new(getter.ZipDecompressor),
|
|
|
|
"tar.bz2": new(getter.TarBzip2Decompressor),
|
|
"tar.tbz2": new(getter.TarBzip2Decompressor),
|
|
|
|
"tar.gz": new(getter.TarGzipDecompressor),
|
|
"tgz": new(getter.TarGzipDecompressor),
|
|
|
|
"tar.xz": new(getter.TarXzDecompressor),
|
|
"txz": new(getter.TarXzDecompressor),
|
|
}
|
|
|
|
var goGetterGetters = map[string]getter.Getter{
|
|
"file": new(getter.FileGetter),
|
|
"gcs": new(getter.GCSGetter),
|
|
"git": new(getter.GitGetter),
|
|
"hg": new(getter.HgGetter),
|
|
"s3": new(getter.S3Getter),
|
|
"http": getterHTTPGetter,
|
|
"https": getterHTTPGetter,
|
|
}
|
|
|
|
var getterHTTPClient = cleanhttp.DefaultClient()
|
|
|
|
var getterHTTPGetter = &getter.HttpGetter{
|
|
Client: getterHTTPClient,
|
|
Netrc: true,
|
|
}
|
|
|
|
// A reusingGetter is a helper for the module installer that remembers
|
|
// the final resolved addresses of all of the sources it has already been
|
|
// asked to install, and will copy from a prior installation directory if
|
|
// it has the same resolved source address.
|
|
//
|
|
// The keys in a reusingGetter are the normalized (post-detection) package
|
|
// addresses, and the values are the paths where each source was previously
|
|
// installed. (Users of this map should treat the keys as addrs.ModulePackage
|
|
// values, but we can't type them that way because the addrs package
|
|
// imports getmodules in order to indirectly access our go-getter
|
|
// configuration.)
|
|
type reusingGetter map[string]string
|
|
|
|
// getWithGoGetter fetches the package at the given address into the given
|
|
// target directory. The given address must already be in normalized form
|
|
// (using NormalizePackageAddress) or else the behavior is undefined.
|
|
//
|
|
// This function deals only in entire packages, so it's always the caller's
|
|
// responsibility to handle any subdirectory specification and select a
|
|
// suitable subdirectory of the given installation directory after installation
|
|
// has succeeded.
|
|
//
|
|
// This function would ideally accept packageAddr as a value of type
|
|
// addrs.ModulePackage, but we can't do that because the addrs package
|
|
// depends on this package for package address parsing. Therefore we just
|
|
// use a string here but assume that the caller got that value by calling
|
|
// the String method on a valid addrs.ModulePackage value.
|
|
//
|
|
// The errors returned by this function are those surfaced by the underlying
|
|
// go-getter library, which have very inconsistent quality as
|
|
// end-user-actionable error messages. At this time we do not have any
|
|
// reasonable way to improve these error messages at this layer because
|
|
// the underlying errors are not separately recognizable.
|
|
func (g reusingGetter) getWithGoGetter(ctx context.Context, instPath, packageAddr string) error {
|
|
var err error
|
|
|
|
if prevDir, exists := g[packageAddr]; exists {
|
|
log.Printf("[TRACE] getmodules: copying previous install of %q from %s to %s", packageAddr, prevDir, instPath)
|
|
err := os.Mkdir(instPath, os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %s", instPath, err)
|
|
}
|
|
err = copy.CopyDir(instPath, prevDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to copy from %s to %s: %s", prevDir, instPath, err)
|
|
}
|
|
} else {
|
|
log.Printf("[TRACE] getmodules: fetching %q to %q", packageAddr, instPath)
|
|
client := getter.Client{
|
|
Src: packageAddr,
|
|
Dst: instPath,
|
|
Pwd: instPath,
|
|
|
|
Mode: getter.ClientModeDir,
|
|
|
|
Detectors: goGetterNoDetectors, // our caller should've already done detection
|
|
Decompressors: goGetterDecompressors,
|
|
Getters: goGetterGetters,
|
|
Ctx: ctx,
|
|
}
|
|
err = client.Get()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Remember where we installed this so we might reuse this directory
|
|
// on subsequent calls to avoid re-downloading.
|
|
g[packageAddr] = instPath
|
|
}
|
|
|
|
// If we get down here then we've either downloaded the package or
|
|
// copied a previous tree we downloaded, and so either way we should
|
|
// have got the full module package structure written into instPath.
|
|
return nil
|
|
}
|