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