]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // getter is a package for downloading files or directories from a variety of |
2 | // protocols. | |
3 | // | |
4 | // getter is unique in its ability to download both directories and files. | |
5 | // It also detects certain source strings to be protocol-specific URLs. For | |
6 | // example, "github.com/hashicorp/go-getter" would turn into a Git URL and | |
7 | // use the Git protocol. | |
8 | // | |
9 | // Protocols and detectors are extensible. | |
10 | // | |
11 | // To get started, see Client. | |
12 | package getter | |
13 | ||
14 | import ( | |
15 | "bytes" | |
16 | "fmt" | |
17 | "net/url" | |
18 | "os/exec" | |
19 | "regexp" | |
20 | "syscall" | |
15c0b25d AP |
21 | |
22 | cleanhttp "github.com/hashicorp/go-cleanhttp" | |
bae9f6d2 JC |
23 | ) |
24 | ||
25 | // Getter defines the interface that schemes must implement to download | |
26 | // things. | |
27 | type Getter interface { | |
28 | // Get downloads the given URL into the given directory. This always | |
29 | // assumes that we're updating and gets the latest version that it can. | |
30 | // | |
31 | // The directory may already exist (if we're updating). If it is in a | |
32 | // format that isn't understood, an error should be returned. Get shouldn't | |
33 | // simply nuke the directory. | |
34 | Get(string, *url.URL) error | |
35 | ||
36 | // GetFile downloads the give URL into the given path. The URL must | |
37 | // reference a single file. If possible, the Getter should check if | |
38 | // the remote end contains the same file and no-op this operation. | |
39 | GetFile(string, *url.URL) error | |
40 | ||
41 | // ClientMode returns the mode based on the given URL. This is used to | |
42 | // allow clients to let the getters decide which mode to use. | |
43 | ClientMode(*url.URL) (ClientMode, error) | |
107c1cdb ND |
44 | |
45 | // SetClient allows a getter to know it's client | |
46 | // in order to access client's Get functions or | |
47 | // progress tracking. | |
48 | SetClient(*Client) | |
bae9f6d2 JC |
49 | } |
50 | ||
51 | // Getters is the mapping of scheme to the Getter implementation that will | |
52 | // be used to get a dependency. | |
53 | var Getters map[string]Getter | |
54 | ||
55 | // forcedRegexp is the regular expression that finds forced getters. This | |
56 | // syntax is schema::url, example: git::https://foo.com | |
57 | var forcedRegexp = regexp.MustCompile(`^([A-Za-z0-9]+)::(.+)$`) | |
58 | ||
15c0b25d AP |
59 | // httpClient is the default client to be used by HttpGetters. |
60 | var httpClient = cleanhttp.DefaultClient() | |
61 | ||
bae9f6d2 | 62 | func init() { |
15c0b25d AP |
63 | httpGetter := &HttpGetter{ |
64 | Netrc: true, | |
65 | } | |
bae9f6d2 JC |
66 | |
67 | Getters = map[string]Getter{ | |
68 | "file": new(FileGetter), | |
69 | "git": new(GitGetter), | |
107c1cdb | 70 | "gcs": new(GCSGetter), |
bae9f6d2 JC |
71 | "hg": new(HgGetter), |
72 | "s3": new(S3Getter), | |
73 | "http": httpGetter, | |
74 | "https": httpGetter, | |
75 | } | |
76 | } | |
77 | ||
78 | // Get downloads the directory specified by src into the folder specified by | |
79 | // dst. If dst already exists, Get will attempt to update it. | |
80 | // | |
81 | // src is a URL, whereas dst is always just a file path to a folder. This | |
82 | // folder doesn't need to exist. It will be created if it doesn't exist. | |
107c1cdb | 83 | func Get(dst, src string, opts ...ClientOption) error { |
bae9f6d2 JC |
84 | return (&Client{ |
85 | Src: src, | |
86 | Dst: dst, | |
87 | Dir: true, | |
107c1cdb | 88 | Options: opts, |
bae9f6d2 JC |
89 | }).Get() |
90 | } | |
91 | ||
92 | // GetAny downloads a URL into the given destination. Unlike Get or | |
93 | // GetFile, both directories and files are supported. | |
94 | // | |
95 | // dst must be a directory. If src is a file, it will be downloaded | |
96 | // into dst with the basename of the URL. If src is a directory or | |
97 | // archive, it will be unpacked directly into dst. | |
107c1cdb | 98 | func GetAny(dst, src string, opts ...ClientOption) error { |
bae9f6d2 JC |
99 | return (&Client{ |
100 | Src: src, | |
101 | Dst: dst, | |
102 | Mode: ClientModeAny, | |
107c1cdb | 103 | Options: opts, |
bae9f6d2 JC |
104 | }).Get() |
105 | } | |
106 | ||
107 | // GetFile downloads the file specified by src into the path specified by | |
108 | // dst. | |
107c1cdb | 109 | func GetFile(dst, src string, opts ...ClientOption) error { |
bae9f6d2 JC |
110 | return (&Client{ |
111 | Src: src, | |
112 | Dst: dst, | |
113 | Dir: false, | |
107c1cdb | 114 | Options: opts, |
bae9f6d2 JC |
115 | }).Get() |
116 | } | |
117 | ||
118 | // getRunCommand is a helper that will run a command and capture the output | |
119 | // in the case an error happens. | |
120 | func getRunCommand(cmd *exec.Cmd) error { | |
121 | var buf bytes.Buffer | |
122 | cmd.Stdout = &buf | |
123 | cmd.Stderr = &buf | |
124 | err := cmd.Run() | |
125 | if err == nil { | |
126 | return nil | |
127 | } | |
128 | if exiterr, ok := err.(*exec.ExitError); ok { | |
129 | // The program has exited with an exit code != 0 | |
130 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { | |
131 | return fmt.Errorf( | |
132 | "%s exited with %d: %s", | |
133 | cmd.Path, | |
134 | status.ExitStatus(), | |
135 | buf.String()) | |
136 | } | |
137 | } | |
138 | ||
139 | return fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) | |
140 | } | |
141 | ||
142 | // getForcedGetter takes a source and returns the tuple of the forced | |
143 | // getter and the raw URL (without the force syntax). | |
144 | func getForcedGetter(src string) (string, string) { | |
145 | var forced string | |
146 | if ms := forcedRegexp.FindStringSubmatch(src); ms != nil { | |
147 | forced = ms[1] | |
148 | src = ms[2] | |
149 | } | |
150 | ||
151 | return forced, src | |
152 | } |