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 }