github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/Godeps/_workspace/src/google.golang.org/cloud/compute/metadata/metadata.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     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
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net"
    27  	"net/http"
    28  	"net/url"
    29  	"os"
    30  	"strings"
    31  	"sync"
    32  	"time"
    33  
    34  	"google.golang.org/cloud/internal"
    35  )
    36  
    37  // metadataIP is the documented metadata server IP address.
    38  const metadataIP = "169.254.169.254"
    39  
    40  type cachedValue struct {
    41  	k    string
    42  	trim bool
    43  	mu   sync.Mutex
    44  	v    string
    45  }
    46  
    47  var (
    48  	projID  = &cachedValue{k: "project/project-id", trim: true}
    49  	projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
    50  	instID  = &cachedValue{k: "instance/id", trim: true}
    51  )
    52  
    53  var (
    54  	metaClient = &http.Client{
    55  		Transport: &internal.Transport{
    56  			Base: &http.Transport{
    57  				Dial: (&net.Dialer{
    58  					Timeout:   2 * time.Second,
    59  					KeepAlive: 30 * time.Second,
    60  				}).Dial,
    61  				ResponseHeaderTimeout: 2 * time.Second,
    62  			},
    63  		},
    64  	}
    65  	subscribeClient = &http.Client{
    66  		Transport: &internal.Transport{
    67  			Base: &http.Transport{
    68  				Dial: (&net.Dialer{
    69  					Timeout:   2 * time.Second,
    70  					KeepAlive: 30 * time.Second,
    71  				}).Dial,
    72  			},
    73  		},
    74  	}
    75  )
    76  
    77  // NotDefinedError is returned when requested metadata is not defined.
    78  //
    79  // The underlying string is the suffix after "/computeMetadata/v1/".
    80  //
    81  // This error is not returned if the value is defined to be the empty
    82  // string.
    83  type NotDefinedError string
    84  
    85  func (suffix NotDefinedError) Error() string {
    86  	return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
    87  }
    88  
    89  // Get returns a value from the metadata service.
    90  // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
    91  //
    92  // If the GCE_METADATA_HOST environment variable is not defined, a default of
    93  // 169.254.169.254 will be used instead.
    94  //
    95  // If the requested metadata is not defined, the returned error will
    96  // be of type NotDefinedError.
    97  func Get(suffix string) (string, error) {
    98  	val, _, err := getETag(metaClient, suffix)
    99  	return val, err
   100  }
   101  
   102  // getETag returns a value from the metadata service as well as the associated
   103  // ETag using the provided client. This func is otherwise equivalent to Get.
   104  func getETag(client *http.Client, suffix string) (value, etag string, err error) {
   105  	// Using a fixed IP makes it very difficult to spoof the metadata service in
   106  	// a container, which is an important use-case for local testing of cloud
   107  	// deployments. To enable spoofing of the metadata service, the environment
   108  	// variable GCE_METADATA_HOST is first inspected to decide where metadata
   109  	// requests shall go.
   110  	host := os.Getenv("GCE_METADATA_HOST")
   111  	if host == "" {
   112  		// Using 169.254.169.254 instead of "metadata" here because Go
   113  		// binaries built with the "netgo" tag and without cgo won't
   114  		// know the search suffix for "metadata" is
   115  		// ".google.internal", and this IP address is documented as
   116  		// being stable anyway.
   117  		host = metadataIP
   118  	}
   119  	url := "http://" + host + "/computeMetadata/v1/" + suffix
   120  	req, _ := http.NewRequest("GET", url, nil)
   121  	req.Header.Set("Metadata-Flavor", "Google")
   122  	res, err := client.Do(req)
   123  	if err != nil {
   124  		return "", "", err
   125  	}
   126  	defer res.Body.Close()
   127  	if res.StatusCode == http.StatusNotFound {
   128  		return "", "", NotDefinedError(suffix)
   129  	}
   130  	if res.StatusCode != 200 {
   131  		return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
   132  	}
   133  	all, err := ioutil.ReadAll(res.Body)
   134  	if err != nil {
   135  		return "", "", err
   136  	}
   137  	return string(all), res.Header.Get("Etag"), nil
   138  }
   139  
   140  func getTrimmed(suffix string) (s string, err error) {
   141  	s, err = Get(suffix)
   142  	s = strings.TrimSpace(s)
   143  	return
   144  }
   145  
   146  func (c *cachedValue) get() (v string, err error) {
   147  	defer c.mu.Unlock()
   148  	c.mu.Lock()
   149  	if c.v != "" {
   150  		return c.v, nil
   151  	}
   152  	if c.trim {
   153  		v, err = getTrimmed(c.k)
   154  	} else {
   155  		v, err = Get(c.k)
   156  	}
   157  	if err == nil {
   158  		c.v = v
   159  	}
   160  	return
   161  }
   162  
   163  var onGCE struct {
   164  	sync.Mutex
   165  	set bool
   166  	v   bool
   167  }
   168  
   169  // OnGCE reports whether this process is running on Google Compute Engine.
   170  func OnGCE() bool {
   171  	defer onGCE.Unlock()
   172  	onGCE.Lock()
   173  	if onGCE.set {
   174  		return onGCE.v
   175  	}
   176  	onGCE.set = true
   177  	onGCE.v = testOnGCE()
   178  	return onGCE.v
   179  }
   180  
   181  func testOnGCE() bool {
   182  	cancel := make(chan struct{})
   183  	defer close(cancel)
   184  
   185  	resc := make(chan bool, 2)
   186  
   187  	// Try two strategies in parallel.
   188  	// See https://github.com/GoogleCloudPlatform/gcloud-golang/issues/194
   189  	go func() {
   190  		req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
   191  		req.Cancel = cancel
   192  		res, err := metaClient.Do(req)
   193  		if err != nil {
   194  			resc <- false
   195  			return
   196  		}
   197  		defer res.Body.Close()
   198  		resc <- res.Header.Get("Metadata-Flavor") == "Google"
   199  	}()
   200  
   201  	go func() {
   202  		addrs, err := net.LookupHost("metadata.google.internal")
   203  		if err != nil || len(addrs) == 0 {
   204  			resc <- false
   205  			return
   206  		}
   207  		resc <- strsContains(addrs, metadataIP)
   208  	}()
   209  
   210  	return <-resc
   211  }
   212  
   213  // Subscribe subscribes to a value from the metadata service.
   214  // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
   215  // The suffix may contain query parameters.
   216  //
   217  // Subscribe calls fn with the latest metadata value indicated by the provided
   218  // suffix. If the metadata value is deleted, fn is called with the empty string
   219  // and ok false. Subscribe blocks until fn returns a non-nil error or the value
   220  // is deleted. Subscribe returns the error value returned from the last call to
   221  // fn, which may be nil when ok == false.
   222  func Subscribe(suffix string, fn func(v string, ok bool) error) error {
   223  	const failedSubscribeSleep = time.Second * 5
   224  
   225  	// First check to see if the metadata value exists at all.
   226  	val, lastETag, err := getETag(subscribeClient, suffix)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	if err := fn(val, true); err != nil {
   232  		return err
   233  	}
   234  
   235  	ok := true
   236  	if strings.ContainsRune(suffix, '?') {
   237  		suffix += "&wait_for_change=true&last_etag="
   238  	} else {
   239  		suffix += "?wait_for_change=true&last_etag="
   240  	}
   241  	for {
   242  		val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
   243  		if err != nil {
   244  			if _, deleted := err.(NotDefinedError); !deleted {
   245  				time.Sleep(failedSubscribeSleep)
   246  				continue // Retry on other errors.
   247  			}
   248  			ok = false
   249  		}
   250  		lastETag = etag
   251  
   252  		if err := fn(val, ok); err != nil || !ok {
   253  			return err
   254  		}
   255  	}
   256  }
   257  
   258  // ProjectID returns the current instance's project ID string.
   259  func ProjectID() (string, error) { return projID.get() }
   260  
   261  // NumericProjectID returns the current instance's numeric project ID.
   262  func NumericProjectID() (string, error) { return projNum.get() }
   263  
   264  // InternalIP returns the instance's primary internal IP address.
   265  func InternalIP() (string, error) {
   266  	return getTrimmed("instance/network-interfaces/0/ip")
   267  }
   268  
   269  // ExternalIP returns the instance's primary external (public) IP address.
   270  func ExternalIP() (string, error) {
   271  	return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
   272  }
   273  
   274  // Hostname returns the instance's hostname. This will be of the form
   275  // "<instanceID>.c.<projID>.internal".
   276  func Hostname() (string, error) {
   277  	return getTrimmed("instance/hostname")
   278  }
   279  
   280  // InstanceTags returns the list of user-defined instance tags,
   281  // assigned when initially creating a GCE instance.
   282  func InstanceTags() ([]string, error) {
   283  	var s []string
   284  	j, err := Get("instance/tags")
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
   289  		return nil, err
   290  	}
   291  	return s, nil
   292  }
   293  
   294  // InstanceID returns the current VM's numeric instance ID.
   295  func InstanceID() (string, error) {
   296  	return instID.get()
   297  }
   298  
   299  // InstanceName returns the current VM's instance ID string.
   300  func InstanceName() (string, error) {
   301  	host, err := Hostname()
   302  	if err != nil {
   303  		return "", err
   304  	}
   305  	return strings.Split(host, ".")[0], nil
   306  }
   307  
   308  // Zone returns the current VM's zone, such as "us-central1-b".
   309  func Zone() (string, error) {
   310  	zone, err := getTrimmed("instance/zone")
   311  	// zone is of the form "projects/<projNum>/zones/<zoneName>".
   312  	if err != nil {
   313  		return "", err
   314  	}
   315  	return zone[strings.LastIndex(zone, "/")+1:], nil
   316  }
   317  
   318  // InstanceAttributes returns the list of user-defined attributes,
   319  // assigned when initially creating a GCE VM instance. The value of an
   320  // attribute can be obtained with InstanceAttributeValue.
   321  func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
   322  
   323  // ProjectAttributes returns the list of user-defined attributes
   324  // applying to the project as a whole, not just this VM.  The value of
   325  // an attribute can be obtained with ProjectAttributeValue.
   326  func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
   327  
   328  func lines(suffix string) ([]string, error) {
   329  	j, err := Get(suffix)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	s := strings.Split(strings.TrimSpace(j), "\n")
   334  	for i := range s {
   335  		s[i] = strings.TrimSpace(s[i])
   336  	}
   337  	return s, nil
   338  }
   339  
   340  // InstanceAttributeValue returns the value of the provided VM
   341  // instance attribute.
   342  //
   343  // If the requested attribute is not defined, the returned error will
   344  // be of type NotDefinedError.
   345  //
   346  // InstanceAttributeValue may return ("", nil) if the attribute was
   347  // defined to be the empty string.
   348  func InstanceAttributeValue(attr string) (string, error) {
   349  	return Get("instance/attributes/" + attr)
   350  }
   351  
   352  // ProjectAttributeValue returns the value of the provided
   353  // project attribute.
   354  //
   355  // If the requested attribute is not defined, the returned error will
   356  // be of type NotDefinedError.
   357  //
   358  // ProjectAttributeValue may return ("", nil) if the attribute was
   359  // defined to be the empty string.
   360  func ProjectAttributeValue(attr string) (string, error) {
   361  	return Get("project/attributes/" + attr)
   362  }
   363  
   364  // Scopes returns the service account scopes for the given account.
   365  // The account may be empty or the string "default" to use the instance's
   366  // main account.
   367  func Scopes(serviceAccount string) ([]string, error) {
   368  	if serviceAccount == "" {
   369  		serviceAccount = "default"
   370  	}
   371  	return lines("instance/service-accounts/" + serviceAccount + "/scopes")
   372  }
   373  
   374  func strsContains(ss []string, s string) bool {
   375  	for _, v := range ss {
   376  		if v == s {
   377  			return true
   378  		}
   379  	}
   380  	return false
   381  }