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 }