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.
 
 
 

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