diff --git a/google-cloud-storage/acceptance/storage/bucket_ip_filter_test.rb b/google-cloud-storage/acceptance/storage/bucket_ip_filter_test.rb new file mode 100644 index 000000000000..ba8d2a37b46a --- /dev/null +++ b/google-cloud-storage/acceptance/storage/bucket_ip_filter_test.rb @@ -0,0 +1,113 @@ +# Copyright 2026 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. + +require_relative "../storage_helper" +describe Google::Cloud::Storage::Bucket, :storage do + let(:ip_filter_disabled) do + { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: ["0.0.0.0/0", "::/0"] + } + } + end + + let(:ip_filter_disabled_update) do + { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: ["8.8.8.8/32"] + }, + vpc_network_sources: [ + { + network: "projects/#{storage.project}/global/networks/default", + allowed_ip_cidr_ranges: ["0.0.0.0/0"] + } + ] + } + end + let(:bucket_name) { "#{$bucket_names.first}-ip-filter" } + let :bucket do + storage.bucket(bucket_name, projection: "full") || + storage.create_bucket(bucket_name, ip_filter: ip_filter_disabled) + end + + after(:all) do + safe_gcs_execute { bucket.delete } + end + + it "returns a 400 error when provided an invalid IP CIDR range" do + invalid_ip_filter = { + mode: "Enabled", + public_network_source: { + allowed_ip_cidr_ranges: ["invalid-ip-range"] + } + } + + # Verify that creating a bucket with an invalid CIDR raises an error + err = expect { + storage.create_bucket "#{bucket_name}-invalid", ip_filter: invalid_ip_filter + }.must_raise Google::Cloud::InvalidArgumentError + + _(err.message).must_match /invalid/i + end + + it "creates, gets, updates, and deletes a bucket with ip_filter" do + + + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0", "::/0"] + + # Get the bucket and verify ip_filter + bucket = storage.bucket bucket_name, projection: "full" + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0", "::/0"] + + # list_bucket_ip_filters + found = false + storage.buckets(projection: "full").all do |b| + found = true if b.name == bucket_name + end + _(found).must_equal true + + # Update the ip_filter + safe_gcs_execute do + bucket.update do |b| + b.ip_filter = ip_filter_disabled_update + end + end + + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["8.8.8.8/32"] + _(bucket.ip_filter.vpc_network_sources.first.network).must_equal "projects/#{storage.project}/global/networks/default" + _(bucket.ip_filter.vpc_network_sources.first.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0"] + + # Disable ip_filter and clear network sources + safe_gcs_execute do + bucket.ip_filter = { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: [] + }, + vpc_network_sources: [] + } + end + + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_be_nil + _(bucket.ip_filter.vpc_network_sources).must_be_nil + + end +end diff --git a/google-cloud-storage/lib/google/cloud/storage/bucket.rb b/google-cloud-storage/lib/google/cloud/storage/bucket.rb index 50cd2c739fcd..c311aa302a63 100644 --- a/google-cloud-storage/lib/google/cloud/storage/bucket.rb +++ b/google-cloud-storage/lib/google/cloud/storage/bucket.rb @@ -1576,6 +1576,80 @@ def delete if_metageneration_match: nil, if_metageneration_not_match: nil user_project: user_project end + ## + # The bucket's IP filter configuration. + # This value can be modified by calling {#ip_filter=}. + # + # @note The IP filter metadata is not returned by the API by default. + # You must retrieve the bucket with the `projection: :full` (or `"full"`) + # option to access this configuration. See {Project#bucket}. + # + # @return [Google::Apis::StorageV1::Bucket::IpFilter, nil] The bucket's IP filter configuration, + # or `nil` if not configured. + # + # @example + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # + # bucket = storage.bucket "my-bucket", projection: "full" + # ip_filter = bucket.ip_filter + # if ip_filter + # puts "Mode: #{ip_filter.mode}" + # puts "Public CIDR: #{ip_filter.public_network_source&.allowed_ip_cidr_ranges}" + # end + # + def ip_filter + @gapi.ip_filter + end + + ## + # Sets the value for IP filter in the bucket. This value can + # be queried by calling {#ip_filter}. + # + # @param [Google::Apis::StorageV1::Bucket::IpFilter, Hash] new_ip_filter The bucket's new IP filter. + # Acceptable Hash structure: + # * `:mode` (String) - The mode of the IP filter. Acceptable values are: "Disabled", "Enabled" + # * `:public_network_source` (Hash) - The public network source configuration: + # * `:allowed_ip_cidr_ranges` (Array) - Array of IP CIDR ranges allowed for public access. + # * `:vpc_network_sources` (Array) - The VPC network sources configuration: + # * `:network` (String) - The VPC network resource path, e.g. "projects/PROJECT_ID/global/networks/NETWORK_NAME". + # * `:allowed_ip_cidr_ranges` (Array) - Array of IP CIDR ranges allowed for VPC access. + # * `:allow_cross_org_vpcs` (Boolean) - Whether to allow cross-org VPC access. + # * `:allow_all_service_agent_access` (Boolean) - Whether to allow all service agent access. + # + # @example Enable IP filter with Hash: + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # + # bucket = storage.bucket "my-bucket", projection: "full" + # bucket.ip_filter = { + # mode: "Enabled", + # allow_all_service_agent_access: true, + # public_network_source: { + # allowed_ip_cidr_ranges: ["0.0.0.0/0", "::/0"] + # } + # } + # + # @example Clear/delete IP filter: + # require "google/cloud/storage" + # + # storage = Google::Cloud::Storage.new + # + # bucket = storage.bucket "my-bucket", projection: "full" + # bucket.ip_filter = { + # mode: "Disabled", + # public_network_source: { + # allowed_ip_cidr_ranges: [] + # } + # } + # + def ip_filter= new_ip_filter + @gapi.ip_filter = new_ip_filter || {} + patch_gapi! :ip_filter + end + ## # Retrieves a list of files matching the criteria. # @@ -3337,9 +3411,14 @@ def create_notification topic, custom_attrs: nil, event_types: nil, ## # Reloads the bucket with current data from the Storage service. # - def reload! + # @param [String] projection Set of properties to return. Accepted values + # are `noAcl` and `full`. The default value is `noAcl`. If set to `full`, + # the bucket will include additional metadata, such as ACL policies and + # IP filter settings. + # + def reload! projection: nil ensure_service! - @gapi = service.get_bucket name, user_project: user_project + @gapi = service.get_bucket name, user_project: user_project, projection: projection # If NotFound then lazy will never be unset @lazy = nil self 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 2378ed0034a9..6b3e7710f0f8 100644 --- a/google-cloud-storage/lib/google/cloud/storage/bucket/list.rb +++ b/google-cloud-storage/lib/google/cloud/storage/bucket/list.rb @@ -81,9 +81,13 @@ def next ensure_service! gapi = @service.list_buckets prefix: @prefix, token: @token, max: @max, user_project: @user_project, - soft_deleted: @soft_deleted + soft_deleted: @soft_deleted, + return_partial_success: !@unreachable.nil?, + projection: @projection Bucket::List.from_gapi gapi, @service, @prefix, @max, - user_project: @user_project + user_project: @user_project, soft_deleted: @soft_deleted, + return_partial_success: !@unreachable.nil?, + projection: @projection end ## @@ -155,7 +159,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, soft_deleted: nil, return_partial_success: nil + user_project: nil, soft_deleted: nil, return_partial_success: nil, projection: nil buckets = new(Array(gapi_list.items).map do |gapi_object| Bucket.from_gapi gapi_object, service, user_project: user_project end) @@ -165,6 +169,7 @@ def self.from_gapi gapi_list, service, prefix = nil, max = nil, buckets.instance_variable_set :@max, max buckets.instance_variable_set :@user_project, user_project buckets.instance_variable_set :@soft_deleted, soft_deleted + buckets.instance_variable_set :@projection, projection buckets.instance_variable_set :@unreachable, Array(gapi_list.unreachable) if return_partial_success 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 47e4ffc32da4..024005b853fb 100644 --- a/google-cloud-storage/lib/google/cloud/storage/project.rb +++ b/google-cloud-storage/lib/google/cloud/storage/project.rb @@ -159,8 +159,12 @@ def add_custom_header header_name, header_value # # See also {Bucket#requester_pays=} and {Bucket#requester_pays}. # @param [Boolean] return_partial_success - # If true, the response will contain a list of unreachable buckets. - # If false, ListBuckets will throw an error if there are any unreachable buckets. + # If true, the response will contain a list of unreachable buckets. + # If false, ListBuckets will throw an error if there are any unreachable buckets. + # @param [String] projection Set of properties to return. Accepted values + # are `noAcl` and `full`. The default value is `noAcl`. If set to `full`, + # the bucket will include additional metadata, such as ACL policies and + # IP filter settings. # # @return [Array] (See # {Google::Cloud::Storage::Bucket::List}) @@ -214,11 +218,11 @@ def add_custom_header header_name, header_value # puts unreachable_bucket_name # end # - def buckets prefix: nil, token: nil, max: nil, user_project: nil, soft_deleted: nil, return_partial_success: nil + def buckets prefix: nil, token: nil, max: nil, user_project: nil, soft_deleted: nil, return_partial_success: nil, projection: nil gapi = service.list_buckets \ - prefix: prefix, token: token, max: max, user_project: user_project, soft_deleted: soft_deleted, return_partial_success: return_partial_success + prefix: prefix, token: token, max: max, user_project: user_project, soft_deleted: soft_deleted, return_partial_success: return_partial_success, projection: projection Bucket::List.from_gapi \ - gapi, service, prefix, max, user_project: user_project, soft_deleted: soft_deleted, return_partial_success: return_partial_success + gapi, service, prefix, max, user_project: user_project, soft_deleted: soft_deleted, return_partial_success: return_partial_success, projection: projection end alias find_buckets buckets @@ -249,6 +253,10 @@ def buckets prefix: nil, token: nil, max: nil, user_project: nil, soft_deleted: # 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. + # @param [String] projection Set of properties to return. Accepted values + # are `noAcl` and `full`. The default value is `noAcl`. If set to `full`, + # the bucket will include additional metadata, such as ACL policies and + # IP filter settings. # # The value provided will be applied to all operations on the returned # bucket instance and its files. @@ -298,7 +306,8 @@ def bucket bucket_name, soft_deleted: nil, if_metageneration_match: nil, if_metageneration_not_match: nil, - user_project: nil + user_project: nil, + projection: nil if skip_lookup return Bucket.new_lazy bucket_name, service, user_project: user_project @@ -308,7 +317,8 @@ def bucket bucket_name, if_metageneration_not_match: if_metageneration_not_match, user_project: user_project, soft_deleted: soft_deleted, - generation: generation + generation: generation, + projection: projection Bucket.from_gapi gapi, service, user_project: user_project rescue Google::Cloud::NotFoundError @@ -423,7 +433,23 @@ def bucket bucket_name, # See also {Bucket#requester_pays=} and {Bucket#requester_pays}. # @param [Boolean] enable_object_retention # When set to true, object retention is enabled for this bucket. + # @param [String] projection Set of properties to return. Accepted values + # are `noAcl` and `full`. The default value is `noAcl`. If set to `full`, + # the bucket will include additional metadata, such as ACL policies and + # IP filter settings. # + # @param [Hash] ip_filter The bucket's IP filter configuration. + # Acceptable values are: + # * A {Google::Apis::StorageV1::Bucket::IpFilter} object. + # * A Hash that can be converted to a {Google::Apis::StorageV1::Bucket::IpFilter} object: + # * `:mode` (String) - The mode of the IP filter. Acceptable values are: "Disabled", "Enabled" + # * `:public_network_source` (Hash) - The public network source configuration: + # * `:allowed_ip_cidr_ranges` (Array) - Array of IP CIDR ranges allowed for public access. + # * `:vpc_network_sources` (Array) - The VPC network sources configuration: + # * `:network` (String) - The VPC network resource path, e.g. "projects/PROJECT_ID/global/networks/NETWORK_NAME". + # * `:allowed_ip_cidr_ranges` (Array) - Array of IP CIDR ranges allowed for VPC access. + # * `:allow_cross_org_vpcs` (Boolean) - Whether to allow cross-org VPC access. + # * `:allow_all_service_agent_access` (Boolean) - Whether to allow all service agent access. # @yield [bucket] a block for configuring the bucket before it is # created # @yieldparam [Bucket] bucket the bucket object to be configured @@ -477,12 +503,16 @@ def create_bucket bucket_name, user_project: nil, autoclass_enabled: false, enable_object_retention: nil, - hierarchical_namespace: nil + hierarchical_namespace: nil, + ip_filter: nil, + projection: nil + params = { name: bucket_name, location: location, custom_placement_config: custom_placement_config, - hierarchical_namespace: hierarchical_namespace + hierarchical_namespace: hierarchical_namespace, + ip_filter: ip_filter }.delete_if { |_, v| v.nil? } new_bucket = Google::Apis::StorageV1::Bucket.new(**params) storage_class = storage_class_for storage_class @@ -496,6 +526,7 @@ def create_bucket bucket_name, b.versioning = versioning unless versioning.nil? b.requester_pays = requester_pays unless requester_pays.nil? b.hierarchical_namespace = hierarchical_namespace unless hierarchical_namespace.nil? + b.ip_filter = ip_filter unless ip_filter.nil? end yield updater if block_given? updater.check_for_changed_labels! @@ -504,7 +535,8 @@ def create_bucket bucket_name, gapi = service.insert_bucket \ new_bucket, acl: acl_rule(acl), default_acl: acl_rule(default_acl), user_project: user_project, - enable_object_retention: enable_object_retention + enable_object_retention: enable_object_retention, + projection: projection Bucket.from_gapi gapi, service, user_project: user_project end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity diff --git a/google-cloud-storage/lib/google/cloud/storage/service.rb b/google-cloud-storage/lib/google/cloud/storage/service.rb index fd87490a5c62..25bef1b4c7cd 100644 --- a/google-cloud-storage/lib/google/cloud/storage/service.rb +++ b/google-cloud-storage/lib/google/cloud/storage/service.rb @@ -96,13 +96,14 @@ 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, soft_deleted: nil, return_partial_success: nil, options: {} + def list_buckets prefix: nil, token: nil, max: nil, user_project: nil, soft_deleted: nil, return_partial_success: nil, projection: nil, options: {} execute do service.list_buckets \ @project, prefix: prefix, page_token: token, max_results: max, user_project: user_project(user_project), soft_deleted: soft_deleted, return_partial_success: return_partial_success, + projection: projection, options: options end end @@ -116,6 +117,7 @@ def get_bucket bucket_name, user_project: nil, soft_deleted: nil, generation: nil, + projection: nil, options: {} execute do service.get_bucket bucket_name, @@ -124,6 +126,7 @@ def get_bucket bucket_name, user_project: user_project(user_project), soft_deleted: soft_deleted, generation: generation, + projection: projection, options: options end end @@ -133,13 +136,14 @@ def get_bucket bucket_name, # Returns Google::Apis::StorageV1::Bucket. def insert_bucket bucket_gapi, acl: nil, default_acl: nil, user_project: nil, enable_object_retention: nil, - options: {} + projection: nil, options: {} execute do service.insert_bucket \ @project, bucket_gapi, predefined_acl: acl, predefined_default_object_acl: default_acl, user_project: user_project(user_project), + projection: projection, options: options, enable_object_retention: enable_object_retention end diff --git a/google-cloud-storage/samples/acceptance/buckets_test.rb b/google-cloud-storage/samples/acceptance/buckets_test.rb index a8a9326a5e41..1635cff21f46 100644 --- a/google-cloud-storage/samples/acceptance/buckets_test.rb +++ b/google-cloud-storage/samples/acceptance/buckets_test.rb @@ -21,6 +21,12 @@ require_relative "../storage_create_bucket_class_location" require_relative "../storage_create_bucket_dual_region" require_relative "../storage_create_bucket_hierarchical_namespace" +require_relative "../storage_create_bucket_with_ip_filter" +require_relative "../storage_get_bucket_ip_filter" +require_relative "../storage_disable_ip_filtering" +require_relative "../storage_delete_bucket_ip_filter" +require_relative "../storage_enable_bucket_ip_filter" +require_relative "../storage_list_bucket_ip_filters" require_relative "../storage_create_bucket_with_object_retention" require_relative "../storage_define_bucket_website_configuration" require_relative "../storage_delete_bucket" @@ -172,6 +178,70 @@ end end + describe "storage_bucket_ip_filter" do + let(:bucket_name) { random_bucket_name } + + after :all do + delete_bucket_helper bucket_name + end + + it "creates, updates, gets, lists, and removes IP filter config" do + # Creates IP filter enabled bucket + expected = "Created bucket #{bucket_name} with IP filter.\n" + retry_resource_exhaustion do + assert_output expected do + create_bucket_with_ip_filter bucket_name: bucket_name + end + end + + # Disables IP filter of an existing bucket + expected = "Disabled IP filtering for bucket #{bucket_name}.\n" + retry_resource_exhaustion do + assert_output expected do + disable_ip_filtering bucket_name: bucket_name + end + end + + # Gets IP filter of an existing bucket + expected = "Bucket #{bucket_name} has IP filter mode: Disabled.\n" \ + "Allowed public network CIDR ranges: 8.8.8.8/32.\n" + retry_resource_exhaustion do + assert_output expected do + get_bucket_ip_filter bucket_name: bucket_name + end + end + + # Lists IP filter configurations for buckets in the project + retry_resource_exhaustion do + out, _err = capture_io do + list_bucket_ip_filters + end + # Assert that the specific bucket we created in this test suite + # appears in the list with its IP filter mode + assert_includes out, "Bucket Name: #{bucket_name}, IP Filtering Mode: Disabled" + end + + # Deletes IP filter of an existing bucket + expected = "Deleted IP filter for bucket #{bucket_name}.\n" + retry_resource_exhaustion do + assert_output expected do + delete_bucket_ip_filter bucket_name: bucket_name + end + end + + # Enables IP filter of an existing bucket + # We are intentionally setting the mode to "Disabled" to test the update functionality + # In real use-case, user would be setting it to "Enabled" + expected = "Enabled IP filter for bucket #{bucket_name}.\n" + retry_resource_exhaustion do + assert_output expected do + enable_bucket_ip_filter bucket_name: bucket_name, mode: "Disabled" + end + end + end + + end + describe "storage_bucket_encryption_enforcement_config" do bucket_name = random_bucket_name diff --git a/google-cloud-storage/samples/storage_create_bucket_with_ip_filter.rb b/google-cloud-storage/samples/storage_create_bucket_with_ip_filter.rb new file mode 100644 index 000000000000..9c56446db9f2 --- /dev/null +++ b/google-cloud-storage/samples/storage_create_bucket_with_ip_filter.rb @@ -0,0 +1,42 @@ +# Copyright 2026 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_create_bucket_with_ip_filter] +def create_bucket_with_ip_filter bucket_name: + # The ID to give your GCS bucket + # bucket_name = "your-unique-bucket-name" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + ip_filter = { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: [ + "0.0.0.0/0", "::/0" + ] + } + } + + bucket = storage.create_bucket bucket_name do |b| + b.ip_filter = ip_filter + b.uniform_bucket_level_access = true + end + puts "Created bucket #{bucket.name} with IP filter." +end +# [END storage_create_bucket_with_ip_filter] + +if $PROGRAM_NAME == __FILE__ + create_bucket_with_ip_filter bucket_name: ARGV.shift +end diff --git a/google-cloud-storage/samples/storage_delete_bucket_ip_filter.rb b/google-cloud-storage/samples/storage_delete_bucket_ip_filter.rb new file mode 100644 index 000000000000..f5366527769b --- /dev/null +++ b/google-cloud-storage/samples/storage_delete_bucket_ip_filter.rb @@ -0,0 +1,41 @@ +# Copyright 2026 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_delete_ip_filtering_rules] +def delete_bucket_ip_filter bucket_name: + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + bucket = storage.bucket bucket_name + + # Clear IP filter configuration by setting it to empty/disabled + bucket.update do |b| + b.ip_filter = { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: [] + } + } + end + + puts "Deleted IP filter for bucket #{bucket_name}." +end +# [END storage_delete_ip_filtering_rules] + +if $PROGRAM_NAME == __FILE__ + delete_bucket_ip_filter bucket_name: ARGV.shift +end diff --git a/google-cloud-storage/samples/storage_disable_ip_filtering.rb b/google-cloud-storage/samples/storage_disable_ip_filtering.rb new file mode 100644 index 000000000000..c0afbc530783 --- /dev/null +++ b/google-cloud-storage/samples/storage_disable_ip_filtering.rb @@ -0,0 +1,44 @@ +# Copyright 2026 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_disable_ip_filtering] +def disable_ip_filtering bucket_name: + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + bucket = storage.bucket bucket_name + + ip_filter = { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: [ + "8.8.8.8/32" + ] + } + } + + bucket.update do |b| + b.ip_filter = ip_filter + end + + puts "Disabled IP filtering for bucket #{bucket_name}." +end +# [END storage_disable_ip_filtering] + +if $PROGRAM_NAME == __FILE__ + disable_ip_filtering bucket_name: ARGV.shift +end diff --git a/google-cloud-storage/samples/storage_enable_bucket_ip_filter.rb b/google-cloud-storage/samples/storage_enable_bucket_ip_filter.rb new file mode 100644 index 000000000000..ed61043b7251 --- /dev/null +++ b/google-cloud-storage/samples/storage_enable_bucket_ip_filter.rb @@ -0,0 +1,44 @@ +# Copyright 2026 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_enable_bucket_ip_filter] +def enable_bucket_ip_filter bucket_name:, mode: "Enabled" + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + bucket = storage.bucket bucket_name + + # Enable the IP filter configuration. + ip_filter = { + mode: mode, + allow_all_service_agent_access: true, + public_network_source: { + allowed_ip_cidr_ranges: ["0.0.0.0/0", "::/0"] + } + } + + bucket.update do |b| + b.ip_filter = ip_filter + end + + puts "Enabled IP filter for bucket #{bucket_name}." +end +# [END storage_enable_bucket_ip_filter] + +if $PROGRAM_NAME == __FILE__ + enable_bucket_ip_filter bucket_name: ARGV.shift, mode: ARGV.shift +end diff --git a/google-cloud-storage/samples/storage_get_bucket_ip_filter.rb b/google-cloud-storage/samples/storage_get_bucket_ip_filter.rb new file mode 100644 index 000000000000..6787caaee7f8 --- /dev/null +++ b/google-cloud-storage/samples/storage_get_bucket_ip_filter.rb @@ -0,0 +1,39 @@ +# Copyright 2026 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_bucket_ip_filter] +def get_bucket_ip_filter bucket_name: + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + bucket = storage.bucket bucket_name, projection: "full" + ip_filter = bucket.ip_filter + + if ip_filter + puts "Bucket #{bucket.name} has IP filter mode: #{ip_filter.mode}." + if ip_filter.public_network_source + puts "Allowed public network CIDR ranges: #{ip_filter.public_network_source.allowed_ip_cidr_ranges.join(', ')}." + end + else + puts "Bucket #{bucket.name} does not have an IP filter configuration." + end +end +# [END storage_get_bucket_ip_filter] + +if $PROGRAM_NAME == __FILE__ + get_bucket_ip_filter bucket_name: ARGV.shift +end diff --git a/google-cloud-storage/samples/storage_list_bucket_ip_filters.rb b/google-cloud-storage/samples/storage_list_bucket_ip_filters.rb new file mode 100644 index 000000000000..fb2f632b4af0 --- /dev/null +++ b/google-cloud-storage/samples/storage_list_bucket_ip_filters.rb @@ -0,0 +1,37 @@ +# Copyright 2026 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_bucket_ip_filters] +def list_bucket_ip_filters + # The ID of your GCP project + # project_id = "your-project-id" + + require "google/cloud/storage" + + storage = Google::Cloud::Storage.new + + puts "Buckets:" + # Use projection: "full" to ensure IP filter metadata is returned + storage.buckets(projection: "full").all do |bucket| + ip_filter = bucket.ip_filter + mode = ip_filter ? ip_filter.mode : "Not Configured" + + puts "Bucket Name: #{bucket.name}, IP Filtering Mode: #{mode}" + end +end +# [END storage_list_bucket_ip_filters] + +if $PROGRAM_NAME == __FILE__ + list_bucket_ip_filters +end diff --git a/google-cloud-storage/test/google/cloud/storage/bucket_ip_filter_test.rb b/google-cloud-storage/test/google/cloud/storage/bucket_ip_filter_test.rb new file mode 100644 index 000000000000..9706f05c8b74 --- /dev/null +++ b/google-cloud-storage/test/google/cloud/storage/bucket_ip_filter_test.rb @@ -0,0 +1,151 @@ +# Copyright 2026 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. + +require "helper" + +describe Google::Cloud::Storage::Bucket, :ip_filter, :mock_storage do + let(:bucket_name) { "new-bucket-#{Time.now.to_i}" } + let(:bucket_hash) { random_bucket_hash name: bucket_name } + let(:bucket_gapi) { Google::Apis::StorageV1::Bucket.from_json bucket_hash.to_json } + let(:bucket) { Google::Cloud::Storage::Bucket.from_gapi bucket_gapi, storage.service } + + let(:ip_filter_hash) do + { + "mode" => "Disabled", + "publicNetworkSource" => { + "allowedIpCidrRanges" => ["0.0.0.0/0", "::/0"] + } + } + end + let(:ip_filter_gapi) { Google::Apis::StorageV1::Bucket::IpFilter.from_json ip_filter_hash.to_json } + + it "knows its ip_filter value" do + _(bucket.ip_filter).must_be_nil + + bucket_gapi.ip_filter = ip_filter_gapi + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0", "::/0"] + end + + + it "updates its ip_filter" do + mock = Minitest::Mock.new + mock.expect :update_bucket, resp_bucket_gapi(bucket_hash, ip_filter: ip_filter_gapi), + [bucket_name, patch_bucket_gapi(ip_filter: ip_filter_gapi)], **update_bucket_args(options: {retries: 0}) + + bucket.service.mocked_service = mock + + _(bucket.ip_filter).must_be_nil + + bucket.update do |b| + b.ip_filter = ip_filter_gapi + end + + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0", "::/0"] + + mock.verify + end + + it "enables its ip_filter" do + enable_ip_filter_hash = { + "mode" => "Enabled", + "allowAllServiceAgentAccess" => true, + "publicNetworkSource" => { + "allowedIpCidrRanges" => ["0.0.0.0/0", "::/0"] + } + } + enable_ip_filter_gapi = Google::Apis::StorageV1::Bucket::IpFilter.from_json enable_ip_filter_hash.to_json + + mock = Minitest::Mock.new + mock.expect :update_bucket, resp_bucket_gapi(bucket_hash, ip_filter: enable_ip_filter_gapi), + [bucket_name, patch_bucket_gapi(ip_filter: enable_ip_filter_gapi)], **update_bucket_args(options: {retries: 0}) + + bucket.service.mocked_service = mock + + bucket.update do |b| + b.ip_filter = { + mode: "Enabled", + allow_all_service_agent_access: true, + public_network_source: { + allowed_ip_cidr_ranges: ["0.0.0.0/0", "::/0"] + } + } + end + + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Enabled" + _(bucket.ip_filter.allow_all_service_agent_access).must_equal true + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0", "::/0"] + + mock.verify + end + + it "deletes its ip_filter" do + delete_ip_filter_hash = { + "mode" => "Disabled", + "publicNetworkSource" => { + "allowedIpCidrRanges" => [] + } + } + delete_ip_filter_gapi = Google::Apis::StorageV1::Bucket::IpFilter.from_json delete_ip_filter_hash.to_json + + mock = Minitest::Mock.new + mock.expect :update_bucket, resp_bucket_gapi(bucket_hash, ip_filter: delete_ip_filter_gapi), + [bucket_name, patch_bucket_gapi(ip_filter: delete_ip_filter_gapi)], **update_bucket_args(options: {retries: 0}) + + bucket.service.mocked_service = mock + + bucket.update do |b| + b.ip_filter = { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: [] + } + } + end + + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_be_empty + + mock.verify + end + + it "clears its ip_filter with nil" do + mock = Minitest::Mock.new + mock.expect :patch_bucket, resp_bucket_gapi(bucket_hash, ip_filter: Google::Apis::StorageV1::Bucket::IpFilter.new), + [bucket_name, patch_bucket_gapi(ip_filter: Google::Apis::StorageV1::Bucket::IpFilter.new)], **patch_bucket_args(options: {retries: 0}) + + bucket.service.mocked_service = mock + + bucket.ip_filter = nil + + mock.verify + end + + def patch_bucket_gapi ip_filter: nil + Google::Apis::StorageV1::Bucket.new( + ip_filter: ip_filter + ) + end + + def resp_bucket_gapi bucket_hash, ip_filter: nil + b = Google::Apis::StorageV1::Bucket.from_json bucket_hash.to_json + b.ip_filter = ip_filter + b + end +end 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 c07bff384157..19905377a496 100644 --- a/google-cloud-storage/test/google/cloud/storage/project_test.rb +++ b/google-cloud-storage/test/google/cloud/storage/project_test.rb @@ -156,7 +156,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name @@ -172,7 +172,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, location: bucket_location resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, location: bucket_location @@ -189,7 +189,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, autoclass_enabled: bucket_autoclass_enabled resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, autoclass_enabled: bucket_autoclass_enabled @@ -205,7 +205,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, storage_class: bucket_storage_class resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, storage_class: bucket_storage_class @@ -221,7 +221,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, versioning: Google::Apis::StorageV1::Bucket::Versioning.new(enabled: true) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, versioning: true @@ -237,7 +237,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, logging: Google::Apis::StorageV1::Bucket::Logging.new(log_bucket: bucket_logging_bucket, log_object_prefix: bucket_logging_prefix) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, logging_bucket: bucket_logging_bucket, logging_prefix: bucket_logging_prefix @@ -254,7 +254,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, website: Google::Apis::StorageV1::Bucket::Website.new(main_page_suffix: bucket_website_main, not_found_page: bucket_website_404) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, website_main: bucket_website_main, website_404: bucket_website_404 @@ -271,7 +271,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, billing: Google::Apis::StorageV1::Bucket::Billing.new(requester_pays: bucket_requester_pays) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name do |b| @@ -289,7 +289,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, billing: Google::Apis::StorageV1::Bucket::Billing.new(requester_pays: bucket_requester_pays) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: "test", enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: "test", enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, user_project: true do |b| @@ -308,7 +308,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, billing: Google::Apis::StorageV1::Bucket::Billing.new(requester_pays: bucket_requester_pays) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: "my-other-project", enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: "my-other-project", enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, user_project: "my-other-project" do |b| @@ -327,7 +327,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, cors: bucket_cors_gapi resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -349,7 +349,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name, lifecycle: lifecycle_gapi(lifecycle_rule_gapi("SetStorageClass", storage_class: "NEARLINE", age: 32)) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -369,7 +369,7 @@ def creds.is_a? target created_bucket = create_bucket_gapi bucket_name created_bucket.labels = { "env" => "production", "foo" => "bar" } resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -391,7 +391,7 @@ def creds.is_a? target created_bucket = create_bucket_gapi bucket_name created_bucket.encryption = encryption_gapi(key_name: kms_key) resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -411,7 +411,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: "private", predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: "private", predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, acl: "private" @@ -426,7 +426,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: "publicRead", predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: "publicRead", predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, acl: :public @@ -441,7 +441,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: "private", user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: "private", user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, default_acl: :private @@ -456,7 +456,7 @@ def creds.is_a? target mock = Minitest::Mock.new created_bucket = create_bucket_gapi bucket_name resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: "publicRead", user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: "publicRead", user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, default_acl: "public" @@ -480,7 +480,7 @@ def creds.is_a? target retention_period: bucket_retention_period, effective_time: bucket_retention_effective_at ) - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -506,7 +506,7 @@ def creds.is_a? target mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: bucket_enable_object_retention, - options: {} + projection: nil, options: {} storage.service.mocked_service = mock @@ -523,7 +523,7 @@ def creds.is_a? target created_bucket = create_bucket_gapi bucket_name created_bucket.default_event_based_hold = true resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -545,7 +545,7 @@ def creds.is_a? target created_bucket.rpo = "ASYNC_TURBO" resp_bucket = bucket_with_location created_bucket, location_type: "dual-region" - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name do |b| @@ -566,7 +566,7 @@ def creds.is_a? target created_bucket.hierarchical_namespace = hierarchical_namespace_object resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name do |b| @@ -586,7 +586,7 @@ def creds.is_a? target created_bucket.hierarchical_namespace = { enabled: false } resp_bucket = bucket_with_location created_bucket - mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, options: {} + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} storage.service.mocked_service = mock bucket = storage.create_bucket bucket_name, hierarchical_namespace: { enabled: false } @@ -597,6 +597,38 @@ def creds.is_a? target _(bucket.hierarchical_namespace[:enabled]).must_equal false end + it "creates a bucket with block ip_filter" do + mock = Minitest::Mock.new + created_bucket = create_bucket_gapi bucket_name + ip_filter_hash = { + "mode" => "Disabled", + "publicNetworkSource" => { + "allowedIpCidrRanges" => ["0.0.0.0/0", "::/0"] + } + } + ip_filter_gapi = Google::Apis::StorageV1::Bucket::IpFilter.from_json ip_filter_hash.to_json + created_bucket.ip_filter = ip_filter_gapi + resp_bucket = bucket_with_location created_bucket + + mock.expect :insert_bucket, resp_bucket, [project, created_bucket], predefined_acl: nil, predefined_default_object_acl: nil, user_project: nil, enable_object_retention: nil, projection: nil, options: {} + storage.service.mocked_service = mock + + bucket = storage.create_bucket bucket_name do |b| + b.ip_filter = { + mode: "Disabled", + public_network_source: { + allowed_ip_cidr_ranges: ["0.0.0.0/0", "::/0"] + } + } + end + mock.verify + + _(bucket).must_be_kind_of Google::Cloud::Storage::Bucket + _(bucket.ip_filter).wont_be_nil + _(bucket.ip_filter.mode).must_equal "Disabled" + _(bucket.ip_filter.public_network_source.allowed_ip_cidr_ranges).must_equal ["0.0.0.0/0", "::/0"] + end + it "raises when creating a bucket with a blank name" do bucket_name = "" @@ -615,7 +647,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -633,7 +665,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, soft_deleted: nil,return_partial_success: 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,return_partial_success: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -651,7 +683,7 @@ def stub.insert_bucket *args mock = Minitest::Mock.new mock.expect :list_buckets, list_buckets_gapi(num_buckets,"next_page_token",soft_deleted), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: true, - return_partial_success: nil, options: {} + return_partial_success: nil, projection: nil, options: {} storage.service.mocked_service = mock buckets = storage.buckets soft_deleted: true @@ -668,8 +700,8 @@ def stub.insert_bucket *args 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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -688,8 +720,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, soft_deleted: nil,return_partial_success: 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,return_partial_success: 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,return_partial_success: nil, projection: 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,return_partial_success: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -710,7 +742,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: nil, projection: nil, options: {} storage.service.mocked_service = mock @@ -727,8 +759,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -746,8 +778,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -765,8 +797,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -779,8 +811,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -793,8 +825,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -807,8 +839,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, soft_deleted: nil, return_partial_success: 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, return_partial_success: 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -821,8 +853,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", soft_deleted: nil, return_partial_success: 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, return_partial_success: nil, 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -836,8 +868,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", soft_deleted: nil, return_partial_success: 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, return_partial_success: nil, 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, return_partial_success: nil, projection: 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, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock @@ -1049,7 +1081,7 @@ def stub.insert_bucket *args mock = Minitest::Mock.new - mock.expect :list_buckets, list_unreachable_buckets_gapi(2,nil,nil,unreachable_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil,return_partial_success: true, options: {} + mock.expect :list_buckets, list_unreachable_buckets_gapi(2,nil,nil,unreachable_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil,return_partial_success: true, projection: nil, options: {} storage.service.mocked_service = mock @@ -1065,7 +1097,7 @@ def stub.insert_bucket *args mock = Minitest::Mock.new - mock.expect :list_buckets, list_unreachable_buckets_gapi(2,nil,nil,unreachable_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil,return_partial_success: true, options: {} + mock.expect :list_buckets, list_unreachable_buckets_gapi(2,nil,nil,unreachable_buckets), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil,return_partial_success: true, projection: nil, options: {} storage.service.mocked_service = mock @@ -1079,7 +1111,7 @@ def stub.insert_bucket *args it "returns nil array for unreachable buckets if return_partial_success is passed as false" do mock = Minitest::Mock.new - mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, return_partial_success: false, options: {} + mock.expect :list_buckets, list_buckets_gapi(2), [project], prefix: nil, page_token: nil, max_results: nil, user_project: nil, soft_deleted: nil, return_partial_success: false, projection: nil, options: {} storage.service.mocked_service = mock diff --git a/google-cloud-storage/test/helper.rb b/google-cloud-storage/test/helper.rb index 36a314e6222e..f81015ef4a98 100644 --- a/google-cloud-storage/test/helper.rb +++ b/google-cloud-storage/test/helper.rb @@ -347,6 +347,7 @@ def get_bucket_args if_metageneration_match: nil, user_project: nil, generation: nil, soft_deleted: nil, + projection: nil, options: {} { if_metageneration_match: if_metageneration_match, @@ -354,6 +355,7 @@ def get_bucket_args if_metageneration_match: nil, user_project: user_project, generation: generation, soft_deleted: soft_deleted, + projection: projection, options: options } end