...

Source file src/cloud.google.com/go/compute/metadata/metadata.go

Documentation: cloud.google.com/go/compute/metadata

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

View as plain text