github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/getmodules/getter.go (about) 1 package getmodules 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 8 cleanhttp "github.com/hashicorp/go-cleanhttp" 9 getter "github.com/hashicorp/go-getter" 10 "github.com/hashicorp/terraform/internal/copy" 11 ) 12 13 // We configure our own go-getter detector and getter sets here, because 14 // the set of sources we support is part of Terraform's documentation and 15 // so we don't want any new sources introduced in go-getter to sneak in here 16 // and work even though they aren't documented. This also insulates us from 17 // any meddling that might be done by other go-getter callers linked into our 18 // executable. 19 // 20 // Note that over time we've found go-getter's design to be not wholly fit 21 // for Terraform's purposes in various ways, and so we're continuing to use 22 // it here because our backward compatibility with earlier versions depends 23 // on it, but we use go-getter very carefully and always only indirectly via 24 // the public API of this package so that we can get the subset of the 25 // go-getter functionality we need while working around some of the less 26 // helpful parts of its design. See the comments in various other functions 27 // in this package which call into go-getter for more information on what 28 // tradeoffs we're making here. 29 30 var goGetterDetectors = []getter.Detector{ 31 new(getter.GitHubDetector), 32 new(getter.GitDetector), 33 34 // Because historically BitBucket supported both Git and Mercurial 35 // repositories but used the same repository URL syntax for both, 36 // this detector takes the unusual step of actually reaching out 37 // to the BitBucket API to recognize the repository type. That 38 // means there's the possibility of an outgoing network request 39 // inside what is otherwise normally just a local string manipulation 40 // operation, but we continue to accept this for now. 41 // 42 // Perhaps a future version of go-getter will remove the check now 43 // that BitBucket only supports Git anyway. Aside from this historical 44 // exception, we should avoid adding any new detectors that make network 45 // requests in here, and limit ourselves only to ones that can operate 46 // entirely through local string manipulation. 47 new(getter.BitBucketDetector), 48 49 new(getter.GCSDetector), 50 new(getter.S3Detector), 51 new(fileDetector), 52 } 53 54 var goGetterNoDetectors = []getter.Detector{} 55 56 var goGetterDecompressors = map[string]getter.Decompressor{ 57 "bz2": new(getter.Bzip2Decompressor), 58 "gz": new(getter.GzipDecompressor), 59 "xz": new(getter.XzDecompressor), 60 "zip": new(getter.ZipDecompressor), 61 62 "tar.bz2": new(getter.TarBzip2Decompressor), 63 "tar.tbz2": new(getter.TarBzip2Decompressor), 64 65 "tar.gz": new(getter.TarGzipDecompressor), 66 "tgz": new(getter.TarGzipDecompressor), 67 68 "tar.xz": new(getter.TarXzDecompressor), 69 "txz": new(getter.TarXzDecompressor), 70 } 71 72 var goGetterGetters = map[string]getter.Getter{ 73 "file": new(getter.FileGetter), 74 "gcs": new(getter.GCSGetter), 75 "git": new(getter.GitGetter), 76 "hg": new(getter.HgGetter), 77 "s3": new(getter.S3Getter), 78 "http": getterHTTPGetter, 79 "https": getterHTTPGetter, 80 } 81 82 var getterHTTPClient = cleanhttp.DefaultClient() 83 84 var getterHTTPGetter = &getter.HttpGetter{ 85 Client: getterHTTPClient, 86 Netrc: true, 87 } 88 89 // A reusingGetter is a helper for the module installer that remembers 90 // the final resolved addresses of all of the sources it has already been 91 // asked to install, and will copy from a prior installation directory if 92 // it has the same resolved source address. 93 // 94 // The keys in a reusingGetter are the normalized (post-detection) package 95 // addresses, and the values are the paths where each source was previously 96 // installed. (Users of this map should treat the keys as addrs.ModulePackage 97 // values, but we can't type them that way because the addrs package 98 // imports getmodules in order to indirectly access our go-getter 99 // configuration.) 100 type reusingGetter map[string]string 101 102 // getWithGoGetter fetches the package at the given address into the given 103 // target directory. The given address must already be in normalized form 104 // (using NormalizePackageAddress) or else the behavior is undefined. 105 // 106 // This function deals only in entire packages, so it's always the caller's 107 // responsibility to handle any subdirectory specification and select a 108 // suitable subdirectory of the given installation directory after installation 109 // has succeeded. 110 // 111 // This function would ideally accept packageAddr as a value of type 112 // addrs.ModulePackage, but we can't do that because the addrs package 113 // depends on this package for package address parsing. Therefore we just 114 // use a string here but assume that the caller got that value by calling 115 // the String method on a valid addrs.ModulePackage value. 116 // 117 // The errors returned by this function are those surfaced by the underlying 118 // go-getter library, which have very inconsistent quality as 119 // end-user-actionable error messages. At this time we do not have any 120 // reasonable way to improve these error messages at this layer because 121 // the underlying errors are not separately recognizable. 122 func (g reusingGetter) getWithGoGetter(instPath, packageAddr string) error { 123 var err error 124 125 if prevDir, exists := g[packageAddr]; exists { 126 log.Printf("[TRACE] getmodules: copying previous install of %q from %s to %s", packageAddr, prevDir, instPath) 127 err := os.Mkdir(instPath, os.ModePerm) 128 if err != nil { 129 return fmt.Errorf("failed to create directory %s: %s", instPath, err) 130 } 131 err = copy.CopyDir(instPath, prevDir) 132 if err != nil { 133 return fmt.Errorf("failed to copy from %s to %s: %s", prevDir, instPath, err) 134 } 135 } else { 136 log.Printf("[TRACE] getmodules: fetching %q to %q", packageAddr, instPath) 137 client := getter.Client{ 138 Src: packageAddr, 139 Dst: instPath, 140 Pwd: instPath, 141 142 Mode: getter.ClientModeDir, 143 144 Detectors: goGetterNoDetectors, // our caller should've already done detection 145 Decompressors: goGetterDecompressors, 146 Getters: goGetterGetters, 147 } 148 err = client.Get() 149 if err != nil { 150 return err 151 } 152 // Remember where we installed this so we might reuse this directory 153 // on subsequent calls to avoid re-downloading. 154 g[packageAddr] = instPath 155 } 156 157 // If we get down here then we've either downloaded the package or 158 // copied a previous tree we downloaded, and so either way we should 159 // have got the full module package structure written into instPath. 160 return nil 161 }