aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/registry/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/registry/client.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/registry/client.go227
1 files changed, 227 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/registry/client.go b/vendor/github.com/hashicorp/terraform/registry/client.go
new file mode 100644
index 0000000..a18e6b8
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/registry/client.go
@@ -0,0 +1,227 @@
1package registry
2
3import (
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
22const (
23 xTerraformGet = "X-Terraform-Get"
24 xTerraformVersion = "X-Terraform-Version"
25 requestTimeout = 10 * time.Second
26 serviceID = "modules.v1"
27)
28
29var tfVersion = version.String()
30
31// Client provides methods to query Terraform Registries.
32type Client struct {
33 // this is the client to be used for all requests.
34 client *http.Client
35
36 // services is a required *disco.Disco, which may have services and
37 // credentials pre-loaded.
38 services *disco.Disco
39}
40
41// NewClient returns a new initialized registry client.
42func NewClient(services *disco.Disco, client *http.Client) *Client {
43 if services == nil {
44 services = disco.New()
45 }
46
47 if client == nil {
48 client = httpclient.New()
49 client.Timeout = requestTimeout
50 }
51
52 services.Transport = client.Transport
53
54 return &Client{
55 client: client,
56 services: services,
57 }
58}
59
60// Discover queries the host, and returns the url for the registry.
61func (c *Client) Discover(host svchost.Hostname) (*url.URL, error) {
62 service, err := c.services.DiscoverServiceURL(host, serviceID)
63 if err != nil {
64 return nil, err
65 }
66 if !strings.HasSuffix(service.Path, "/") {
67 service.Path += "/"
68 }
69 return service, nil
70}
71
72// Versions queries the registry for a module, and returns the available versions.
73func (c *Client) Versions(module *regsrc.Module) (*response.ModuleVersions, error) {
74 host, err := module.SvcHost()
75 if err != nil {
76 return nil, err
77 }
78
79 service, err := c.Discover(host)
80 if err != nil {
81 return nil, err
82 }
83
84 p, err := url.Parse(path.Join(module.Module(), "versions"))
85 if err != nil {
86 return nil, err
87 }
88
89 service = service.ResolveReference(p)
90
91 log.Printf("[DEBUG] fetching module versions from %q", service)
92
93 req, err := http.NewRequest("GET", service.String(), nil)
94 if err != nil {
95 return nil, err
96 }
97
98 c.addRequestCreds(host, req)
99 req.Header.Set(xTerraformVersion, tfVersion)
100
101 resp, err := c.client.Do(req)
102 if err != nil {
103 return nil, err
104 }
105 defer resp.Body.Close()
106
107 switch resp.StatusCode {
108 case http.StatusOK:
109 // OK
110 case http.StatusNotFound:
111 return nil, &errModuleNotFound{addr: module}
112 default:
113 return nil, fmt.Errorf("error looking up module versions: %s", resp.Status)
114 }
115
116 var versions response.ModuleVersions
117
118 dec := json.NewDecoder(resp.Body)
119 if err := dec.Decode(&versions); err != nil {
120 return nil, err
121 }
122
123 for _, mod := range versions.Modules {
124 for _, v := range mod.Versions {
125 log.Printf("[DEBUG] found available version %q for %s", v.Version, mod.Source)
126 }
127 }
128
129 return &versions, nil
130}
131
132func (c *Client) addRequestCreds(host svchost.Hostname, req *http.Request) {
133 creds, err := c.services.CredentialsForHost(host)
134 if err != nil {
135 log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err)
136 return
137 }
138
139 if creds != nil {
140 creds.PrepareRequest(req)
141 }
142}
143
144// Location find the download location for a specific version module.
145// This returns a string, because the final location may contain special go-getter syntax.
146func (c *Client) Location(module *regsrc.Module, version string) (string, error) {
147 host, err := module.SvcHost()
148 if err != nil {
149 return "", err
150 }
151
152 service, err := c.Discover(host)
153 if err != nil {
154 return "", err
155 }
156
157 var p *url.URL
158 if version == "" {
159 p, err = url.Parse(path.Join(module.Module(), "download"))
160 } else {
161 p, err = url.Parse(path.Join(module.Module(), version, "download"))
162 }
163 if err != nil {
164 return "", err
165 }
166 download := service.ResolveReference(p)
167
168 log.Printf("[DEBUG] looking up module location from %q", download)
169
170 req, err := http.NewRequest("GET", download.String(), nil)
171 if err != nil {
172 return "", err
173 }
174
175 c.addRequestCreds(host, req)
176 req.Header.Set(xTerraformVersion, tfVersion)
177
178 resp, err := c.client.Do(req)
179 if err != nil {
180 return "", err
181 }
182 defer resp.Body.Close()
183
184 // there should be no body, but save it for logging
185 body, err := ioutil.ReadAll(resp.Body)
186 if err != nil {
187 return "", fmt.Errorf("error reading response body from registry: %s", err)
188 }
189
190 switch resp.StatusCode {
191 case http.StatusOK, http.StatusNoContent:
192 // OK
193 case http.StatusNotFound:
194 return "", fmt.Errorf("module %q version %q not found", module, version)
195 default:
196 // anything else is an error:
197 return "", fmt.Errorf("error getting download location for %q: %s resp:%s", module, resp.Status, body)
198 }
199
200 // the download location is in the X-Terraform-Get header
201 location := resp.Header.Get(xTerraformGet)
202 if location == "" {
203 return "", fmt.Errorf("failed to get download URL for %q: %s resp:%s", module, resp.Status, body)
204 }
205
206 // If location looks like it's trying to be a relative URL, treat it as
207 // one.
208 //
209 // We don't do this for just _any_ location, since the X-Terraform-Get
210 // header is a go-getter location rather than a URL, and so not all
211 // possible values will parse reasonably as URLs.)
212 //
213 // When used in conjunction with go-getter we normally require this header
214 // to be an absolute URL, but we are more liberal here because third-party
215 // registry implementations may not "know" their own absolute URLs if
216 // e.g. they are running behind a reverse proxy frontend, or such.
217 if strings.HasPrefix(location, "/") || strings.HasPrefix(location, "./") || strings.HasPrefix(location, "../") {
218 locationURL, err := url.Parse(location)
219 if err != nil {
220 return "", fmt.Errorf("invalid relative URL for %q: %s", module, err)
221 }
222 locationURL = download.ResolveReference(locationURL)
223 location = locationURL.String()
224 }
225
226 return location, nil
227}