Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Storage): softDelete object in gcs buckets #7154

Merged
merged 6 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Storage/src/Bucket.php
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,44 @@ public function object($name, array $options = [])
);
}

/**
* Restores an object.
*
* Example:
* ```
* $object = $bucket->restore('file.txt');
* ```
*
* @param string $name The name of the object to restore.
* @param string $generation Request a specific generation of the object.
* @param array $options [optional] {
* Configuration Options.
*
* @type string $ifMetagenerationMatch If set, only restores
* if its metageneration matches this value.
* @type string $ifMetagenerationNotMatch If set, only restores
* if its metageneration does not match this value.
* }
* @return StorageObject
*/
public function restore($name, $generation, array $options = [])
{
$res = $this->connection->restoreObject([
'bucket' => $this->identity['bucket'],
'object' => $name,
'generation' => $generation
] + $options);
return new StorageObject(
$this->connection,
$name,
$this->identity['bucket'],
$generation,
$res + array_filter([
'requesterProjectId' => $this->identity['userProject']
])
);
}

/**
* Fetches all objects in the bucket.
*
Expand Down
5 changes: 5 additions & 0 deletions Storage/src/Connection/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ public function patchBucket(array $args = []);
*/
public function deleteObject(array $args = []);

/**
* @param array $args
*/
public function restoreObject(array $args = []);

/**
* @param array $args
*/
Expand Down
26 changes: 21 additions & 5 deletions Storage/src/Connection/Rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ public function deleteObject(array $args = [])
return $this->send('objects', 'delete', $args);
}

/**
* @param array $args
*/
public function restoreObject(array $args = [])
{
return $this->send('objects', 'restore', $args);
}

/**
* @param array $args
*/
Expand Down Expand Up @@ -614,14 +622,22 @@ private function buildDownloadObjectParams(array $args)
'restDelayFunction' => null
]);

$queryOptions = [
'generation' => $args['generation'],
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
'alt' => 'media',
'userProject' => $args['userProject'],
];
if (isset($args['softDeleted'])) {
// alt param cannot be specified with softDeleted param. See:
// https://cloud.google.com/storage/docs/json_api/v1/objects/get
unset($args['alt']);
$queryOptions['softDeleted'] = $args['softDeleted'];
}

$uri = $this->expandUri($this->apiEndpoint . self::DOWNLOAD_PATH, [
'bucket' => $args['bucket'],
'object' => $args['object'],
'query' => [
'generation' => $args['generation'],
'alt' => 'media',
'userProject' => $args['userProject']
]
'query' => $queryOptions,
]);

return [
Expand Down
2 changes: 1 addition & 1 deletion Storage/src/StorageObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public function acl()
public function exists(array $options = [])
{
try {
$this->connection->getObject($this->identity + $options + ['fields' => 'name']);
$this->connection->getObject($options + array_filter($this->identity) + ['fields' => 'name']);
vishwarajanand marked this conversation as resolved.
Show resolved Hide resolved
} catch (NotFoundException $ex) {
return false;
}
Expand Down
18 changes: 18 additions & 0 deletions Storage/tests/Snippet/BucketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,24 @@ public function testDelete()
$snippet->invoke();
}

public function testRestore()
{
$snippet = $this->snippetFromMethod(Bucket::class, 'restore');
$snippet->addLocal('bucket', $this->bucket);

$this->connection->restoreObject(Argument::any())
->willReturn([
'name' => 'file.txt',
'generation' => 'abc'
]);

$restoredObject = $this->bucket->restore('file.txt', 'abc');

$this->assertInstanceOf(StorageObject::class, $restoredObject);
$this->assertEquals('file.txt', $restoredObject->name());
$this->assertEquals('abc', $restoredObject->info()['generation']);
}

public function testUpdate()
{
$snippet = $this->snippetFromMethod(Bucket::class, 'update');
Expand Down
26 changes: 26 additions & 0 deletions Storage/tests/System/ManageBucketsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,32 @@ public function testUpdateBucket()
$this->assertEquals($options['website'], $info['website']);
}

public function testSoftDeletePolicy()
{
$durationSecond = 8*24*60*60;
// set soft delete policy
self::$bucket->update([
'softDeletePolicy' => [
'retentionDurationSeconds' => $durationSecond
]
]);
$this->assertArrayHasKey('softDeletePolicy', self::$bucket->info());
$this->assertEquals(
$durationSecond,
self::$bucket->info()['softDeletePolicy']['retentionDurationSeconds']
);

// remove soft delete policy
self::$bucket->update([
'softDeletePolicy' => []
]);
$this->assertArrayHasKey('softDeletePolicy', self::$bucket->info());
$this->assertEquals(
0,
self::$bucket->info()['softDeletePolicy']['retentionDurationSeconds']
);
}

/**
* @group storage-bucket-lifecycle
* @dataProvider lifecycleRules
Expand Down
38 changes: 38 additions & 0 deletions Storage/tests/System/ManageObjectsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,34 @@ public function testComposeObjects($object)

$this->assertEquals($name, $composedObject->name());
$this->assertEquals($expectedContent, $composedObject->downloadAsString());
return $composedObject;
}

/**
* @depends testComposeObjects
*/
public function testSoftDeleteObject($object)
{
// Set soft delete policy
self::$bucket->update([
'softDeletePolicy' => [
'retentionDurationSeconds' => 8*24*60*60
]
]);

$this->assertObjectExists($object);

$object->delete();

$this->assertObjectExists($object, [], false);
$this->assertObjectExists($object, [
'softDeleted' => true,
'generation' => $object->info()['generation']
], true);

self::$bucket->restore($object->name(), $object->info()['generation']);

$this->assertObjectExists($object);
}

public function testRotatesCustomerSuppliedEncrpytion()
Expand Down Expand Up @@ -414,4 +442,14 @@ public function testStringNormalization()
$this->assertSame($expectedContent, $actualContent);
}
}

private function assertObjectExists($object, $options = [], $isPresent = true)
{
$this->assertEquals($isPresent, $object->exists($options));
$objects = self::$bucket->objects($options);
$objects = array_map(function ($o) {
return $o->name();
}, iterator_to_array($objects));
$this->assertEquals($isPresent, in_array($object->name(), $objects));
}
}
16 changes: 16 additions & 0 deletions Storage/tests/Unit/BucketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,22 @@ public function testDelete()
$this->assertNull($bucket->delete());
}

public function testRestore()
{
$this->connection->restoreObject(Argument::any())
->willReturn([
'name' => 'file.txt',
'generation' => 'abc'
]);

$bucket = $this->getBucket();
$restoredObject = $bucket->restore('file.txt', 'abc');

$this->assertInstanceOf(StorageObject::class, $restoredObject);
$this->assertEquals('file.txt', $restoredObject->name());
$this->assertEquals('abc', $restoredObject->info()['generation']);
}

public function testComposeThrowsExceptionWithLessThanTwoSources()
{
$this->expectException(InvalidArgumentException::class);
Expand Down
Loading