mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-16 11:42:58 -06:00
74761b2f8b
There was an unintended regression in go-getter v1.5.9's GitGetter which caused us to temporarily fork that particular getter into Terraform to expedite a fix. However, upstream v1.5.10 now includes a functionally-equivalent fix and so we can heal that fork by upgrading. We'd also neglected to update the Module Sources docs when upgrading to go-getter v1.5.9 originally and so we were missing documentation about the new "depth" argument to enable shadow cloning, which I've added retroactively here along with documenting its restriction of only supporting named refs. This new go-getter release also introduces a new credentials-passing method for the Google Cloud Storage getter, and so we must incorporate that into the Terraform-level documentation about module sources.
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
|
|
}
|