From 6f8557525b361efd10667a7e8db0b12c3214362e Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 3 Oct 2018 13:51:39 -0700 Subject: [PATCH] Bucket lock samples and tests (#418) * Bucket lock samples and tests * Address feedback * Address lint issues * Revert +git from storage dep version * Fix lint issue and merge issue --- samples/README.md | 51 +++ samples/bucketLock.js | 468 +++++++++++++++++++++++++ samples/files.js | 6 +- samples/system-test/bucketLock.test.js | 180 ++++++++++ 4 files changed, 704 insertions(+), 1 deletion(-) create mode 100644 samples/bucketLock.js create mode 100644 samples/system-test/bucketLock.test.js diff --git a/samples/README.md b/samples/README.md index f964723630..13c44bfe3a 100644 --- a/samples/README.md +++ b/samples/README.md @@ -18,6 +18,7 @@ * [Files](#files) * [Notifications](#notifications) * [Requester Pays](#requester-pays) + * [Bucket Lock](#bucket-lock) ## Before you begin @@ -270,5 +271,55 @@ For more information, see https://cloud.google.com/storage/docs [requesterPays_5_docs]: https://cloud.google.com/storage/docs [requesterPays_5_code]: requesterPays.js +### Bucket Lock + +View the [source code][bucketLock_6_code]. + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/bucketLock.js,samples/README.md) + +__Usage:__ `node bucketLock.js --help` + +``` +bucketLock.js + +Commands: + bucketLock.js set-retention-policy Defines a retention policy on a given bucket. + bucketLock.js remove-retention-policy Removes a retention policy on a given bucket if the + policy is unlocked. + bucketLock.js get-retention-policy Get a retention policy for a given bucket. + bucketLock.js enable-default-event-based-hold Enable default event-based hold for a given bucket. + bucketLock.js disable-default-event-based-hold Disable default event-based hold for a given bucket. + bucketLock.js set-event-based-hold Set an event-based hold for a given file. + bucketLock.js release-event-based-hold Release an event-based hold for a given file. + + bucketLock.js set-temporary-hold Set a temporary hold for a given file. + bucketLock.js release-temporary-hold Release a temporary hold for a given file. + +Options: + --version Show version number [boolean] + --help Show help [boolean] + +Examples: + node bucketLock.js set-retention-policy my-bucket 5 Defines a retention policy of 5 seconds on a + "my-bucket". + node bucketLock.js remove-retention-policy my-bucket Removes a retention policy from "my-bucket". + node bucketLock.js get-retention-policy my-bucket Get the retention policy for "my-bucket". + node bucketLock.js enable-default-event-based-hold my-bucket Enable a default event-based hold for "my-bucket". + node bucketLock.js disable-default-event-based-hold Disable a default-event based hold for "my-bucket". + my-bucket + node bucketLock.js get-default-event-based-hold my-bucket Get the value of a default-event-based hold for + "my-bucket". + node bucketLock.js set-event-based-hold my-bucket my-file Sets an event-based hold on "my-file". + node bucketLock.js release-event-based-hold my-bucket Releases an event-based hold on "my-file". + my-file + node bucketLock.js set-temporary-hold my-bucket my-file Sets a temporary hold on "my-file". + node bucketLock.js release-temporary-hold my-bucket my-file Releases a temporary hold on "my-file". + +For more information, see https://cloud.google.com/storage/docs +``` + +[bucketLock_6_docs]: https://cloud.google.com/storage/docs +[bucketLock_6_code]: bucketLock.js + [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png [shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/README.md diff --git a/samples/bucketLock.js b/samples/bucketLock.js new file mode 100644 index 0000000000..31110e1c5c --- /dev/null +++ b/samples/bucketLock.js @@ -0,0 +1,468 @@ +/** + * Copyright 2018, Google, Inc. + * 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, + * 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. + */ + +/** + * This application demonstrates how to use Bucket Lock operations on buckets + * and objects using the Google Cloud Storage API. + * + * For more information read the documentation + * at https://cloud.google.com/storage/docs/bucket-lock + */ + +'use strict'; +function setRetentionPolicy(bucketName, retentionPeriod) { + // [START storage_set_retention_policy] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + storage + .bucket(bucketName) + .setRetentionPeriod(retentionPeriod) + .then(response => { + const metadata = response[0]; + console.log( + `Bucket ${bucketName} retention period set for ${ + metadata.retentionPolicy.retentionPeriod + } seconds.` + ); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_set_retention_policy] +} + +function getRetentionPolicy(bucketName) { + // [START storage_get_retention_policy] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + storage + .bucket(bucketName) + .getMetadata() + .then(results => { + const metadata = results[0]; + if (metadata.hasOwnProperty('retentionPolicy')) { + const retentionPolicy = metadata.retentionPolicy; + console.log('A retention policy exists!'); + console.log(`Period: ${retentionPolicy.retentionPeriod}`); + console.log(`Effective time: ${retentionPolicy.effectiveTime}`); + if (retentionPolicy.hasOwnProperty('isLocked')) { + console.log('Policy is locked'); + } else { + console.log('Policy is unlocked'); + } + } + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_get_retention_policy] +} + +function removeRetentionPolicy(bucketName) { + // [START storage_remove_retention_policy] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + storage + .bucket(bucketName) + .getMetadata() + .then(results => { + const metadata = results[0]; + if ( + metadata.hasOwnProperty('retentionPolicy') && + metadata.retentionPolicy.hasOwnProperty('isLocked') + ) { + console.log( + 'Unable to remove retention period as retention policy is locked.' + ); + return null; + } else { + return storage + .bucket(bucketName) + .removeRetentionPeriod() + .then(() => { + console.log(`Removed bucket ${bucketName} retention policy.`); + }); + } + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_remove_retention_policy] +} + +function lockRetentionPolicy(bucketName) { + // [START storage_lock_retention_policy] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + // get_bucket gets the current metageneration value for the bucket, + // required by lock_retention_policy. + storage + .bucket(bucketName) + .getMetadata() + .then(results => { + const unlockedMetadata = results[0]; + // Warning: Once a retention policy is locked it cannot be unlocked + // and retention period can only be increased. + return storage + .bucket(bucketName) + .lock(unlockedMetadata.metageneration) + .then(results => { + const lockedMetadata = results[0]; + console.log(`Retention policy for ${bucketName} is now locked.`); + console.log( + `Retention policy effective as of ${ + lockedMetadata.retentionPolicy.effectiveTime + }` + ); + }); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_lock_retention_policy] +} + +function enableDefaultEventBasedHold(bucketName) { + // [START storage_enable_default_event_based_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + + // Enables a default event-based hold for the bucket. + storage + .bucket(bucketName) + .setMetadata({ + defaultEventBasedHold: true, + }) + .then(() => { + console.log(`Default event-based hold was enabled for ${bucketName}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_enable_default_event_based_hold] +} + +function disableDefaultEventBasedHold(bucketName) { + // [START storage_disable_default_event_based_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + + // Disables a default event-based hold for a bucket. + storage + .bucket(bucketName) + .setMetadata({ + defaultEventBasedHold: false, + }) + .then(() => { + console.log(`Default event-based hold was disabled for ${bucketName}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_disable_default_event_based_hold] +} + +function getDefaultEventBasedHold(bucketName) { + // [START storage_get_default_event_based_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + + // get bucketName metadata + storage + .bucket(bucketName) + .getMetadata() + .then(results => { + const metadata = results[0]; + console.log( + `Default event-based hold: ${metadata.defaultEventBasedHold}.` + ); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_get_default_event_based_hold] +} + +function setEventBasedHold(bucketName, fileName) { + // [START storage_set_event_based_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + // const filename = 'File to access, e.g. file.txt'; + + // Set event-based hold + storage + .bucket(bucketName) + .file(fileName) + .setMetadata({ + eventBasedHold: true, + }) + .then(() => { + console.log(`Event-based hold was set for ${fileName}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_set_event_based_hold] +} + +function releaseEventBasedHold(bucketName, fileName) { + // [START storage_release_event_based_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + // const filename = 'File to access, e.g. file.txt'; + + storage + .bucket(bucketName) + .file(fileName) + .setMetadata({ + eventBasedHold: false, + }) + .then(() => { + console.log(`Event-based hold was released for ${fileName}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_release_event_based_hold] +} + +function setTemporarydHold(bucketName, fileName) { + // [START storage_set_temporary_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + // const filename = 'File to access, e.g. file.txt'; + + storage + .bucket(bucketName) + .file(fileName) + .setMetadata({ + temporaryHold: true, + }) + .then(() => { + console.log(`Temporary hold was set for ${fileName}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_set_temporary_hold] +} + +function releaseTemporaryHold(bucketName, fileName) { + // [START storage_release_temporary_hold] + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const bucketName = 'Name of a bucket, e.g. my-bucket'; + // const filename = 'File to access, e.g. file.txt'; + + storage + .bucket(bucketName) + .file(fileName) + .setMetadata({ + temporaryHold: false, + }) + .then(() => { + console.log(`Temporary hold was released for ${fileName}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END storage_release_temporary_hold] +} + +require(`yargs`) + .demand(1) + .command( + `set-retention-policy `, + `Defines a retention policy on a given bucket.`, + {}, + opts => setRetentionPolicy(opts.bucketName, opts.period) + ) + .command( + `remove-retention-policy `, + `Removes a retention policy on a given bucket if the policy is unlocked.`, + {}, + opts => removeRetentionPolicy(opts.bucketName) + ) + .command( + `get-retention-policy `, + `Get a retention policy for a given bucket.`, + {}, + opts => getRetentionPolicy(opts.bucketName) + ) + .command( + `lock-retention-policy `, + `Lock a retention policy for a given bucket.`, + {}, + opts => lockRetentionPolicy(opts.bucketName) + ) + .command( + `enable-default-event-based-hold `, + `Enable default event-based hold for a given bucket.`, + {}, + opts => enableDefaultEventBasedHold(opts.bucketName) + ) + .command( + `disable-default-event-based-hold `, + `Disable default event-based hold for a given bucket.`, + {}, + opts => disableDefaultEventBasedHold(opts.bucketName) + ) + .command( + `get-default-event-based-hold `, + `Get default event-based hold for a given bucket.`, + {}, + opts => getDefaultEventBasedHold(opts.bucketName) + ) + .command( + `set-event-based-hold `, + `Set an event-based hold for a given file.`, + {}, + opts => setEventBasedHold(opts.bucketName, opts.fileName) + ) + .command( + `release-event-based-hold `, + `Release an event-based hold for a given file.`, + {}, + opts => releaseEventBasedHold(opts.bucketName, opts.fileName) + ) + .command( + `set-temporary-hold `, + `Set a temporary hold for a given file.`, + {}, + opts => setTemporarydHold(opts.bucketName, opts.fileName) + ) + .command( + `release-temporary-hold `, + `Release a temporary hold for a given file.`, + {}, + opts => releaseTemporaryHold(opts.bucketName, opts.fileName) + ) + .example( + `node $0 set-retention-policy my-bucket 5`, + `Defines a retention policy of 5 seconds on a "my-bucket".` + ) + .example( + `node $0 remove-retention-policy my-bucket`, + `Removes a retention policy from "my-bucket".` + ) + .example( + `node $0 get-retention-policy my-bucket`, + `Get the retention policy for "my-bucket".` + ) + .example( + `node $0 lock-retention-policy my-bucket`, + `Lock the retention policy for "my-bucket".` + ) + .example( + `node $0 enable-default-event-based-hold my-bucket`, + `Enable a default event-based hold for "my-bucket".` + ) + .example( + `node $0 disable-default-event-based-hold my-bucket`, + `Disable a default-event based hold for "my-bucket".` + ) + .example( + `node $0 get-default-event-based-hold my-bucket`, + `Get the value of a default-event-based hold for "my-bucket".` + ) + .example( + `node $0 set-event-based-hold my-bucket my-file`, + `Sets an event-based hold on "my-file".` + ) + .example( + `node $0 release-event-based-hold my-bucket my-file`, + `Releases an event-based hold on "my-file".` + ) + .example( + `node $0 set-temporary-hold my-bucket my-file`, + `Sets a temporary hold on "my-file".` + ) + .example( + `node $0 release-temporary-hold my-bucket my-file`, + `Releases a temporary hold on "my-file".` + ) + .wrap(120) + .recommendCommands() + .epilogue(`For more information, see https://cloud.google.com/storage/docs`) + .help() + .strict().argv; diff --git a/samples/files.js b/samples/files.js index 1b0ee7da73..ffd3d81afa 100644 --- a/samples/files.js +++ b/samples/files.js @@ -247,8 +247,12 @@ async function getMetadata(bucketName, filename) { console.log(`Content-disposition: ${metadata.contentDisposition}`); console.log(`Content-encoding: ${metadata.contentEncoding}`); console.log(`Content-language: ${metadata.contentLanguage}`); - console.log(`Metadata: ${metadata.metadata}`); console.log(`Media link: ${metadata.mediaLink}`); + console.log(`KMS Key Name: ${metadata.kmsKeyName}`); + console.log(`Temporary Hold: ${metadata.temporaryHold}`); + console.log(`Event-based hold: ${metadata.eventBasedHold}`); + console.log(`Effective Expiration Time: ${metadata.effectiveExpirationTime}`); + console.log(`Metadata: ${metadata.metadata}`); // [END storage_get_metadata] } diff --git a/samples/system-test/bucketLock.test.js b/samples/system-test/bucketLock.test.js new file mode 100644 index 0000000000..5afe73da07 --- /dev/null +++ b/samples/system-test/bucketLock.test.js @@ -0,0 +1,180 @@ +/** + * Copyright 2017, Google, Inc. + * 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, + * 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. + */ + +'use strict'; + +const path = require(`path`); +const {Storage} = require(`@google-cloud/storage`); +const test = require(`ava`); +const tools = require(`@google-cloud/nodejs-repo-tools`); +const uuid = require(`uuid`); + +const storage = new Storage(); +const cwd = path.join(__dirname, `..`); +const cmd = `node bucketLock.js`; +const bucketName = `nodejs-storage-samples-${uuid.v4()}`; +const bucket = storage.bucket(bucketName); +const fileName = `test.txt`; + +const uploadFilePath = path.join(cwd, `resources`, fileName); + +test.before(tools.checkCredentials); +test.before(async () => { + await bucket.create(); +}); +test.before(async () => { + await bucket.upload(uploadFilePath); +}); + +test.after.always(async () => { + try { + await bucket.deleteFiles({force: true}); + } catch (err) {} // ignore error + try { + await bucket.delete(); + } catch (err) {} // ignore error +}); + +test.beforeEach(tools.stubConsole); +test.afterEach.always(tools.restoreConsole); + +test.serial(`should set a retention policy on a bucket`, async t => { + const retentionPeriod = 5; + const results = await tools.runAsyncWithIO( + `${cmd} set-retention-policy ${bucketName} ${retentionPeriod}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp( + `Bucket ${bucketName} retention period set for ${retentionPeriod} seconds.` + ) + ); +}); + +test.serial(`should get a retention policy on a bucket`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} get-retention-policy ${bucketName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`A retention policy exists!`) + ); +}); + +test.serial(`should enable default event-based hold on a bucket`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} enable-default-event-based-hold ${bucketName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Default event-based hold was enabled for ${bucketName}.`) + ); +}); + +test.serial(`should get default event-based hold on a bucket`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} get-default-event-based-hold ${bucketName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Default event-based hold: true.`) + ); +}); + +test.serial(`should disable default event-based hold on a bucket`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} disable-default-event-based-hold ${bucketName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Default event-based hold was disabled for ${bucketName}.`) + ); +}); + +test.serial(`should set an event-based hold on a file`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} set-event-based-hold ${bucketName} ${fileName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Event-based hold was set for ${fileName}.`) + ); +}); + +test.serial(`should release an event-based hold on a file`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} release-event-based-hold ${bucketName} ${fileName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Event-based hold was released for ${fileName}.`) + ); +}); + +test.serial(`should remove a retention policy on a bucket`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} remove-retention-policy ${bucketName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Removed bucket ${bucketName} retention policy.`) + ); +}); + +test.serial(`should set an temporary hold on a file`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} set-temporary-hold ${bucketName} ${fileName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Temporary hold was set for ${fileName}.`) + ); +}); + +test.serial(`should release an temporary hold on a file`, async t => { + const results = await tools.runAsyncWithIO( + `${cmd} release-temporary-hold ${bucketName} ${fileName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Temporary hold was released for ${fileName}.`) + ); +}); + +test.serial(`should lock a bucket with a retention policy`, async t => { + const retentionPeriod = 5; + await tools.runAsyncWithIO( + `${cmd} set-retention-policy ${bucketName} ${retentionPeriod}`, + cwd + ); + const results = await tools.runAsyncWithIO( + `${cmd} lock-retention-policy ${bucketName}`, + cwd + ); + t.regex( + results.stdout + results.stderr, + new RegExp(`Retention policy for ${bucketName} is now locked.`) + ); +});