blob: b19be1e1f18463270a47f16b482aa57019a7750a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux,!android
// Package cloudvm provides functions to test whether the current process is
// running on Google Compute Engine or Amazon Web Services, and to extract
// settings from this environment.
package cloudvm
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"sync"
"time"
"v.io/x/ref/lib/stats"
)
// This URL returns the external IP address assigned to the local GCE instance.
// If a HTTP GET request fails for any reason, this is not a GCE instance. If
// the result of the GET request doesn't contain a "Metadata-Flavor: Google"
// header, it is also not a GCE instance. The body of the document contains the
// external IP address, if present. Otherwise, the body is empty.
// See https://developers.google.com/compute/docs/metadata for details.
const gceUrl = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
const awsUrl = "http://169.254.169.254/latest/meta-data/public-ipv4"
var (
onceGCE sync.Once
onceAWS sync.Once
onGCE bool
onAWS bool
externalIP net.IP
)
func InitGCE(timeout time.Duration, cancel <-chan struct{}) {
onceGCE.Do(func() {
if onAWS {
return
}
gceTest(timeout, cancel)
})
}
func InitAWS(timeout time.Duration, cancel <-chan struct{}) {
onceAWS.Do(func() {
if onGCE {
return
}
awsTest(timeout, cancel)
})
}
func RunningOnGCE() bool {
return onGCE
}
func RunningOnAWS() bool {
return onAWS
}
// ExternalIPAddress returns the external IP address of this Google Compute
// Engine or AWS instance, or nil if there is none. Must be called after
// InitGCE / InitAWS.
func ExternalIPAddress() net.IP {
return externalIP
}
func gceTest(timeout time.Duration, cancel <-chan struct{}) {
var err error
if externalIP, err = gceGetIP(gceUrl, timeout, cancel); err != nil {
return
}
vars := []struct {
name, url string
}{
{"system/gce/project-id", "http://metadata.google.internal/computeMetadata/v1/project/project-id"},
{"system/gce/zone", "http://metadata.google.internal/computeMetadata/v1/instance/zone"},
}
for _, v := range vars {
body, err := gceGetMeta(v.url, timeout, cancel)
if err != nil || body == "" {
return
}
stats.NewString(v.name).Set(body)
}
onGCE = true
}
func gceGetIP(url string, timeout time.Duration, cancel <-chan struct{}) (net.IP, error) {
body, err := gceGetMeta(url, timeout, cancel)
if err != nil {
return nil, err
}
return net.ParseIP(body), nil
}
func gceGetMeta(url string, timeout time.Duration, cancel <-chan struct{}) (string, error) {
client := &http.Client{Timeout: timeout}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Cancel = cancel
req.Header.Add("Metadata-Flavor", "Google")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("http error: %d", resp.StatusCode)
}
if flavor := resp.Header["Metadata-Flavor"]; len(flavor) != 1 || flavor[0] != "Google" {
return "", fmt.Errorf("unexpected http header: %q", flavor)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func awsTest(timeout time.Duration, cancel <-chan struct{}) {
client := &http.Client{Timeout: timeout}
req, err := http.NewRequest("GET", awsUrl, nil)
if err != nil {
return
}
req.Cancel = cancel
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return
}
if server := resp.Header["Server"]; len(server) != 1 || server[0] != "EC2ws" {
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
externalIP = net.ParseIP(string(body))
onAWS = true
}