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