]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/registry/client.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / registry / client.go
1 package registry
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "log"
8 "net/http"
9 "net/url"
10 "path"
11 "strings"
12 "time"
13
14 "github.com/hashicorp/terraform/httpclient"
15 "github.com/hashicorp/terraform/registry/regsrc"
16 "github.com/hashicorp/terraform/registry/response"
17 "github.com/hashicorp/terraform/svchost"
18 "github.com/hashicorp/terraform/svchost/disco"
19 "github.com/hashicorp/terraform/version"
20 )
21
22 const (
23 xTerraformGet = "X-Terraform-Get"
24 xTerraformVersion = "X-Terraform-Version"
25 requestTimeout = 10 * time.Second
26 modulesServiceID = "modules.v1"
27 providersServiceID = "providers.v1"
28 )
29
30 var tfVersion = version.String()
31
32 // Client provides methods to query Terraform Registries.
33 type Client struct {
34 // this is the client to be used for all requests.
35 client *http.Client
36
37 // services is a required *disco.Disco, which may have services and
38 // credentials pre-loaded.
39 services *disco.Disco
40 }
41
42 // NewClient returns a new initialized registry client.
43 func NewClient(services *disco.Disco, client *http.Client) *Client {
44 if services == nil {
45 services = disco.New()
46 }
47
48 if client == nil {
49 client = httpclient.New()
50 client.Timeout = requestTimeout
51 }
52
53 services.Transport = client.Transport
54
55 return &Client{
56 client: client,
57 services: services,
58 }
59 }
60
61 // Discover queries the host, and returns the url for the registry.
62 func (c *Client) Discover(host svchost.Hostname, serviceID string) (*url.URL, error) {
63 service, err := c.services.DiscoverServiceURL(host, serviceID)
64 if err != nil {
65 return nil, &ServiceUnreachableError{err}
66 }
67 if !strings.HasSuffix(service.Path, "/") {
68 service.Path += "/"
69 }
70 return service, nil
71 }
72
73 // ModuleVersions queries the registry for a module, and returns the available versions.
74 func (c *Client) ModuleVersions(module *regsrc.Module) (*response.ModuleVersions, error) {
75 host, err := module.SvcHost()
76 if err != nil {
77 return nil, err
78 }
79
80 service, err := c.Discover(host, modulesServiceID)
81 if err != nil {
82 return nil, err
83 }
84
85 p, err := url.Parse(path.Join(module.Module(), "versions"))
86 if err != nil {
87 return nil, err
88 }
89
90 service = service.ResolveReference(p)
91
92 log.Printf("[DEBUG] fetching module versions from %q", service)
93
94 req, err := http.NewRequest("GET", service.String(), nil)
95 if err != nil {
96 return nil, err
97 }
98
99 c.addRequestCreds(host, req)
100 req.Header.Set(xTerraformVersion, tfVersion)
101
102 resp, err := c.client.Do(req)
103 if err != nil {
104 return nil, err
105 }
106 defer resp.Body.Close()
107
108 switch resp.StatusCode {
109 case http.StatusOK:
110 // OK
111 case http.StatusNotFound:
112 return nil, &errModuleNotFound{addr: module}
113 default:
114 return nil, fmt.Errorf("error looking up module versions: %s", resp.Status)
115 }
116
117 var versions response.ModuleVersions
118
119 dec := json.NewDecoder(resp.Body)
120 if err := dec.Decode(&versions); err != nil {
121 return nil, err
122 }
123
124 for _, mod := range versions.Modules {
125 for _, v := range mod.Versions {
126 log.Printf("[DEBUG] found available version %q for %s", v.Version, mod.Source)
127 }
128 }
129
130 return &versions, nil
131 }
132
133 func (c *Client) addRequestCreds(host svchost.Hostname, req *http.Request) {
134 creds, err := c.services.CredentialsForHost(host)
135 if err != nil {
136 log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err)
137 return
138 }
139
140 if creds != nil {
141 creds.PrepareRequest(req)
142 }
143 }
144
145 // ModuleLocation find the download location for a specific version module.
146 // This returns a string, because the final location may contain special go-getter syntax.
147 func (c *Client) ModuleLocation(module *regsrc.Module, version string) (string, error) {
148 host, err := module.SvcHost()
149 if err != nil {
150 return "", err
151 }
152
153 service, err := c.Discover(host, modulesServiceID)
154 if err != nil {
155 return "", err
156 }
157
158 var p *url.URL
159 if version == "" {
160 p, err = url.Parse(path.Join(module.Module(), "download"))
161 } else {
162 p, err = url.Parse(path.Join(module.Module(), version, "download"))
163 }
164 if err != nil {
165 return "", err
166 }
167 download := service.ResolveReference(p)
168
169 log.Printf("[DEBUG] looking up module location from %q", download)
170
171 req, err := http.NewRequest("GET", download.String(), nil)
172 if err != nil {
173 return "", err
174 }
175
176 c.addRequestCreds(host, req)
177 req.Header.Set(xTerraformVersion, tfVersion)
178
179 resp, err := c.client.Do(req)
180 if err != nil {
181 return "", err
182 }
183 defer resp.Body.Close()
184
185 // there should be no body, but save it for logging
186 body, err := ioutil.ReadAll(resp.Body)
187 if err != nil {
188 return "", fmt.Errorf("error reading response body from registry: %s", err)
189 }
190
191 switch resp.StatusCode {
192 case http.StatusOK, http.StatusNoContent:
193 // OK
194 case http.StatusNotFound:
195 return "", fmt.Errorf("module %q version %q not found", module, version)
196 default:
197 // anything else is an error:
198 return "", fmt.Errorf("error getting download location for %q: %s resp:%s", module, resp.Status, body)
199 }
200
201 // the download location is in the X-Terraform-Get header
202 location := resp.Header.Get(xTerraformGet)
203 if location == "" {
204 return "", fmt.Errorf("failed to get download URL for %q: %s resp:%s", module, resp.Status, body)
205 }
206
207 // If location looks like it's trying to be a relative URL, treat it as
208 // one.
209 //
210 // We don't do this for just _any_ location, since the X-Terraform-Get
211 // header is a go-getter location rather than a URL, and so not all
212 // possible values will parse reasonably as URLs.)
213 //
214 // When used in conjunction with go-getter we normally require this header
215 // to be an absolute URL, but we are more liberal here because third-party
216 // registry implementations may not "know" their own absolute URLs if
217 // e.g. they are running behind a reverse proxy frontend, or such.
218 if strings.HasPrefix(location, "/") || strings.HasPrefix(location, "./") || strings.HasPrefix(location, "../") {
219 locationURL, err := url.Parse(location)
220 if err != nil {
221 return "", fmt.Errorf("invalid relative URL for %q: %s", module, err)
222 }
223 locationURL = download.ResolveReference(locationURL)
224 location = locationURL.String()
225 }
226
227 return location, nil
228 }
229
230 // TerraformProviderVersions queries the registry for a provider, and returns the available versions.
231 func (c *Client) TerraformProviderVersions(provider *regsrc.TerraformProvider) (*response.TerraformProviderVersions, error) {
232 host, err := provider.SvcHost()
233 if err != nil {
234 return nil, err
235 }
236
237 service, err := c.Discover(host, providersServiceID)
238 if err != nil {
239 return nil, err
240 }
241
242 p, err := url.Parse(path.Join(provider.TerraformProvider(), "versions"))
243 if err != nil {
244 return nil, err
245 }
246
247 service = service.ResolveReference(p)
248
249 log.Printf("[DEBUG] fetching provider versions from %q", service)
250
251 req, err := http.NewRequest("GET", service.String(), nil)
252 if err != nil {
253 return nil, err
254 }
255
256 c.addRequestCreds(host, req)
257 req.Header.Set(xTerraformVersion, tfVersion)
258
259 resp, err := c.client.Do(req)
260 if err != nil {
261 return nil, err
262 }
263 defer resp.Body.Close()
264
265 switch resp.StatusCode {
266 case http.StatusOK:
267 // OK
268 case http.StatusNotFound:
269 return nil, &errProviderNotFound{addr: provider}
270 default:
271 return nil, fmt.Errorf("error looking up provider versions: %s", resp.Status)
272 }
273
274 var versions response.TerraformProviderVersions
275
276 dec := json.NewDecoder(resp.Body)
277 if err := dec.Decode(&versions); err != nil {
278 return nil, err
279 }
280
281 return &versions, nil
282 }
283
284 // TerraformProviderLocation queries the registry for a provider download metadata
285 func (c *Client) TerraformProviderLocation(provider *regsrc.TerraformProvider, version string) (*response.TerraformProviderPlatformLocation, error) {
286 host, err := provider.SvcHost()
287 if err != nil {
288 return nil, err
289 }
290
291 service, err := c.Discover(host, providersServiceID)
292 if err != nil {
293 return nil, err
294 }
295
296 p, err := url.Parse(path.Join(
297 provider.TerraformProvider(),
298 version,
299 "download",
300 provider.OS,
301 provider.Arch,
302 ))
303 if err != nil {
304 return nil, err
305 }
306
307 service = service.ResolveReference(p)
308
309 log.Printf("[DEBUG] fetching provider location from %q", service)
310
311 req, err := http.NewRequest("GET", service.String(), nil)
312 if err != nil {
313 return nil, err
314 }
315
316 c.addRequestCreds(host, req)
317 req.Header.Set(xTerraformVersion, tfVersion)
318
319 resp, err := c.client.Do(req)
320 if err != nil {
321 return nil, err
322 }
323 defer resp.Body.Close()
324
325 var loc response.TerraformProviderPlatformLocation
326
327 dec := json.NewDecoder(resp.Body)
328 if err := dec.Decode(&loc); err != nil {
329 return nil, err
330 }
331
332 switch resp.StatusCode {
333 case http.StatusOK, http.StatusNoContent:
334 // OK
335 case http.StatusNotFound:
336 return nil, fmt.Errorf("provider %q version %q not found", provider.TerraformProvider(), version)
337 default:
338 // anything else is an error:
339 return nil, fmt.Errorf("error getting download location for %q: %s", provider.TerraformProvider(), resp.Status)
340 }
341
342 return &loc, nil
343 }