diff --git a/pubsub/integration_test.go b/pubsub/integration_test.go index 42d2c7d971a..a497e0178b3 100644 --- a/pubsub/integration_test.go +++ b/pubsub/integration_test.go @@ -2060,3 +2060,25 @@ func TestIntegration_TopicUpdateSchema(t *testing.T) { t.Fatalf("schema settings for update -want, +got: %v", diff) } } + +func TestIntegration_DetectProjectID(t *testing.T) { + ctx := context.Background() + testCreds := testutil.Credentials(ctx) + if testCreds == nil { + t.Skip("test credentials not present, skipping") + } + + goodClient, err := NewClient(ctx, DetectProjectID, option.WithCredentials(testCreds)) + if err != nil { + t.Errorf("test pubsub.NewClient: %v", err) + } + if goodClient.Project() != testutil.ProjID() { + t.Errorf("client.Project() got %q, want %q", goodClient.Project(), testutil.ProjID()) + } + + badTS := testutil.ErroringTokenSource{} + + if badClient, err := NewClient(ctx, DetectProjectID, option.WithTokenSource(badTS)); err == nil { + t.Errorf("expected error from bad token source, NewClient succeeded with project: %s", badClient.projectID) + } +} diff --git a/pubsub/pubsub.go b/pubsub/pubsub.go index 0b44ea5583e..7c67840d640 100644 --- a/pubsub/pubsub.go +++ b/pubsub/pubsub.go @@ -16,6 +16,7 @@ package pubsub // import "cloud.google.com/go/pubsub" import ( "context" + "errors" "fmt" "os" "reflect" @@ -23,6 +24,7 @@ import ( "strings" "time" + "cloud.google.com/go/internal/detect" vkit "cloud.google.com/go/pubsub/apiv1" "cloud.google.com/go/pubsub/internal" gax "github.com/googleapis/gax-go/v2" @@ -113,6 +115,20 @@ func mergeSubscriberCallOptions(a *vkit.SubscriberCallOptions, b *vkit.Subscribe return res } +// 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*" + +// ErrEmptyProjectID denotes that the project string passed into NewClient was empty. +// Please provide a valid project ID or use the DetectProjectID sentinel value to detect +// project ID from well defined sources. +var ErrEmptyProjectID = errors.New("pubsub: projectID string is empty") + // NewClient creates a new PubSub client. It uses a default configuration. func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (c *Client, err error) { return NewClientWithConfig(ctx, projectID, nil, opts...) @@ -120,6 +136,9 @@ func NewClient(ctx context.Context, projectID string, opts ...option.ClientOptio // NewClientWithConfig creates a new PubSub client. func NewClientWithConfig(ctx context.Context, projectID string, config *ClientConfig, opts ...option.ClientOption) (c *Client, err error) { + if projectID == "" { + return nil, ErrEmptyProjectID + } var o []option.ClientOption // Environment variables for gcloud emulator: // https://cloud.google.com/sdk/gcloud/reference/beta/emulators/pubsub/ @@ -157,6 +176,13 @@ func NewClientWithConfig(ctx context.Context, projectID string, config *ClientCo subc.CallOptions = mergeSubscriberCallOptions(subc.CallOptions, config.SubscriberCallOptions) } pubc.SetGoogleClientInfo("gccl", internal.Version) + + // Handle project autodetection. + projectID, err = detect.ProjectID(ctx, projectID, "", opts...) + if err != nil { + return nil, err + } + return &Client{ projectID: projectID, pubc: pubc, @@ -164,6 +190,12 @@ func NewClientWithConfig(ctx context.Context, projectID string, config *ClientCo }, nil } +// Project returns the project ID or number for this instance of the client, which may have +// either been explicitly specified or autodetected. +func (c *Client) Project() string { + return c.projectID +} + // Close releases any resources held by the client, // such as memory and goroutines. // diff --git a/pubsub/pubsub_test.go b/pubsub/pubsub_test.go index ebb409dde9c..247fda1b47c 100644 --- a/pubsub/pubsub_test.go +++ b/pubsub/pubsub_test.go @@ -117,3 +117,11 @@ func TestClient_ApplyClientConfig(t *testing.T) { } } } + +func TestClient_EmptyProjectID(t *testing.T) { + ctx := context.Background() + _, err := NewClient(ctx, "") + if err != ErrEmptyProjectID { + t.Fatalf("passing empty project ID got %v, want%v", err, ErrEmptyProjectID) + } +}