From 8241e91e78d47b4cdaea2d941f75fd6a4fa29230 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 29 Oct 2024 17:59:11 +0530 Subject: [PATCH] feat: add support for restore token (#2548) * Adds support for the restore token feature * description fix * lint fix --- src/file.ts | 9 +++++ system-test/storage.ts | 73 ++++++++++++++++++++++++++++++++++++++++ test/transfer-manager.ts | 1 - 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/file.ts b/src/file.ts index e808f5c9a..d8ff1a216 100644 --- a/src/file.ts +++ b/src/file.ts @@ -178,6 +178,7 @@ export interface GetFileMetadataCallback { export interface GetFileOptions extends GetConfig { userProject?: string; generation?: number; + restoreToken?: string; softDeleted?: boolean; } @@ -354,6 +355,7 @@ export interface FileOptions { crc32cGenerator?: CRC32CValidatorGenerator; encryptionKey?: string | Buffer; generation?: number | string; + restoreToken?: string; kmsKeyName?: string; preconditionOpts?: PreconditionOptions; userProject?: string; @@ -450,6 +452,7 @@ export interface SetStorageClassCallback { export interface RestoreOptions extends PreconditionOptions { generation: number; + restoreToken?: string; projection?: 'full' | 'noAcl'; } @@ -471,6 +474,7 @@ export interface FileMetadata extends BaseMetadata { eventBasedHold?: boolean | null; readonly eventBasedHoldReleaseTime?: string; generation?: string | number; + restoreToken?: string; hardDeleteTime?: string; kmsKeyName?: string; md5Hash?: string; @@ -547,6 +551,7 @@ class File extends ServiceObject { name: string; generation?: number; + restoreToken?: string; parent!: Bucket; private encryptionKey?: string | Buffer; @@ -844,6 +849,8 @@ class File extends ServiceObject { * @param {string} [options.userProject] The ID of the project which will be * billed for the request. * @param {number} [options.generation] The generation number to get + * @param {string} [options.restoreToken] If this is a soft-deleted object in an HNS-enabled bucket, returns the restore token which will + * be necessary to restore it if there's a name conflict with another object. * @param {boolean} [options.softDeleted] If true, returns the soft-deleted object. Object `generation` is required if `softDeleted` is set to True. * @param {GetFileCallback} [callback] Callback function. @@ -3707,6 +3714,8 @@ class File extends ServiceObject { * @param {string} [userProject] The ID of the project which will be * billed for the request. * @param {number} [generation] If present, selects a specific revision of this object. + * @param {string} [restoreToken] Returns an option that must be specified when getting a soft-deleted object from an HNS-enabled + * bucket that has a naming and generation conflict with another object in the same bucket. * @param {string} [projection] Specifies the set of properties to return. If used, must be 'full' or 'noAcl'. * @param {string | number} [ifGenerationMatch] Request proceeds if the generation of the target resource * matches the value used in the precondition. diff --git a/system-test/storage.ts b/system-test/storage.ts index 4b004c830..6c33cf7ac 100644 --- a/system-test/storage.ts +++ b/system-test/storage.ts @@ -792,6 +792,7 @@ describe('storage', function () { describe('soft-delete', () => { let bucket: Bucket; + let hnsBucket: Bucket; const SOFT_DELETE_RETENTION_SECONDS = 7 * 24 * 60 * 60; //7 days in seconds; beforeEach(async () => { @@ -802,11 +803,26 @@ describe('storage', function () { retentionDurationSeconds: SOFT_DELETE_RETENTION_SECONDS, }, }); + + hnsBucket = storage.bucket(generateName()); + await storage.createBucket(hnsBucket.name, { + hierarchicalNamespace: {enabled: true}, + iamConfiguration: { + uniformBucketLevelAccess: { + enabled: true, + }, + }, + softDeletePolicy: { + retentionDurationSeconds: SOFT_DELETE_RETENTION_SECONDS, + }, + }); }); afterEach(async () => { await bucket.deleteFiles({force: true, versions: true}); await bucket.delete(); + await hnsBucket.deleteFiles({force: true, versions: true}); + await hnsBucket.delete(); }); it('should set softDeletePolicy correctly', async () => { @@ -862,6 +878,63 @@ describe('storage', function () { [files] = await bucket.getFiles(); assert.strictEqual(files.length, 1); }); + + it('should LIST soft-deleted files with restore token', async () => { + const f1 = hnsBucket.file('file5a'); + const f2 = hnsBucket.file('file5b'); + await f1.save('file5a'); + await f2.save('file5b'); + await f1.delete(); + await f2.delete(); + const [notSoftDeletedFiles] = await hnsBucket.getFiles(); + assert.strictEqual(notSoftDeletedFiles.length, 0); + const [softDeletedFiles] = await hnsBucket.getFiles({softDeleted: true}); + assert.strictEqual(softDeletedFiles.length, 2); + assert.notStrictEqual( + softDeletedFiles![0].metadata.restoreToken, + undefined + ); + }); + + it('should GET a soft-deleted file with restore token', async () => { + const f1 = hnsBucket.file('file6'); + await f1.save('file6'); + const [metadata] = await f1.getMetadata(); + await f1.delete(); + const [softDeletedFile] = await f1.get({ + softDeleted: true, + generation: parseInt(metadata.generation?.toString() || '0'), + }); + assert(softDeletedFile); + assert.strictEqual( + softDeletedFile.metadata.generation, + metadata.generation + ); + assert.notStrictEqual(softDeletedFile.metadata.restoreToken, undefined); + }); + + it('should restore a soft-deleted file using restoreToken', async () => { + const f1 = hnsBucket.file('file7'); + await f1.save('file7'); + const [metadata] = await f1.getMetadata(); + await f1.delete(); + let [files] = await hnsBucket.getFiles(); + assert.strictEqual(files.length, 0); + const [softDeletedFile] = await f1.get({ + softDeleted: true, + generation: parseInt(metadata.generation?.toString() || '0'), + }); + assert(softDeletedFile); + const restoredFile = await f1.restore({ + generation: parseInt( + softDeletedFile.metadata.generation?.toString() || '0' + ), + restoreToken: softDeletedFile.metadata.restoreToken, + }); + assert(restoredFile); + [files] = await hnsBucket.getFiles(); + assert.strictEqual(files.length, 1); + }); }); describe('dual-region', () => { diff --git a/test/transfer-manager.ts b/test/transfer-manager.ts index 6280a5c44..c2d0d750c 100644 --- a/test/transfer-manager.ts +++ b/test/transfer-manager.ts @@ -26,7 +26,6 @@ import { MultiPartUploadError, MultiPartUploadHelper, UploadOptions, - UploadManyFilesOptions, TransferManager, Storage, DownloadResponse,