Copyright 2014 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Package metadata provides access to Google Compute Engine (GCE) metadata and API service accounts. This package is a wrapper around the GCE metadata service, as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "cloud.google.com/go/compute/metadata"

import (
	
	
	
	
	
	
	
	
	
	
	
	
)

metadataIP is the documented metadata server IP address.
	metadataIP = "169.254.169.254"
metadataHostEnv is the environment variable specifying the GCE metadata hostname. If empty, the default value of metadataIP ("169.254.169.254") is used instead. This is variable name is not defined by any spec, as far as I know; it was made up for the Go package.
	metadataHostEnv = "GCE_METADATA_HOST"

	userAgent = "gcloud-golang/0.1"
)

type cachedValue struct {
	k    string
	trim bool
	mu   sync.Mutex
	v    string
}

var (
	projID  = &cachedValue{k: "project/project-id", trim: true}
	projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
	instID  = &cachedValue{k: "instance/id", trim: true}
)

var defaultClient = &Client{hc: &http.Client{
	Transport: &http.Transport{
		Dial: (&net.Dialer{
			Timeout:   2 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
	},
}}
NotDefinedError is returned when requested metadata is not defined. The underlying string is the suffix after "/computeMetadata/v1/". This error is not returned if the value is defined to be the empty string.
type NotDefinedError string

func ( NotDefinedError) () string {
	return fmt.Sprintf("metadata: GCE metadata %q not defined", string())
}

func ( *cachedValue) ( *Client) ( string,  error) {
	defer .mu.Unlock()
	.mu.Lock()
	if .v != "" {
		return .v, nil
	}
	if .trim {
		,  = .getTrimmed(.k)
	} else {
		,  = .Get(.k)
	}
	if  == nil {
		.v = 
	}
	return
}

var (
	onGCEOnce sync.Once
	onGCE     bool
)
OnGCE reports whether this process is running on Google Compute Engine.
func () bool {
	onGCEOnce.Do(initOnGCE)
	return onGCE
}

func () {
	onGCE = testOnGCE()
}

The user explicitly said they're on GCE, so trust them.
	if os.Getenv(metadataHostEnv) != "" {
		return true
	}

	,  := context.WithCancel(context.Background())
	defer ()

	 := make(chan bool, 2)
Try two strategies in parallel. See https://github.com/googleapis/google-cloud-go/issues/194
	go func() {
		,  := http.NewRequest("GET", "http://"+metadataIP, nil)
		.Header.Set("User-Agent", userAgent)
		,  := defaultClient.hc.Do(.WithContext())
		if  != nil {
			 <- false
			return
		}
		defer .Body.Close()
		 <- .Header.Get("Metadata-Flavor") == "Google"
	}()

	go func() {
		,  := net.DefaultResolver.LookupHost(, "metadata.google.internal")
		if  != nil || len() == 0 {
			 <- false
			return
		}
		 <- strsContains(, metadataIP)
	}()

	 := systemInfoSuggestsGCE()
	if  {
		 := <-
The first strategy succeeded, so let's use it.
			return true
Wait for either the DNS or metadata server probe to contradict the other one and say we are running on GCE. Give it a lot of time to do so, since the system info already suggests we're running on a GCE BIOS.
		 := time.NewTimer(5 * time.Second)
		defer .Stop()
		select {
		case  = <-:
			return 
Too slow. Who knows what this system is.
			return false
		}
	}
There's no hint from the system info that we're running on GCE, so use the first probe's result as truth, whether it's true or false. The goal here is to optimize for speed for users who are NOT running on GCE. We can't assume that either a DNS lookup or an HTTP request to a blackholed IP address is fast. Worst case this should return when the metaClient's Transport.ResponseHeaderTimeout or Transport.Dial.Timeout fires (in two seconds).
	return <-
}
systemInfoSuggestsGCE reports whether the local system (without doing network requests) suggests that we're running on GCE. If this returns true, testOnGCE tries a bit harder to reach its metadata server.
We don't have any non-Linux clues available, at least yet.
		return false
	}
	,  := ioutil.ReadFile("/sys/class/dmi/id/product_name")
	 := strings.TrimSpace(string())
	return  == "Google" ||  == "Google Compute Engine"
}
Subscribe calls Client.Subscribe on the default client.
func ( string,  func( string,  bool) error) error {
	return defaultClient.Subscribe(, )
}
Get calls Client.Get on the default client.
func ( string) (string, error) { return defaultClient.Get() }
ProjectID returns the current instance's project ID string.
func () (string, error) { return defaultClient.ProjectID() }
NumericProjectID returns the current instance's numeric project ID.
InternalIP returns the instance's primary internal IP address.
ExternalIP returns the instance's primary external (public) IP address.
Email calls Client.Email on the default client.
func ( string) (string, error) { return defaultClient.Email() }
Hostname returns the instance's hostname. This will be of the form "<instanceID>.c.<projID>.internal".
func () (string, error) { return defaultClient.Hostname() }
InstanceTags returns the list of user-defined instance tags, assigned when initially creating a GCE instance.
InstanceID returns the current VM's numeric instance ID.
InstanceName returns the current VM's instance ID string.
Zone returns the current VM's zone, such as "us-central1-b".
func () (string, error) { return defaultClient.Zone() }
InstanceAttributes calls Client.InstanceAttributes on the default client.
ProjectAttributes calls Client.ProjectAttributes on the default client.
InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
Scopes calls Client.Scopes on the default client.
func ( string) ([]string, error) { return defaultClient.Scopes() }

func ( []string,  string) bool {
	for ,  := range  {
		if  ==  {
			return true
		}
	}
	return false
}
A Client provides metadata.
type Client struct {
	hc *http.Client
}
NewClient returns a Client that can be used to fetch metadata. Returns the client that uses the specified http.Client for HTTP requests. If nil is specified, returns the default client.
func ( *http.Client) *Client {
	if  == nil {
		return defaultClient
	}

	return &Client{hc: }
}
getETag returns a value from the metadata service as well as the associated ETag. This func is otherwise equivalent to Get.
Using a fixed IP makes it very difficult to spoof the metadata service in a container, which is an important use-case for local testing of cloud deployments. To enable spoofing of the metadata service, the environment variable GCE_METADATA_HOST is first inspected to decide where metadata requests shall go.
Using 169.254.169.254 instead of "metadata" here because Go binaries built with the "netgo" tag and without cgo won't know the search suffix for "metadata" is ".google.internal", and this IP address is documented as being stable anyway.
		 = metadataIP
	}
	 = strings.TrimLeft(, "/")
	 := "http://" +  + "/computeMetadata/v1/" + 
	,  := http.NewRequest("GET", , nil)
	if  != nil {
		return "", "", 
	}
	.Header.Set("Metadata-Flavor", "Google")
	.Header.Set("User-Agent", userAgent)
	,  := .hc.Do()
	if  != nil {
		return "", "", 
	}
	defer .Body.Close()
	if .StatusCode == http.StatusNotFound {
		return "", "", NotDefinedError()
	}
	,  := ioutil.ReadAll(.Body)
	if  != nil {
		return "", "", 
	}
	if .StatusCode != 200 {
		return "", "", &Error{Code: .StatusCode, Message: string()}
	}
	return string(), .Header.Get("Etag"), nil
}
Get returns a value from the metadata service. The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". If the GCE_METADATA_HOST environment variable is not defined, a default of 169.254.169.254 will be used instead. If the requested metadata is not defined, the returned error will be of type NotDefinedError.
func ( *Client) ( string) (string, error) {
	, ,  := .getETag()
	return , 
}

func ( *Client) ( string) ( string,  error) {
	,  = .Get()
	 = strings.TrimSpace()
	return
}

func ( *Client) ( string) ([]string, error) {
	,  := .Get()
	if  != nil {
		return nil, 
	}
	 := strings.Split(strings.TrimSpace(), "\n")
	for  := range  {
		[] = strings.TrimSpace([])
	}
	return , nil
}
ProjectID returns the current instance's project ID string.
func ( *Client) () (string, error) { return projID.get() }
NumericProjectID returns the current instance's numeric project ID.
func ( *Client) () (string, error) { return projNum.get() }
InstanceID returns the current VM's numeric instance ID.
func ( *Client) () (string, error) { return instID.get() }
InternalIP returns the instance's primary internal IP address.
func ( *Client) () (string, error) {
	return .getTrimmed("instance/network-interfaces/0/ip")
}
Email returns the email address associated with the service account. The account may be empty or the string "default" to use the instance's main account.
func ( *Client) ( string) (string, error) {
	if  == "" {
		 = "default"
	}
	return .getTrimmed("instance/service-accounts/" +  + "/email")
}
ExternalIP returns the instance's primary external (public) IP address.
func ( *Client) () (string, error) {
	return .getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
Hostname returns the instance's hostname. This will be of the form "<instanceID>.c.<projID>.internal".
func ( *Client) () (string, error) {
	return .getTrimmed("instance/hostname")
}
InstanceTags returns the list of user-defined instance tags, assigned when initially creating a GCE instance.
func ( *Client) () ([]string, error) {
	var  []string
	,  := .Get("instance/tags")
	if  != nil {
		return nil, 
	}
	if  := json.NewDecoder(strings.NewReader()).Decode(&);  != nil {
		return nil, 
	}
	return , nil
}
InstanceName returns the current VM's instance ID string.
func ( *Client) () (string, error) {
	return .getTrimmed("instance/name")
}
Zone returns the current VM's zone, such as "us-central1-b".
func ( *Client) () (string, error) {
zone is of the form "projects/<projNum>/zones/<zoneName>".
	if  != nil {
		return "", 
	}
	return [strings.LastIndex(, "/")+1:], nil
}
InstanceAttributes returns the list of user-defined attributes, assigned when initially creating a GCE VM instance. The value of an attribute can be obtained with InstanceAttributeValue.
func ( *Client) () ([]string, error) { return .lines("instance/attributes/") }
ProjectAttributes returns the list of user-defined attributes applying to the project as a whole, not just this VM. The value of an attribute can be obtained with ProjectAttributeValue.
func ( *Client) () ([]string, error) { return .lines("project/attributes/") }
InstanceAttributeValue returns the value of the provided VM instance attribute. If the requested attribute is not defined, the returned error will be of type NotDefinedError. InstanceAttributeValue may return ("", nil) if the attribute was defined to be the empty string.
func ( *Client) ( string) (string, error) {
	return .Get("instance/attributes/" + )
}
ProjectAttributeValue returns the value of the provided project attribute. If the requested attribute is not defined, the returned error will be of type NotDefinedError. ProjectAttributeValue may return ("", nil) if the attribute was defined to be the empty string.
func ( *Client) ( string) (string, error) {
	return .Get("project/attributes/" + )
}
Scopes returns the service account scopes for the given account. The account may be empty or the string "default" to use the instance's main account.
func ( *Client) ( string) ([]string, error) {
	if  == "" {
		 = "default"
	}
	return .lines("instance/service-accounts/" +  + "/scopes")
}
Subscribe subscribes to a value from the metadata service. The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". The suffix may contain query parameters. Subscribe calls fn with the latest metadata value indicated by the provided suffix. If the metadata value is deleted, fn is called with the empty string and ok false. Subscribe blocks until fn returns a non-nil error or the value is deleted. Subscribe returns the error value returned from the last call to fn, which may be nil when ok == false.
func ( *Client) ( string,  func( string,  bool) error) error {
	const  = time.Second * 5
First check to see if the metadata value exists at all.
	, ,  := .getETag()
	if  != nil {
		return 
	}

	if  := (, true);  != nil {
		return 
	}

	 := true
	if strings.ContainsRune(, '?') {
		 += "&wait_for_change=true&last_etag="
	} else {
		 += "?wait_for_change=true&last_etag="
	}
	for {
		, ,  := .getETag( + url.QueryEscape())
		if  != nil {
			if ,  := .(NotDefinedError); ! {
				time.Sleep()
				continue // Retry on other errors.
			}
			 = false
		}
		 = 

		if  := (, );  != nil || ! {
			return 
		}
	}
}
Error contains an error response from the server.
Code is the HTTP response status code.
Message is the server response message.
	Message string
}

func ( *Error) () string {
	return fmt.Sprintf("compute: Received %d `%s`", .Code, .Message)