Skip to content

Commit

Permalink
JavaScript (v3): S3 - Standardize object-locking example.
Browse files Browse the repository at this point in the history
  • Loading branch information
cpyle0819 committed Oct 7, 2024
1 parent 347e86d commit e2b3196
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 89 deletions.
20 changes: 10 additions & 10 deletions javascriptv3/example_code/libs/scenario/scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`,
);
}
}
Expand Down Expand Up @@ -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.`,
);
}

Expand All @@ -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.`,
);
}

Expand All @@ -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}.`,
);
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
confirmUpdateRetention,
createBuckets,
createBucketsAction,
getBucketPrefix,
populateBuckets,
populateBucketsAction,
setLegalHoldFileEnabledAction,
Expand All @@ -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"),
Expand Down
216 changes: 137 additions & 79 deletions javascriptv3/example_code/s3/scenarios/object-locking/setup.steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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 },
);

Expand All @@ -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;
}
}
});

/**
Expand All @@ -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 },
);

Expand All @@ -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;
}
}
});

/**
Expand All @@ -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 },
);

Expand Down Expand Up @@ -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,
Expand All @@ -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 },
);

Expand Down Expand Up @@ -403,6 +460,7 @@ const setRetentionPeriodFileRetentionAction = (scenarios, client) =>
);

export {
getBucketPrefix,
createBuckets,
confirmCreateBuckets,
createBucketsAction,
Expand Down

0 comments on commit e2b3196

Please sign in to comment.