Skip to content

Commit

Permalink
firestore: add ProjectiD detection with sentinel value
Browse files Browse the repository at this point in the history
Following suit with CL 38450, this change adds ProjectID
detection with the sentinel value "*detect-project-id*"
which when used to create a NewClient will make resolution
of the projectID from the Application Default Credentials (ADC).

This CL also fixes a datastore.TestDetectProjectID which
had a condition that would panic if err == nil and didn't
actually check if a mismatched error message.

Updates #1294

Change-Id: I95caac31dc867a5e7f390b47daeb5dfb7368a5de
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/39550
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jean de Klerk <deklerk@google.com>
  • Loading branch information
odeke-em committed Apr 5, 2019
1 parent 220aa11 commit 1de5fdf
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 9 deletions.
11 changes: 2 additions & 9 deletions datastore/integration_test.go
Expand Up @@ -31,7 +31,6 @@ import (

"cloud.google.com/go/internal/testutil"
"cloud.google.com/go/rpcreplay"
"golang.org/x/oauth2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/grpc"
Expand Down Expand Up @@ -1265,7 +1264,6 @@ func TestDetectProjectID(t *testing.T) {
ctx := context.Background()

creds := testutil.Credentials(ctx, ScopeDatastore)
ts := fakets{}
if creds == nil {
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
}
Expand All @@ -1275,15 +1273,10 @@ func TestDetectProjectID(t *testing.T) {
t.Errorf("NewClient: %v", err)
}

ts := testutil.ErroringTokenSource{}
// Try to use creds without project ID.
_, err := NewClient(ctx, DetectProjectID, option.WithTokenSource(ts))
if err == nil && err.Error() != "datastore: see the docs on DetectProjectID" {
if err == nil || err.Error() != "datastore: see the docs on DetectProjectID" {
t.Errorf("expected an error while using TokenSource that does not have a project ID")
}
}

type fakets struct{}

func (f fakets) Token() (*oauth2.Token, error) {
return nil, errors.New("shouldn't see this")
}
22 changes: 22 additions & 0 deletions firestore/client.go
Expand Up @@ -30,6 +30,7 @@ import (
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/transport"
pb "google.golang.org/genproto/googleapis/firestore/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
Expand All @@ -41,6 +42,15 @@ import (
// the resource being operated on.
const resourcePrefixHeader = "google-cloud-resource-prefix"

// DetectProjectID is a sentinel value that instructs NewClient to detect the
// project ID. It is given in place of the projectID argument. NewClient will
// use the project ID from the given credentials or the default credentials
// (https://developers.google.com/accounts/docs/application-default-credentials)
// if no credentials were provided. When providing credentials, not all
// options will allow NewClient to extract the project ID. Specifically a JWT
// does not have the project ID encoded.
const DetectProjectID = "*detect-project-id*"

// A Client provides access to the Firestore service.
type Client struct {
c *vkit.Client
Expand All @@ -61,6 +71,18 @@ func NewClient(ctx context.Context, projectID string, opts ...option.ClientOptio
o = []option.ClientOption{option.WithGRPCConn(conn)}
}
o = append(o, opts...)

if projectID == DetectProjectID {
creds, err := transport.Creds(ctx, o...)
if err != nil {
return nil, fmt.Errorf("fetching creds: %v", err)
}
if creds.ProjectID == "" {
return nil, errors.New("firestore: see the docs on DetectProjectID")
}
projectID = creds.ProjectID
}

vc, err := vkit.NewClient(ctx, o...)
if err != nil {
return nil, err
Expand Down
24 changes: 24 additions & 0 deletions firestore/integration_test.go
Expand Up @@ -1337,3 +1337,27 @@ func (h testHelper) mustSet(doc *DocumentRef, data interface{}, opts ...SetOptio
}
return wr
}

func TestDetectProjectID(t *testing.T) {
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()

creds := testutil.Credentials(ctx)
if creds == nil {
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
}

// Use creds with project ID.
if _, err := NewClient(ctx, DetectProjectID, option.WithCredentials(creds)); err != nil {
t.Errorf("NewClient: %v", err)
}

ts := testutil.ErroringTokenSource{}
// Try to use creds without project ID.
_, err := NewClient(ctx, DetectProjectID, option.WithTokenSource(ts))
if err == nil || err.Error() != "firestore: see the docs on DetectProjectID" {
t.Errorf("expected an error while using TokenSource that does not have a project ID")
}
}
11 changes: 11 additions & 0 deletions internal/testutil/context.go
Expand Up @@ -17,6 +17,7 @@ package testutil

import (
"context"
"errors"
"fmt"
"io/ioutil"
"log"
Expand Down Expand Up @@ -139,3 +140,13 @@ func CanReplay(replayFilename string) bool {
_, err := os.Stat(replayFilename)
return err == nil
}

// ErroringTokenSource is a token source for testing purposes,
// to always return a non-nil error to its caller. It is useful
// when testing error responses with bad oauth2 credentials.
type ErroringTokenSource struct{}

// Token implements oauth2.TokenSource, returning a nil oauth2.Token and a non-nil error.
func (fts ErroringTokenSource) Token() (*oauth2.Token, error) {
return nil, errors.New("intentional error")
}

0 comments on commit 1de5fdf

Please sign in to comment.