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 "runtime" 31 "strings" 32 "sync" 33 "time" 34 35 "golang.org/x/net/context" 36 "golang.org/x/net/context/ctxhttp" 37 ) 38 39 const ( 40 // metadataIP is the documented metadata server IP address. 41 metadataIP = "169.254.169.254" 42 43 // metadataHostEnv is the environment variable specifying the 44 // GCE metadata hostname. If empty, the default value of 45 // metadataIP ("169.254.169.254") is used instead. 46 // This is variable name is not defined by any spec, as far as 47 // I know; it was made up for the Go package. 48 metadataHostEnv = "GCE_METADATA_HOST" 49 50 userAgent = "gcloud-golang/0.1" 51 ) 52 53 type cachedValue struct { 54 k string 55 trim bool 56 mu sync.Mutex 57 v string 58 } 59 60 var ( 61 projID = &cachedValue{k: "project/project-id", trim: true} 62 projNum = &cachedValue{k: "project/numeric-project-id", trim: true} 63 instID = &cachedValue{k: "instance/id", trim: true} 64 ) 65 66 var ( 67 metaClient = &http.Client{ 68 Transport: &http.Transport{ 69 Dial: (&net.Dialer{ 70 Timeout: 2 * time.Second, 71 KeepAlive: 30 * time.Second, 72 }).Dial, 73 ResponseHeaderTimeout: 2 * time.Second, 74 }, 75 } 76 subscribeClient = &http.Client{ 77 Transport: &http.Transport{ 78 Dial: (&net.Dialer{ 79 Timeout: 2 * time.Second, 80 KeepAlive: 30 * time.Second, 81 }).Dial, 82 }, 83 } 84 ) 85 86 // NotDefinedError is returned when requested metadata is not defined. 87 // 88 // The underlying string is the suffix after "/computeMetadata/v1/". 89 // 90 // This error is not returned if the value is defined to be the empty 91 // string. 92 type NotDefinedError string 93 94 func (suffix NotDefinedError) Error() string { 95 return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) 96 } 97 98 // Get returns a value from the metadata service. 99 // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". 100 // 101 // If the GCE_METADATA_HOST environment variable is not defined, a default of 102 // 169.254.169.254 will be used instead. 103 // 104 // If the requested metadata is not defined, the returned error will 105 // be of type NotDefinedError. 106 func Get(suffix string) (string, error) { 107 val, _, err := getETag(metaClient, suffix) 108 return val, err 109 } 110 111 // getETag returns a value from the metadata service as well as the associated 112 // ETag using the provided client. This func is otherwise equivalent to Get. 113 func getETag(client *http.Client, suffix string) (value, etag string, err error) { 114 // Using a fixed IP makes it very difficult to spoof the metadata service in 115 // a container, which is an important use-case for local testing of cloud 116 // deployments. To enable spoofing of the metadata service, the environment 117 // variable GCE_METADATA_HOST is first inspected to decide where metadata 118 // requests shall go. 119 host := os.Getenv(metadataHostEnv) 120 if host == "" { 121 // Using 169.254.169.254 instead of "metadata" here because Go 122 // binaries built with the "netgo" tag and without cgo won't 123 // know the search suffix for "metadata" is 124 // ".google.internal", and this IP address is documented as 125 // being stable anyway. 126 host = metadataIP 127 } 128 url := "http://" + host + "/computeMetadata/v1/" + suffix 129 req, _ := http.NewRequest("GET", url, nil) 130 req.Header.Set("Metadata-Flavor", "Google") 131 req.Header.Set("User-Agent", userAgent) 132 res, err := client.Do(req) 133 if err != nil { 134 return "", "", err 135 } 136 defer res.Body.Close() 137 if res.StatusCode == http.StatusNotFound { 138 return "", "", NotDefinedError(suffix) 139 } 140 if res.StatusCode != 200 { 141 return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url) 142 } 143 all, err := ioutil.ReadAll(res.Body) 144 if err != nil { 145 return "", "", err 146 } 147 return string(all), res.Header.Get("Etag"), nil 148 } 149 150 func getTrimmed(suffix string) (s string, err error) { 151 s, err = Get(suffix) 152 s = strings.TrimSpace(s) 153 return 154 } 155 156 func (c *cachedValue) get() (v string, err error) { 157 defer c.mu.Unlock() 158 c.mu.Lock() 159 if c.v != "" { 160 return c.v, nil 161 } 162 if c.trim { 163 v, err = getTrimmed(c.k) 164 } else { 165 v, err = Get(c.k) 166 } 167 if err == nil { 168 c.v = v 169 } 170 return 171 } 172 173 var ( 174 onGCEOnce sync.Once 175 onGCE bool 176 ) 177 178 // OnGCE reports whether this process is running on Google Compute Engine. 179 func OnGCE() bool { 180 onGCEOnce.Do(initOnGCE) 181 return onGCE 182 } 183 184 func initOnGCE() { 185 onGCE = testOnGCE() 186 } 187 188 func testOnGCE() bool { 189 // The user explicitly said they're on GCE, so trust them. 190 if os.Getenv(metadataHostEnv) != "" { 191 return true 192 } 193 194 ctx, cancel := context.WithCancel(context.Background()) 195 defer cancel() 196 197 resc := make(chan bool, 2) 198 199 // Try two strategies in parallel. 200 // See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194 201 go func() { 202 req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) 203 req.Header.Set("User-Agent", userAgent) 204 res, err := ctxhttp.Do(ctx, metaClient, req) 205 if err != nil { 206 resc <- false 207 return 208 } 209 defer res.Body.Close() 210 resc <- res.Header.Get("Metadata-Flavor") == "Google" 211 }() 212 213 go func() { 214 addrs, err := net.LookupHost("metadata.google.internal") 215 if err != nil || len(addrs) == 0 { 216 resc <- false 217 return 218 } 219 resc <- strsContains(addrs, metadataIP) 220 }() 221 222 tryHarder := systemInfoSuggestsGCE() 223 if tryHarder { 224 res := <-resc 225 if res { 226 // The first strategy succeeded, so let's use it. 227 return true 228 } 229 // Wait for either the DNS or metadata server probe to 230 // contradict the other one and say we are running on 231 // GCE. Give it a lot of time to do so, since the system 232 // info already suggests we're running on a GCE BIOS. 233 timer := time.NewTimer(5 * time.Second) 234 defer timer.Stop() 235 select { 236 case res = <-resc: 237 return res 238 case <-timer.C: 239 // Too slow. Who knows what this system is. 240 return false 241 } 242 } 243 244 // There's no hint from the system info that we're running on 245 // GCE, so use the first probe's result as truth, whether it's 246 // true or false. The goal here is to optimize for speed for 247 // users who are NOT running on GCE. We can't assume that 248 // either a DNS lookup or an HTTP request to a blackholed IP 249 // address is fast. Worst case this should return when the 250 // metaClient's Transport.ResponseHeaderTimeout or 251 // Transport.Dial.Timeout fires (in two seconds). 252 return <-resc 253 } 254 255 // systemInfoSuggestsGCE reports whether the local system (without 256 // doing network requests) suggests that we're running on GCE. If this 257 // returns true, testOnGCE tries a bit harder to reach its metadata 258 // server. 259 func systemInfoSuggestsGCE() bool { 260 if runtime.GOOS != "linux" { 261 // We don't have any non-Linux clues available, at least yet. 262 return false 263 } 264 slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") 265 name := strings.TrimSpace(string(slurp)) 266 return name == "Google" || name == "Google Compute Engine" 267 } 268 269 // Subscribe subscribes to a value from the metadata service. 270 // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". 271 // The suffix may contain query parameters. 272 // 273 // Subscribe calls fn with the latest metadata value indicated by the provided 274 // suffix. If the metadata value is deleted, fn is called with the empty string 275 // and ok false. Subscribe blocks until fn returns a non-nil error or the value 276 // is deleted. Subscribe returns the error value returned from the last call to 277 // fn, which may be nil when ok == false. 278 func Subscribe(suffix string, fn func(v string, ok bool) error) error { 279 const failedSubscribeSleep = time.Second * 5 280 281 // First check to see if the metadata value exists at all. 282 val, lastETag, err := getETag(subscribeClient, suffix) 283 if err != nil { 284 return err 285 } 286 287 if err := fn(val, true); err != nil { 288 return err 289 } 290 291 ok := true 292 if strings.ContainsRune(suffix, '?') { 293 suffix += "&wait_for_change=true&last_etag=" 294 } else { 295 suffix += "?wait_for_change=true&last_etag=" 296 } 297 for { 298 val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag)) 299 if err != nil { 300 if _, deleted := err.(NotDefinedError); !deleted { 301 time.Sleep(failedSubscribeSleep) 302 continue // Retry on other errors. 303 } 304 ok = false 305 } 306 lastETag = etag 307 308 if err := fn(val, ok); err != nil || !ok { 309 return err 310 } 311 } 312 } 313 314 // ProjectID returns the current instance's project ID string. 315 func ProjectID() (string, error) { return projID.get() } 316 317 // NumericProjectID returns the current instance's numeric project ID. 318 func NumericProjectID() (string, error) { return projNum.get() } 319 320 // InternalIP returns the instance's primary internal IP address. 321 func InternalIP() (string, error) { 322 return getTrimmed("instance/network-interfaces/0/ip") 323 } 324 325 // ExternalIP returns the instance's primary external (public) IP address. 326 func ExternalIP() (string, error) { 327 return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") 328 } 329 330 // Hostname returns the instance's hostname. This will be of the form 331 // "<instanceID>.c.<projID>.internal". 332 func Hostname() (string, error) { 333 return getTrimmed("instance/hostname") 334 } 335 336 // InstanceTags returns the list of user-defined instance tags, 337 // assigned when initially creating a GCE instance. 338 func InstanceTags() ([]string, error) { 339 var s []string 340 j, err := Get("instance/tags") 341 if err != nil { 342 return nil, err 343 } 344 if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { 345 return nil, err 346 } 347 return s, nil 348 } 349 350 // InstanceID returns the current VM's numeric instance ID. 351 func InstanceID() (string, error) { 352 return instID.get() 353 } 354 355 // InstanceName returns the current VM's instance ID string. 356 func InstanceName() (string, error) { 357 host, err := Hostname() 358 if err != nil { 359 return "", err 360 } 361 return strings.Split(host, ".")[0], nil 362 } 363 364 // Zone returns the current VM's zone, such as "us-central1-b". 365 func Zone() (string, error) { 366 zone, err := getTrimmed("instance/zone") 367 // zone is of the form "projects/<projNum>/zones/<zoneName>". 368 if err != nil { 369 return "", err 370 } 371 return zone[strings.LastIndex(zone, "/")+1:], nil 372 } 373 374 // InstanceAttributes returns the list of user-defined attributes, 375 // assigned when initially creating a GCE VM instance. The value of an 376 // attribute can be obtained with InstanceAttributeValue. 377 func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") } 378 379 // ProjectAttributes returns the list of user-defined attributes 380 // applying to the project as a whole, not just this VM. The value of 381 // an attribute can be obtained with ProjectAttributeValue. 382 func ProjectAttributes() ([]string, error) { return lines("project/attributes/") } 383 384 func lines(suffix string) ([]string, error) { 385 j, err := Get(suffix) 386 if err != nil { 387 return nil, err 388 } 389 s := strings.Split(strings.TrimSpace(j), "\n") 390 for i := range s { 391 s[i] = strings.TrimSpace(s[i]) 392 } 393 return s, nil 394 } 395 396 // InstanceAttributeValue returns the value of the provided VM 397 // instance attribute. 398 // 399 // If the requested attribute is not defined, the returned error will 400 // be of type NotDefinedError. 401 // 402 // InstanceAttributeValue may return ("", nil) if the attribute was 403 // defined to be the empty string. 404 func InstanceAttributeValue(attr string) (string, error) { 405 return Get("instance/attributes/" + attr) 406 } 407 408 // ProjectAttributeValue returns the value of the provided 409 // project attribute. 410 // 411 // If the requested attribute is not defined, the returned error will 412 // be of type NotDefinedError. 413 // 414 // ProjectAttributeValue may return ("", nil) if the attribute was 415 // defined to be the empty string. 416 func ProjectAttributeValue(attr string) (string, error) { 417 return Get("project/attributes/" + attr) 418 } 419 420 // Scopes returns the service account scopes for the given account. 421 // The account may be empty or the string "default" to use the instance's 422 // main account. 423 func Scopes(serviceAccount string) ([]string, error) { 424 if serviceAccount == "" { 425 serviceAccount = "default" 426 } 427 return lines("instance/service-accounts/" + serviceAccount + "/scopes") 428 } 429 430 func strsContains(ss []string, s string) bool { 431 for _, v := range ss { 432 if v == s { 433 return true 434 } 435 } 436 return false 437 } 438