From f332b8f7e74873da733d30e8898c14ef9feed60d Mon Sep 17 00:00:00 2001 From: Alex Hong <9397363+hongalex@users.noreply.github.com> Date: Thu, 22 Jun 2023 18:36:40 -0700 Subject: [PATCH 1/2] feat(pubsub): enable project autodetection and detect empty project --- pubsub/integration_test.go | 18 ++++++++++++++++++ pubsub/pubsub.go | 32 ++++++++++++++++++++++++++++++++ pubsub/pubsub_test.go | 8 ++++++++ 3 files changed, 58 insertions(+) diff --git a/pubsub/integration_test.go b/pubsub/integration_test.go index 42d2c7d971a..6a120d5e0e9 100644 --- a/pubsub/integration_test.go +++ b/pubsub/integration_test.go @@ -2060,3 +2060,21 @@ 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") + } + + if _, err := NewClient(ctx, DetectProjectID, option.WithCredentials(testCreds)); err != nil { + t.Errorf("test pubsub.NewClient: %v", err) + } + + 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 cdd4abd9226..8206aad5fef 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) + } +} From 744bbee94e3b957084daaa2ee2c613db117ff5bd Mon Sep 17 00:00:00 2001 From: Alex Hong <9397363+hongalex@users.noreply.github.com> Date: Thu, 22 Jun 2023 18:48:31 -0700 Subject: [PATCH 2/2] test client.Project() as well --- pubsub/integration_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubsub/integration_test.go b/pubsub/integration_test.go index 6a120d5e0e9..a497e0178b3 100644 --- a/pubsub/integration_test.go +++ b/pubsub/integration_test.go @@ -2068,9 +2068,13 @@ func TestIntegration_DetectProjectID(t *testing.T) { t.Skip("test credentials not present, skipping") } - if _, err := NewClient(ctx, DetectProjectID, option.WithCredentials(testCreds)); err != nil { + 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{}