github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/internal/initwd/getter.go (about)

     1  package initwd
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	cleanhttp "github.com/hashicorp/go-cleanhttp"
    11  	getter "github.com/hashicorp/go-getter"
    12  	"github.com/hashicorp/terraform/registry/regsrc"
    13  )
    14  
    15  // We configure our own go-getter detector and getter sets here, because
    16  // the set of sources we support is part of Terraform's documentation and
    17  // so we don't want any new sources introduced in go-getter to sneak in here
    18  // and work even though they aren't documented. This also insulates us from
    19  // any meddling that might be done by other go-getter callers linked into our
    20  // executable.
    21  
    22  var goGetterDetectors = []getter.Detector{
    23  	new(getter.GitHubDetector),
    24  	new(getter.GitDetector),
    25  	new(getter.BitBucketDetector),
    26  	new(getter.GCSDetector),
    27  	new(getter.S3Detector),
    28  	new(getter.FileDetector),
    29  }
    30  
    31  var goGetterNoDetectors = []getter.Detector{}
    32  
    33  var goGetterDecompressors = map[string]getter.Decompressor{
    34  	"bz2": new(getter.Bzip2Decompressor),
    35  	"gz":  new(getter.GzipDecompressor),
    36  	"xz":  new(getter.XzDecompressor),
    37  	"zip": new(getter.ZipDecompressor),
    38  
    39  	"tar.bz2":  new(getter.TarBzip2Decompressor),
    40  	"tar.tbz2": new(getter.TarBzip2Decompressor),
    41  
    42  	"tar.gz": new(getter.TarGzipDecompressor),
    43  	"tgz":    new(getter.TarGzipDecompressor),
    44  
    45  	"tar.xz": new(getter.TarXzDecompressor),
    46  	"txz":    new(getter.TarXzDecompressor),
    47  }
    48  
    49  var goGetterGetters = map[string]getter.Getter{
    50  	"file":  new(getter.FileGetter),
    51  	"gcs":   new(getter.GCSGetter),
    52  	"git":   new(getter.GitGetter),
    53  	"hg":    new(getter.HgGetter),
    54  	"s3":    new(getter.S3Getter),
    55  	"http":  getterHTTPGetter,
    56  	"https": getterHTTPGetter,
    57  }
    58  
    59  var getterHTTPClient = cleanhttp.DefaultClient()
    60  
    61  var getterHTTPGetter = &getter.HttpGetter{
    62  	Client: getterHTTPClient,
    63  	Netrc:  true,
    64  }
    65  
    66  // A reusingGetter is a helper for the module installer that remembers
    67  // the final resolved addresses of all of the sources it has already been
    68  // asked to install, and will copy from a prior installation directory if
    69  // it has the same resolved source address.
    70  //
    71  // The keys in a reusingGetter are resolved and trimmed source addresses
    72  // (with a scheme always present, and without any "subdir" component),
    73  // and the values are the paths where each source was previously installed.
    74  type reusingGetter map[string]string
    75  
    76  // getWithGoGetter retrieves the package referenced in the given address
    77  // into the installation path and then returns the full path to any subdir
    78  // indicated in the address.
    79  //
    80  // The errors returned by this function are those surfaced by the underlying
    81  // go-getter library, which have very inconsistent quality as
    82  // end-user-actionable error messages. At this time we do not have any
    83  // reasonable way to improve these error messages at this layer because
    84  // the underlying errors are not separately recognizable.
    85  func (g reusingGetter) getWithGoGetter(instPath, addr string) (string, error) {
    86  	packageAddr, subDir := splitAddrSubdir(addr)
    87  
    88  	log.Printf("[DEBUG] will download %q to %s", packageAddr, instPath)
    89  
    90  	realAddr, err := getter.Detect(packageAddr, instPath, goGetterDetectors)
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  
    95  	if isMaybeRelativeLocalPath(realAddr) {
    96  		return "", &MaybeRelativePathErr{addr}
    97  	}
    98  
    99  	var realSubDir string
   100  	realAddr, realSubDir = splitAddrSubdir(realAddr)
   101  	if realSubDir != "" {
   102  		subDir = filepath.Join(realSubDir, subDir)
   103  	}
   104  
   105  	if realAddr != packageAddr {
   106  		log.Printf("[TRACE] go-getter detectors rewrote %q to %q", packageAddr, realAddr)
   107  	}
   108  
   109  	if prevDir, exists := g[realAddr]; exists {
   110  		log.Printf("[TRACE] copying previous install %s to %s", prevDir, instPath)
   111  		err := os.Mkdir(instPath, os.ModePerm)
   112  		if err != nil {
   113  			return "", fmt.Errorf("failed to create directory %s: %s", instPath, err)
   114  		}
   115  		err = copyDir(instPath, prevDir)
   116  		if err != nil {
   117  			return "", fmt.Errorf("failed to copy from %s to %s: %s", prevDir, instPath, err)
   118  		}
   119  	} else {
   120  		log.Printf("[TRACE] fetching %q to %q", realAddr, instPath)
   121  		client := getter.Client{
   122  			Src: realAddr,
   123  			Dst: instPath,
   124  			Pwd: instPath,
   125  
   126  			Mode: getter.ClientModeDir,
   127  
   128  			Detectors:     goGetterNoDetectors, // we already did detection above
   129  			Decompressors: goGetterDecompressors,
   130  			Getters:       goGetterGetters,
   131  		}
   132  		err = client.Get()
   133  		if err != nil {
   134  			return "", err
   135  		}
   136  		// Remember where we installed this so we might reuse this directory
   137  		// on subsequent calls to avoid re-downloading.
   138  		g[realAddr] = instPath
   139  	}
   140  
   141  	// Our subDir string can contain wildcards until this point, so that
   142  	// e.g. a subDir of * can expand to one top-level directory in a .tar.gz
   143  	// archive. Now that we've expanded the archive successfully we must
   144  	// resolve that into a concrete path.
   145  	var finalDir string
   146  	if subDir != "" {
   147  		finalDir, err = getter.SubdirGlob(instPath, subDir)
   148  		log.Printf("[TRACE] expanded %q to %q", subDir, finalDir)
   149  		if err != nil {
   150  			return "", err
   151  		}
   152  	} else {
   153  		finalDir = instPath
   154  	}
   155  
   156  	// If we got this far then we have apparently succeeded in downloading
   157  	// the requested object!
   158  	return filepath.Clean(finalDir), nil
   159  }
   160  
   161  // splitAddrSubdir splits the given address (which is assumed to be a
   162  // registry address or go-getter-style address) into a package portion
   163  // and a sub-directory portion.
   164  //
   165  // The package portion defines what should be downloaded and then the
   166  // sub-directory portion, if present, specifies a sub-directory within
   167  // the downloaded object (an archive, VCS repository, etc) that contains
   168  // the module's configuration files.
   169  //
   170  // The subDir portion will be returned as empty if no subdir separator
   171  // ("//") is present in the address.
   172  func splitAddrSubdir(addr string) (packageAddr, subDir string) {
   173  	return getter.SourceDirSubdir(addr)
   174  }
   175  
   176  var localSourcePrefixes = []string{
   177  	"./",
   178  	"../",
   179  	".\\",
   180  	"..\\",
   181  }
   182  
   183  func isLocalSourceAddr(addr string) bool {
   184  	for _, prefix := range localSourcePrefixes {
   185  		if strings.HasPrefix(addr, prefix) {
   186  			return true
   187  		}
   188  	}
   189  	return false
   190  }
   191  
   192  func isRegistrySourceAddr(addr string) bool {
   193  	_, err := regsrc.ParseModuleSource(addr)
   194  	return err == nil
   195  }
   196  
   197  type MaybeRelativePathErr struct {
   198  	Addr string
   199  }
   200  
   201  func (e *MaybeRelativePathErr) Error() string {
   202  	return fmt.Sprintf("Terraform cannot determine the module source for %s", e.Addr)
   203  }
   204  
   205  func isMaybeRelativeLocalPath(addr string) bool {
   206  	if strings.HasPrefix(addr, "file://") {
   207  		_, err := os.Stat(addr[7:])
   208  		if err != nil {
   209  			return true
   210  		}
   211  	}
   212  	return false
   213  }