google: add DefaultCredentials function
This new function allows reading the project ID from a service account
JSON file without an additional disk read.
Change-Id: I1f03ca3ca39a2ae3bd6524367c17761b0f08de45
Reviewed-on: https://go-review.googlesource.com/32876
Reviewed-by: Jaana Burcu Dogan <jbd@google.com>
diff --git a/google/appengine.go b/google/appengine.go
index dc993ef..4243f4c 100644
--- a/google/appengine.go
+++ b/google/appengine.go
@@ -20,6 +20,9 @@
// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
+// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
+var appengineAppIDFunc func(c context.Context) string
+
// AppEngineTokenSource returns a token source that fetches tokens
// issued to the current App Engine application's service account.
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
diff --git a/google/appengine_hook.go b/google/appengine_hook.go
index 4f42c8b..6f66411 100644
--- a/google/appengine_hook.go
+++ b/google/appengine_hook.go
@@ -10,4 +10,5 @@
func init() {
appengineTokenFunc = appengine.AccessToken
+ appengineAppIDFunc = appengine.AppID
}
diff --git a/google/appenginevm_hook.go b/google/appenginevm_hook.go
index 633611c..1074780 100644
--- a/google/appenginevm_hook.go
+++ b/google/appenginevm_hook.go
@@ -11,4 +11,5 @@
func init() {
appengineVM = true
appengineTokenFunc = appengine.AccessToken
+ appengineAppIDFunc = appengine.AppID
}
diff --git a/google/default.go b/google/default.go
index c572d1a..b45e796 100644
--- a/google/default.go
+++ b/google/default.go
@@ -18,16 +18,16 @@
"golang.org/x/oauth2"
)
-// DefaultClient returns an HTTP Client that uses the
-// DefaultTokenSource to obtain authentication credentials.
-//
-// This client should be used when developing services
-// that run on Google App Engine or Google Compute Engine
-// and use "Application Default Credentials."
-//
+// DefaultCredentials holds "Application Default Credentials".
// For more details, see:
// https://developers.google.com/accounts/docs/application-default-credentials
-//
+type DefaultCredentials struct {
+ ProjectID string // may be empty
+ TokenSource oauth2.TokenSource
+}
+
+// DefaultClient returns an HTTP Client that uses the
+// DefaultTokenSource to obtain authentication credentials.
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
ts, err := DefaultTokenSource(ctx, scope...)
if err != nil {
@@ -36,8 +36,18 @@
return oauth2.NewClient(ctx, ts), nil
}
-// DefaultTokenSource is a token source that uses
+// DefaultTokenSource returns the token source for
// "Application Default Credentials".
+// It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource.
+func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
+ creds, err := FindDefaultCredentials(ctx, scope...)
+ if err != nil {
+ return nil, err
+ }
+ return creds.TokenSource, nil
+}
+
+// FindDefaultCredentials searches for "Application Default Credentials".
//
// It looks for credentials in the following places,
// preferring the first location found:
@@ -51,45 +61,40 @@
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
// credentials from the metadata server.
// (In this final case any provided scopes are ignored.)
-//
-// For more details, see:
-// https://developers.google.com/accounts/docs/application-default-credentials
-//
-func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
+func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCredentials, error) {
// First, try the environment variable.
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
if filename := os.Getenv(envVar); filename != "" {
- ts, err := tokenSourceFromFile(ctx, filename, scope)
+ creds, err := readCredentialsFile(ctx, filename, scope)
if err != nil {
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
}
- return ts, nil
+ return creds, nil
}
// Second, try a well-known file.
filename := wellKnownFile()
- _, err := os.Stat(filename)
- if err == nil {
- ts, err2 := tokenSourceFromFile(ctx, filename, scope)
- if err2 == nil {
- return ts, nil
- }
- err = err2
- } else if os.IsNotExist(err) {
- err = nil // ignore this error
- }
- if err != nil {
+ if creds, err := readCredentialsFile(ctx, filename, scope); err == nil {
+ return creds, nil
+ } else if !os.IsNotExist(err) {
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
}
// Third, if we're on Google App Engine use those credentials.
if appengineTokenFunc != nil && !appengineVM {
- return AppEngineTokenSource(ctx, scope...), nil
+ return &DefaultCredentials{
+ ProjectID: appengineAppIDFunc(ctx),
+ TokenSource: AppEngineTokenSource(ctx, scope...),
+ }, nil
}
// Fourth, if we're on Google Compute Engine use the metadata server.
if metadata.OnGCE() {
- return ComputeTokenSource(""), nil
+ id, _ := metadata.ProjectID()
+ return &DefaultCredentials{
+ ProjectID: id,
+ TokenSource: ComputeTokenSource(""),
+ }, nil
}
// None are found; return helpful error.
@@ -105,7 +110,7 @@
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
}
-func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) {
+func readCredentialsFile(ctx context.Context, filename string, scopes []string) (*DefaultCredentials, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
@@ -114,5 +119,12 @@
if err := json.Unmarshal(b, &f); err != nil {
return nil, err
}
- return f.tokenSource(ctx, scopes)
+ ts, err := f.tokenSource(ctx, append([]string(nil), scopes...))
+ if err != nil {
+ return nil, err
+ }
+ return &DefaultCredentials{
+ ProjectID: f.ProjectID,
+ TokenSource: ts,
+ }, nil
}
diff --git a/google/google.go b/google/google.go
index 0704732..66a8b0e 100644
--- a/google/google.go
+++ b/google/google.go
@@ -112,6 +112,7 @@
PrivateKeyID string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
TokenURL string `json:"token_uri"`
+ ProjectID string `json:"project_id"`
// User Credential fields
// (These typically come from gcloud auth.)