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.
20package metadata // import "cloud.google.com/go/compute/metadata"
21
22import (
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
37const (
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
51type cachedValue struct {
52	k    string
53	trim bool
54	mu   sync.Mutex
55	v    string
56}
57
58var (
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
64var (
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.
90type NotDefinedError string
91
92func (suffix NotDefinedError) Error() string {
93	return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
94}
95
96func (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
113var (
114	onGCEOnce sync.Once
115	onGCE     bool
116)
117
118// OnGCE reports whether this process is running on Google Compute Engine.
119func OnGCE() bool {
120	onGCEOnce.Do(initOnGCE)
121	return onGCE
122}
123
124func initOnGCE() {
125	onGCE = testOnGCE()
126}
127
128func 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.
199func 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).
211func 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.
216func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
217
218// ProjectID returns the current instance's project ID string.
219func ProjectID() (string, error) { return defaultClient.ProjectID() }
220
221// NumericProjectID returns the current instance's numeric project ID.
222func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
223
224// InternalIP returns the instance's primary internal IP address.
225func InternalIP() (string, error) { return defaultClient.InternalIP() }
226
227// ExternalIP returns the instance's primary external (public) IP address.
228func 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".
232func 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.
236func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
237
238// InstanceID returns the current VM's numeric instance ID.
239func InstanceID() (string, error) { return defaultClient.InstanceID() }
240
241// InstanceName returns the current VM's instance ID string.
242func InstanceName() (string, error) { return defaultClient.InstanceName() }
243
244// Zone returns the current VM's zone, such as "us-central1-b".
245func Zone() (string, error) { return defaultClient.Zone() }
246
247// InstanceAttributes calls Client.InstanceAttributes on the default client.
248func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
249
250// ProjectAttributes calls Client.ProjectAttributes on the default client.
251func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
252
253// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
254func InstanceAttributeValue(attr string) (string, error) {
255	return defaultClient.InstanceAttributeValue(attr)
256}
257
258// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
259func ProjectAttributeValue(attr string) (string, error) {
260	return defaultClient.ProjectAttributeValue(attr)
261}
262
263// Scopes calls Client.Scopes on the default client.
264func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
265
266func 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.
276type 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.
282func 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.
288func (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.
333func (c *Client) Get(suffix string) (string, error) {
334	val, _, err := c.getETag(suffix)
335	return val, err
336}
337
338func (c *Client) getTrimmed(suffix string) (s string, err error) {
339	s, err = c.Get(suffix)
340	s = strings.TrimSpace(s)
341	return
342}
343
344func (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.
357func (c *Client) ProjectID() (string, error) { return projID.get(c) }
358
359// NumericProjectID returns the current instance's numeric project ID.
360func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
361
362// InstanceID returns the current VM's numeric instance ID.
363func (c *Client) InstanceID() (string, error) { return instID.get(c) }
364
365// InternalIP returns the instance's primary internal IP address.
366func (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.
371func (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".
377func (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.
383func (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.
396func (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".
405func (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.
417func (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.
422func (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.
432func (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.
444func (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.
451func (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.
467func (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.
504type 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
511func (e *Error) Error() string {
512	return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
513}
514