diff --git a/pubsub/api/README.md b/pubsub/api/README.md index 22756c122..482272aeb 100644 --- a/pubsub/api/README.md +++ b/pubsub/api/README.md @@ -64,6 +64,67 @@ Usage: create_topic.php $projectId $topicName @param string $topicName The Pub/Sub topic name. ``` +## PHPUnit Tests + +At this time, the GitHub actions in this repo fail to run the tests written in this folder. The developer is responsible for locally running and confirming their samples and corresponding tests. + +### PubSub Emulator +Some tests in the pubsubTest.php requires PubSub emulator. These tests start with ```$this->requireEnv('PUBSUB_EMULATOR_HOST')```. + +#### Prerequisites +- Python +``` +xcode-select --install +brew install pyenv +pyenv install +python3 --version +``` +- JDK +``` +brew install openjdk +export JAVA_HOME= +export PATH="$JAVA_HOME/bin:$PATH" +``` + +Once python, JDK, and GCloud CLI are installed, follow [these instructions](https://cloud.google.com/pubsub/docs/emulator) to run the emulator. + +### Setting up environment variables +Open a new tab in terminal, separate from the one running your emulator. + +``` +// php-docs-samples/testing folder +$ cd ../../../testing + +$ export GOOGLE_PROJECT_ID= +$ export GOOGLE_PUBSUB_TOPIC= +$ export GOOGLE_PUBSUB_STORAGE_BUCKET= +$ export GOOGLE_PUBSUB_SUBSCRIPTION= + +// only set if your test requires the emulator +$ export PUBSUB_EMULATOR_HOST=localhost: + +// unset the PUBSUB emulator host variable if you want to run a test that doesn't require an emulator +$ unset PUBSUB_EMULATOR +``` + +### Running the tests +Run your test(s) like so in the same terminal tab that you set your env variables in the previous step. + +``` +// Run all tests in pubsubTest.php. --verbose tag is recommended to see any issues or stack trace +$ php-docs-samples/testing/vendor/bin/phpunit ../pubsub/api/test/pubsubTest.php --verbose + +// Run a single test in pubsubTest.php +$ php-docs-samples/testing/vendor/bin/phpunit ../pubsub/api/test/pubsubTest.php --filter testSubscriptionPolicy --verbose +``` + +## Fixing Styling Errors +If you create a PR and the Lint / styles (pull_request) check fails, this is a quick fix. + +``` +$ php-docs-samples/testing/vendor/bin/php-cs-fixer fix +``` + ## Troubleshooting If you get the following error, set the environment variable `GCLOUD_PROJECT` to your project ID: diff --git a/pubsub/api/src/create_topic_with_aws_msk_ingestion.php b/pubsub/api/src/create_topic_with_aws_msk_ingestion.php new file mode 100644 index 000000000..b3d04c170 --- /dev/null +++ b/pubsub/api/src/create_topic_with_aws_msk_ingestion.php @@ -0,0 +1,71 @@ + $projectId, + ]); + + $topic = $pubsub->createTopic($topicName, [ + 'ingestionDataSourceSettings' => [ + 'aws_msk' => [ + 'cluster_arn' => $clusterArn, + 'topic' => $mskTopic, + 'aws_role_arn' => $awsRoleArn, + 'gcp_service_account' => $gcpServiceAccount + ] + ] + ]); + + printf('Topic created: %s' . PHP_EOL, $topic->name()); +} +# [END pubsub_create_topic_with_aws_msk_ingestion] +require_once __DIR__ . '/../../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/pubsub/api/src/create_topic_with_cloud_storage_ingestion.php b/pubsub/api/src/create_topic_with_cloud_storage_ingestion.php new file mode 100644 index 000000000..7510c7ec3 --- /dev/null +++ b/pubsub/api/src/create_topic_with_cloud_storage_ingestion.php @@ -0,0 +1,91 @@ +setSeconds($datetime->getTimestamp()) + ->setNanos($datetime->format('u') * 1000); + + $cloudStorageData = [ + 'bucket' => $bucket, + 'minimum_object_create_time' => $timestamp + ]; + + $cloudStorageData[$inputFormat . '_format'] = match($inputFormat) { + 'text' => new TextFormat(['delimiter' => $textDelimiter]), + 'avro' => new AvroFormat(), + 'pubsub_avro' => new PubSubAvroFormat(), + default => throw new \InvalidArgumentException( + 'inputFormat must be in (\'text\', \'avro\', \'pubsub_avro\'); got value: ' . $inputFormat + ) + }; + + if (!empty($matchGlob)) { + $cloudStorageData['match_glob'] = $matchGlob; + } + + $pubsub = new PubSubClient([ + 'projectId' => $projectId, + ]); + + $topic = $pubsub->createTopic($topicName, [ + 'ingestionDataSourceSettings' => [ + 'cloud_storage' => $cloudStorageData + ] + ]); + + printf('Topic created: %s' . PHP_EOL, $topic->name()); +} +# [END pubsub_create_topic_with_cloud_storage_ingestion] +require_once __DIR__ . '/../../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/pubsub/api/src/create_unwrapped_push_subscription.php b/pubsub/api/src/create_unwrapped_push_subscription.php new file mode 100644 index 000000000..6d30ab84d --- /dev/null +++ b/pubsub/api/src/create_unwrapped_push_subscription.php @@ -0,0 +1,57 @@ + $projectId, + ]); + $pubsub->subscribe($subscriptionId, $topicName, [ + 'pushConfig' => [ + 'no_wrapper' => [ + 'write_metadata' => true + ] + ] + ]); + printf('Unwrapped push subscription created: %s' . PHP_EOL, $subscriptionId); +} +# [END pubsub_create_unwrapped_push_subscription] +require_once __DIR__ . '/../../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/pubsub/api/src/optimistic_subscribe.php b/pubsub/api/src/optimistic_subscribe.php new file mode 100644 index 000000000..dc6f5004f --- /dev/null +++ b/pubsub/api/src/optimistic_subscribe.php @@ -0,0 +1,66 @@ + $projectId, + ]); + + $subscription = $pubsub->subscription($subscriptionId); + + try { + $messages = $subscription->pull(); + foreach ($messages as $message) { + printf('PubSub Message: %s' . PHP_EOL, $message->data()); + $subscription->acknowledge($message); + } + } catch (NotFoundException $e) { // Subscription is not found + printf('Exception Message: %s' . PHP_EOL, $e->getMessage()); + printf('StackTrace: %s' . PHP_EOL, $e->getTraceAsString()); + // Create subscription and retry the pull. Any messages published before subscription creation would not be received. + $pubsub->subscribe($subscriptionId, $topicName); + optimistic_subscribe($projectId, $topicName, $subscriptionId); + } +} +# [END pubsub_optimistic_subscribe] +require_once __DIR__ . '/../../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/pubsub/api/src/subscribe_exactly_once_delivery.php b/pubsub/api/src/subscribe_exactly_once_delivery.php index 1c33c16e1..e048fba4e 100644 --- a/pubsub/api/src/subscribe_exactly_once_delivery.php +++ b/pubsub/api/src/subscribe_exactly_once_delivery.php @@ -38,6 +38,8 @@ function subscribe_exactly_once_delivery( ): void { $pubsub = new PubSubClient([ 'projectId' => $projectId, + // use the apiEndpoint option to set a regional endpoint + 'apiEndpoint' => 'us-west1-pubsub.googleapis.com:443' ]); $subscription = $pubsub->subscription($subscriptionId); diff --git a/pubsub/api/src/subscriber_error_listener.php b/pubsub/api/src/subscriber_error_listener.php new file mode 100644 index 000000000..6e8e5fcf2 --- /dev/null +++ b/pubsub/api/src/subscriber_error_listener.php @@ -0,0 +1,61 @@ + $projectId, + ]); + $subscription = $pubsub->subscription($subscriptionId, $topicName); + + try { + $messages = $subscription->pull(); + foreach ($messages as $message) { + printf('PubSub Message: %s' . PHP_EOL, $message->data()); + $subscription->acknowledge($message); + } + } catch (\Exception $e) { // Handle unrecoverable exceptions + printf('Exception Message: %s' . PHP_EOL, $e->getMessage()); + printf('StackTrace: %s' . PHP_EOL, $e->getTraceAsString()); + } +} +# [END pubsub_subscriber_error_listener] +require_once __DIR__ . '/../../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/pubsub/api/src/update_topic_type.php b/pubsub/api/src/update_topic_type.php new file mode 100644 index 000000000..8d179a719 --- /dev/null +++ b/pubsub/api/src/update_topic_type.php @@ -0,0 +1,73 @@ + $projectId, + ]); + + $topic = $pubsub->topic($topicName); + + $topic->update([ + 'ingestionDataSourceSettings' => [ + 'aws_kinesis' => [ + 'stream_arn' => $streamArn, + 'consumer_arn' => $consumerArn, + 'aws_role_arn' => $awsRoleArn, + 'gcp_service_account' => $gcpServiceAccount + ] + ] + ], [ + 'updateMask' => [ + 'ingestionDataSourceSettings' + ] + ]); + + printf('Topic updated: %s' . PHP_EOL, $topic->name()); +} +# [END pubsub_update_topic_type] +require_once __DIR__ . '/../../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/pubsub/api/test/pubsubTest.php b/pubsub/api/test/pubsubTest.php index 929372e5b..3bc53d0cc 100644 --- a/pubsub/api/test/pubsubTest.php +++ b/pubsub/api/test/pubsubTest.php @@ -1,4 +1,5 @@ assertMatchesRegularExpression('/Created subscription with ordering/', $output); $this->assertMatchesRegularExpression('/\"enableMessageOrdering\":true/', $output); } + + public function testCreateAndDeleteUnwrappedSubscription() + { + $topic = $this->requireEnv('GOOGLE_PUBSUB_TOPIC'); + $subscription = 'test-subscription-' . rand(); + $output = $this->runFunctionSnippet('create_unwrapped_push_subscription', [ + self::$projectId, + $topic, + $subscription, + ]); + + $this->assertMatchesRegularExpression('/Unwrapped push subscription created:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $subscription), $output); + + $output = $this->runFunctionSnippet('delete_subscription', [ + self::$projectId, + $subscription, + ]); + + $this->assertMatchesRegularExpression('/Subscription deleted:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $subscription), $output); + } + + public function testSubscriberErrorListener() + { + $topic = $this->requireEnv('GOOGLE_PUBSUB_TOPIC'); + $subscription = 'test-subscription-' . rand(); + + // Create subscription + $output = $this->runFunctionSnippet('create_subscription', [ + self::$projectId, + $topic, + $subscription, + ]); + $this->assertMatchesRegularExpression('/Subscription created:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $subscription), $output); + + // Publish Message + $testMessage = 'This is a test message'; + $output = $this->runFunctionSnippet('publish_message', [ + self::$projectId, + $topic, + $testMessage, + ]); + $this->assertMatchesRegularExpression('/Message published/', $output); + + // Pull messages from subscription with error listener + $output = $this->runFunctionSnippet('subscriber_error_listener', [ + self::$projectId, + $topic, + $subscription + ]); + // Published message should be received as expected and no exception should be thrown + $this->assertMatchesRegularExpression(sprintf('/PubSub Message: %s/', $testMessage), $output); + $this->assertDoesNotMatchRegularExpression('/Exception Message/', $output); + + // Delete subscription + $output = $this->runFunctionSnippet('delete_subscription', [ + self::$projectId, + $subscription, + ]); + $this->assertMatchesRegularExpression('/Subscription deleted:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $subscription), $output); + + // Pull messages from a non-existent subscription with error listener + $subscription = 'test-subscription-' . rand(); + $output = $this->runFunctionSnippet('subscriber_error_listener', [ + self::$projectId, + $topic, + $subscription + ]); + // NotFound exception should be caught and printed + $this->assertMatchesRegularExpression('/Exception Message/', $output); + $this->assertMatchesRegularExpression(sprintf('/Resource not found \(resource=%s\)/', $subscription), $output); + } + + public function testOptimisticSubscribe() + { + $topic = $this->requireEnv('GOOGLE_PUBSUB_TOPIC'); + $subcriptionId = 'test-subscription-' . rand(); + + $output = $this->runFunctionSnippet('optimistic_subscribe', [ + self::$projectId, + $topic, + $subcriptionId + ]); + $this->assertMatchesRegularExpression('/Exception Message/', $output); + $this->assertMatchesRegularExpression(sprintf('/Resource not found \(resource=%s\)/', $subcriptionId), $output); + + $testMessage = 'This is a test message'; + $output = $this->runFunctionSnippet('publish_message', [ + self::$projectId, + $topic, + $testMessage, + ]); + $this->assertMatchesRegularExpression('/Message published/', $output); + $output = $this->runFunctionSnippet('optimistic_subscribe', [ + self::$projectId, + $topic, + $subcriptionId + ]); + $this->assertMatchesRegularExpression(sprintf('/PubSub Message: %s/', $testMessage), $output); + $this->assertDoesNotMatchRegularExpression('/Exception Message/', $output); + $this->assertDoesNotMatchRegularExpression(sprintf('/Resource not found \(resource=%s\)/', $subcriptionId), $output); + + // Delete subscription + $output = $this->runFunctionSnippet('delete_subscription', [ + self::$projectId, + $subcriptionId, + ]); + $this->assertMatchesRegularExpression('/Subscription deleted:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $subcriptionId), $output); + } + + public function testUpdateTopicType() + { + $topic = 'test-topic-' . rand(); + $output = $this->runFunctionSnippet('create_topic', [ + self::$projectId, + $topic, + ]); + + $this->assertMatchesRegularExpression('/Topic created:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $topic), $output); + + $output = $this->runFunctionSnippet('update_topic_type', [ + self::$projectId, + $topic, + 'arn:aws:kinesis:us-west-2:111111111111:stream/fake-stream-name', + 'arn:aws:kinesis:us-west-2:111111111111:stream/fake-stream-name/consumer/consumer-1:1111111111', + self::$awsRoleArn, + self::$gcpServiceAccount + ]); + + $this->assertMatchesRegularExpression('/Topic updated:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $topic), $output); + } + + public function testCreateTopicWithCloudStorageIngestion() + { + $this->requireEnv('PUBSUB_EMULATOR_HOST'); + + $topic = 'test-topic-' . rand(); + $output = $this->runFunctionSnippet('create_topic_with_cloud_storage_ingestion', [ + self::$projectId, + $topic, + $this->requireEnv('GOOGLE_PUBSUB_STORAGE_BUCKET'), + 'text', + '1970-01-01T00:00:00Z', + "\n", + '**.txt' + ]); + $this->assertMatchesRegularExpression('/Topic created:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $topic), $output); + + $output = $this->runFunctionSnippet('delete_topic', [ + self::$projectId, + $topic, + ]); + $this->assertMatchesRegularExpression('/Topic deleted:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $topic), $output); + } + + public function testCreateTopicWithAwsMskIngestion() + { + $this->requireEnv('PUBSUB_EMULATOR_HOST'); + + $topic = 'test-topic-' . rand(); + $output = $this->runFunctionSnippet('create_topic_with_aws_msk_ingestion', [ + self::$projectId, + $topic, + 'arn:aws:kafka:us-east-1:111111111111:cluster/fake-cluster-name/11111111-1111-1', + 'fake-msk-topic-name', + self::$awsRoleArn, + self::$gcpServiceAccount + ]); + $this->assertMatchesRegularExpression('/Topic created:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $topic), $output); + + $output = $this->runFunctionSnippet('delete_topic', [ + self::$projectId, + $topic, + ]); + $this->assertMatchesRegularExpression('/Topic deleted:/', $output); + $this->assertMatchesRegularExpression(sprintf('/%s/', $topic), $output); + } } diff --git a/storage/src/disable_soft_delete.php b/storage/src/disable_soft_delete.php new file mode 100644 index 000000000..6749f0136 --- /dev/null +++ b/storage/src/disable_soft_delete.php @@ -0,0 +1,55 @@ +bucket($bucketName); + $x = $bucket->update([ + 'softDeletePolicy' => [ + 'retentionDurationSeconds' => 0, + ], + ]); + printf('Bucket %s soft delete policy was disabled' . PHP_EOL, $bucketName); + } catch (\Throwable $th) { + print_r($th); + } + +} +# [END storage_disable_soft_delete] + +// 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/storage/src/get_soft_delete_policy.php b/storage/src/get_soft_delete_policy.php new file mode 100644 index 000000000..19a80eea2 --- /dev/null +++ b/storage/src/get_soft_delete_policy.php @@ -0,0 +1,61 @@ +bucket($bucketName); + $bucket->reload(); + + if ($bucket->info()['softDeletePolicy']['retentionDurationSeconds'] === '0') { + printf('Bucket %s soft delete policy was disabled' . PHP_EOL, $bucketName); + } else { + printf('Soft delete Policy for ' . $bucketName . PHP_EOL); + printf( + 'Soft delete Period: %d seconds' . PHP_EOL, + $bucket->info()['softDeletePolicy']['retentionDurationSeconds'] + ); + if ($bucket->info()['softDeletePolicy']['effectiveTime']) { + printf( + 'Effective Time: %s' . PHP_EOL, + $bucket->info()['softDeletePolicy']['effectiveTime'] + ); + } + } +} +# [END storage_get_soft_delete_policy] + +// 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/storage/src/list_soft_deleted_object_versions.php b/storage/src/list_soft_deleted_object_versions.php new file mode 100644 index 000000000..146632713 --- /dev/null +++ b/storage/src/list_soft_deleted_object_versions.php @@ -0,0 +1,50 @@ +bucket($bucketName); + $options = ['softDeleted' => true, 'matchGlob' => $objectName]; + foreach ($bucket->objects($options) as $object) { + printf('Object: %s' . PHP_EOL, $object->name()); + } +} +# [END storage_list_soft_deleted_object_versions] + +// 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/storage/src/list_soft_deleted_objects.php b/storage/src/list_soft_deleted_objects.php new file mode 100644 index 000000000..265959498 --- /dev/null +++ b/storage/src/list_soft_deleted_objects.php @@ -0,0 +1,48 @@ +bucket($bucketName); + $options = ['softDeleted' => true]; + foreach ($bucket->objects($options) as $object) { + printf('Object: %s' . PHP_EOL, $object->name()); + } +} +# [END storage_list_soft_deleted_objects] + +// 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/storage/src/restore_soft_deleted_object.php b/storage/src/restore_soft_deleted_object.php new file mode 100644 index 000000000..51c8f4e5b --- /dev/null +++ b/storage/src/restore_soft_deleted_object.php @@ -0,0 +1,51 @@ +bucket($bucketName); + $bucket->restore($objectName, $generation); + + printf('Soft deleted object %s was restored.' . PHP_EOL, $objectName); +} +# [END storage_restore_object] + +// 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/storage/src/set_soft_delete_policy.php b/storage/src/set_soft_delete_policy.php new file mode 100644 index 000000000..dae280463 --- /dev/null +++ b/storage/src/set_soft_delete_policy.php @@ -0,0 +1,50 @@ +bucket($bucketName); + $bucket->update([ + 'softDeletePolicy' => [ + 'retentionDurationSeconds' => 864000, + ], + ]); + printf('Bucket %s soft delete policy set to 10 days' . PHP_EOL, $bucketName); +} +# [END storage_set_soft_delete_policy] + +// 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/storage/test/ObjectsTest.php b/storage/test/ObjectsTest.php index 483bfc345..5cf9ab3f3 100644 --- a/storage/test/ObjectsTest.php +++ b/storage/test/ObjectsTest.php @@ -410,4 +410,83 @@ public function testGetMetadata() $this->assertStringNotContainsString('Custom Time', $output); $this->assertStringNotContainsString('Retention Expiration Time', $output); } + + public function testListSoftDeletedObjects() + { + $bucket = self::$storage->bucket(self::$bucketName); + $bucket->update([ + 'softDeletePolicy' => [ + 'retentionDuration' => 604800, + ], + ]); + + $objectName = uniqid('soft-deleted-object-'); + $object = $bucket->upload('content', ['name' => $objectName]); + $object->delete(); + + $output = self::runFunctionSnippet('list_soft_deleted_objects', [ + self::$bucketName, + ]); + + $this->assertStringContainsString('Object:', $output); + } + + public function testListSoftDeletedObjectVersions() + { + $bucket = self::$storage->bucket(self::$bucketName); + $bucket->update([ + 'softDeletePolicy' => [ + 'retentionDuration' => 604800, + ], + ]); + + $objectName1 = 'soft-deleted-object-1'; + $object1 = $bucket->upload('content', ['name' => $objectName1]); + $object1->delete(); + + $objectName2 = 'soft-deleted-object-2'; + $object2 = $bucket->upload('content', ['name' => $objectName2]); + $object2->delete(); + + $output = self::runFunctionSnippet('list_soft_deleted_object_versions', [ + self::$bucketName, + $objectName1 + ]); + + $this->assertStringContainsString($objectName1, $output); + $this->assertStringNotContainsString($objectName2, $output); + } + + public function testRestoreSoftDeletedObject() + { + $bucket = self::$storage->bucket(self::$bucketName); + $bucket->update([ + 'softDeletePolicy' => [ + 'retentionDuration' => 60, + ], + ]); + + $objectName = uniqid('soft-deleted-object-'); + $object = $bucket->upload('content', ['name' => $objectName]); + $info = $object->reload(); + $object->delete(); + + $this->assertFalse($object->exists()); + + $output = self::runFunctionSnippet('restore_soft_deleted_object', [ + self::$bucketName, + $objectName, + $info['generation'] + ]); + + $object = $bucket->object($objectName); + $this->assertTrue($object->exists()); + $this->assertEquals( + sprintf( + 'Soft deleted object %s was restored.' . PHP_EOL, + $objectName + ), + $output + ); + } } diff --git a/storage/test/storageTest.php b/storage/test/storageTest.php index ab144489e..9ac16e8a6 100644 --- a/storage/test/storageTest.php +++ b/storage/test/storageTest.php @@ -969,6 +969,90 @@ public function testSetBucketWithAutoclass() ); } + public function testGetSoftDeletePolicy() + { + $bucketName = uniqid('samples-get-soft-delete-policy-'); + $bucket = self::$storage->createBucket($bucketName, [ + 'softDeletePolicy' => [ + 'retentionDurationSeconds' => 604800, + ], + ]); + + $output = self::runFunctionSnippet('get_soft_delete_policy', [ + $bucketName, + ]); + $info = $bucket->info(); + $bucket->delete(); + + if ($info['softDeletePolicy']['retentionDurationSeconds'] === '0') { + $this->assertStringContainsString( + sprintf('Bucket %s soft delete policy was disabled', $bucketName), + $output + ); + } else { + $duration = $info['softDeletePolicy']['retentionDurationSeconds']; + $effectiveTime = $info['softDeletePolicy']['effectiveTime']; + $outputString = <<assertEquals($output, $outputString); + } + } + + public function testSetSoftDeletePolicy() + { + $bucketName = uniqid('samples-set-soft-delete-policy-'); + $bucket = self::$storage->createBucket($bucketName); + $info = $bucket->reload(); + + $this->assertNotEquals('864000', $info['softDeletePolicy']['retentionDurationSeconds']); + $output = self::runFunctionSnippet('set_soft_delete_policy', [ + $bucketName + ]); + $info = $bucket->reload(); + $this->assertEquals('864000', $info['softDeletePolicy']['retentionDurationSeconds']); + $bucket->delete(); + + $this->assertStringContainsString( + sprintf( + 'Bucket %s soft delete policy set to 10 days', + $bucketName + ), + $output + ); + } + + public function testDisableSoftDelete() + { + $bucketName = uniqid('samples-disable-soft-delete-'); + $bucket = self::$storage->createBucket($bucketName, [ + 'softDeletePolicy' => [ + 'retentionDurationSeconds' => 604800, + ], + ]); + $info = $bucket->reload(); + + $this->assertEquals('604800', $info['softDeletePolicy']['retentionDurationSeconds']); + + $output = self::runFunctionSnippet('disable_soft_delete', [ + $bucketName + ]); + $info = $bucket->reload(); + $this->assertEquals('0', $info['softDeletePolicy']['retentionDurationSeconds']); + $bucket->delete(); + + $this->assertStringContainsString( + sprintf( + 'Bucket %s soft delete policy was disabled', + $bucketName + ), + $output + ); + } + public function testDeleteFileArchivedGeneration() { $bucket = self::$storage->createBucket(uniqid('samples-delete-file-archived-generation-'), [ diff --git a/storagebatchoperations/README.md b/storagebatchoperations/README.md new file mode 100644 index 000000000..5ed579182 --- /dev/null +++ b/storagebatchoperations/README.md @@ -0,0 +1,61 @@ +# Google Cloud Storage Batch Operations Samples + +## Description + +All code in the snippets directory demonstrates how to invoke +[Cloud Storage Batch Operations][google-cloud-php-storage-batch-operations] from PHP. + +[cloud-storage-batch-operations]: https://cloud.google.com/storage/docs/batch-operations/overview + +## Setup: + +1. **Enable APIs** - [Enable the Storage Batch Operations Service API](https://console.cloud.google.com/flows/enableapi?apiid=storage.googleapis.com) + and create a new project or select an existing project. +2. **Download The Credentials** - Click "Go to credentials" after enabling the APIs. Click "New Credentials" + and select "Service Account Key". Create a new service account, use the JSON key type, and + select "Create". Once downloaded, set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` + to the path of the JSON key that was downloaded. +3. **Clone the repo** and cd into this directory + + ```sh + $ git clone https://github.com/GoogleCloudPlatform/php-docs-samples + $ cd php-docs-samples/storagebatchoperations + ``` +4. **Install dependencies** via [Composer](http://getcomposer.org/doc/00-intro.md). + Run `php composer.phar install` (if composer is installed locally) or `composer install` + (if composer is installed globally). + + +## Samples + +To run the Storage Batch Operations Samples, run any of the files in `src/` on the CLI: + +``` +$ php src/create_job.php + +Usage: create_job.php $jobId $bucketName $objectPrefix + + @param string $projectId The Project ID + @param string $jobId The new Job ID + @param string $bucketName The Storage bucket name + @param string $objectPrefix The Object prefix +``` + +## The client library + +This sample uses the [Cloud Storage Batch Operations Client Library for PHP][google-cloud-php-storage-batch-operations]. +You can read the documentation for more details on API usage and use GitHub +to [browse the source][google-cloud-php-source] and [report issues][google-cloud-php-issues]. + +[google-cloud-php-storage-batch-operations]: https://cloud.google.com/php/docs/reference/cloud-storagebatchoperations/latest +[google-cloud-php-source]: https://github.com/GoogleCloudPlatform/google-cloud-php +[google-cloud-php-issues]: https://github.com/GoogleCloudPlatform/google-cloud-php/issues +[google-cloud-sdk]: https://cloud.google.com/sdk/ + +## Contributing changes + +* See [CONTRIBUTING.md](../../CONTRIBUTING.md) + +## Licensing + +* See [LICENSE](../../LICENSE) diff --git a/storagebatchoperations/composer.json b/storagebatchoperations/composer.json new file mode 100644 index 000000000..e4f2639c5 --- /dev/null +++ b/storagebatchoperations/composer.json @@ -0,0 +1,8 @@ +{ + "require": { + "google/cloud-storagebatchoperations": "0.1.1" + }, + "require-dev": { + "google/cloud-storage": "^1.48.1" + } +} diff --git a/storagebatchoperations/phpunit.xml.dist b/storagebatchoperations/phpunit.xml.dist new file mode 100644 index 000000000..e6e259d21 --- /dev/null +++ b/storagebatchoperations/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + ./src + + + ./vendor + + + + + + + + test + + + + + + + diff --git a/storagebatchoperations/src/cancel_job.php b/storagebatchoperations/src/cancel_job.php new file mode 100644 index 000000000..b89503a86 --- /dev/null +++ b/storagebatchoperations/src/cancel_job.php @@ -0,0 +1,59 @@ +locationName($projectId, 'global'); + $formattedName = $parent . '/jobs/' . $jobId; + + $request = new CancelJobRequest([ + 'name' => $formattedName, + ]); + + $storageBatchOperationsClient->cancelJob($request); + + printf('Cancelled job: %s', $formattedName); +} +# [END storage_batch_cancel_job] + +// 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/storagebatchoperations/src/create_job.php b/storagebatchoperations/src/create_job.php new file mode 100644 index 000000000..5c57ac77f --- /dev/null +++ b/storagebatchoperations/src/create_job.php @@ -0,0 +1,76 @@ +locationName($projectId, 'global'); + + $prefixListConfig = new PrefixList(['included_object_prefixes' => [$objectPrefix]]); + $bucket = new Bucket(['bucket' => $bucketName, 'prefix_list' => $prefixListConfig]); + $bucketList = new BucketList(['buckets' => [$bucket]]); + + $deleteObject = new DeleteObject(['permanent_object_deletion_enabled' => false]); + + $job = new Job(['bucket_list' => $bucketList, 'delete_object' => $deleteObject]); + + $request = new CreateJobRequest([ + 'parent' => $parent, + 'job_id' => $jobId, + 'job' => $job, + ]); + $response = $storageBatchOperationsClient->createJob($request); + + printf('Created job: %s', $response->getName()); +} +# [END storage_batch_create_job] + +// 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/storagebatchoperations/src/delete_job.php b/storagebatchoperations/src/delete_job.php new file mode 100644 index 000000000..6c1621e3a --- /dev/null +++ b/storagebatchoperations/src/delete_job.php @@ -0,0 +1,59 @@ +locationName($projectId, 'global'); + $formattedName = $parent . '/jobs/' . $jobId; + + $request = new DeleteJobRequest([ + 'name' => $formattedName, + ]); + + $storageBatchOperationsClient->deleteJob($request); + + printf('Deleted job: %s', $formattedName); +} +# [END storage_batch_delete_job] + +// 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/storagebatchoperations/src/get_job.php b/storagebatchoperations/src/get_job.php new file mode 100644 index 000000000..f6e4438ea --- /dev/null +++ b/storagebatchoperations/src/get_job.php @@ -0,0 +1,59 @@ +locationName($projectId, 'global'); + $formattedName = $parent . '/jobs/' . $jobId; + + $request = new GetJobRequest([ + 'name' => $formattedName, + ]); + + $response = $storageBatchOperationsClient->getJob($request); + + printf('Got job: %s', $response->getName()); +} +# [END storage_batch_get_job] + +// 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/storagebatchoperations/src/list_jobs.php b/storagebatchoperations/src/list_jobs.php new file mode 100644 index 000000000..68161b628 --- /dev/null +++ b/storagebatchoperations/src/list_jobs.php @@ -0,0 +1,58 @@ +locationName($projectId, 'global'); + + $request = new ListJobsRequest([ + 'parent' => $parent, + ]); + + $jobs = $storageBatchOperationsClient->listJobs($request); + + foreach ($jobs as $job) { + printf('Job name: %s' . PHP_EOL, $job->getName()); + } +} +# [END storage_batch_list_jobs] + +// 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/storagebatchoperations/test/StorageBatchOperationsTest.php b/storagebatchoperations/test/StorageBatchOperationsTest.php new file mode 100644 index 000000000..0eb22636d --- /dev/null +++ b/storagebatchoperations/test/StorageBatchOperationsTest.php @@ -0,0 +1,170 @@ +locationName(self::$projectId, 'global'); + self::$jobName = self::$parent . '/jobs/' . self::$jobId; + + self::$bucket = self::$storage->createBucket(sprintf('php-gcs-sbo-sample-%s', $uniqueBucketId)); + + $objectName = self::$objectPrefix . '-object-1.txt'; + self::$bucket->upload('test content', ['name' => $objectName]); + + } + + public static function tearDownAfterClass(): void + { + foreach (self::$bucket->objects(['versions' => true]) as $object) { + $object->delete(); + } + self::$bucket->delete(); + } + + public function testCreateJob() + { + $output = $this->runFunctionSnippet('create_job', [ + self::$projectId, self::$jobId, self::$bucket->name(), self::$objectPrefix + ]); + + $this->assertStringContainsString( + sprintf('Created job: %s', self::$parent), + $output + ); + } + + /** + * @depends testCreateJob + */ + public function testGetJob() + { + $output = $this->runFunctionSnippet('get_job', [ + self::$projectId, self::$jobId + ]); + + $this->assertStringContainsString( + self::$jobName, + $output + ); + } + + /** + * @depends testGetJob + */ + public function testListJobs() + { + $output = $this->runFunctionSnippet('list_jobs', [ + self::$projectId + ]); + + $this->assertStringContainsString( + self::$jobName, + $output + ); + } + + /** + * @depends testListJobs + */ + public function testCancelJob() + { + $output = $this->runFunctionSnippet('cancel_job', [ + self::$projectId, self::$jobId + ]); + + $this->assertStringContainsString( + sprintf('Cancelled job: %s', self::$jobName), + $output + ); + } + + /** + * @depends testCancelJob + */ + public function testDeleteJob() + { + $attempt = 0; + $maxAttempts = 10; + $jobReadyForDeletion = false; + while ($attempt < $maxAttempts && !$jobReadyForDeletion) { + $attempt++; + $request = new GetJobRequest([ + 'name' => self::$jobName, + ]); + + $response = self::$storageBatchOperationsClient->getJob($request); + $state = $response->getState(); + $status = \Google\Cloud\StorageBatchOperations\V1\Job\State::name($state); + + // A job is typically deletable if it's not in a creating/pending/running state + // Consider PENDING or IN_PROGRESS as states to wait out. + // For immediate deletion, maybe it needs to be SUCCEEDED or FAILED or CANCELED. + if ($status !== 'STATE_UNSPECIFIED' && $status !== 'RUNNING') { + $jobReadyForDeletion = true; + } + + if (!$jobReadyForDeletion && $attempt < $maxAttempts) { + sleep(10); // Wait 10 seconds + } + } + + if (!$jobReadyForDeletion) { + $this->fail('Job did not reach a deletable state within the allowed time.'); + } + + // Now attempt to delete the job + $output = $this->runFunctionSnippet('delete_job', [ + self::$projectId, self::$jobId + ]); + + $this->assertStringContainsString( + sprintf('Deleted job: %s', self::$jobName), + $output + ); + } +}