diff --git a/storagecontrol/delete_folder_recursive.py b/storagecontrol/delete_folder_recursive.py new file mode 100644 index 00000000000..ee810a8969d --- /dev/null +++ b/storagecontrol/delete_folder_recursive.py @@ -0,0 +1,48 @@ +# 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. + +import sys + +# [START storage_control_delete_folder_recursive] +from google.cloud import storage_control_v2 + + +def delete_folder_recursive(bucket_name: str, folder_name: str) -> None: + # The ID of your GCS bucket + # bucket_name = "your-unique-bucket-name" + + # The name of the folder to be deleted recursively + # folder_name = "folder-name" + + storage_control_client = storage_control_v2.StorageControlClient() + # The storage bucket path uses the global access pattern, in which the "_" + # denotes this bucket exists in the global namespace. + folder_path = storage_control_client.folder_path( + project="_", bucket=bucket_name, folder=folder_name + ) + + request = storage_control_v2.DeleteFolderRecursiveRequest( + name=folder_path, + ) + operation = storage_control_client.delete_folder_recursive(request=request) + operation.result() + + print(f"Deleted folder recursively: {folder_path}") + + +# [END storage_control_delete_folder_recursive] + + +if __name__ == "__main__": + delete_folder_recursive(bucket_name=sys.argv[1], folder_name=sys.argv[2]) diff --git a/storagecontrol/hierarchical-namespace/delete_empty_folders.py b/storagecontrol/hierarchical-namespace/delete_empty_folders.py index 2aa9d8bf054..cae0c763de3 100644 --- a/storagecontrol/hierarchical-namespace/delete_empty_folders.py +++ b/storagecontrol/hierarchical-namespace/delete_empty_folders.py @@ -13,7 +13,6 @@ # limitations under the License. import concurrent.futures -import logging import threading import time @@ -22,6 +21,8 @@ from google.cloud import storage_control_v2 import grpc +import logging + ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor # This script may be used to recursively delete a large number of nested empty diff --git a/storagecontrol/managed_folder_list.py b/storagecontrol/managed_folder_list.py index e2ef9703b23..1a056c2b560 100644 --- a/storagecontrol/managed_folder_list.py +++ b/storagecontrol/managed_folder_list.py @@ -46,6 +46,8 @@ def list_managed_folders(bucket_name: str = "your-bucket-name") -> None: print(f"Managed folders in bucket {bucket_resource_name}:") for managed_folder in managed_folders: print(f"\t{managed_folder.name}") + + # [END storage_control_managed_folder_list] diff --git a/storagecontrol/managed_folder_list_test.py b/storagecontrol/managed_folder_list_test.py index e0cb30170c8..cf6dd54feb7 100644 --- a/storagecontrol/managed_folder_list_test.py +++ b/storagecontrol/managed_folder_list_test.py @@ -13,10 +13,8 @@ # limitations under the License. from google.cloud import storage - -import pytest - import managed_folder_list +import pytest def test_storage_control_managed_folder_list( diff --git a/storagecontrol/snippets_test.py b/storagecontrol/snippets_test.py index 218a3b7f26a..12e739b1855 100644 --- a/storagecontrol/snippets_test.py +++ b/storagecontrol/snippets_test.py @@ -12,21 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from google.cloud import storage - -import pytest - import create_folder import delete_folder +import delete_folder_recursive import get_folder +from google.api_core import exceptions +from google.cloud import storage import list_folders import managed_folder_create import managed_folder_delete import managed_folder_get import managed_folder_list +import pytest import rename_folder - # === Folders === # @@ -67,6 +66,41 @@ def test_folder_create_get_list_rename_delete( assert new_name in out +def test_delete_folder_recursive( + capsys: pytest.LogCaptureFixture, hns_enabled_bucket: storage.Bucket, uuid_name: str +) -> None: + bucket_name = hns_enabled_bucket.name + parent_folder = f"parent-{uuid_name}" + child_folder = f"parent-{uuid_name}/child-{uuid_name}" + + # Create parent folder + create_folder.create_folder(bucket_name=bucket_name, folder_name=parent_folder) + # Create child folder + create_folder.create_folder(bucket_name=bucket_name, folder_name=child_folder) + + # Call delete folder recursive + try: + delete_folder_recursive.delete_folder_recursive( + bucket_name=bucket_name, folder_name=parent_folder + ) + except exceptions.InvalidArgument as e: + if "Recursive folder delete is not enabled for this bucket" in str(e): + pytest.skip("Recursive folder delete is not enabled for this bucket.") + raise e + out, _ = capsys.readouterr() + assert ( + f"Deleted folder recursively: projects/_/buckets/{bucket_name}/folders/{parent_folder}" + in out + ) + + # Verify folders are deleted (NotFound exceptions) + with pytest.raises(exceptions.NotFound): + get_folder.get_folder(bucket_name=bucket_name, folder_name=parent_folder) + + with pytest.raises(exceptions.NotFound): + get_folder.get_folder(bucket_name=bucket_name, folder_name=child_folder) + + # === Managed Folders === #