From 920ad5c81be7a047f25cb1646a4c4509ba1b4d47 Mon Sep 17 00:00:00 2001 From: Corey Pyle Date: Mon, 7 Oct 2024 11:39:33 -0400 Subject: [PATCH] JavaScript (v3): S3 - Standardize object-locking example. --- .../example_code/libs/scenario/scenario.js | 20 +- .../s3/scenarios/object-locking/index.js | 2 + .../scenarios/object-locking/setup.steps.js | 216 +++++++++++------- 3 files changed, 149 insertions(+), 89 deletions(-) diff --git a/javascriptv3/example_code/libs/scenario/scenario.js b/javascriptv3/example_code/libs/scenario/scenario.js index b9cbccd8468..ae8d1a5315b 100644 --- a/javascriptv3/example_code/libs/scenario/scenario.js +++ b/javascriptv3/example_code/libs/scenario/scenario.js @@ -46,10 +46,10 @@ export class Step { console.log( `[DEBUG ${new Date().toISOString()}] Handling step: ${ this.constructor.name - }<${this.name}>` + }<${this.name}>`, ); console.log( - `[DEBUG ${new Date().toISOString()}] State: ${JSON.stringify(state)}` + `[DEBUG ${new Date().toISOString()}] State: ${JSON.stringify(state)}`, ); } } @@ -162,7 +162,7 @@ export class ScenarioInput extends Step { return true; } throw new Error( - `Error handling ScenarioInput. confirmAll was selected for ${this.name} but no default was provided.` + `Error handling ScenarioInput. confirmAll was selected for ${this.name} but no default was provided.`, ); } @@ -181,7 +181,7 @@ export class ScenarioInput extends Step { break; default: throw new Error( - `Error handling ScenarioInput, ${this.stepOptions?.type} is not supported.` + `Error handling ScenarioInput, ${this.stepOptions?.type} is not supported.`, ); } @@ -203,7 +203,7 @@ export class ScenarioInput extends Step { if (!rawChoices) { throw new Error( - `Error handling ScenarioInput. Could not get choices for ${this.name}.` + `Error handling ScenarioInput. Could not get choices for ${this.name}.`, ); } @@ -235,7 +235,7 @@ export class ScenarioInput extends Step { state[this.name] = this.default; } else if (!result.length) { throw new Error( - `Error handing ScenarioInput. Result of ${this.name} was empty.` + `Error handing ScenarioInput. Result of ${this.name} was empty.`, ); } else { state[this.name] = result; @@ -261,9 +261,9 @@ export class ScenarioInput extends Step { if (!result && this.default) { state[this.name] = this.default; - } else if (!result) { + } else if (result == null) { throw new Error( - `Error handing ScenarioInput. Result of ${this.name} was empty.` + `Error handing ScenarioInput. Result of ${this.name} was empty.`, ); } else { state[this.name] = result; @@ -289,7 +289,7 @@ export class ScenarioInput extends Step { state[this.name] = this.default; } else if (result === undefined) { throw new Error( - `Error handing ScenarioInput. Result of ${this.name} was empty.` + `Error handing ScenarioInput. Result of ${this.name} was empty.`, ); } else { state[this.name] = result; @@ -362,7 +362,7 @@ export class ScenarioAction extends Step { output && (await this.stepOptions.whileConfig.output.handle( state, - stepHandlerOptions + stepHandlerOptions, )); await input.handle(state, stepHandlerOptions); runAction = whileFn(state); diff --git a/javascriptv3/example_code/s3/scenarios/object-locking/index.js b/javascriptv3/example_code/s3/scenarios/object-locking/index.js index 2ca5ce42cbc..6a6c26f2779 100644 --- a/javascriptv3/example_code/s3/scenarios/object-locking/index.js +++ b/javascriptv3/example_code/s3/scenarios/object-locking/index.js @@ -19,6 +19,7 @@ import { confirmUpdateRetention, createBuckets, createBucketsAction, + getBucketPrefix, populateBuckets, populateBucketsAction, setLegalHoldFileEnabledAction, @@ -45,6 +46,7 @@ export const getWorkflowStages = (scenarios, initialState = {}) => { welcome(scenarios), welcomeContinue(scenarios), exitOnFalse(scenarios, "welcomeContinue"), + getBucketPrefix(scenarios), createBuckets(scenarios), confirmCreateBuckets(scenarios), exitOnFalse(scenarios, "confirmCreateBuckets"), diff --git a/javascriptv3/example_code/s3/scenarios/object-locking/setup.steps.js b/javascriptv3/example_code/s3/scenarios/object-locking/setup.steps.js index 5a791ea3330..53d6b016b96 100644 --- a/javascriptv3/example_code/s3/scenarios/object-locking/setup.steps.js +++ b/javascriptv3/example_code/s3/scenarios/object-locking/setup.steps.js @@ -12,8 +12,15 @@ import { PutObjectRetentionCommand, ObjectLockLegalHoldStatus, ObjectLockRetentionMode, + GetBucketVersioningCommand, + BucketAlreadyExists, + BucketAlreadyOwnedByYou, + S3ServiceException, + waitUntilBucketExists, } from "@aws-sdk/client-s3"; +import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js"; + /** * @typedef {import("@aws-doc-sdk-examples/lib/scenario/index.js")} Scenarios */ @@ -22,19 +29,26 @@ import { * @typedef {import("@aws-sdk/client-s3").S3Client} S3Client */ -const bucketPrefix = "js-object-locking"; +/** + * @param {Scenarios} scenarios + */ +const getBucketPrefix = (scenarios) => + new scenarios.ScenarioInput( + "bucketPrefix", + "Provide a prefix that will be used for bucket creation.", + { type: "input", default: "amzn-s3-demo-bucket" }, + ); /** * @param {Scenarios} scenarios - * @param {S3Client} client */ const createBuckets = (scenarios) => new scenarios.ScenarioOutput( "createBuckets", - `The following buckets will be created: - ${bucketPrefix}-no-lock with object lock False. - ${bucketPrefix}-lock-enabled with object lock True. - ${bucketPrefix}-retention-after-creation with object lock False.`, + (state) => `The following buckets will be created: + ${state.bucketPrefix}-no-lock with object lock False. + ${state.bucketPrefix}-lock-enabled with object lock True. + ${state.bucketPrefix}-retention-after-creation with object lock False.`, { preformatted: true }, ); @@ -52,22 +66,42 @@ const confirmCreateBuckets = (scenarios) => */ const createBucketsAction = (scenarios, client) => new scenarios.ScenarioAction("createBucketsAction", async (state) => { - const noLockBucketName = `${bucketPrefix}-no-lock`; - const lockEnabledBucketName = `${bucketPrefix}-lock-enabled`; - const retentionBucketName = `${bucketPrefix}-retention-after-creation`; + const noLockBucketName = `${state.bucketPrefix}-no-lock`; + const lockEnabledBucketName = `${state.bucketPrefix}-lock-enabled`; + const retentionBucketName = `${state.bucketPrefix}-retention-after-creation`; - await client.send(new CreateBucketCommand({ Bucket: noLockBucketName })); - await client.send( - new CreateBucketCommand({ - Bucket: lockEnabledBucketName, - ObjectLockEnabledForBucket: true, - }), - ); - await client.send(new CreateBucketCommand({ Bucket: retentionBucketName })); + try { + await client.send(new CreateBucketCommand({ Bucket: noLockBucketName })); + await waitUntilBucketExists({ client }, { Bucket: noLockBucketName }); + await client.send( + new CreateBucketCommand({ + Bucket: lockEnabledBucketName, + ObjectLockEnabledForBucket: true, + }), + ); + await waitUntilBucketExists( + { client }, + { Bucket: lockEnabledBucketName }, + ); + await client.send( + new CreateBucketCommand({ Bucket: retentionBucketName }), + ); + await waitUntilBucketExists({ client }, { Bucket: retentionBucketName }); - state.noLockBucketName = noLockBucketName; - state.lockEnabledBucketName = lockEnabledBucketName; - state.retentionBucketName = retentionBucketName; + state.noLockBucketName = noLockBucketName; + state.lockEnabledBucketName = lockEnabledBucketName; + state.retentionBucketName = retentionBucketName; + } catch (caught) { + if ( + caught instanceof BucketAlreadyExists || + caught instanceof BucketAlreadyOwnedByYou + ) { + console.error(`${caught.name}: ${caught.message}`); + state.earlyExit = true; + } else { + throw caught; + } + } }); /** @@ -76,13 +110,13 @@ const createBucketsAction = (scenarios, client) => const populateBuckets = (scenarios) => new scenarios.ScenarioOutput( "populateBuckets", - `The following test files will be created: - file0.txt in ${bucketPrefix}-no-lock. - file1.txt in ${bucketPrefix}-no-lock. - file0.txt in ${bucketPrefix}-lock-enabled. - file1.txt in ${bucketPrefix}-lock-enabled. - file0.txt in ${bucketPrefix}-retention-after-creation. - file1.txt in ${bucketPrefix}-retention-after-creation.`, + (state) => `The following test files will be created: + file0.txt in ${state.bucketPrefix}-no-lock. + file1.txt in ${state.bucketPrefix}-no-lock. + file0.txt in ${state.bucketPrefix}-lock-enabled. + file1.txt in ${state.bucketPrefix}-lock-enabled. + file0.txt in ${state.bucketPrefix}-retention-after-creation. + file1.txt in ${state.bucketPrefix}-retention-after-creation.`, { preformatted: true }, ); @@ -102,54 +136,64 @@ const confirmPopulateBuckets = (scenarios) => */ const populateBucketsAction = (scenarios, client) => new scenarios.ScenarioAction("populateBucketsAction", async (state) => { - await client.send( - new PutObjectCommand({ - Bucket: state.noLockBucketName, - Key: "file0.txt", - Body: "Content", - ChecksumAlgorithm: ChecksumAlgorithm.SHA256, - }), - ); - await client.send( - new PutObjectCommand({ - Bucket: state.noLockBucketName, - Key: "file1.txt", - Body: "Content", - ChecksumAlgorithm: ChecksumAlgorithm.SHA256, - }), - ); - await client.send( - new PutObjectCommand({ - Bucket: state.lockEnabledBucketName, - Key: "file0.txt", - Body: "Content", - ChecksumAlgorithm: ChecksumAlgorithm.SHA256, - }), - ); - await client.send( - new PutObjectCommand({ - Bucket: state.lockEnabledBucketName, - Key: "file1.txt", - Body: "Content", - ChecksumAlgorithm: ChecksumAlgorithm.SHA256, - }), - ); - await client.send( - new PutObjectCommand({ - Bucket: state.retentionBucketName, - Key: "file0.txt", - Body: "Content", - ChecksumAlgorithm: ChecksumAlgorithm.SHA256, - }), - ); - await client.send( - new PutObjectCommand({ - Bucket: state.retentionBucketName, - Key: "file1.txt", - Body: "Content", - ChecksumAlgorithm: ChecksumAlgorithm.SHA256, - }), - ); + try { + await client.send( + new PutObjectCommand({ + Bucket: state.noLockBucketName, + Key: "file0.txt", + Body: "Content", + ChecksumAlgorithm: ChecksumAlgorithm.SHA256, + }), + ); + await client.send( + new PutObjectCommand({ + Bucket: state.noLockBucketName, + Key: "file1.txt", + Body: "Content", + ChecksumAlgorithm: ChecksumAlgorithm.SHA256, + }), + ); + await client.send( + new PutObjectCommand({ + Bucket: state.lockEnabledBucketName, + Key: "file0.txt", + Body: "Content", + ChecksumAlgorithm: ChecksumAlgorithm.SHA256, + }), + ); + await client.send( + new PutObjectCommand({ + Bucket: state.lockEnabledBucketName, + Key: "file1.txt", + Body: "Content", + ChecksumAlgorithm: ChecksumAlgorithm.SHA256, + }), + ); + await client.send( + new PutObjectCommand({ + Bucket: state.retentionBucketName, + Key: "file0.txt", + Body: "Content", + ChecksumAlgorithm: ChecksumAlgorithm.SHA256, + }), + ); + await client.send( + new PutObjectCommand({ + Bucket: state.retentionBucketName, + Key: "file1.txt", + Body: "Content", + ChecksumAlgorithm: ChecksumAlgorithm.SHA256, + }), + ); + } catch (caught) { + if (caught instanceof S3ServiceException) { + console.error( + `Error from S3 while uploading object. ${caught.name}: ${caught.message}`, + ); + } else { + throw caught; + } + } }); /** @@ -158,8 +202,10 @@ const populateBucketsAction = (scenarios, client) => const updateRetention = (scenarios) => new scenarios.ScenarioOutput( "updateRetention", - `A bucket can be configured to use object locking with a default retention period. - A default retention period will be configured for ${bucketPrefix}-retention-after-creation.`, + ( + state, + ) => `A bucket can be configured to use object locking with a default retention period. +A default retention period will be configured for ${state.bucketPrefix}-retention-after-creation.`, { preformatted: true }, ); @@ -189,6 +235,17 @@ const updateRetentionAction = (scenarios, client) => }), ); + const getBucketVersioning = new GetBucketVersioningCommand({ + Bucket: state.retentionBucketName, + }); + + await retry({ intervalInMs: 500, maxRetries: 10 }, async () => { + const { Status } = await client.send(getBucketVersioning); + if (Status !== "Enabled") { + throw new Error(`Bucket versioning is not enabled.`); + } + }); + await client.send( new PutObjectLockConfigurationCommand({ Bucket: state.retentionBucketName, @@ -211,8 +268,8 @@ const updateRetentionAction = (scenarios, client) => const updateLockPolicy = (scenarios) => new scenarios.ScenarioOutput( "updateLockPolicy", - `Object lock policies can also be added to existing buckets. - An object lock policy will be added to ${bucketPrefix}-lock-enabled.`, + (state) => `Object lock policies can also be added to existing buckets. +An object lock policy will be added to ${state.bucketPrefix}-lock-enabled.`, { preformatted: true }, ); @@ -403,6 +460,7 @@ const setRetentionPeriodFileRetentionAction = (scenarios, client) => ); export { + getBucketPrefix, createBuckets, confirmCreateBuckets, createBucketsAction,