2019-01-09 03:39:14 +01:00
|
|
|
package initwd
|
2019-01-09 01:21:31 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
|
|
|
getter "github.com/hashicorp/go-getter"
|
|
|
|
"github.com/hashicorp/terraform/registry/regsrc"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
var goGetterDetectors = []getter.Detector{
|
|
|
|
new(getter.GitHubDetector),
|
|
|
|
new(getter.BitBucketDetector),
|
|
|
|
new(getter.S3Detector),
|
2019-01-09 03:39:14 +01:00
|
|
|
new(getter.FileDetector),
|
2019-01-09 01:21:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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{
|
2019-01-09 03:39:14 +01:00
|
|
|
"file": new(getter.FileGetter),
|
2019-01-09 01:21:31 +01:00
|
|
|
"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 resolved and trimmed source addresses
|
|
|
|
// (with a scheme always present, and without any "subdir" component),
|
|
|
|
// and the values are the paths where each source was previously installed.
|
|
|
|
type reusingGetter map[string]string
|
|
|
|
|
|
|
|
// getWithGoGetter retrieves the package referenced in the given address
|
|
|
|
// into the installation path and then returns the full path to any subdir
|
|
|
|
// indicated in the address.
|
|
|
|
//
|
|
|
|
// 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
|
2019-03-11 23:25:21 +01:00
|
|
|
// the underlying errors are not separately recognizable.
|
2019-01-09 01:21:31 +01:00
|
|
|
func (g reusingGetter) getWithGoGetter(instPath, addr string) (string, error) {
|
|
|
|
packageAddr, subDir := splitAddrSubdir(addr)
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] will download %q to %s", packageAddr, instPath)
|
|
|
|
|
|
|
|
realAddr, err := getter.Detect(packageAddr, instPath, getter.Detectors)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2019-03-13 19:17:14 +01:00
|
|
|
if isMaybeRelativeLocalPath(realAddr) {
|
|
|
|
return "", &MaybeRelativePathErr{addr}
|
|
|
|
}
|
|
|
|
|
2019-01-09 01:21:31 +01:00
|
|
|
var realSubDir string
|
|
|
|
realAddr, realSubDir = splitAddrSubdir(realAddr)
|
|
|
|
if realSubDir != "" {
|
|
|
|
subDir = filepath.Join(realSubDir, subDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
if realAddr != packageAddr {
|
|
|
|
log.Printf("[TRACE] go-getter detectors rewrote %q to %q", packageAddr, realAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
if prevDir, exists := g[realAddr]; exists {
|
|
|
|
log.Printf("[TRACE] copying previous install %s to %s", prevDir, instPath)
|
|
|
|
err := os.Mkdir(instPath, os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed to create directory %s: %s", instPath, err)
|
|
|
|
}
|
|
|
|
err = copyDir(instPath, prevDir)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed to copy from %s to %s: %s", prevDir, instPath, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Printf("[TRACE] fetching %q to %q", realAddr, instPath)
|
|
|
|
client := getter.Client{
|
|
|
|
Src: realAddr,
|
|
|
|
Dst: instPath,
|
|
|
|
Pwd: instPath,
|
|
|
|
|
|
|
|
Mode: getter.ClientModeDir,
|
|
|
|
|
|
|
|
Detectors: goGetterNoDetectors, // we already did detection above
|
|
|
|
Decompressors: goGetterDecompressors,
|
|
|
|
Getters: goGetterGetters,
|
|
|
|
}
|
|
|
|
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[realAddr] = instPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// Our subDir string can contain wildcards until this point, so that
|
|
|
|
// e.g. a subDir of * can expand to one top-level directory in a .tar.gz
|
|
|
|
// archive. Now that we've expanded the archive successfully we must
|
|
|
|
// resolve that into a concrete path.
|
|
|
|
var finalDir string
|
|
|
|
if subDir != "" {
|
|
|
|
finalDir, err = getter.SubdirGlob(instPath, subDir)
|
|
|
|
log.Printf("[TRACE] expanded %q to %q", subDir, finalDir)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
finalDir = instPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we got this far then we have apparently succeeded in downloading
|
|
|
|
// the requested object!
|
|
|
|
return filepath.Clean(finalDir), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// splitAddrSubdir splits the given address (which is assumed to be a
|
|
|
|
// registry address or go-getter-style address) into a package portion
|
|
|
|
// and a sub-directory portion.
|
|
|
|
//
|
|
|
|
// The package portion defines what should be downloaded and then the
|
|
|
|
// sub-directory portion, if present, specifies a sub-directory within
|
|
|
|
// the downloaded object (an archive, VCS repository, etc) that contains
|
|
|
|
// the module's configuration files.
|
|
|
|
//
|
|
|
|
// The subDir portion will be returned as empty if no subdir separator
|
|
|
|
// ("//") is present in the address.
|
|
|
|
func splitAddrSubdir(addr string) (packageAddr, subDir string) {
|
|
|
|
return getter.SourceDirSubdir(addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
var localSourcePrefixes = []string{
|
|
|
|
"./",
|
|
|
|
"../",
|
|
|
|
".\\",
|
|
|
|
"..\\",
|
|
|
|
}
|
|
|
|
|
|
|
|
func isLocalSourceAddr(addr string) bool {
|
|
|
|
for _, prefix := range localSourcePrefixes {
|
|
|
|
if strings.HasPrefix(addr, prefix) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func isRegistrySourceAddr(addr string) bool {
|
|
|
|
_, err := regsrc.ParseModuleSource(addr)
|
|
|
|
return err == nil
|
|
|
|
}
|
2019-03-11 23:25:21 +01:00
|
|
|
|
2019-03-13 19:17:14 +01:00
|
|
|
type MaybeRelativePathErr struct {
|
|
|
|
Addr string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *MaybeRelativePathErr) Error() string {
|
|
|
|
return fmt.Sprintf("Terraform cannot determine the module source for %s", e.Addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func isMaybeRelativeLocalPath(addr string) bool {
|
|
|
|
if strings.HasPrefix(addr, "file://") {
|
|
|
|
_, err := os.Stat(addr[7:])
|
|
|
|
if err != nil {
|
|
|
|
return true
|
2019-03-11 23:25:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|