diff --git a/google/cloud/storage/bucket_metadata.cc b/google/cloud/storage/bucket_metadata.cc index cedc404ddedf7..5f18fe186bc51 100644 --- a/google/cloud/storage/bucket_metadata.cc +++ b/google/cloud/storage/bucket_metadata.cc @@ -102,6 +102,7 @@ bool operator==(BucketMetadata const& lhs, BucketMetadata const& rhs) { && lhs.logging_ == rhs.logging_ // && lhs.metageneration_ == rhs.metageneration_ // && lhs.name_ == rhs.name_ // + && lhs.object_retention_ == rhs.object_retention_ // && lhs.owner_ == rhs.owner_ // && lhs.project_number_ == rhs.project_number_ // && lhs.retention_policy_ == rhs.retention_policy_ // @@ -182,6 +183,9 @@ std::ostream& operator<<(std::ostream& os, BucketMetadata const& rhs) { os << ", metageneration=" << rhs.metageneration() << ", name=" << rhs.name(); + if (rhs.has_object_retention()) { + os << ", object_retention=" << rhs.object_retention(); + } if (rhs.has_owner()) { os << ", owner.entity=" << rhs.owner().entity << ", owner.entity_id=" << rhs.owner().entity_id; diff --git a/google/cloud/storage/bucket_metadata.h b/google/cloud/storage/bucket_metadata.h index b5312c954e849..44e004c25d532 100644 --- a/google/cloud/storage/bucket_metadata.h +++ b/google/cloud/storage/bucket_metadata.h @@ -25,6 +25,7 @@ #include "google/cloud/storage/bucket_iam_configuration.h" #include "google/cloud/storage/bucket_lifecycle.h" #include "google/cloud/storage/bucket_logging.h" +#include "google/cloud/storage/bucket_object_retention.h" #include "google/cloud/storage/bucket_retention_policy.h" #include "google/cloud/storage/bucket_rpo.h" #include "google/cloud/storage/bucket_soft_delete_policy.h" @@ -411,6 +412,30 @@ class BucketMetadata { return *this; } + /// Returns true if the bucket `object_retention` attribute is present. + bool has_object_retention() const { return object_retention_.has_value(); } + + /** + * Returns the owner. + * + * It is undefined behavior to call `owner()` if `has_owner()` is false. + */ + BucketObjectRetention const& object_retention() const { + return *object_retention_; + } + + /// @note this is only intended for mocking. + BucketMetadata& set_object_retention(BucketObjectRetention v) { + object_retention_ = std::move(v); + return *this; + } + + /// @note this is only intended for mocking. + BucketMetadata& reset_object_retention() { + object_retention_.reset(); + return *this; + } + /// Returns true if the bucket `owner` attribute is present. bool has_owner() const { return owner_.has_value(); } /** @@ -648,6 +673,7 @@ class BucketMetadata { absl::optional logging_; std::int64_t metageneration_{0}; std::string name_; + absl::optional object_retention_; absl::optional owner_; std::int64_t project_number_ = 0; absl::optional retention_policy_; diff --git a/google/cloud/storage/bucket_metadata_test.cc b/google/cloud/storage/bucket_metadata_test.cc index 56c4492f39b54..f341a016c583c 100644 --- a/google/cloud/storage/bucket_metadata_test.cc +++ b/google/cloud/storage/bucket_metadata_test.cc @@ -152,6 +152,9 @@ BucketMetadata CreateBucketMetadataForTest() { }, "metageneration": "4", "name": "test-bucket", + "objectRetention": { + "mode": "Enabled" + }, "owner": { "entity": "project-owners-123456789", "entityId": "test-owner-id-123" @@ -274,6 +277,10 @@ TEST(BucketMetadataTest, Parse) { EXPECT_EQ(4, actual.metageneration()); EXPECT_EQ("test-bucket", actual.name()); + // object_retention + ASSERT_TRUE(actual.has_object_retention()); + EXPECT_TRUE(actual.object_retention().enabled); + // owner EXPECT_EQ("project-owners-123456789", actual.owner().entity); EXPECT_EQ("test-owner-id-123", actual.owner().entity_id); @@ -392,6 +399,11 @@ TEST(BucketMetadataTest, IOStream) { // name() EXPECT_THAT(actual, HasSubstr("name=test-bucket")); + // object_retention() + EXPECT_THAT( + actual, + HasSubstr("object_retention=BucketObjectRetention={enabled=true}")); + // project_team() EXPECT_THAT(actual, HasSubstr("project-owners-123456789")); EXPECT_THAT(actual, HasSubstr("test-owner-id-123")); @@ -900,6 +912,24 @@ TEST(BucketMetadataTest, ResetLogging) { EXPECT_THAT(os.str(), Not(HasSubstr("logging."))); } +TEST(BucketMetadataTest, SetObjectRetention) { + auto const expected = CreateBucketMetadataForTest(); + auto copy = expected; + copy.set_object_retention(BucketObjectRetention{false}); + ASSERT_TRUE(copy.has_object_retention()); + EXPECT_FALSE(copy.object_retention().enabled); + EXPECT_NE(expected, copy); +} + +TEST(BucketMetadataTest, ResetObjectRetention) { + auto const expected = CreateBucketMetadataForTest(); + ASSERT_TRUE(expected.has_object_retention()); + auto copy = expected; + copy.reset_object_retention(); + ASSERT_FALSE(copy.has_object_retention()); + EXPECT_NE(expected, copy); +} + /// @test Verify we can change the retention policy in BucketMetadata. TEST(BucketMetadataTest, SetRetentionPolicy) { auto expected = CreateBucketMetadataForTest(); diff --git a/google/cloud/storage/bucket_object_retention.cc b/google/cloud/storage/bucket_object_retention.cc new file mode 100644 index 0000000000000..4cfc24dc34c24 --- /dev/null +++ b/google/cloud/storage/bucket_object_retention.cc @@ -0,0 +1,31 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/storage/bucket_object_retention.h" +#include + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +std::ostream& operator<<(std::ostream& os, BucketObjectRetention const& rhs) { + return os << "BucketObjectRetention={enabled=" + << (rhs.enabled ? "true" : "false") << "}"; +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google diff --git a/google/cloud/storage/bucket_object_retention.h b/google/cloud/storage/bucket_object_retention.h new file mode 100644 index 0000000000000..68c6539ad0a71 --- /dev/null +++ b/google/cloud/storage/bucket_object_retention.h @@ -0,0 +1,53 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_BUCKET_OBJECT_RETENTION_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_BUCKET_OBJECT_RETENTION_H + +#include "google/cloud/storage/version.h" +#include + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * The soft delete policy for a bucket. + * + * The soft delete policy prevents soft-deleted objects from being permanently + * deleted. + */ +struct BucketObjectRetention { + bool enabled; +}; + +inline bool operator==(BucketObjectRetention const& lhs, + BucketObjectRetention const& rhs) { + return lhs.enabled == rhs.enabled; +} + +inline bool operator!=(BucketObjectRetention const& lhs, + BucketObjectRetention const& rhs) { + return !(lhs == rhs); +} + +std::ostream& operator<<(std::ostream& os, BucketObjectRetention const& rhs); + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_BUCKET_OBJECT_RETENTION_H diff --git a/google/cloud/storage/bucket_object_retention_test.cc b/google/cloud/storage/bucket_object_retention_test.cc new file mode 100644 index 0000000000000..162ef6800b857 --- /dev/null +++ b/google/cloud/storage/bucket_object_retention_test.cc @@ -0,0 +1,45 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/storage/bucket_object_retention.h" +#include "google/cloud/internal/absl_str_cat_quiet.h" +#include +#include + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { + +TEST(BucketObjectRetention, Compare) { + auto const a = BucketObjectRetention{true}; + auto const b = BucketObjectRetention{false}; + + EXPECT_EQ(a, a); + EXPECT_NE(a, b); +} + +TEST(BucketObjectRetention, IOStream) { + std::ostringstream os; + os << BucketObjectRetention{true}; + auto const actual = os.str(); + EXPECT_EQ(actual, "BucketObjectRetention={enabled=true}"); +} + +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google diff --git a/google/cloud/storage/client.h b/google/cloud/storage/client.h index 3934938e46f0a..d794ab9dd3c14 100644 --- a/google/cloud/storage/client.h +++ b/google/cloud/storage/client.h @@ -381,9 +381,9 @@ class Client { * @param metadata the metadata for the new Bucket. The `name` field is * ignored in favor of @p bucket_name. * @param options a list of optional query parameters and/or request headers. - * Valid types for this operation include `PredefinedAcl`, - * `PredefinedDefaultObjectAcl`, `Projection`, `UserProject`, - * and `OverrideDefaultProject`. + * Valid types for this operation include `EnableObjectRetention`, + * `PredefinedAcl`, `PredefinedDefaultObjectAcl`, `Projection`, + * `UserProject`, and `OverrideDefaultProject`. * * @par Idempotency * This operation is always idempotent. It fails if the bucket already exists. @@ -424,9 +424,10 @@ class Client { * @param metadata the metadata for the new Bucket. The `name` field is * ignored in favor of @p bucket_name. * @param options a list of optional query parameters and/or request headers. - * Valid types for this operation include `PredefinedAcl`, - * `PredefinedDefaultObjectAcl`, `Projection`, and `UserProject`. - * `OverrideDefaultProject` is accepted, but has no effect. + * Valid types for this operation include `EnableObjectRetention`, + * `PredefinedAcl`, `PredefinedDefaultObjectAcl`, `Projection`, and + * `UserProject`. The function also accepts `OverrideDefaultProject`, but + * this option has no effect. * * @par Idempotency * This operation is always idempotent. It fails if the bucket already exists. diff --git a/google/cloud/storage/enable_object_retention.h b/google/cloud/storage/enable_object_retention.h new file mode 100644 index 0000000000000..cefe455ad3fab --- /dev/null +++ b/google/cloud/storage/enable_object_retention.h @@ -0,0 +1,42 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_ENABLE_OBJECT_RETENTION_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_ENABLE_OBJECT_RETENTION_H + +#include "google/cloud/storage/internal/well_known_parameters_impl.h" +#include "google/cloud/storage/version.h" + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * Set this parameter to `true` to create buckets with object retention enabled. + */ +struct EnableObjectRetention + : public internal::WellKnownParameter { + using WellKnownParameter::WellKnownParameter; + static char const* well_known_parameter_name() { + return "enableObjectRetention"; + } +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_ENABLE_OBJECT_RETENTION_H diff --git a/google/cloud/storage/examples/CMakeLists.txt b/google/cloud/storage/examples/CMakeLists.txt index b9fbe0d51e435..b8266956719be 100644 --- a/google/cloud/storage/examples/CMakeLists.txt +++ b/google/cloud/storage/examples/CMakeLists.txt @@ -37,6 +37,7 @@ set(storage_examples storage_bucket_cors_samples.cc storage_bucket_default_kms_key_samples.cc storage_bucket_iam_samples.cc + storage_bucket_object_retention_samples.cc storage_bucket_requester_pays_samples.cc storage_bucket_samples.cc storage_bucket_soft_delete_samples.cc diff --git a/google/cloud/storage/examples/storage_bucket_object_retention_samples.cc b/google/cloud/storage/examples/storage_bucket_object_retention_samples.cc new file mode 100644 index 0000000000000..55cab9f5d3e7c --- /dev/null +++ b/google/cloud/storage/examples/storage_bucket_object_retention_samples.cc @@ -0,0 +1,117 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/storage/client.h" +#include "google/cloud/storage/examples/storage_examples_common.h" +#include "google/cloud/internal/getenv.h" +#include +#include +#include + +namespace { + +void CreateBucketWithObjectRetention(google::cloud::storage::Client client, + std::vector const& argv) { + //! [create-bucket-with-object-retention] + namespace gcs = ::google::cloud::storage; + using ::google::cloud::StatusOr; + [](gcs::Client client, std::string const& bucket_name, + std::string const& project_id) { + auto bucket = client.CreateBucket(bucket_name, gcs::BucketMetadata{}, + gcs::EnableObjectRetention(true), + gcs::OverrideDefaultProject(project_id)); + if (!bucket) throw std::move(bucket).status(); + + if (!bucket->has_object_retention()) { + throw std::runtime_error("missing object retention in new bucket"); + } + std::cout << "Successfully created bucket " << bucket_name + << " with object retention: " << bucket->object_retention() + << "\n"; + } + //! [create-bucket-with-object-retention] + (std::move(client), argv.at(0), argv.at(1)); +} + +void GetBucketObjectRetention(google::cloud::storage::Client client, + std::vector const& argv) { + //! [get-bucket-object-retention] + namespace gcs = ::google::cloud::storage; + using ::google::cloud::StatusOr; + [](gcs::Client client, std::string const& bucket_name) { + auto bucket = client.GetBucketMetadata(bucket_name); + if (!bucket) throw std::move(bucket).status(); + + if (!bucket->has_object_retention()) { + std::cout << "Bucket " << bucket->name() + << " does not have object retention enabled\n"; + return; + } + std::cout << "Bucket " << bucket->name() + << " has a object retention enabled: " + << bucket->object_retention() << "\n"; + } + //! [get-bucket-object-retention] + (std::move(client), argv.at(0)); +} + +void RunAll(std::vector const& argv) { + namespace examples = ::google::cloud::storage::examples; + namespace gcs = ::google::cloud::storage; + + if (!argv.empty()) throw examples::Usage{"auto"}; + if (examples::UsingEmulator()) return; + examples::CheckEnvironmentVariablesAreSet({ + "GOOGLE_CLOUD_PROJECT", + }); + auto const project_id = + google::cloud::internal::GetEnv("GOOGLE_CLOUD_PROJECT").value(); + auto generator = google::cloud::internal::DefaultPRNG(std::random_device{}()); + auto const bucket_name = examples::MakeRandomBucketName(generator); + + auto client = gcs::Client(); + + std::cout << "Running the CreateBucketWithSoftDelete() example" << std::endl; + CreateBucketWithObjectRetention(client, {bucket_name, project_id}); + + // In GCS a single project cannot create or delete buckets more often than + // once every two seconds. We will pause until that time before deleting the + // bucket. + auto pause = std::chrono::steady_clock::now() + std::chrono::seconds(2); + + std::cout << "\nRunning the GetBucketObjectRetention() example" << std::endl; + GetBucketObjectRetention(client, {bucket_name}); + + if (!examples::UsingEmulator()) std::this_thread::sleep_until(pause); + (void)examples::RemoveBucketAndContents(client, bucket_name); +} + +} // namespace + +int main(int argc, char* argv[]) { + namespace examples = ::google::cloud::storage::examples; + auto make_entry = [](std::string const& name, + std::vector arg_names, + examples::ClientCommand const& cmd) { + arg_names.insert(arg_names.begin(), ""); + return examples::CreateCommandEntry(name, std::move(arg_names), cmd); + }; + examples::Example example({ + make_entry("create-bucket-with-object-retention", {""}, + CreateBucketWithObjectRetention), + make_entry("get-bucket-object-retention", {}, GetBucketObjectRetention), + {"auto", RunAll}, + }); + return example.Run(argc, argv); +} diff --git a/google/cloud/storage/examples/storage_examples.bzl b/google/cloud/storage/examples/storage_examples.bzl index 6617459365c92..d9edc6377673e 100644 --- a/google/cloud/storage/examples/storage_examples.bzl +++ b/google/cloud/storage/examples/storage_examples.bzl @@ -22,6 +22,7 @@ storage_examples = [ "storage_bucket_cors_samples.cc", "storage_bucket_default_kms_key_samples.cc", "storage_bucket_iam_samples.cc", + "storage_bucket_object_retention_samples.cc", "storage_bucket_requester_pays_samples.cc", "storage_bucket_samples.cc", "storage_bucket_soft_delete_samples.cc", diff --git a/google/cloud/storage/google_cloud_cpp_storage.bzl b/google/cloud/storage/google_cloud_cpp_storage.bzl index a97ce6da7af6e..58dcbc4d91638 100644 --- a/google/cloud/storage/google_cloud_cpp_storage.bzl +++ b/google/cloud/storage/google_cloud_cpp_storage.bzl @@ -29,6 +29,7 @@ google_cloud_cpp_storage_hdrs = [ "bucket_lifecycle.h", "bucket_logging.h", "bucket_metadata.h", + "bucket_object_retention.h", "bucket_retention_policy.h", "bucket_rpo.h", "bucket_soft_delete_policy.h", @@ -37,6 +38,7 @@ google_cloud_cpp_storage_hdrs = [ "client.h", "client_options.h", "download_options.h", + "enable_object_retention.h", "hash_mismatch_error.h", "hashing_options.h", "headers_map.h", @@ -164,6 +166,7 @@ google_cloud_cpp_storage_srcs = [ "bucket_iam_configuration.cc", "bucket_logging.cc", "bucket_metadata.cc", + "bucket_object_retention.cc", "bucket_retention_policy.cc", "bucket_soft_delete_policy.cc", "client.cc", diff --git a/google/cloud/storage/google_cloud_cpp_storage.cmake b/google/cloud/storage/google_cloud_cpp_storage.cmake index 833fbfd4e4bcf..0dd15cc02a1f5 100644 --- a/google/cloud/storage/google_cloud_cpp_storage.cmake +++ b/google/cloud/storage/google_cloud_cpp_storage.cmake @@ -43,6 +43,8 @@ add_library( bucket_logging.h bucket_metadata.cc bucket_metadata.h + bucket_object_retention.cc + bucket_object_retention.h bucket_retention_policy.cc bucket_retention_policy.h bucket_rpo.h @@ -55,6 +57,7 @@ add_library( client_options.cc client_options.h download_options.h + enable_object_retention.h hash_mismatch_error.h hashing_options.cc hashing_options.h @@ -425,6 +428,7 @@ if (BUILD_TESTING) bucket_cors_entry_test.cc bucket_iam_configuration_test.cc bucket_metadata_test.cc + bucket_object_retention_test.cc bucket_soft_delete_policy_test.cc client_bucket_acl_test.cc client_bucket_test.cc diff --git a/google/cloud/storage/internal/bucket_metadata_parser.cc b/google/cloud/storage/internal/bucket_metadata_parser.cc index a68f5f41bd6c8..dac162cd070dc 100644 --- a/google/cloud/storage/internal/bucket_metadata_parser.cc +++ b/google/cloud/storage/internal/bucket_metadata_parser.cc @@ -222,6 +222,14 @@ std::map ParseLabels(nlohmann::json const& json) { return value; } +Status ParseObjectRetention(BucketMetadata& meta, nlohmann::json const& json) { + auto l = json.find("objectRetention"); + if (l == json.end()) return Status{}; + auto enabled = l->value("mode", "") == "Enabled"; + meta.set_object_retention(BucketObjectRetention{enabled}); + return Status{}; +} + Status ParseOwner(BucketMetadata& meta, nlohmann::json const& json) { if (!json.contains("owner")) return Status{}; auto const& o = json["owner"]; @@ -570,6 +578,7 @@ StatusOr BucketMetadataParser::FromJson( meta.set_project_number(*v); return Status{}; }, + ParseObjectRetention, ParseOwner, ParseRetentionPolicy, [](BucketMetadata& meta, nlohmann::json const& json) { diff --git a/google/cloud/storage/internal/bucket_requests.h b/google/cloud/storage/internal/bucket_requests.h index d9f28a39769cf..d644d714d5e67 100644 --- a/google/cloud/storage/internal/bucket_requests.h +++ b/google/cloud/storage/internal/bucket_requests.h @@ -16,6 +16,7 @@ #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_BUCKET_REQUESTS_H #include "google/cloud/storage/bucket_metadata.h" +#include "google/cloud/storage/enable_object_retention.h" #include "google/cloud/storage/iam_policy.h" #include "google/cloud/storage/internal/generic_request.h" #include "google/cloud/storage/internal/http_response.h" @@ -90,9 +91,9 @@ std::ostream& operator<<(std::ostream& os, GetBucketMetadataRequest const& r); * Represents a request to the `Buckets: insert` API. */ class CreateBucketRequest - : public GenericRequest { + : public GenericRequest { public: CreateBucketRequest() = default; explicit CreateBucketRequest(std::string project_id, BucketMetadata metadata) diff --git a/google/cloud/storage/storage_client_unit_tests.bzl b/google/cloud/storage/storage_client_unit_tests.bzl index d38b0dab3021d..9bad5f333cf7c 100644 --- a/google/cloud/storage/storage_client_unit_tests.bzl +++ b/google/cloud/storage/storage_client_unit_tests.bzl @@ -22,6 +22,7 @@ storage_client_unit_tests = [ "bucket_cors_entry_test.cc", "bucket_iam_configuration_test.cc", "bucket_metadata_test.cc", + "bucket_object_retention_test.cc", "bucket_soft_delete_policy_test.cc", "client_bucket_acl_test.cc", "client_bucket_test.cc",