]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/cloud.google.com/go/compute/metadata/metadata.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / cloud.google.com / go / compute / metadata / metadata.go
1 // Copyright 2014 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Package metadata provides access to Google Compute Engine (GCE)
16 // metadata and API service accounts.
17 //
18 // This package is a wrapper around the GCE metadata service,
19 // as documented at https://developers.google.com/compute/docs/metadata.
20 package metadata // import "cloud.google.com/go/compute/metadata"
21
22 import (
23 "context"
24 "encoding/json"
25 "fmt"
26 "io/ioutil"
27 "net"
28 "net/http"
29 "net/url"
30 "os"
31 "runtime"
32 "strings"
33 "sync"
34 "time"
35 )
36
37 const (
38 // metadataIP is the documented metadata server IP address.
39 metadataIP = "169.254.169.254"
40
41 // metadataHostEnv is the environment variable specifying the
42 // GCE metadata hostname. If empty, the default value of
43 // metadataIP ("169.254.169.254") is used instead.
44 // This is variable name is not defined by any spec, as far as
45 // I know; it was made up for the Go package.
46 metadataHostEnv = "GCE_METADATA_HOST"
47
48 userAgent = "gcloud-golang/0.1"
49 )
50
51 type cachedValue struct {
52 k string
53 trim bool
54 mu sync.Mutex
55 v string
56 }
57
58 var (
59 projID = &cachedValue{k: "project/project-id", trim: true}
60 projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
61 instID = &cachedValue{k: "instance/id", trim: true}
62 )
63
64 var (
65 defaultClient = &Client{hc: &http.Client{
66 Transport: &http.Transport{
67 Dial: (&net.Dialer{
68 Timeout: 2 * time.Second,
69 KeepAlive: 30 * time.Second,
70 }).Dial,
71 ResponseHeaderTimeout: 2 * time.Second,
72 },
73 }}
74 subscribeClient = &Client{hc: &http.Client{
75 Transport: &http.Transport{
76 Dial: (&net.Dialer{
77 Timeout: 2 * time.Second,
78 KeepAlive: 30 * time.Second,
79 }).Dial,
80 },
81 }}
82 )
83
84 // NotDefinedError is returned when requested metadata is not defined.
85 //
86 // The underlying string is the suffix after "/computeMetadata/v1/".
87 //
88 // This error is not returned if the value is defined to be the empty
89 // string.
90 type NotDefinedError string
91
92 func (suffix NotDefinedError) Error() string {
93 return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
94 }
95
96 func (c *cachedValue) get(cl *Client) (v string, err error) {
97 defer c.mu.Unlock()
98 c.mu.Lock()
99 if c.v != "" {
100 return c.v, nil
101 }
102 if c.trim {
103 v, err = cl.getTrimmed(c.k)
104 } else {
105 v, err = cl.Get(c.k)
106 }
107 if err == nil {
108 c.v = v
109 }
110 return
111 }
112
113 var (
114 onGCEOnce sync.Once
115 onGCE bool
116 )
117
118 // OnGCE reports whether this process is running on Google Compute Engine.
119 func OnGCE() bool {
120 onGCEOnce.Do(initOnGCE)
121 return onGCE
122 }
123
124 func initOnGCE() {
125 onGCE = testOnGCE()
126 }
127
128 func testOnGCE() bool {
129 // The user explicitly said they're on GCE, so trust them.
130 if os.Getenv(metadataHostEnv) != "" {
131 return true
132 }
133
134 ctx, cancel := context.WithCancel(context.Background())
135 defer cancel()
136
137 resc := make(chan bool, 2)
138
139 // Try two strategies in parallel.
140 // See https://github.com/googleapis/google-cloud-go/issues/194
141 go func() {
142 req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
143 req.Header.Set("User-Agent", userAgent)
144 res, err := defaultClient.hc.Do(req.WithContext(ctx))
145 if err != nil {
146 resc <- false
147 return
148 }
149 defer res.Body.Close()
150 resc <- res.Header.Get("Metadata-Flavor") == "Google"
151 }()
152
153 go func() {
154 addrs, err := net.LookupHost("metadata.google.internal")
155 if err != nil || len(addrs) == 0 {
156 resc <- false
157 return
158 }
159 resc <- strsContains(addrs, metadataIP)
160 }()
161
162 tryHarder := systemInfoSuggestsGCE()
163 if tryHarder {
164 res := <-resc
165 if res {
166 // The first strategy succeeded, so let's use it.
167 return true
168 }
169 // Wait for either the DNS or metadata server probe to
170 // contradict the other one and say we are running on
171 // GCE. Give it a lot of time to do so, since the system
172 // info already suggests we're running on a GCE BIOS.
173 timer := time.NewTimer(5 * time.Second)
174 defer timer.Stop()
175 select {
176 case res = <-resc:
177 return res
178 case <-timer.C:
179 // Too slow. Who knows what this system is.
180 return false
181 }
182 }
183
184 // There's no hint from the system info that we're running on
185 // GCE, so use the first probe's result as truth, whether it's
186 // true or false. The goal here is to optimize for speed for
187 // users who are NOT running on GCE. We can't assume that
188 // either a DNS lookup or an HTTP request to a blackholed IP
189 // address is fast. Worst case this should return when the
190 // metaClient's Transport.ResponseHeaderTimeout or
191 // Transport.Dial.Timeout fires (in two seconds).
192 return <-resc
193 }
194
195 // systemInfoSuggestsGCE reports whether the local system (without
196 // doing network requests) suggests that we're running on GCE. If this
197 // returns true, testOnGCE tries a bit harder to reach its metadata
198 // server.
199 func systemInfoSuggestsGCE() bool {
200 if runtime.GOOS != "linux" {
201 // We don't have any non-Linux clues available, at least yet.
202 return false
203 }
204 slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
205 name := strings.TrimSpace(string(slurp))
206 return name == "Google" || name == "Google Compute Engine"
207 }
208
209 // Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
210 // ResponseHeaderTimeout).
211 func Subscribe(suffix string, fn func(v string, ok bool) error) error {
212 return subscribeClient.Subscribe(suffix, fn)
213 }
214
215 // Get calls Client.Get on the default client.
216 func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
217
218 // ProjectID returns the current instance's project ID string.
219 func ProjectID() (string, error) { return defaultClient.ProjectID() }
220
221 // NumericProjectID returns the current instance's numeric project ID.
222 func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
223
224 // InternalIP returns the instance's primary internal IP address.
225 func InternalIP() (string, error) { return defaultClient.InternalIP() }
226
227 // ExternalIP returns the instance's primary external (public) IP address.
228 func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
229
230 // Hostname returns the instance's hostname. This will be of the form
231 // "<instanceID>.c.<projID>.internal".
232 func Hostname() (string, error) { return defaultClient.Hostname() }
233
234 // InstanceTags returns the list of user-defined instance tags,
235 // assigned when initially creating a GCE instance.
236 func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
237
238 // InstanceID returns the current VM's numeric instance ID.
239 func InstanceID() (string, error) { return defaultClient.InstanceID() }
240
241 // InstanceName returns the current VM's instance ID string.
242 func InstanceName() (string, error) { return defaultClient.InstanceName() }
243
244 // Zone returns the current VM's zone, such as "us-central1-b".
245 func Zone() (string, error) { return defaultClient.Zone() }
246
247 // InstanceAttributes calls Client.InstanceAttributes on the default client.
248 func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
249
250 // ProjectAttributes calls Client.ProjectAttributes on the default client.
251 func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
252
253 // InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
254 func InstanceAttributeValue(attr string) (string, error) {
255 return defaultClient.InstanceAttributeValue(attr)
256 }
257
258 // ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
259 func ProjectAttributeValue(attr string) (string, error) {
260 return defaultClient.ProjectAttributeValue(attr)
261 }
262
263 // Scopes calls Client.Scopes on the default client.
264 func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
265
266 func strsContains(ss []string, s string) bool {
267 for _, v := range ss {
268 if v == s {
269 return true
270 }
271 }
272 return false
273 }
274
275 // A Client provides metadata.
276 type Client struct {
277 hc *http.Client
278 }
279
280 // NewClient returns a Client that can be used to fetch metadata. All HTTP requests
281 // will use the given http.Client instead of the default client.
282 func NewClient(c *http.Client) *Client {
283 return &Client{hc: c}
284 }
285
286 // getETag returns a value from the metadata service as well as the associated ETag.
287 // This func is otherwise equivalent to Get.
288 func (c *Client) getETag(suffix string) (value, etag string, err error) {
289 // Using a fixed IP makes it very difficult to spoof the metadata service in
290 // a container, which is an important use-case for local testing of cloud
291 // deployments. To enable spoofing of the metadata service, the environment
292 // variable GCE_METADATA_HOST is first inspected to decide where metadata
293 // requests shall go.
294 host := os.Getenv(metadataHostEnv)
295 if host == "" {
296 // Using 169.254.169.254 instead of "metadata" here because Go
297 // binaries built with the "netgo" tag and without cgo won't
298 // know the search suffix for "metadata" is
299 // ".google.internal", and this IP address is documented as
300 // being stable anyway.
301 host = metadataIP
302 }
303 u := "http://" + host + "/computeMetadata/v1/" + suffix
304 req, _ := http.NewRequest("GET", u, nil)
305 req.Header.Set("Metadata-Flavor", "Google")
306 req.Header.Set("User-Agent", userAgent)
307 res, err := c.hc.Do(req)
308 if err != nil {
309 return "", "", err
310 }
311 defer res.Body.Close()
312 if res.StatusCode == http.StatusNotFound {
313 return "", "", NotDefinedError(suffix)
314 }
315 all, err := ioutil.ReadAll(res.Body)
316 if err != nil {
317 return "", "", err
318 }
319 if res.StatusCode != 200 {
320 return "", "", &Error{Code: res.StatusCode, Message: string(all)}
321 }
322 return string(all), res.Header.Get("Etag"), nil
323 }
324
325 // Get returns a value from the metadata service.
326 // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
327 //
328 // If the GCE_METADATA_HOST environment variable is not defined, a default of
329 // 169.254.169.254 will be used instead.
330 //
331 // If the requested metadata is not defined, the returned error will
332 // be of type NotDefinedError.
333 func (c *Client) Get(suffix string) (string, error) {
334 val, _, err := c.getETag(suffix)
335 return val, err
336 }
337
338 func (c *Client) getTrimmed(suffix string) (s string, err error) {
339 s, err = c.Get(suffix)
340 s = strings.TrimSpace(s)
341 return
342 }
343
344 func (c *Client) lines(suffix string) ([]string, error) {
345 j, err := c.Get(suffix)
346 if err != nil {
347 return nil, err
348 }
349 s := strings.Split(strings.TrimSpace(j), "\n")
350 for i := range s {
351 s[i] = strings.TrimSpace(s[i])
352 }
353 return s, nil
354 }
355
356 // ProjectID returns the current instance's project ID string.
357 func (c *Client) ProjectID() (string, error) { return projID.get(c) }
358
359 // NumericProjectID returns the current instance's numeric project ID.
360 func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
361
362 // InstanceID returns the current VM's numeric instance ID.
363 func (c *Client) InstanceID() (string, error) { return instID.get(c) }
364
365 // InternalIP returns the instance's primary internal IP address.
366 func (c *Client) InternalIP() (string, error) {
367 return c.getTrimmed("instance/network-interfaces/0/ip")
368 }
369
370 // ExternalIP returns the instance's primary external (public) IP address.
371 func (c *Client) ExternalIP() (string, error) {
372 return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
373 }
374
375 // Hostname returns the instance's hostname. This will be of the form
376 // "<instanceID>.c.<projID>.internal".
377 func (c *Client) Hostname() (string, error) {
378 return c.getTrimmed("instance/hostname")
379 }
380
381 // InstanceTags returns the list of user-defined instance tags,
382 // assigned when initially creating a GCE instance.
383 func (c *Client) InstanceTags() ([]string, error) {
384 var s []string
385 j, err := c.Get("instance/tags")
386 if err != nil {
387 return nil, err
388 }
389 if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
390 return nil, err
391 }
392 return s, nil
393 }
394
395 // InstanceName returns the current VM's instance ID string.
396 func (c *Client) InstanceName() (string, error) {
397 host, err := c.Hostname()
398 if err != nil {
399 return "", err
400 }
401 return strings.Split(host, ".")[0], nil
402 }
403
404 // Zone returns the current VM's zone, such as "us-central1-b".
405 func (c *Client) Zone() (string, error) {
406 zone, err := c.getTrimmed("instance/zone")
407 // zone is of the form "projects/<projNum>/zones/<zoneName>".
408 if err != nil {
409 return "", err
410 }
411 return zone[strings.LastIndex(zone, "/")+1:], nil
412 }
413
414 // InstanceAttributes returns the list of user-defined attributes,
415 // assigned when initially creating a GCE VM instance. The value of an
416 // attribute can be obtained with InstanceAttributeValue.
417 func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
418
419 // ProjectAttributes returns the list of user-defined attributes
420 // applying to the project as a whole, not just this VM. The value of
421 // an attribute can be obtained with ProjectAttributeValue.
422 func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
423
424 // InstanceAttributeValue returns the value of the provided VM
425 // instance attribute.
426 //
427 // If the requested attribute is not defined, the returned error will
428 // be of type NotDefinedError.
429 //
430 // InstanceAttributeValue may return ("", nil) if the attribute was
431 // defined to be the empty string.
432 func (c *Client) InstanceAttributeValue(attr string) (string, error) {
433 return c.Get("instance/attributes/" + attr)
434 }
435
436 // ProjectAttributeValue returns the value of the provided
437 // project attribute.
438 //
439 // If the requested attribute is not defined, the returned error will
440 // be of type NotDefinedError.
441 //
442 // ProjectAttributeValue may return ("", nil) if the attribute was
443 // defined to be the empty string.
444 func (c *Client) ProjectAttributeValue(attr string) (string, error) {
445 return c.Get("project/attributes/" + attr)
446 }
447
448 // Scopes returns the service account scopes for the given account.
449 // The account may be empty or the string "default" to use the instance's
450 // main account.
451 func (c *Client) Scopes(serviceAccount string) ([]string, error) {
452 if serviceAccount == "" {
453 serviceAccount = "default"
454 }
455 return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
456 }
457
458 // Subscribe subscribes to a value from the metadata service.
459 // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
460 // The suffix may contain query parameters.
461 //
462 // Subscribe calls fn with the latest metadata value indicated by the provided
463 // suffix. If the metadata value is deleted, fn is called with the empty string
464 // and ok false. Subscribe blocks until fn returns a non-nil error or the value
465 // is deleted. Subscribe returns the error value returned from the last call to
466 // fn, which may be nil when ok == false.
467 func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
468 const failedSubscribeSleep = time.Second * 5
469
470 // First check to see if the metadata value exists at all.
471 val, lastETag, err := c.getETag(suffix)
472 if err != nil {
473 return err
474 }
475
476 if err := fn(val, true); err != nil {
477 return err
478 }
479
480 ok := true
481 if strings.ContainsRune(suffix, '?') {
482 suffix += "&wait_for_change=true&last_etag="
483 } else {
484 suffix += "?wait_for_change=true&last_etag="
485 }
486 for {
487 val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
488 if err != nil {
489 if _, deleted := err.(NotDefinedError); !deleted {
490 time.Sleep(failedSubscribeSleep)
491 continue // Retry on other errors.
492 }
493 ok = false
494 }
495 lastETag = etag
496
497 if err := fn(val, ok); err != nil || !ok {
498 return err
499 }
500 }
501 }
502
503 // Error contains an error response from the server.
504 type Error struct {
505 // Code is the HTTP response status code.
506 Code int
507 // Message is the server response message.
508 Message string
509 }
510
511 func (e *Error) Error() string {
512 return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
513 }