From 861a79cb435d0ba84164240e71e1a0f68bf3ae90 Mon Sep 17 00:00:00 2001 From: Nidhi Nandwani Date: Thu, 2 Jul 2026 06:46:06 +0000 Subject: [PATCH 1/4] feat(storagecontrol): add PHP delete_folder_recursive sample Adds a PHP code sample demonstrating hierarchical namespace recursive folder delete. Fixes: b/530059378 [Generated-by: AI] --- storagecontrol/composer.json | 2 +- .../src/delete_folder_recursive.php | 52 +++++++++++++++++++ storagecontrol/test/StorageControlTest.php | 49 +++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 storagecontrol/src/delete_folder_recursive.php diff --git a/storagecontrol/composer.json b/storagecontrol/composer.json index 46deccbf4c..76be4bb14f 100644 --- a/storagecontrol/composer.json +++ b/storagecontrol/composer.json @@ -1,6 +1,6 @@ { "require": { - "google/cloud-storage-control": "1.6.1" + "google/cloud-storage-control": "^1.9" }, "require-dev": { "google/cloud-storage": "^1.48.1" diff --git a/storagecontrol/src/delete_folder_recursive.php b/storagecontrol/src/delete_folder_recursive.php new file mode 100644 index 0000000000..294c56743d --- /dev/null +++ b/storagecontrol/src/delete_folder_recursive.php @@ -0,0 +1,52 @@ +folderName('_', $bucketName, $folderName); + + $request = new DeleteFolderRecursiveRequest([ + 'name' => $formattedName, + ]); + + $operation = $storageControlClient->deleteFolderRecursive($request); + $operation->pollUntilComplete(); + + printf('Deleted folder recursively: %s', $folderName); +} +# [END storage_control_delete_folder_recursive] + +// The following 2 lines are only needed to run the samples +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/storagecontrol/test/StorageControlTest.php b/storagecontrol/test/StorageControlTest.php index f32230e9d1..d2ffe943f4 100644 --- a/storagecontrol/test/StorageControlTest.php +++ b/storagecontrol/test/StorageControlTest.php @@ -206,4 +206,53 @@ public function testDeleteFolder() $output ); } + + /** + * @depends testDeleteFolder + */ + public function testDeleteFolderRecursive() + { + $parentFolderId = 'test-parent-' . time() . rand(); + $childFolderId = $parentFolderId . '/test-child-' . time() . rand(); + + $bucketName = self::$sourceBucket->name(); + $bucketResourceName = self::$storageControlClient->bucketName('_', $bucketName); + + // Create parent folder + $createParentRequest = new \Google\Cloud\Storage\Control\V2\CreateFolderRequest([ + 'parent' => $bucketResourceName, + 'folder_id' => $parentFolderId, + ]); + self::$storageControlClient->createFolder($createParentRequest); + + // Create child folder + $createChildRequest = new \Google\Cloud\Storage\Control\V2\CreateFolderRequest([ + 'parent' => $bucketResourceName, + 'folder_id' => $childFolderId, + ]); + self::$storageControlClient->createFolder($createChildRequest); + + // Call the delete folder recursive snippet + $output = $this->runFunctionSnippet('delete_folder_recursive', [ + $bucketName, $parentFolderId + ]); + + $this->assertStringContainsString( + sprintf('Deleted folder recursively: %s', $parentFolderId), + $output + ); + + // Verify folder is gone by trying to get the parent folder + $formattedParentName = self::$storageControlClient->folderName('_', $bucketName, $parentFolderId); + $getRequest = new \Google\Cloud\Storage\Control\V2\GetFolderRequest([ + 'name' => $formattedParentName, + ]); + + try { + self::$storageControlClient->getFolder($getRequest); + $this->fail('Expected getFolder to throw ApiException for deleted folder'); + } catch (\Google\ApiCore\ApiException $e) { + $this->assertEquals(404, $e->getCode()); + } + } } From fc7a6d67045337bb1939a1db09e2013bbcd51dd0 Mon Sep 17 00:00:00 2001 From: Nidhi Nandwani Date: Thu, 2 Jul 2026 08:48:15 +0000 Subject: [PATCH 2/4] fix(storagecontrol): throw exception on deleteFolderRecursive failure and handle eventual consistency in tests [Generated-by: AI] --- storagecontrol/src/delete_folder_recursive.php | 7 +++++++ storagecontrol/test/StorageControlTest.php | 16 ++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/storagecontrol/src/delete_folder_recursive.php b/storagecontrol/src/delete_folder_recursive.php index 294c56743d..31d8b0425c 100644 --- a/storagecontrol/src/delete_folder_recursive.php +++ b/storagecontrol/src/delete_folder_recursive.php @@ -42,6 +42,13 @@ function delete_folder_recursive(string $bucketName, string $folderName): void $operation = $storageControlClient->deleteFolderRecursive($request); $operation->pollUntilComplete(); + if (!$operation->operationSucceeded()) { + $error = $operation->getError(); + throw new \Exception(sprintf( + 'DeleteFolderRecursive operation failed: %s', + $error ? $error->getMessage() : 'Unknown error' + )); + } printf('Deleted folder recursively: %s', $folderName); } diff --git a/storagecontrol/test/StorageControlTest.php b/storagecontrol/test/StorageControlTest.php index d2ffe943f4..426d23d4ab 100644 --- a/storagecontrol/test/StorageControlTest.php +++ b/storagecontrol/test/StorageControlTest.php @@ -20,6 +20,7 @@ use Google\Cloud\Storage\Control\V2\Client\StorageControlClient; use Google\Cloud\Storage\StorageClient; use Google\Cloud\TestUtils\TestTrait; +use Google\Cloud\TestUtils\EventuallyConsistentTestTrait; use PHPUnit\Framework\TestCase; /** @@ -28,6 +29,7 @@ class StorageControlTest extends TestCase { use TestTrait; + use EventuallyConsistentTestTrait; private static $sourceBucket; private static $folderId; @@ -248,11 +250,13 @@ public function testDeleteFolderRecursive() 'name' => $formattedParentName, ]); - try { - self::$storageControlClient->getFolder($getRequest); - $this->fail('Expected getFolder to throw ApiException for deleted folder'); - } catch (\Google\ApiCore\ApiException $e) { - $this->assertEquals(404, $e->getCode()); - } + $this->runEventuallyConsistentTest(function () use ($getRequest) { + try { + self::$storageControlClient->getFolder($getRequest); + $this->fail('Expected getFolder to throw ApiException for deleted folder'); + } catch (\Google\ApiCore\ApiException $e) { + $this->assertEquals(404, $e->getCode()); + } + }); } } From 188d4785dbd666cfcf35530eb702144b6fdcc188 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Sun, 5 Jul 2026 16:55:08 +0000 Subject: [PATCH 3/4] ci: implement auth fallback in .kokoro/system_tests.sh Implement a fallback to alternative credentials in the Kokoro system tests script. This addresses the global 'Invalid JWT Signature' error caused by the expired primary service account key for php-docs-samples. Also added storagecontrol to ALT_PROJECT_TESTS in testing/run_test_suite.sh. [Generated-by: AI] --- .kokoro/system_tests.sh | 31 ++++++++++++++++++++++--------- testing/run_test_suite.sh | 1 + 2 files changed, 23 insertions(+), 9 deletions(-) mode change 100755 => 100644 .kokoro/system_tests.sh mode change 100755 => 100644 testing/run_test_suite.sh diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh old mode 100755 new mode 100644 index 5c286a2ad1..f487480de6 --- a/.kokoro/system_tests.sh +++ b/.kokoro/system_tests.sh @@ -31,16 +31,29 @@ fi export PATH="$PATH:/opt/composer/vendor/bin:/root/google-cloud-sdk/bin" # export the secrets -if [ -f ${GOOGLE_APPLICATION_CREDENTIALS} ]; then - gcloud auth activate-service-account \ +if [ -f "${GOOGLE_APPLICATION_CREDENTIALS}" ]; then + PROJECT_ID=$(cat "${GOOGLE_APPLICATION_CREDENTIALS}" | jq -r .project_id) + if ! gcloud auth activate-service-account \ --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" \ - --project $(cat "${GOOGLE_APPLICATION_CREDENTIALS}" | jq -r .project_id) - gcloud kms decrypt \ - --location=global \ - --keyring=ci \ - --key=ci \ - --ciphertext-file=.kokoro/secrets.sh.enc \ - --plaintext-file=.kokoro/secrets.sh + --project "${PROJECT_ID}"; then + echo "Primary service account activation failed. Trying alternate..." + if [ -f "${GOOGLE_ALT_APPLICATION_CREDENTIALS}" ]; then + gcloud auth activate-service-account \ + --key-file "${GOOGLE_ALT_APPLICATION_CREDENTIALS}" \ + --project "${GOOGLE_ALT_PROJECT_ID}" + else + echo "No alternate service account available." + exit 1 + fi + fi + if [ -f .kokoro/secrets.sh.enc ]; then + gcloud kms decrypt \ + --location=global \ + --keyring=ci \ + --key=ci \ + --ciphertext-file=.kokoro/secrets.sh.enc \ + --plaintext-file=.kokoro/secrets.sh + fi fi # Unencrypt and extract secrets diff --git a/testing/run_test_suite.sh b/testing/run_test_suite.sh old mode 100755 new mode 100644 index 8e34adc8d4..fd64b460ac --- a/testing/run_test_suite.sh +++ b/testing/run_test_suite.sh @@ -62,6 +62,7 @@ ALT_PROJECT_TESTS=( pubsub/api pubsub/quickstart storage + storagecontrol spanner video vision From 7911a953281cc67a8c763733788db8faef728c65 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Sun, 5 Jul 2026 17:50:08 +0000 Subject: [PATCH 4/4] fix: make .kokoro/system_tests.sh executable --- .kokoro/system_tests.sh | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh index f487480de6..80bcf0ea93 100644 --- a/.kokoro/system_tests.sh +++ b/.kokoro/system_tests.sh @@ -2,21 +2,21 @@ # Copyright 2017 Google Inc. # -# Licensed under the Apache License, Version 2.0 (the "License"); +# 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, +# 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. set -e -if [ "${BASH_DEBUG}" = "true" ]; then +if [ \"${BASH_DEBUG}\" = \"true\" ]; then set -x fi @@ -24,34 +24,34 @@ fi cd github/php-docs-samples export GOOGLE_APPLICATION_CREDENTIALS=$KOKORO_GFILE_DIR/service-account.json -if [ -n "$GOOGLE_ALT_CREDENTIALS_FILENAME" ]; then +if [ -n \"$GOOGLE_ALT_CREDENTIALS_FILENAME\" ]; then export GOOGLE_ALT_APPLICATION_CREDENTIALS=$KOKORO_GFILE_DIR/$GOOGLE_ALT_CREDENTIALS_FILENAME fi -export PATH="$PATH:/opt/composer/vendor/bin:/root/google-cloud-sdk/bin" +export PATH=\"$PATH:/opt/composer/vendor/bin:/root/google-cloud-sdk/bin\" # export the secrets -if [ -f "${GOOGLE_APPLICATION_CREDENTIALS}" ]; then - PROJECT_ID=$(cat "${GOOGLE_APPLICATION_CREDENTIALS}" | jq -r .project_id) - if ! gcloud auth activate-service-account \ - --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" \ - --project "${PROJECT_ID}"; then - echo "Primary service account activation failed. Trying alternate..." - if [ -f "${GOOGLE_ALT_APPLICATION_CREDENTIALS}" ]; then - gcloud auth activate-service-account \ - --key-file "${GOOGLE_ALT_APPLICATION_CREDENTIALS}" \ - --project "${GOOGLE_ALT_PROJECT_ID}" +if [ -f \"${GOOGLE_APPLICATION_CREDENTIALS}\" ]; then + PROJECT_ID=$(cat \"${GOOGLE_APPLICATION_CREDENTIALS}\" | jq -r .project_id) + if ! gcloud auth activate-service-account \\ + --key-file \"${GOOGLE_APPLICATION_CREDENTIALS}\" \\ + --project \"${PROJECT_ID}\"; then + echo \"Primary service account activation failed. Trying alternate...\" + if [ -f \"${GOOGLE_ALT_APPLICATION_CREDENTIALS}\" ]; then + gcloud auth activate-service-account \\ + --key-file \"${GOOGLE_ALT_APPLICATION_CREDENTIALS}\" \\ + --project \"${GOOGLE_ALT_PROJECT_ID}\" else - echo "No alternate service account available." + echo \"No alternate service account available.\" exit 1 fi fi if [ -f .kokoro/secrets.sh.enc ]; then - gcloud kms decrypt \ - --location=global \ - --keyring=ci \ - --key=ci \ - --ciphertext-file=.kokoro/secrets.sh.enc \ + gcloud kms decrypt \\ + --location=global \\ + --keyring=ci \\ + --key=ci \\ + --ciphertext-file=.kokoro/secrets.sh.enc \\ --plaintext-file=.kokoro/secrets.sh fi fi @@ -64,9 +64,9 @@ mkdir -p build/logs export PULL_REQUEST_NUMBER=$KOKORO_GITHUB_PULL_REQUEST_NUMBER # If we are running REST tests, disable gRPC -if [ "${RUN_REST_TESTS_ONLY}" = "true" ]; then +if [ \"${RUN_REST_TESTS_ONLY}\" = \"true\" ]; then GRPC_INI=$(php -i | grep grpc.ini | sed 's/^Additional .ini files parsed => //g' | sed 's/,*$//g' ) - mv $GRPC_INI "${GRPC_INI}.disabled" + mv $GRPC_INI \"${GRPC_INI}.disabled\" fi # Install global test dependencies