1package initwd
2
3import (
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
22var goGetterNoDetectors = []getter.Detector{}
23
24var 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
40var 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
50var getterHTTPClient = cleanhttp.DefaultClient()
51
52var 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.
65type 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.
76func (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.
163func splitAddrSubdir(addr string) (packageAddr, subDir string) {
164	return getter.SourceDirSubdir(addr)
165}
166
167var localSourcePrefixes = []string{
168	"./",
169	"../",
170	".\\",
171	"..\\",
172}
173
174func 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
183func isRegistrySourceAddr(addr string) bool {
184	_, err := regsrc.ParseModuleSource(addr)
185	return err == nil
186}
187
188type MaybeRelativePathErr struct {
189	Addr string
190}
191
192func (e *MaybeRelativePathErr) Error() string {
193	return fmt.Sprintf("Terraform cannot determine the module source for %s", e.Addr)
194}
195
196func 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}
205