diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/registry/client.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/registry/client.go | 227 |
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 @@ | |||
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 | serviceID = "modules.v1" | ||
27 | ) | ||
28 | |||
29 | var tfVersion = version.String() | ||
30 | |||
31 | // Client provides methods to query Terraform Registries. | ||
32 | type 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. | ||
42 | func 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. | ||
61 | func (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. | ||
73 | func (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 | |||
132 | func (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. | ||
146 | func (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 | } | ||