You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

504 lines
16 KiB

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