Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions storage/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,11 @@ func (it *ObjectIterator) Next() (*ObjectAttrs, error) {
// whose names begin with the prefix. By default, all buckets in the project
// are returned.
//
// To receive a partial list of buckets when some are unavailable, set the
// iterator's ReturnPartialSuccess field to true. You can then call the
// iterator's Unreachable method to retrieve the names of the unreachable
// buckets.
//
// Note: The returned iterator is not safe for concurrent operations without explicit synchronization.
func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator {
o := makeStorageOpts(true, c.retry, "")
Expand All @@ -2343,12 +2348,24 @@ func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator
type BucketIterator struct {
// Prefix restricts the iterator to buckets whose names begin with it.
Prefix string

ctx context.Context
projectID string
buckets []*BucketAttrs
pageInfo *iterator.PageInfo
nextFunc func() error
// If true, the iterator will return a partial result of buckets even if
// some buckets are unreachable. Call the Unreachable() method to retrieve the
// list of unreachable buckets. By default (false), the iterator will return
// an error if any buckets are unreachable.
ReturnPartialSuccess bool

ctx context.Context
projectID string
buckets []*BucketAttrs
unreachable []string
pageInfo *iterator.PageInfo
nextFunc func() error
}

// Unreachable returns a list of bucket names that could not be reached
// during the iteration if ReturnPartialSuccess was set to true.
func (it *BucketIterator) Unreachable() []string {
return it.unreachable
}

// Next returns the next result. Its second return value is iterator.Done if
Expand Down
105 changes: 105 additions & 0 deletions storage/bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/google/go-cmp/cmp"
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/api/googleapi"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
raw "google.golang.org/api/storage/v1"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -1674,3 +1675,107 @@ func TestDefaultSignBlobRetry(t *testing.T) {
t.Fatalf("BucketHandle.SignedURL: %v", err)
}
}

func TestBucketIterator_PartialSuccess(t *testing.T) {
ctx := context.Background()
const projectID = "test-project"

// Mocked JSON responses from the GCS API.
partialSuccessListBucketsResponse := `{
"kind": "storage#buckets",
"nextPageToken": "",
"items": [
{
"kind": "storage#bucket",
"id": "bucket-1",
"name": "bucket-1"
},
{
"kind": "storage#bucket",
"id": "bucket-2",
"name": "bucket-2"
}
],
"unreachable": [
"projects/_/buckets/unreachable-1",
"projects/_/buckets/unreachable-2"
]
}`

listBucketsResponse := `{
"kind": "storage#buckets",
"nextPageToken": "",
"items": [
{
"kind": "storage#bucket",
"id": "bucket-1",
"name": "bucket-1"
},
{
"kind": "storage#bucket",
"id": "bucket-2",
"name": "bucket-2"
}
]
}`

testCases := []struct {
name string
returnPartialSuccess bool
jsonResponse string
wantUnreachable []string
}{
{
name: "ReturnPartialSuccess is true",
returnPartialSuccess: true,
jsonResponse: partialSuccessListBucketsResponse,
wantUnreachable: []string{"projects/_/buckets/unreachable-1", "projects/_/buckets/unreachable-2"},
},
{
name: "ReturnPartialSuccess is false",
returnPartialSuccess: false,
jsonResponse: listBucketsResponse,
wantUnreachable: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mt := mockTransport{}
mt.addResult(&http.Response{
StatusCode: 200,
Body: bodyReader(tc.jsonResponse),
Header: http.Header{"Content-Type": []string{"application/json"}},
}, nil)

client, err := NewClient(ctx, option.WithHTTPClient(&http.Client{Transport: &mt}))
if err != nil {
t.Fatalf("NewClient: %v", err)
}

it := client.Buckets(ctx, projectID)
it.ReturnPartialSuccess = tc.returnPartialSuccess

var buckets []*BucketAttrs
for {
b, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
t.Fatalf("it.Next() got err: %v", err)
}
buckets = append(buckets, b)
}

if got, want := len(buckets), 2; got != want {
t.Errorf("got %d buckets, want %d", got, want)
}

unreachable := it.Unreachable()
if diff := cmp.Diff(unreachable, tc.wantUnreachable); diff != "" {
t.Errorf("Unreachable() mismatch (-want +got):\n%s", diff)
}
})
}
}
42 changes: 21 additions & 21 deletions storage/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,57 @@ retract [v1.25.0, v1.27.0] // due to https://github.com/googleapis/google-cloud-

require (
cloud.google.com/go v0.121.6
cloud.google.com/go/auth v0.16.5
cloud.google.com/go/compute/metadata v0.8.0
cloud.google.com/go/auth v0.17.0
cloud.google.com/go/compute/metadata v0.9.0
cloud.google.com/go/iam v1.5.2
cloud.google.com/go/longrunning v0.6.7
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/googleapis/gax-go/v2 v2.15.0
go.opentelemetry.io/contrib/detectors/gcp v1.36.0
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel v1.37.0
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0
go.opentelemetry.io/otel/sdk v1.36.0
go.opentelemetry.io/otel/sdk/metric v1.36.0
go.opentelemetry.io/otel/trace v1.36.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
google.golang.org/api v0.247.0
go.opentelemetry.io/otel/sdk v1.37.0
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
golang.org/x/oauth2 v0.33.0
golang.org/x/sync v0.18.0
google.golang.org/api v0.256.0
google.golang.org/genproto v0.0.0-20250603155806-513f23925822
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c
google.golang.org/grpc v1.74.3
google.golang.org/protobuf v1.36.7
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
)

require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/martian/v3 v3.3.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.12.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.14.0 // indirect
)
Loading
Loading