blob: 2fac1a8afec6b8f99e92a5c9284846b183881b2e [file] [log] [blame]
package metadata
import (
"encoding/json"
"fmt"
"io"
"net/http"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"golang.org/x/oauth2"
)
// GCE project level metadata keys.
const (
// APIKEY is used for access to APIs that don't need OAuth 2.0.
APIKEY = "apikey"
// COOKIESALT, CLIENT_ID, and CLIENT_SECRET are used for login.
COOKIESALT = "cookiesalt"
CLIENT_ID = "client_id"
CLIENT_SECRET = "client_secret"
// GMAIL_CACHED_TOKEN, GMAIL_CLIENT_ID, and GMAIL_CLIENT_SECRET are used for sending mail
// from alerts@skia.org.
GMAIL_CACHED_TOKEN = "gmail_cached_token"
GMAIL_CACHED_TOKEN_AUTOROLL = "gmail_cached_token_autoroll"
GMAIL_CLIENT_ID = "gmail_clientid"
GMAIL_CLIENT_SECRET = "gmail_clientsecret"
// METADATA_PATH_PREFIX_TMPL is the template for the first part of the
// metadata URL. The placeholder is for the level ("instance" or
// "project").
METADATA_PATH_PREFIX_TMPL = "/computeMetadata/v1/%s"
// METADATA_SUB_URL_TMPL is the URL template for metadata. The
// placeholders are for the level ("instance" or "project") and the
// metadata key.
METADATA_SUB_URL_TMPL = METADATA_PATH_PREFIX_TMPL + "/attributes/%s"
// METADATA_URL_PREFIX is the prefix of the metadata URL.
METADATA_URL_PREFIX = "http://metadata"
// METADATA_URL is the URL template for metadata. The placeholders are
// for the level ("instance" or "project") and the metadata key.
METADATA_URL = METADATA_URL_PREFIX + METADATA_SUB_URL_TMPL
// WEBHOOK_REQUEST_SALT is used to authenticate webhook requests. The value stored in
// Metadata is base64-encoded.
// Value created 2015-08-10 with
// dd if=/dev/random iflag=fullblock bs=64 count=1 | base64 -w 0
WEBHOOK_REQUEST_SALT = "webhook_request_salt"
// JWT_SERVICE_ACCOUNT is the JSON formatted service account.
JWT_SERVICE_ACCOUNT = "jwt_service_account"
// NSQ_TEST_SERVER refers to a test server in GCE which runs NSQ for testing purposes.
NSQ_TEST_SERVER = "nsq-test-server"
// Metadata levels.
LEVEL_INSTANCE = "instance"
LEVEL_PROJECT = "project"
// The "Metadata-Flavor: Google" header must be set for HTTP requests
// to the metadata server.
HEADER_MD_FLAVOR_KEY = "Metadata-Flavor"
HEADER_MD_FLAVOR_VAL = "Google"
)
var (
// Metadata path for a default service account token.
TOKEN_PATH = fmt.Sprintf(METADATA_PATH_PREFIX_TMPL, LEVEL_INSTANCE) + "/service-accounts/default/token"
// Full metadata URL for a default service account token.
TOKEN_URL = METADATA_URL_PREFIX + TOKEN_PATH
)
// getUrl retrieves the given metadata URL.
func getUrl(url string) (string, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("metadata.Get() failed to build request: %s", err)
}
c := httputils.NewTimeoutClient()
req.Header.Add(HEADER_MD_FLAVOR_KEY, HEADER_MD_FLAVOR_VAL)
resp, err := c.Do(req)
if err != nil {
return "", fmt.Errorf("metadata.Get() failed to make HTTP request for %s: %s", url, err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP response has status %d", resp.StatusCode)
}
defer util.Close(resp.Body)
value, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("Failed to read %s from metadata server: %s", url, err)
}
return string(value), nil
}
// get retrieves the named value from the Metadata server. See
// https://developers.google.com/compute/docs/metadata
//
// level should be either "instance" or "project" for the kind of
// metadata to retrieve.
func get(name string, level string) (string, error) {
return getUrl(fmt.Sprintf(METADATA_URL, level, name))
}
// Get retrieves the named value from the instance Metadata server. See
// https://developers.google.com/compute/docs/metadata
func Get(name string) (string, error) {
return get(name, LEVEL_INSTANCE)
}
// GetWithDefault is Get, but returns the default value on error.
func GetWithDefault(name, defaultValue string) string {
if ret, err := Get(name); err == nil {
return ret
} else {
sklog.Warningf("Unable to obtain %q from metadata server: %v", name, err)
return defaultValue
}
}
// ProjectGet retrieves the named value from the project Metadata server. See
// https://developers.google.com/compute/docs/metadata
func ProjectGet(name string) (string, error) {
return get(name, LEVEL_PROJECT)
}
// ProjectGetWithDefault is ProjectGet, but returns the default value on error.
func ProjectGetWithDefault(name, defaultValue string) string {
if ret, err := ProjectGet(name); err == nil {
return ret
} else {
sklog.Warningf("Unable to obtain %q from metadata server: %v", name, err)
return defaultValue
}
}
// MustGet is Get() that panics on error.
func MustGet(keyname string) string {
value, err := Get(keyname)
if err != nil {
sklog.Fatalf("Unable to obtain %q from metadata server: %s.", keyname, err)
}
return value
}
func Must(s string, err error) string {
if err != nil {
sklog.Fatalf("Failed to read metadata: %s.", err)
}
return s
}
// NSQDTestServerAddr returns the address of a test NSQD server used for testing. If
// not running in GCE, this is the local machine.
func NSQDTestServerAddr() string {
server := ProjectGetWithDefault(NSQ_TEST_SERVER, "127.0.0.1")
sklog.Errorf("Got test NSQ server: %s", server)
return fmt.Sprintf("%s:4150", server)
}
// GetToken returns a default service account token.
func GetToken() (*oauth2.Token, error) {
tokString, err := getUrl(TOKEN_URL)
if err != nil {
return nil, err
}
var tok oauth2.Token
if err := json.Unmarshal([]byte(tokString), &tok); err != nil {
return nil, err
}
return &tok, nil
}