Skip to content

Commit b839b6b

Browse files
authored
feat(storage): add support for partial list bucket (#15763)
1 parent c03c99b commit b839b6b

22 files changed

+518
-6
lines changed

ci/cloudbuild/builds/lib/integration.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ source module ci/lib/io.sh
3131
export PATH="${HOME}/.local/bin:${PATH}"
3232
python3 -m pip uninstall -y --quiet googleapis-storage-testbench
3333
python3 -m pip install --upgrade --user --quiet --disable-pip-version-check \
34-
"git+https://github.com/googleapis/storage-testbench@v0.52.0"
34+
"git+https://github.com/googleapis/storage-testbench@v0.59.0"
3535

3636
# Some of the tests will need a valid roots.pem file.
3737
rm -f /dev/shm/roots.pem

google/cloud/storage/client.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "google/cloud/storage/internal/signed_url_requests.h"
2222
#include "google/cloud/storage/internal/storage_connection.h"
2323
#include "google/cloud/storage/internal/tuple_filter.h"
24+
#include "google/cloud/storage/list_buckets_extended_reader.h"
2425
#include "google/cloud/storage/list_buckets_reader.h"
2526
#include "google/cloud/storage/list_hmac_keys_reader.h"
2627
#include "google/cloud/storage/list_objects_and_prefixes_reader.h"
@@ -366,6 +367,56 @@ class Client {
366367
std::forward<Options>(options)...);
367368
}
368369

370+
/**
371+
* Fetches the list of buckets and unreachable resources for the default
372+
* project.
373+
*
374+
* This function will return an error if it cannot determine the "default"
375+
* project. The default project is found by looking, in order, for:
376+
* - Any parameters of type `OverrideDefaultProject`, with a value.
377+
* - Any `google::cloud::storage::ProjectIdOption` value in any parameters of
378+
* type `google::cloud::Options{}`.
379+
* - Any `google::cloud::storage::ProjectIdOption` value provided in the
380+
* `google::cloud::Options{}` passed to the constructor.
381+
* - The value from the `GOOGLE_CLOUD_PROJECT` environment variable.
382+
*
383+
* @param options a list of optional query parameters and/or request headers.
384+
* Valid types for this operation include `MaxResults`, `Prefix`,
385+
* `Projection`, `UserProject`, `OverrideDefaultProject`, and
386+
* `ReturnPartialSuccess`.
387+
*
388+
* @par Idempotency
389+
* This is a read-only operation and is always idempotent.
390+
*
391+
* @par Example
392+
* @snippet storage_bucket_samples.cc list buckets partial success
393+
*/
394+
template <typename... Options>
395+
ListBucketsExtendedReader ListBucketsExtended(Options&&... options) {
396+
auto opts = SpanOptions(std::forward<Options>(options)...);
397+
auto project_id = storage_internal::RequestProjectId(
398+
GCP_ERROR_INFO(), opts, std::forward<Options>(options)...);
399+
if (!project_id) {
400+
return google::cloud::internal::MakeErrorPaginationRange<
401+
ListBucketsExtendedReader>(std::move(project_id).status());
402+
}
403+
google::cloud::internal::OptionsSpan const span(std::move(opts));
404+
405+
internal::ListBucketsRequest request(*std::move(project_id));
406+
request.set_multiple_options(std::forward<Options>(options)...);
407+
auto& client = connection_;
408+
return google::cloud::internal::MakePaginationRange<
409+
ListBucketsExtendedReader>(
410+
request,
411+
[client](internal::ListBucketsRequest const& r) {
412+
return client->ListBuckets(r);
413+
},
414+
[](internal::ListBucketsResponse res) {
415+
return std::vector<BucketsExtended>{BucketsExtended{
416+
std::move(res.items), std::move(res.unreachable)}};
417+
});
418+
}
419+
369420
/**
370421
* Creates a new Google Cloud Storage bucket using the default project.
371422
*

google/cloud/storage/examples/storage_bucket_samples.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "google/cloud/storage/client.h"
1616
#include "google/cloud/storage/examples/storage_examples_common.h"
17+
#include "google/cloud/storage/list_buckets_extended_reader.h"
1718
#include "google/cloud/internal/getenv.h"
1819
#include <functional>
1920
#include <iostream>
@@ -48,6 +49,35 @@ void ListBuckets(google::cloud::storage::Client client,
4849
(std::move(client));
4950
}
5051

52+
void ListBucketsPartialSuccess(google::cloud::storage::Client client,
53+
std::vector<std::string> const& /*argv*/) {
54+
//! [list buckets partial success] [START
55+
//! storage_list_buckets_partial_success]
56+
namespace gcs = ::google::cloud::storage;
57+
using ::google::cloud::StatusOr;
58+
[](gcs::Client client) {
59+
int count = 0;
60+
gcs::ListBucketsExtendedReader bucket_list = client.ListBucketsExtended();
61+
for (auto&& result : bucket_list) {
62+
if (!result) throw std::move(result).status();
63+
64+
for (auto const& bucket_metadata : result->buckets) {
65+
std::cout << bucket_metadata.name() << "\n";
66+
++count;
67+
}
68+
for (auto const& unreachable : result->unreachable) {
69+
std::cout << "Unreachable location: " << unreachable << "\n";
70+
}
71+
}
72+
73+
if (count == 0) {
74+
std::cout << "No buckets in default project\n";
75+
}
76+
}
77+
//! [list buckets partial success] [END storage_list_buckets_partial_success]
78+
(std::move(client));
79+
}
80+
5181
void ListBucketsForProject(google::cloud::storage::Client client,
5282
std::vector<std::string> const& argv) {
5383
//! [list buckets for project]
@@ -683,6 +713,9 @@ void RunAll(std::vector<std::string> const& argv) {
683713
std::cout << "\nRunning ListBuckets() example" << std::endl;
684714
ListBuckets(client, {});
685715

716+
std::cout << "\nRunning ListBucketsPartialSuccess() example" << std::endl;
717+
ListBucketsPartialSuccess(client, {});
718+
686719
std::cout << "\nRunning CreateBucket() example" << std::endl;
687720
CreateBucket(client, {bucket_name});
688721

@@ -726,6 +759,8 @@ int main(int argc, char* argv[]) {
726759

727760
examples::Example example({
728761
examples::CreateCommandEntry("list-buckets", {}, ListBuckets),
762+
examples::CreateCommandEntry("list-buckets-partial-success", {},
763+
ListBucketsPartialSuccess),
729764
examples::CreateCommandEntry("list-buckets-for-project", {"<project-id>"},
730765
ListBucketsForProject),
731766
make_entry("create-bucket", {}, CreateBucket),

google/cloud/storage/examples/storage_client_initialization_samples.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ void SetClientUniverseDomain(std::vector<std::string> const& argv) {
137137
" <bucket-name> <object-name>"};
138138
}
139139
//! [START storage_set_client_universe_domain] [set-client-universe-domain]
140-
namespace g = ::google::cloud;
141140
namespace gcs = ::google::cloud::storage;
142141
[](std::string const& bucket_name, std::string const& object_name) {
143142
google::cloud::Options options;

google/cloud/storage/google_cloud_cpp_storage.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ google_cloud_cpp_storage_hdrs = [
116116
"internal/unified_rest_credentials.h",
117117
"internal/well_known_parameters_impl.h",
118118
"lifecycle_rule.h",
119+
"list_buckets_extended_reader.h",
119120
"list_buckets_reader.h",
120121
"list_hmac_keys_reader.h",
121122
"list_objects_and_prefixes_reader.h",

google/cloud/storage/google_cloud_cpp_storage.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ add_library(
198198
internal/win32/hash_function_impl.cc
199199
lifecycle_rule.cc
200200
lifecycle_rule.h
201+
list_buckets_extended_reader.h
201202
list_buckets_reader.cc
202203
list_buckets_reader.h
203204
list_hmac_keys_reader.cc
@@ -506,6 +507,7 @@ if (BUILD_TESTING)
506507
internal/tuple_filter_test.cc
507508
internal/unified_rest_credentials_test.cc
508509
lifecycle_rule_test.cc
510+
list_buckets_extended_reader_test.cc
509511
list_buckets_reader_test.cc
510512
list_hmac_keys_reader_test.cc
511513
list_objects_and_prefixes_reader_test.cc

google/cloud/storage/internal/bucket_requests.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ StatusOr<ListBucketsResponse> ListBucketsResponse::FromHttpResponse(
243243
result.items.emplace_back(std::move(*parsed));
244244
}
245245

246+
if (json.count("unreachable") != 0) {
247+
for (auto const& kv : json["unreachable"].items()) {
248+
result.unreachable.emplace_back(kv.value().get<std::string>());
249+
}
250+
}
251+
246252
return result;
247253
}
248254

@@ -256,6 +262,9 @@ std::ostream& operator<<(std::ostream& os, ListBucketsResponse const& r) {
256262
<< ", items={";
257263
std::copy(r.items.begin(), r.items.end(),
258264
std::ostream_iterator<BucketMetadata>(os, "\n "));
265+
os << "}, unreachable={";
266+
std::copy(r.unreachable.begin(), r.unreachable.end(),
267+
std::ostream_iterator<std::string>(os, "\n "));
259268
return os << "}}";
260269
}
261270

google/cloud/storage/internal/bucket_requests.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,18 @@ namespace internal {
3737
*/
3838
class ListBucketsRequest
3939
: public GenericRequest<ListBucketsRequest, MaxResults, Prefix, Projection,
40-
UserProject, OverrideDefaultProject> {
40+
UserProject, OverrideDefaultProject,
41+
ReturnPartialSuccess> {
4142
public:
4243
ListBucketsRequest() = default;
4344
explicit ListBucketsRequest(std::string project_id)
4445
: project_id_(std::move(project_id)) {}
4546

4647
std::string const& project_id() const { return project_id_; }
4748
std::string const& page_token() const { return page_token_; }
49+
bool return_partial_success() const {
50+
return GetOption<ReturnPartialSuccess>().value_or(false);
51+
}
4852
ListBucketsRequest& set_page_token(std::string page_token) {
4953
page_token_ = std::move(page_token);
5054
return *this;
@@ -65,6 +69,7 @@ struct ListBucketsResponse {
6569

6670
std::string next_page_token;
6771
std::vector<BucketMetadata> items;
72+
std::vector<std::string> unreachable;
6873
};
6974

7075
std::ostream& operator<<(std::ostream& os, ListBucketsResponse const& r);

google/cloud/storage/internal/bucket_requests_test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ TEST(ListBucketsRequestTest, OStream) {
134134
EXPECT_THAT(os.str(), HasSubstr("prefix=foo-bar-baz"));
135135
}
136136

137+
TEST(ListBucketsRequestTest, PartialSuccess) {
138+
ListBucketsRequest request("project-to-list");
139+
request.set_multiple_options(ReturnPartialSuccess(true));
140+
141+
std::ostringstream os;
142+
os << request;
143+
EXPECT_THAT(os.str(), HasSubstr("ListBucketsRequest"));
144+
EXPECT_THAT(os.str(), HasSubstr("returnPartialSuccess=true"));
145+
}
146+
137147
TEST(ListBucketsResponseTest, Parse) {
138148
std::string bucket1 = R"""({
139149
"kind": "storage#bucket",

google/cloud/storage/internal/connection_impl_bucket_test.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ TEST(StorageConnectionImpl, ListBucketPermanentFailure) {
6666
EXPECT_THAT(permanent.captured_authority_options(), RetryLoopUsesOptions());
6767
}
6868

69+
TEST(StorageConnectionImpl, ListBucketsPartialResult) {
70+
auto mock = std::make_unique<MockGenericStub>();
71+
EXPECT_CALL(*mock, options);
72+
EXPECT_CALL(*mock, ListBuckets)
73+
.WillOnce([](rest_internal::RestContext&, Options const&,
74+
ListBucketsRequest const& r) {
75+
EXPECT_TRUE(r.return_partial_success());
76+
ListBucketsResponse response;
77+
response.items.emplace_back(BucketMetadata{}.set_name("bucket2"));
78+
response.unreachable.emplace_back("projects/_/buckets/bucket1");
79+
return response;
80+
});
81+
auto client =
82+
StorageConnectionImpl::Create(std::move(mock), RetryTestOptions());
83+
google::cloud::internal::OptionsSpan span(client->options());
84+
auto response =
85+
client->ListBuckets(ListBucketsRequest("test-project")
86+
.set_option(ReturnPartialSuccess(true)));
87+
ASSERT_TRUE(response.ok());
88+
EXPECT_EQ(1, response->items.size());
89+
EXPECT_THAT(response->unreachable,
90+
::testing::ElementsAre("projects/_/buckets/bucket1"));
91+
}
92+
6993
TEST(StorageConnectionImpl, CreateBucketTooManyFailures) {
7094
auto transient = MockRetryClientFunction(TransientError());
7195
auto mock = std::make_unique<MockGenericStub>();

0 commit comments

Comments
 (0)