diff --git a/google-cloud-storage/lib/google/cloud/storage/bucket.rb b/google-cloud-storage/lib/google/cloud/storage/bucket.rb index 5f80729ad74a..3b5ea98a4c70 100644 --- a/google-cloud-storage/lib/google/cloud/storage/bucket.rb +++ b/google-cloud-storage/lib/google/cloud/storage/bucket.rb @@ -2280,6 +2280,31 @@ def signed_url path = nil, end end + # Fetches generation no. of bucket + # @example + # require "google/cloud/storage" + # storage = Google::Cloud::Storage.new + # bucket = storage.bucket "my-bucket" + # generation= bucket.generation + def generation + @gapi.generation + end + + # Fetches soft_delete_time of a soft deleted bucket + # @example + # bucket.delete + # bucket.soft_delete_time + def soft_delete_time + @gapi.soft_delete_time + end + + # Fetches hard_delete_time of a soft deleted bucket + # @example + # bucket.hard_delete_time + def hard_delete_time + @gapi.hard_delete_time + end + ## # Generate a PostObject that includes the fields and URL to # upload objects via HTML forms. diff --git a/google-cloud-storage/lib/google/cloud/storage/bucket/list.rb b/google-cloud-storage/lib/google/cloud/storage/bucket/list.rb index e596fb4fda8a..428300c40df0 100644 --- a/google-cloud-storage/lib/google/cloud/storage/bucket/list.rb +++ b/google-cloud-storage/lib/google/cloud/storage/bucket/list.rb @@ -146,7 +146,7 @@ def all request_limit: nil, &block # @private New Bucket::List from a Google API Client # Google::Apis::StorageV1::Buckets object. def self.from_gapi gapi_list, service, prefix = nil, max = nil, - user_project: nil + user_project: nil, soft_deleted: nil buckets = new(Array(gapi_list.items).map do |gapi_object| Bucket.from_gapi gapi_object, service, user_project: user_project end) @@ -155,6 +155,7 @@ def self.from_gapi gapi_list, service, prefix = nil, max = nil, buckets.instance_variable_set :@prefix, prefix buckets.instance_variable_set :@max, max buckets.instance_variable_set :@user_project, user_project + buckets.instance_variable_set :@soft_deleted, soft_deleted buckets end diff --git a/google-cloud-storage/lib/google/cloud/storage/project.rb b/google-cloud-storage/lib/google/cloud/storage/project.rb index 89ee56952620..1b340763aac9 100644 --- a/google-cloud-storage/lib/google/cloud/storage/project.rb +++ b/google-cloud-storage/lib/google/cloud/storage/project.rb @@ -193,11 +193,20 @@ def add_custom_header header_name, header_value # puts bucket.name # end # - def buckets prefix: nil, token: nil, max: nil, user_project: nil + # @example Retrieve soft deleted buckets + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # + # soft_deleted_buckets = storage.buckets soft_deleted: true + # soft_deleted_buckets.each do |bucket| + # puts bucket.name + # end + def buckets prefix: nil, token: nil, max: nil, user_project: nil, soft_deleted: nil gapi = service.list_buckets \ - prefix: prefix, token: token, max: max, user_project: user_project + prefix: prefix, token: token, max: max, user_project: user_project, soft_deleted: soft_deleted Bucket::List.from_gapi \ - gapi, service, prefix, max, user_project: user_project + gapi, service, prefix, max, user_project: user_project, soft_deleted: soft_deleted end alias find_buckets buckets @@ -223,6 +232,10 @@ def buckets prefix: nil, token: nil, max: nil, user_project: nil # account, transit costs will be billed to the given project. This # parameter is required with requester pays-enabled buckets. The # default is `nil`. + # @param [Integer] generation generation no of bucket + # on whether the bucket's current metageneration matches the given value. + # @param [Boolean] soft_deleted If true, returns the soft-deleted bucket. + # This parameter is required if generation is specified. # # The value provided will be applied to all operations on the returned # bucket instance and its files. @@ -256,9 +269,20 @@ def buckets prefix: nil, token: nil, max: nil, user_project: nil # bucket = storage.bucket "other-project-bucket", # user_project: "my-other-project" # files = bucket.files # Billed to "my-other-project" + # @example With `soft_deleted` set to a true and generation specified: + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # + # bucket = storage.bucket "my-bucket", + # soft_deleted: true, + # generation: 1234567889 + # puts bucket.name # def bucket bucket_name, skip_lookup: false, + generation: nil, + soft_deleted: nil, if_metageneration_match: nil, if_metageneration_not_match: nil, user_project: nil @@ -269,7 +293,10 @@ def bucket bucket_name, gapi = service.get_bucket bucket_name, if_metageneration_match: if_metageneration_match, if_metageneration_not_match: if_metageneration_not_match, - user_project: user_project + user_project: user_project, + soft_deleted: soft_deleted, + generation: generation + Bucket.from_gapi gapi, service, user_project: user_project rescue Google::Cloud::NotFoundError nil @@ -554,6 +581,32 @@ def hmac_keys service_account_email: nil, project_id: nil, max: max, user_project: user_project end + ## + # Restores a soft deleted bucket with bucket name and generation. + # + # @param [String] bucket_name Name of the bucket. + # @param [Fixnum] generation generation of the bucket. + # + # @return [Google::Cloud::Storage::Bucket, nil] Returns nil if bucket + # does not exist + # + # @example + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # generation= 123 + # + # bucket = storage.restore_bucket "my-bucket", generation + # puts bucket.name + # + def restore_bucket bucket_name, + generation, + options: {} + gapi = service.restore_bucket bucket_name, generation, + options: options + Bucket.from_gapi gapi, service + end + ## # Generates a signed URL. See [Signed # URLs](https://cloud.google.com/storage/docs/access-control/signed-urls) diff --git a/google-cloud-storage/lib/google/cloud/storage/service.rb b/google-cloud-storage/lib/google/cloud/storage/service.rb index 49abcf43dcb8..838445227ebd 100644 --- a/google-cloud-storage/lib/google/cloud/storage/service.rb +++ b/google-cloud-storage/lib/google/cloud/storage/service.rb @@ -97,11 +97,12 @@ def project_service_account ## # Retrieves a list of buckets for the given project. - def list_buckets prefix: nil, token: nil, max: nil, user_project: nil, options: {} + def list_buckets prefix: nil, token: nil, max: nil, user_project: nil, soft_deleted: nil, options: {} execute do service.list_buckets \ @project, prefix: prefix, page_token: token, max_results: max, - user_project: user_project(user_project), options: options + user_project: user_project(user_project), + soft_deleted: soft_deleted, options: options end end @@ -112,12 +113,16 @@ def get_bucket bucket_name, if_metageneration_match: nil, if_metageneration_not_match: nil, user_project: nil, + soft_deleted: nil, + generation: nil, options: {} execute do service.get_bucket bucket_name, if_metageneration_match: if_metageneration_match, if_metageneration_not_match: if_metageneration_not_match, user_project: user_project(user_project), + soft_deleted: soft_deleted, + generation: generation, options: options end end @@ -654,6 +659,17 @@ def delete_file bucket_name, end end + ## + # Restore soft deleted bucket + def restore_bucket bucket_name, + generation, + options: {} + execute do + service.restore_bucket bucket_name, generation, + options: options + end + end + ## # Restores a soft-deleted object. def restore_file bucket_name, diff --git a/google-cloud-storage/samples/acceptance/buckets_test.rb b/google-cloud-storage/samples/acceptance/buckets_test.rb index f4f416744fd1..95dbebdcc8b2 100644 --- a/google-cloud-storage/samples/acceptance/buckets_test.rb +++ b/google-cloud-storage/samples/acceptance/buckets_test.rb @@ -40,8 +40,10 @@ require_relative "../storage_get_public_access_prevention" require_relative "../storage_get_requester_pays_status" require_relative "../storage_get_retention_policy" +require_relative "../storage_get_soft_deleted_bucket" require_relative "../storage_get_uniform_bucket_level_access" require_relative "../storage_list_buckets" +require_relative "../storage_list_soft_deleted_buckets" require_relative "../storage_lock_retention_policy" require_relative "../storage_remove_bucket_label" require_relative "../storage_remove_cors_configuration" @@ -119,6 +121,30 @@ end end + describe "storage_soft_deleted_bucket" do + let(:new_bucket_name) { random_bucket_name } + + it "get soft deleted bucket, its soft_delete_time and hard_delete_time" do + new_bucket = storage_client.create_bucket new_bucket_name + new_generation = new_bucket.generation + + delete_bucket_helper new_bucket_name + _out, _err = capture_io do + get_soft_deleted_bucket bucket_name: new_bucket_name, generation: new_generation + end + assert "soft_delete_time ", "Bucket soft_delete_time should be present" + assert "hard_delete_time ", "Bucket hard_delete_time should be present" + end + + it "lists soft deleted buckets" do + list_deleted_bucket, _err = capture_io do + list_soft_deleted_buckets + end + assert list_deleted_bucket, "List of soft deleted bucket should not be blank" + end + end + + describe "storage_create_bucket_dual_region" do it "creates dual region bucket" do location = "US" diff --git a/google-cloud-storage/samples/acceptance/project_test.rb b/google-cloud-storage/samples/acceptance/project_test.rb index 2de54569a0ba..6e3b944109d3 100644 --- a/google-cloud-storage/samples/acceptance/project_test.rb +++ b/google-cloud-storage/samples/acceptance/project_test.rb @@ -14,6 +14,7 @@ require_relative "helper" require_relative "../storage_get_service_account" +require_relative "../storage_restore_bucket" describe "Storage Quickstart" do let(:project) { Google::Cloud::Storage.new } @@ -28,3 +29,17 @@ assert_includes out, "@gs-project-accounts.iam.gserviceaccount.com" end end + +describe "storage_soft_deleted_bucket" do + let(:storage_client) { Google::Cloud::Storage.new } + let(:bucket) { fixture_bucket } + let(:generation) { bucket.gapi.generation } + + it "restores a soft deleted bucket" do + delete_bucket_helper bucket.name + _out, _err = capture_io do + restore_bucket bucket_name: bucket.name, generation: generation + end + assert "soft_delete_time", "#{bucket.name} Bucket restored" + end +end diff --git a/google-cloud-storage/samples/storage_get_soft_deleted_bucket.rb b/google-cloud-storage/samples/storage_get_soft_deleted_bucket.rb new file mode 100644 index 000000000000..22d59149b9bc --- /dev/null +++ b/google-cloud-storage/samples/storage_get_soft_deleted_bucket.rb @@ -0,0 +1,43 @@ +# 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 +# +# http://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. + +# [START storage_get_soft_deleted_bucket] +def get_soft_deleted_bucket bucket_name:, generation: + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + # The generation no of your GCS bucket + # generation = "1234567896987" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + bucket_name = bucket_name.gsub(/[^a-zA-Z0-9\- ]/, "") + + # fetching soft deleted bucket with soft_delete_time and hard_delete_time + deleted_bucket_fetch = storage.bucket bucket_name, generation: generation, soft_deleted: true + + soft_delete_time = deleted_bucket_fetch.soft_delete_time + hard_delete_time = deleted_bucket_fetch.hard_delete_time + + if (soft_delete_time && hard_delete_time).nil? + puts "Not Found" + else + puts "soft_delete_time - #{soft_delete_time}" + puts "hard_delete_time - #{hard_delete_time}" + end +end +# [END storage_get_soft_deleted_bucket] + + +get_soft_deleted_bucket bucket_name: ARGV.shift, generation: ARGV.shift if $PROGRAM_NAME == __FILE__ diff --git a/google-cloud-storage/samples/storage_list_soft_deleted_buckets.rb b/google-cloud-storage/samples/storage_list_soft_deleted_buckets.rb new file mode 100644 index 000000000000..4869fb70d7fc --- /dev/null +++ b/google-cloud-storage/samples/storage_list_soft_deleted_buckets.rb @@ -0,0 +1,30 @@ +# 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 +# +# http://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. + +# [START storage_list_soft_deleted_buckets] +def list_soft_deleted_buckets + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + + # fetching soft deleted bucket list + deleted_buckets = storage.buckets soft_deleted: true + + deleted_buckets.each do |bucket| + puts bucket.name + end +end +# [END storage_list_soft_deleted_buckets] + +list_soft_deleted_buckets if $PROGRAM_NAME == __FILE__ diff --git a/google-cloud-storage/samples/storage_restore_bucket.rb b/google-cloud-storage/samples/storage_restore_bucket.rb new file mode 100644 index 000000000000..c2cee7944174 --- /dev/null +++ b/google-cloud-storage/samples/storage_restore_bucket.rb @@ -0,0 +1,36 @@ +# 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 +# +# http://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. + +# [START storage_restore_bucket] +def restore_bucket bucket_name:, generation: + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + # The generation no of your GCS bucket + # generation = "1234567896987" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + + bucket_restored = storage.restore_bucket bucket_name, generation + + if bucket_restored.name == bucket_name + puts "#{bucket_name} Bucket restored" + else + puts "#{bucket_name} Bucket not restored" + end +end +# [END storage_restore_bucket] + +restore_bucket bucket_name: ARGV.shift, generation: ARGV.shift if $PROGRAM_NAME == __FILE__ diff --git a/google-cloud-storage/test/google/cloud/storage/project_test.rb b/google-cloud-storage/test/google/cloud/storage/project_test.rb index 5e1b88ad99da..942c6177732f 100644 --- a/google-cloud-storage/test/google/cloud/storage/project_test.rb +++ b/google-cloud-storage/test/google/cloud/storage/project_test.rb @@ -615,7 +615,7 @@ def stub.insert_bucket *args num_buckets = 3 mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(num_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(num_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -633,7 +633,7 @@ def stub.insert_bucket *args num_buckets = 3 mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(num_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(num_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -644,10 +644,31 @@ def stub.insert_bucket *args _(buckets.size).must_equal num_buckets end + it "lists deleted buckets" do + num_buckets = 3 + soft_deleted= true + + mock = Minitest::Mock.new + mock.expect :list_buckets, list_buckets_gapi(num_buckets,nil,soft_deleted), [project], prefix: nil, page_token: nil, +max_results: nil, user_project: nil, soft_deleted: true, options: {} + + storage.service.mocked_service = mock + buckets = storage.buckets soft_deleted: true + + mock.verify + + _(buckets.size).must_equal num_buckets + bucket = buckets.first + _(bucket).must_be_kind_of Google::Cloud::Storage::Bucket + _(bucket.generation).wont_be_nil + _(bucket.hard_delete_time).wont_be_nil + _(bucket.soft_delete_time).wont_be_nil + end + it "paginates buckets" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -666,8 +687,8 @@ def stub.insert_bucket *args it "paginates buckets with max set" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: 3, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: 3, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: 3, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: 3, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -688,7 +709,7 @@ def stub.insert_bucket *args num_buckets = 3 mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -705,8 +726,8 @@ def stub.insert_bucket *args it "paginates buckets with next? and next" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -724,8 +745,8 @@ def stub.insert_bucket *args it "paginates buckets with next? and next and max set" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: 3, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: 3, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: 3, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: 3, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -743,8 +764,8 @@ def stub.insert_bucket *args it "paginates buckets with all" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -757,8 +778,8 @@ def stub.insert_bucket *args it "paginates buckets with all and max set" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: 3, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: 3, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: 3, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: "next_page_token", max_results: 3, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -771,8 +792,8 @@ def stub.insert_bucket *args it "iterates buckets with all using Enumerator" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -785,8 +806,8 @@ def stub.insert_bucket *args it "iterates buckets with all and request_limit set" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, options: {} - mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: nil,soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -799,8 +820,8 @@ def stub.insert_bucket *args it "iterates buckets with all and user_project set to true" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: "test", options: {} - mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: "test", options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: "test", soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: "test", soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -814,8 +835,8 @@ def stub.insert_bucket *args it "iterates buckets with all and user_project set to another project ID" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: "my-other-project", options: {} - mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: "my-other-project", options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "next_page_token"), [project], prefix: nil, page_token: nil, max_results: nil, user_project: "my-other-project", soft_deleted: nil, options: {} + mock.expect :list_buckets, list_buckets_gapi(3, "second_page_token"), [project], prefix: nil, page_token: "next_page_token", max_results: nil, user_project: "my-other-project", soft_deleted: nil, options: {} storage.service.mocked_service = mock @@ -843,6 +864,43 @@ def stub.insert_bucket *args _(bucket).wont_be :lazy? end + it "finds a deleted bucket" do + bucket_name = "found-bucket" + generation = 1_733_393_981_548_601_746 + soft_deleted= true + + mock = Minitest::Mock.new + mock.expect :get_bucket, find_bucket_gapi(bucket_name, soft_deleted), + [bucket_name], **get_bucket_args(soft_deleted: soft_deleted, generation: generation) + + storage.service.mocked_service = mock + bucket = storage.bucket bucket_name, soft_deleted: soft_deleted, generation: generation + + mock.verify + + _(bucket.name).must_equal bucket_name + _(bucket.generation).must_equal generation + _(bucket.soft_delete_time).wont_be_nil + _(bucket.hard_delete_time).wont_be_nil + + _(bucket).wont_be :lazy? + end + + it "restores a deleted bucket" do + bucket_name = "found-bucket" + generation = 1_733_393_981_548_601_746 + + mock = Minitest::Mock.new + mock.expect :restore_bucket, restored_bucket_gapi(bucket_name), + [bucket_name, generation], options: {} + + storage.service.mocked_service = mock + bucket = storage.restore_bucket bucket_name, generation + mock.verify + _(bucket.name).must_equal bucket_name + _(bucket.generation).must_equal generation + end + it "finds a bucket with find_bucket alias" do bucket_name = "found-bucket" @@ -1011,17 +1069,23 @@ def create_bucket_gapi name = nil, Google::Apis::StorageV1::Bucket.new **options end - def find_bucket_gapi name = nil - Google::Apis::StorageV1::Bucket.from_json random_bucket_hash(name: name).to_json + + def find_bucket_gapi name = nil, soft_deleted= nil + Google::Apis::StorageV1::Bucket.from_json random_bucket_hash(name: name, soft_deleted: soft_deleted).to_json end - def list_buckets_gapi count = 2, token = nil - buckets = count.times.map { Google::Apis::StorageV1::Bucket.from_json random_bucket_hash.to_json } + def list_buckets_gapi count = 2, token = nil, soft_deleted = nil + buckets = count.times.map { Google::Apis::StorageV1::Bucket.from_json random_bucket_hash(soft_deleted: soft_deleted).to_json } Google::Apis::StorageV1::Buckets.new( kind: "storage#buckets", items: buckets, next_page_token: token ) end + def restored_bucket_gapi name + Google::Apis::StorageV1::Bucket.from_json random_bucket_hash(name: name).to_json + end + + def object_retention_param enable_object_retention enable_object_retention ? Google::Apis::StorageV1::Bucket::ObjectRetention.new(mode: "Enabled") : nil end diff --git a/google-cloud-storage/test/helper.rb b/google-cloud-storage/test/helper.rb index 50455d7c7e16..60ed111f0897 100644 --- a/google-cloud-storage/test/helper.rb +++ b/google-cloud-storage/test/helper.rb @@ -70,15 +70,20 @@ def random_bucket_hash name: random_bucket_name, autoclass_terminal_storage_class: nil, enable_object_retention: nil, effective_time: DateTime.now, - retention_duration_seconds: 604800, # 7 days - hierarchical_namespace: nil + retention_duration_seconds: 604_800, # 7 days + soft_deleted: nil, + hierarchical_namespace: nil, + generation: "1733393981548601746" versioning_config = { "enabled" => versioning } if versioning - { "kind" => "storage#bucket", + + data = { + "kind" => "storage#bucket", "id" => name, "selfLink" => "#{url_root}/b/#{name}", "projectNumber" => "1234567890", "name" => name, "timeCreated" => Time.now, + "generation" => generation, "metageneration" => "1", "owner" => { "entity" => "project-owners-1234567890" }, "location" => location, @@ -96,10 +101,16 @@ def random_bucket_hash name: random_bucket_name, "enableObjectRetention" => enable_object_retention, "softDeletePolicy" => soft_delete_policy_object(retention_duration_seconds: retention_duration_seconds), "hierarchicalNamespace" => hierarchical_namespace - }.delete_if { |_, v| v.nil? } + } + if soft_deleted + soft_delete_policy = soft_delete_policy_object retention_duration_seconds: retention_duration_seconds + data["softDeleteTime"] = soft_delete_policy.effective_time + data["hardDeleteTime"] = soft_delete_policy.effective_time.to_time + retention_duration_seconds + end + data.delete_if { |_, v| v.nil? } end - def soft_delete_policy_object retention_duration_seconds: 604800 # 7 days + def soft_delete_policy_object retention_duration_seconds: 604_800 # 7 days Google::Apis::StorageV1::Bucket::SoftDeletePolicy.new( effective_time: DateTime.now, retention_duration_seconds: retention_duration_seconds @@ -282,11 +293,15 @@ def policy_gapi etag: "CAE=", version: 1, bindings: [] def get_bucket_args if_metageneration_match: nil, if_metageneration_not_match: nil, user_project: nil, + generation: nil, + soft_deleted: nil, options: {} { if_metageneration_match: if_metageneration_match, if_metageneration_not_match: if_metageneration_not_match, user_project: user_project, + generation: generation, + soft_deleted: soft_deleted, options: options } end @@ -554,4 +569,4 @@ def restore_file_gapi bucket, file_name, generation=nil file_hash = random_file_hash(bucket, file_name, generation).to_json Google::Apis::StorageV1::Object.from_json file_hash end -end +end \ No newline at end of file