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