diff --git a/javascriptv3/example_code/libs/utils/util-log.js b/javascriptv3/example_code/libs/utils/util-log.js index 7e89d5c115d..b266abed997 100644 --- a/javascriptv3/example_code/libs/utils/util-log.js +++ b/javascriptv3/example_code/libs/utils/util-log.js @@ -1,11 +1,50 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import readline from "readline"; + import { parseString } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; -const log = (str) => { +export const log = (str) => { const parsed = parseString(str); console.log(parsed); return parsed; }; -export { log }; +export class ProgressBar { + /** + * Create a progress bar that will display in the console. + * @param {Object} config + * @param {string} config.description - The text that will display next to the progress bar. + * @param {number} config.barLength - The length, in characters, of the progress bar. + */ + constructor({ description, barLength }) { + this._currentProgress = 0; + this._barLength = barLength; + this._description = description; + } + + /** + * @param {{ current: number, total: number }} event + */ + update({ current, total }) { + this._currentProgress = current / total; + this._render(); + } + + _render() { + readline.cursorTo(process.stdout, 0); + readline.clearLine(process.stdout, 0); + + const filledLength = Math.round(this._barLength * this._currentProgress); + const bar = + "█".repeat(filledLength) + " ".repeat(this._barLength - filledLength); + + process.stdout.write( + `${this._description} [${bar}] ${this._currentProgress * 100}%`, + ); + + if (this._currentProgress === 1) { + process.stdout.write("\n"); + } + } +} diff --git a/javascriptv3/example_code/libs/utils/util-node.js b/javascriptv3/example_code/libs/utils/util-node.js index 1d7dac9cff3..d7b97d4d520 100644 --- a/javascriptv3/example_code/libs/utils/util-node.js +++ b/javascriptv3/example_code/libs/utils/util-node.js @@ -35,9 +35,9 @@ export const validateArgs = (config, results) => { if (optionRequired && !optionPresent) { errors = errors ?? []; - errors.push(`Missing required argument "${option}".`); + errors.push(`Missing required argument "--${option}".`); } - - return { errors }; } + + return { errors }; }; diff --git a/javascriptv3/example_code/s3/README.md b/javascriptv3/example_code/s3/README.md index dcf64a44b10..f352d4c36da 100644 --- a/javascriptv3/example_code/s3/README.md +++ b/javascriptv3/example_code/s3/README.md @@ -47,7 +47,7 @@ Code excerpts that show you how to call individual service functions. - [CopyObject](actions/copy-object.js#L4) - [CreateBucket](actions/create-bucket.js#L4) -- [DeleteBucket](actions/delete-bucket.js#L6) +- [DeleteBucket](actions/delete-bucket.js#L4) - [DeleteBucketPolicy](actions/delete-bucket-policy.js#L4) - [DeleteBucketWebsite](actions/delete-bucket-website.js#L4) - [DeleteObject](actions/delete-object.js#L4) diff --git a/javascriptv3/example_code/s3/actions/copy-object.js b/javascriptv3/example_code/s3/actions/copy-object.js index 8d85703c075..d2e06145ee1 100644 --- a/javascriptv3/example_code/s3/actions/copy-object.js +++ b/javascriptv3/example_code/s3/actions/copy-object.js @@ -54,28 +54,41 @@ export const main = async ({ // snippet-end:[s3.JavaScript.buckets.copyObjectV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { - sourceBucket: { + sourceBucketName: { type: "string", - default: "source-bucket", + required: true, }, sourceKey: { type: "string", - default: "todo.txt", + required: true, }, destinationBucket: { type: "string", - default: "destination-bucket", + required: true, }, destinationKey: { type: "string", - default: "todo.txt", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/create-bucket.js b/javascriptv3/example_code/s3/actions/create-bucket.js index df4a87b431c..b5b707891c8 100644 --- a/javascriptv3/example_code/s3/actions/create-bucket.js +++ b/javascriptv3/example_code/s3/actions/create-bucket.js @@ -49,16 +49,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.buckets.createBucketV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "bucket-name", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/delete-bucket-policy.js b/javascriptv3/example_code/s3/actions/delete-bucket-policy.js index f945ec53071..48765feb462 100644 --- a/javascriptv3/example_code/s3/actions/delete-bucket-policy.js +++ b/javascriptv3/example_code/s3/actions/delete-bucket-policy.js @@ -42,16 +42,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.policy.deleteBucketPolicyV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/delete-bucket-website.js b/javascriptv3/example_code/s3/actions/delete-bucket-website.js index 5cb5774048a..423632011a7 100644 --- a/javascriptv3/example_code/s3/actions/delete-bucket-website.js +++ b/javascriptv3/example_code/s3/actions/delete-bucket-website.js @@ -46,16 +46,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.website.deleteBucketWebsiteV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/delete-bucket.js b/javascriptv3/example_code/s3/actions/delete-bucket.js index fac997038a5..11cbcfca996 100644 --- a/javascriptv3/example_code/s3/actions/delete-bucket.js +++ b/javascriptv3/example_code/s3/actions/delete-bucket.js @@ -1,29 +1,69 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { fileURLToPath } from "url"; - // snippet-start:[s3.JavaScript.buckets.deleteBucketV3] -import { DeleteBucketCommand, S3Client } from "@aws-sdk/client-s3"; - -const client = new S3Client({}); +import { + DeleteBucketCommand, + S3Client, + S3ServiceException, +} from "@aws-sdk/client-s3"; -// Delete a bucket. -export const main = async () => { +/** + * Delete an Amazon S3 bucket. + * @param {{ bucketName: string }} + */ +export const main = async ({ bucketName }) => { + const client = new S3Client({}); const command = new DeleteBucketCommand({ - Bucket: "test-bucket", + Bucket: bucketName, }); try { - const response = await client.send(command); - console.log(response); - } catch (err) { - console.error(err); + await client.send(command); + console.log(`Bucket was deleted.`); + } catch (caught) { + if ( + caught instanceof S3ServiceException && + caught.name === "NoSuchBucket" + ) { + console.error( + `Error from S3 while deleting bucket. The bucket doesn't exist.`, + ); + } else if (caught instanceof S3ServiceException) { + console.error( + `Error from S3 while deleting the bucket. ${caught.name}: ${caught.message}`, + ); + } else { + throw caught; + } } }; // snippet-end:[s3.JavaScript.buckets.deleteBucketV3] -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/delete-object.js b/javascriptv3/example_code/s3/actions/delete-object.js index 750014bee01..3b0c3b6db66 100644 --- a/javascriptv3/example_code/s3/actions/delete-object.js +++ b/javascriptv3/example_code/s3/actions/delete-object.js @@ -52,20 +52,33 @@ export const main = async ({ bucketName, key }) => { // snippet-end:[s3.JavaScript.buckets.deleteobjectV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, key: { type: "string", - default: "todo.txt", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/delete-objects.js b/javascriptv3/example_code/s3/actions/delete-objects.js index c64c2c0562f..c0fe9633013 100644 --- a/javascriptv3/example_code/s3/actions/delete-objects.js +++ b/javascriptv3/example_code/s3/actions/delete-objects.js @@ -60,19 +60,29 @@ export const main = async ({ bucketName, keys }) => { Example usage: node delete-objects.js --bucketName amzn-s3-demo-bucket obj1.txt obj2.txt */ -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values, positionals } = parseArgs({ - options, - allowPositionals: true, - }); - main({ ...values, keys: positionals }); + const results = parseArgs({ options, allowPositionals: true }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main({ ...results.values, keys: results.positionals }); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-bucket-acl.js b/javascriptv3/example_code/s3/actions/get-bucket-acl.js index c080b4f8968..524437c8be6 100644 --- a/javascriptv3/example_code/s3/actions/get-bucket-acl.js +++ b/javascriptv3/example_code/s3/actions/get-bucket-acl.js @@ -44,15 +44,28 @@ export const main = async ({ bucketName }) => { // Call function if run directly import { parseArgs } from "util"; -import { fileURLToPath } from "url"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-bucket-cors.js b/javascriptv3/example_code/s3/actions/get-bucket-cors.js index 3e9f7fe3bdd..089c17f28db 100644 --- a/javascriptv3/example_code/s3/actions/get-bucket-cors.js +++ b/javascriptv3/example_code/s3/actions/get-bucket-cors.js @@ -53,16 +53,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.cors.getBucketCorsV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-bucket-policy.js b/javascriptv3/example_code/s3/actions/get-bucket-policy.js index 1972c6c0c0e..79d15aec73c 100644 --- a/javascriptv3/example_code/s3/actions/get-bucket-policy.js +++ b/javascriptv3/example_code/s3/actions/get-bucket-policy.js @@ -42,16 +42,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.policy.getBucketPolicyV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-bucket-website.js b/javascriptv3/example_code/s3/actions/get-bucket-website.js index fcadc64cd5c..b37706f41af 100644 --- a/javascriptv3/example_code/s3/actions/get-bucket-website.js +++ b/javascriptv3/example_code/s3/actions/get-bucket-website.js @@ -44,16 +44,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.website.getBucketWebsiteV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-object-legal-hold.js b/javascriptv3/example_code/s3/actions/get-object-legal-hold.js index 6d68cad5fe1..d501c2088a7 100644 --- a/javascriptv3/example_code/s3/actions/get-object-legal-hold.js +++ b/javascriptv3/example_code/s3/actions/get-object-legal-hold.js @@ -44,20 +44,33 @@ export const main = async ({ bucketName, key }) => { }; // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, key: { type: "string", - default: "foo.txt", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-object-lock-configuration.js b/javascriptv3/example_code/s3/actions/get-object-lock-configuration.js index 21df9a04df7..c1d21d6fe64 100644 --- a/javascriptv3/example_code/s3/actions/get-object-lock-configuration.js +++ b/javascriptv3/example_code/s3/actions/get-object-lock-configuration.js @@ -43,16 +43,29 @@ export const main = async ({ bucketName }) => { }; // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-object-retention.js b/javascriptv3/example_code/s3/actions/get-object-retention.js index d3016b3d24b..87a1dde893a 100644 --- a/javascriptv3/example_code/s3/actions/get-object-retention.js +++ b/javascriptv3/example_code/s3/actions/get-object-retention.js @@ -43,20 +43,33 @@ export const main = async ({ bucketName, key }) => { }; // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, key: { type: "string", - default: "foo.txt", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/get-object.js b/javascriptv3/example_code/s3/actions/get-object.js index 65505133d2e..16560424ba4 100644 --- a/javascriptv3/example_code/s3/actions/get-object.js +++ b/javascriptv3/example_code/s3/actions/get-object.js @@ -43,20 +43,33 @@ export const main = async ({ bucketName, key }) => { // snippet-end:[s3.JavaScript.buckets.getobjectV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, key: { type: "string", - default: "foo.txt", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/list-objects.js b/javascriptv3/example_code/s3/actions/list-objects.js index ca2503a015b..a275e74a543 100644 --- a/javascriptv3/example_code/s3/actions/list-objects.js +++ b/javascriptv3/example_code/s3/actions/list-objects.js @@ -51,20 +51,33 @@ export const main = async ({ bucketName, pageSize }) => { // snippet-end:[s3.JavaScript.buckets.listObjectsV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, pageSize: { type: "string", default: "1", }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-bucket-acl.js b/javascriptv3/example_code/s3/actions/put-bucket-acl.js index 0cab6373111..85e8d73b6d1 100644 --- a/javascriptv3/example_code/s3/actions/put-bucket-acl.js +++ b/javascriptv3/example_code/s3/actions/put-bucket-acl.js @@ -13,8 +13,8 @@ import { * * Most Amazon S3 use cases don't require the use of access control lists (ACLs). * We recommend that you disable ACLs, except in unusual circumstances where - * you need to control access for each object individually. - * Consider a policy instead. For more information see https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html. + * you need to control access for each object individually. Consider a policy instead. + * For more information see https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html. * @param {{ bucketName: string, granteeCanonicalUserId: string, ownerCanonicalUserId }} */ export const main = async ({ @@ -69,22 +69,37 @@ export const main = async ({ // snippet-end:[s3.JavaScript.perms.putBucketAclV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, granteeCanonicalUserId: { type: "string", + required: true, }, ownerCanonicalUserId: { type: "string", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-bucket-cors.js b/javascriptv3/example_code/s3/actions/put-bucket-cors.js index 2b064390564..1d3b88527d3 100644 --- a/javascriptv3/example_code/s3/actions/put-bucket-cors.js +++ b/javascriptv3/example_code/s3/actions/put-bucket-cors.js @@ -61,17 +61,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.v3.cors.putBucketCors] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { - const { values } = parseArgs({ - options: { - bucketName: { - type: "string", - default: "amzn-s3-demo-bucket", - }, +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, }, - }); - main(values); + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-bucket-policy.js b/javascriptv3/example_code/s3/actions/put-bucket-policy.js index 68a9b96f0bc..76426154ab7 100644 --- a/javascriptv3/example_code/s3/actions/put-bucket-policy.js +++ b/javascriptv3/example_code/s3/actions/put-bucket-policy.js @@ -60,19 +60,33 @@ export const main = async ({ bucketName, iamRoleArn }) => { // snippet-end:[s3.JavaScript.policy.putBucketPolicyV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, iamRoleArn: { type: "string", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-bucket-website.js b/javascriptv3/example_code/s3/actions/put-bucket-website.js index dff7be3fcac..56feef83b1f 100644 --- a/javascriptv3/example_code/s3/actions/put-bucket-website.js +++ b/javascriptv3/example_code/s3/actions/put-bucket-website.js @@ -58,16 +58,29 @@ export const main = async ({ bucketName }) => { // snippet-end:[s3.JavaScript.website.putBucketWebsiteV3] // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-default-object-lock-configuration.js b/javascriptv3/example_code/s3/actions/put-default-object-lock-configuration.js index 43ac3be3dd5..a8c6daf482c 100644 --- a/javascriptv3/example_code/s3/actions/put-default-object-lock-configuration.js +++ b/javascriptv3/example_code/s3/actions/put-default-object-lock-configuration.js @@ -1,45 +1,93 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { fileURLToPath } from "url"; + import { PutObjectLockConfigurationCommand, S3Client, + S3ServiceException, } from "@aws-sdk/client-s3"; /** - * @param {S3Client} client - * @param {string} bucketName + * Change the default retention settings for an object in an Amazon S3 bucket. + * @param {{ bucketName: string, retentionDays: string }} */ -export const main = async (client, bucketName) => { - const command = new PutObjectLockConfigurationCommand({ - Bucket: bucketName, - // The Object Lock configuration that you want to apply to the specified bucket. - ObjectLockConfiguration: { - ObjectLockEnabled: "Enabled", - Rule: { - DefaultRetention: { - Mode: "GOVERNANCE", - Years: 3, - }, - }, - }, - // Optionally, you can provide additional parameters - // ExpectedBucketOwner: "ACCOUNT_ID", - // RequestPayer: "requester", - // Token: "OPTIONAL_TOKEN", - }); +export const main = async ({ bucketName, retentionDays }) => { + const client = new S3Client({}); try { - const response = await client.send(command); + await client.send( + new PutObjectLockConfigurationCommand({ + Bucket: bucketName, + // The Object Lock configuration that you want to apply to the specified bucket. + ObjectLockConfiguration: { + ObjectLockEnabled: "Enabled", + Rule: { + // The default Object Lock retention mode and period that you want to apply + // to new objects placed in the specified bucket. Bucket settings require + // both a mode and a period. The period can be either Days or Years but + // you must select one. + DefaultRetention: { + // In governance mode, users can't overwrite or delete an object version + // or alter its lock settings unless they have special permissions. With + // governance mode, you protect objects against being deleted by most users, + // but you can still grant some users permission to alter the retention settings + // or delete the objects if necessary. + Mode: "GOVERNANCE", + Days: parseInt(retentionDays), + }, + }, + }, + }), + ); console.log( - `Default Object Lock Configuration updated: ${response.$metadata.httpStatusCode}`, + `Set default retention mode to "GOVERNANCE" with a retention period of ${retentionDays} day(s).`, ); - } catch (err) { - console.error(err); + } catch (caught) { + if ( + caught instanceof S3ServiceException && + caught.name === "NoSuchBucket" + ) { + console.error( + `Error from S3 while setting the default object retention for a bucket. The bucket doesn't exist.`, + ); + } else if (caught instanceof S3ServiceException) { + console.error( + `Error from S3 while setting the default object retention for a bucket. ${caught.name}: ${caught.message}`, + ); + } else { + throw caught; + } } }; -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(new S3Client(), "BUCKET_NAME"); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + retentionDays: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-object-legal-hold.js b/javascriptv3/example_code/s3/actions/put-object-legal-hold.js index 4bbcc243b79..b8fe8966dad 100644 --- a/javascriptv3/example_code/s3/actions/put-object-legal-hold.js +++ b/javascriptv3/example_code/s3/actions/put-object-legal-hold.js @@ -52,24 +52,37 @@ export const main = async ({ bucketName, objectKey, legalHoldStatus }) => { }; // Call function if run directly -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", + required: true, }, objectKey: { type: "string", - default: "file.txt", + required: true, }, legalHoldStatus: { type: "string", default: "ON", }, }; - const { values } = parseArgs({ options }); - main(values); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-object-lock-configuration.js b/javascriptv3/example_code/s3/actions/put-object-lock-configuration.js index 57d0331b532..a22bc225658 100644 --- a/javascriptv3/example_code/s3/actions/put-object-lock-configuration.js +++ b/javascriptv3/example_code/s3/actions/put-object-lock-configuration.js @@ -45,18 +45,28 @@ export const main = async ({ bucketName }) => { // Call function if run directly import { parseArgs } from "util"; -import { isMain } from "@aws-doc-sdk-examples/lib/utils/util-node.js"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; const loadArgs = () => { const options = { bucketName: { type: "string", + required: true, }, }; - return parseArgs({ options }); + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; }; if (isMain(import.meta.url)) { - const { values } = loadArgs(); - main(values); + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-object-retention.js b/javascriptv3/example_code/s3/actions/put-object-retention.js index 48f85c87474..bada468d733 100644 --- a/javascriptv3/example_code/s3/actions/put-object-retention.js +++ b/javascriptv3/example_code/s3/actions/put-object-retention.js @@ -1,40 +1,81 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { fileURLToPath } from "url"; -import { PutObjectRetentionCommand, S3Client } from "@aws-sdk/client-s3"; +import { + PutObjectRetentionCommand, + S3Client, + S3ServiceException, +} from "@aws-sdk/client-s3"; /** - * @param {S3Client} client - * @param {string} bucketName - * @param {string} objectKey + * Place a 24-hour retention period on an object in an Amazon S3 bucket. + * @param {{ bucketName: string, key: string }} */ -export const main = async (client, bucketName, objectKey) => { +export const main = async ({ bucketName, key }) => { + const client = new S3Client({}); const command = new PutObjectRetentionCommand({ Bucket: bucketName, - Key: objectKey, + Key: key, BypassGovernanceRetention: false, - // ChecksumAlgorithm: "ALGORITHM", - // ContentMD5: "MD5_HASH", - // ExpectedBucketOwner: "ACCOUNT_ID", - // RequestPayer: "requester", Retention: { - Mode: "GOVERNANCE", // or "COMPLIANCE" + // In governance mode, users can't overwrite or delete an object version + // or alter its lock settings unless they have special permissions. With + // governance mode, you protect objects against being deleted by most users, + // but you can still grant some users permission to alter the retention settings + // or delete the objects if necessary. + Mode: "GOVERNANCE", RetainUntilDate: new Date(new Date().getTime() + 24 * 60 * 60 * 1000), }, - // VersionId: "OBJECT_VERSION_ID", }); try { - const response = await client.send(command); - console.log( - `Object Retention settings updated: ${response.$metadata.httpStatusCode}`, - ); - } catch (err) { - console.error(err); + await client.send(command); + console.log(`Object Retention settings updated.`); + } catch (caught) { + if ( + caught instanceof S3ServiceException && + caught.name === "NoSuchBucket" + ) { + console.error( + `Error from S3 while modifying the governance mode and retention period on an object. The bucket doesn't exist.`, + ); + } else if (caught instanceof S3ServiceException) { + console.error( + `Error from S3 while modifying the governance mode and retention period on an object. ${caught.name}: ${caught.message}`, + ); + } else { + throw caught; + } } }; -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(new S3Client(), "BUCKET_NAME", "OBJECT_KEY"); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + key: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/actions/put-object.js b/javascriptv3/example_code/s3/actions/put-object.js index bcc4d238a71..caa829d0a40 100644 --- a/javascriptv3/example_code/s3/actions/put-object.js +++ b/javascriptv3/example_code/s3/actions/put-object.js @@ -53,23 +53,31 @@ or the multipart upload API (5TB max).`, node put-object.js --bucketName amzn-s3-demo-bucket --key movies.json \ ../../../../resources/sample_files/movies.json */ -import { fileURLToPath } from "url"; import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; -if (process.argv[1] === fileURLToPath(import.meta.url)) { +const loadArgs = () => { const options = { bucketName: { type: "string", - default: "amzn-s3-demo-bucket", }, key: { type: "string", - default: "demo-key.txt", }, }; - const { values, positionals } = parseArgs({ - options, - allowPositionals: true, - }); - main({ ...values, filePath: positionals[0] }); + const results = parseArgs({ options, allowPositionals: true }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main({ ...results.values, filePath: results.positionals[0] }); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/libs/s3Utils.js b/javascriptv3/example_code/s3/libs/s3Utils.js index e686a8d55b1..47fa051fc17 100644 --- a/javascriptv3/example_code/s3/libs/s3Utils.js +++ b/javascriptv3/example_code/s3/libs/s3Utils.js @@ -12,23 +12,27 @@ import { GetObjectRetentionCommand, DeleteObjectCommand, waitUntilObjectNotExists, + waitUntilBucketExists, + waitUntilBucketNotExists, } from "@aws-sdk/client-s3"; import { client as s3Client } from "../client.js"; -export function createBucket(bucketName) { +export async function createBucket(bucketName) { const createBucketCommand = new CreateBucketCommand({ Bucket: bucketName, }); - return s3Client.send(createBucketCommand); + await s3Client.send(createBucketCommand); + await waitUntilBucketExists({ client: s3Client }, { Bucket: bucketName }); } -export function deleteBucket(bucketName) { +export async function deleteBucket(bucketName) { const deleteBucketCommand = new DeleteBucketCommand({ Bucket: bucketName, }); - return s3Client.send(deleteBucketCommand); + await s3Client.send(deleteBucketCommand); + await waitUntilBucketNotExists({ client: s3Client }, { Bucket: bucketName }); } export async function emptyBucket(bucketName) { diff --git a/javascriptv3/example_code/s3/package.json b/javascriptv3/example_code/s3/package.json index 39ca439cedc..98d8ca23f58 100644 --- a/javascriptv3/example_code/s3/package.json +++ b/javascriptv3/example_code/s3/package.json @@ -10,18 +10,20 @@ "license": "Apache-2.0", "type": "module", "dependencies": { - "@aws-crypto/sha256-browser": "^4.0.0", - "@aws-doc-sdk-examples/lib": "^1.0.0", - "@aws-sdk/client-s3": "^3.272.0", - "@aws-sdk/credential-providers": "^3.276.0", - "@aws-sdk/s3-request-presigner": "^3.276.0", - "@aws-sdk/util-format-url": "^3.272.0", - "@smithy/hash-node": "^2.0.2", - "@smithy/protocol-http": "^2.0.2", - "@smithy/url-parser": "^2.0.2", + "@aws-crypto/sha256-browser": "^5.2.0", + "@aws-doc-sdk-examples/lib": "^1.0.1", + "@aws-sdk/client-s3": "^3.664.0", + "@aws-sdk/credential-providers": "^3.664.0", + "@aws-sdk/lib-storage": "^3.664.0", + "@aws-sdk/s3-request-presigner": "^3.664.0", + "@aws-sdk/util-format-url": "^3.664.0", + "@smithy/hash-node": "^3.0.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/url-parser": "^3.0.7", + "fast-xml-parser": "^4.5.0", "libs": "*" }, "devDependencies": { - "vitest": "^1.6.0" + "vitest": "^2.1.2" } } diff --git a/javascriptv3/example_code/s3/scenarios/multipart-download.js b/javascriptv3/example_code/s3/scenarios/multipart-download.js index 9c6109f6d6b..d559c8b98dd 100644 --- a/javascriptv3/example_code/s3/scenarios/multipart-download.js +++ b/javascriptv3/example_code/s3/scenarios/multipart-download.js @@ -4,8 +4,8 @@ import { fileURLToPath } from "url"; // snippet-start:[javascript.v3.s3.scenarios.multipartdownload] -import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; -import { createWriteStream } from "fs"; +import { GetObjectCommand, NoSuchKey, S3Client } from "@aws-sdk/client-s3"; +import { createWriteStream, rmSync } from "fs"; const s3Client = new S3Client({}); const oneMB = 1024 * 1024; @@ -35,9 +35,6 @@ export const getRangeAndLength = (contentRange) => { export const isComplete = ({ end, length }) => end === length - 1; -// When downloading a large file, you might want to break it down into -// smaller pieces. Amazon S3 accepts a Range header to specify the start -// and end of the byte range to be downloaded. const downloadInChunks = async ({ bucket, key }) => { const writeStream = createWriteStream( fileURLToPath(new URL(`./${key}`, import.meta.url)), @@ -49,28 +46,70 @@ const downloadInChunks = async ({ bucket, key }) => { const { end } = rangeAndLength; const nextRange = { start: end + 1, end: end + oneMB }; - console.log(`Downloading bytes ${nextRange.start} to ${nextRange.end}`); - const { ContentRange, Body } = await getObjectRange({ bucket, key, ...nextRange, }); + console.log(`Downloaded bytes ${nextRange.start} to ${nextRange.end}`); writeStream.write(await Body.transformToByteArray()); rangeAndLength = getRangeAndLength(ContentRange); } }; -export const main = async () => { - await downloadInChunks({ - bucket: "my-cool-bucket", - key: "my-cool-object.txt", - }); +/** + * Download a large object from and Amazon S3 bucket. + * + * When downloading a large file, you might want to break it down into + * smaller pieces. Amazon S3 accepts a Range header to specify the start + * and end of the byte range to be downloaded. + * + * @param {{ bucketName: string, key: string }} + */ +export const main = async ({ bucketName, key }) => { + try { + await downloadInChunks({ + bucket: bucketName, + key: key, + }); + } catch (caught) { + if (caught instanceof NoSuchKey) { + console.error(`Failed to download object. No such key "${key}".`); + rmSync(key); + } + } }; // snippet-end:[javascript.v3.s3.scenarios.multipartdownload] -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + key: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/scenarios/multipart-upload.js b/javascriptv3/example_code/s3/scenarios/multipart-upload.js index 133d98609a9..2dbbc2cf7b9 100644 --- a/javascriptv3/example_code/s3/scenarios/multipart-upload.js +++ b/javascriptv3/example_code/s3/scenarios/multipart-upload.js @@ -1,16 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { fileURLToPath } from "url"; - // snippet-start:[javascript.v3.s3.scenarios.multipartupload] -import { - CreateMultipartUploadCommand, - UploadPartCommand, - CompleteMultipartUploadCommand, - AbortMultipartUploadCommand, - S3Client, -} from "@aws-sdk/client-s3"; +import { S3Client } from "@aws-sdk/client-s3"; +import { Upload } from "@aws-sdk/lib-storage"; + +import { ProgressBar } from "@aws-doc-sdk-examples/lib/utils/util-log.js"; const twentyFiveMB = 25 * 1024 * 1024; @@ -18,86 +13,73 @@ export const createString = (size = twentyFiveMB) => { return "x".repeat(size); }; -export const main = async () => { - const s3Client = new S3Client({}); - const bucketName = "test-bucket"; - const key = "multipart.txt"; +/** + * Create a 25MB file and upload it in parts to the specified + * Amazon S3 bucket. + * @param {{ bucketName: string, key: string }} + */ +export const main = async ({ bucketName, key }) => { const str = createString(); const buffer = Buffer.from(str, "utf8"); - - let uploadId; + const progressBar = new ProgressBar({ + description: `Uploading "${key}" to "${bucketName}"`, + barLength: 30, + }); try { - const multipartUpload = await s3Client.send( - new CreateMultipartUploadCommand({ - Bucket: bucketName, - Key: key, - }), - ); - - uploadId = multipartUpload.UploadId; - - const uploadPromises = []; - // Multipart uploads require a minimum size of 5 MB per part. - const partSize = Math.ceil(buffer.length / 5); - - // Upload each part. - for (let i = 0; i < 5; i++) { - const start = i * partSize; - const end = start + partSize; - uploadPromises.push( - s3Client - .send( - new UploadPartCommand({ - Bucket: bucketName, - Key: key, - UploadId: uploadId, - Body: buffer.subarray(start, end), - PartNumber: i + 1, - }), - ) - .then((d) => { - console.log("Part", i + 1, "uploaded"); - return d; - }), - ); - } - - const uploadResults = await Promise.all(uploadPromises); - - return await s3Client.send( - new CompleteMultipartUploadCommand({ - Bucket: bucketName, - Key: key, - UploadId: uploadId, - MultipartUpload: { - Parts: uploadResults.map(({ ETag }, i) => ({ - ETag, - PartNumber: i + 1, - })), - }, - }), - ); - - // Verify the output by downloading the file from the Amazon Simple Storage Service (Amazon S3) console. - // Because the output is a 25 MB string, text editors might struggle to open the file. - } catch (err) { - console.error(err); - - if (uploadId) { - const abortCommand = new AbortMultipartUploadCommand({ + const upload = new Upload({ + client: new S3Client({}), + params: { Bucket: bucketName, Key: key, - UploadId: uploadId, - }); - - await s3Client.send(abortCommand); + Body: buffer, + }, + }); + + upload.on("httpUploadProgress", ({ loaded, total }) => { + progressBar.update({ current: loaded, total }); + }); + + await upload.done(); + } catch (caught) { + if (caught instanceof Error && caught.name === "AbortError") { + console.error(`Multipart upload was aborted. ${caught.message}`); + } else { + throw caught; } } }; + // snippet-end:[javascript.v3.s3.scenarios.multipartupload] -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + key: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/scenarios/presigned-url-download.js b/javascriptv3/example_code/s3/scenarios/presigned-url-download.js index fa49ea46807..bf796e49807 100644 --- a/javascriptv3/example_code/s3/scenarios/presigned-url-download.js +++ b/javascriptv3/example_code/s3/scenarios/presigned-url-download.js @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { fileURLToPath } from "url"; - // snippet-start:[s3.JavaScript.buckets.getpresignedurlv3] import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { fromIni } from "@aws-sdk/credential-providers"; @@ -33,22 +31,25 @@ const createPresignedUrlWithClient = ({ region, bucket, key }) => { return getSignedUrl(client, command, { expiresIn: 3600 }); }; -export const main = async () => { - const REGION = "us-east-1"; - const BUCKET = "example_bucket"; - const KEY = "example_file.jpg"; - +/** + * Create two presigned urls for downloading an object from an S3 bucket. + * The first presigned URL is created with credentials from the shared INI file + * in the current environment. The second presigned URL is created using an + * existing S3Client instance that has already been provided with credentials. + * @param {{ bucketName: string, key: string, region: string }} + */ +export const main = async ({ bucketName, key, region }) => { try { const noClientUrl = await createPresignedUrlWithoutClient({ - region: REGION, - bucket: BUCKET, - key: KEY, + bucket: bucketName, + region, + key, }); const clientUrl = await createPresignedUrlWithClient({ - region: REGION, - bucket: BUCKET, - key: KEY, + bucket: bucketName, + region, + key, }); console.log("Presigned URL without client"); @@ -57,13 +58,50 @@ export const main = async () => { console.log("Presigned URL with client"); console.log(clientUrl); - } catch (err) { - console.error(err); + } catch (caught) { + if (caught instanceof Error && caught.name === "CredentialsProviderError") { + console.error( + `There was an error getting your credentials. Are your local credentials configured?\n${caught.name}: ${caught.message}`, + ); + } else { + throw caught; + } } }; // snippet-end:[s3.JavaScript.buckets.getpresignedurlv3] -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + key: { + type: "string", + required: true, + }, + region: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/scenarios/presigned-url-upload.js b/javascriptv3/example_code/s3/scenarios/presigned-url-upload.js index f9a5048022d..607ac7f2b6c 100644 --- a/javascriptv3/example_code/s3/scenarios/presigned-url-upload.js +++ b/javascriptv3/example_code/s3/scenarios/presigned-url-upload.js @@ -1,10 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { fileURLToPath } from "url"; - // snippet-start:[s3.JavaScript.buckets.presignedurlv3] import https from "https"; + +import { XMLParser } from "fast-xml-parser"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { fromIni } from "@aws-sdk/credential-providers"; import { HttpRequest } from "@smithy/protocol-http"; @@ -36,7 +36,13 @@ const createPresignedUrlWithClient = ({ region, bucket, key }) => { return getSignedUrl(client, command, { expiresIn: 3600 }); }; -function put(url, data) { +/** + * Make a PUT request to the provided URL. + * + * @param {string} url + * @param {string} data + */ +const put = (url, data) => { return new Promise((resolve, reject) => { const req = https.request( url, @@ -47,7 +53,12 @@ function put(url, data) { responseBody += chunk; }); res.on("end", () => { - resolve(responseBody); + const parser = new XMLParser(); + if (res.statusCode >= 200 && res.statusCode <= 299) { + resolve(parser.parse(responseBody, true)); + } else { + reject(parser.parse(responseBody, true)); + } }); }, ); @@ -57,27 +68,27 @@ function put(url, data) { req.write(data); req.end(); }); -} - -export const main = async () => { - const REGION = "us-east-1"; - const BUCKET = "example_bucket"; - const KEY = "example_file.txt"; +}; - // There are two ways to generate a presigned URL. - // 1. Use createPresignedUrl without the S3 client. - // 2. Use getSignedUrl in conjunction with the S3 client and GetObjectCommand. +/** + * Create two presigned urls for uploading an object to an S3 bucket. + * The first presigned URL is created with credentials from the shared INI file + * in the current environment. The second presigned URL is created using an + * existing S3Client instance that has already been provided with credentials. + * @param {{ bucketName: string, key: string, region: string }} + */ +export const main = async ({ bucketName, key, region }) => { try { const noClientUrl = await createPresignedUrlWithoutClient({ - region: REGION, - bucket: BUCKET, - key: KEY, + bucket: bucketName, + key, + region, }); const clientUrl = await createPresignedUrlWithClient({ - region: REGION, - bucket: BUCKET, - key: KEY, + bucket: bucketName, + region, + key, }); // After you get the presigned URL, you can provide your own file @@ -89,13 +100,50 @@ export const main = async () => { await put(clientUrl, "Hello World"); console.log("\nDone. Check your S3 console."); - } catch (err) { - console.error(err); + } catch (caught) { + if (caught instanceof Error && caught.name === "CredentialsProviderError") { + console.error( + `There was an error getting your credentials. Are your local credentials configured?\n${caught.name}: ${caught.message}`, + ); + } else { + throw caught; + } } }; // snippet-end:[s3.JavaScript.buckets.presignedurlv3] -// Invoke main function if this file was run directly. -if (process.argv[1] === fileURLToPath(import.meta.url)) { - main(); +// Call function if run directly +import { parseArgs } from "util"; +import { + isMain, + validateArgs, +} from "@aws-doc-sdk-examples/lib/utils/util-node.js"; + +const loadArgs = () => { + const options = { + bucketName: { + type: "string", + required: true, + }, + key: { + type: "string", + required: true, + }, + region: { + type: "string", + required: true, + }, + }; + const results = parseArgs({ options }); + const { errors } = validateArgs({ options }, results); + return { errors, results }; +}; + +if (isMain(import.meta.url)) { + const { errors, results } = loadArgs(); + if (!errors) { + main(results.values); + } else { + console.error(errors.join("\n")); + } } diff --git a/javascriptv3/example_code/s3/tests/copy-object.unit.test.js b/javascriptv3/example_code/s3/tests/copy-object.unit.test.js index 48be8bcd4e8..5b90a4810da 100644 --- a/javascriptv3/example_code/s3/tests/copy-object.unit.test.js +++ b/javascriptv3/example_code/s3/tests/copy-object.unit.test.js @@ -19,9 +19,9 @@ vi.doMock("@aws-sdk/client-s3", async () => { const { main } = await import("../actions/copy-object.js"); describe("copy-object", () => { - const sourceBucket = "my-old-bucket"; + const sourceBucket = "amzn-s3-demo-bucket"; const sourceKey = "todo.txt"; - const destinationBucket = "my-new-bucket"; + const destinationBucket = "amzn-s3-demo-bucket1"; const destinationKey = "updated-todo.txt"; it("should log the response from the service", async () => { diff --git a/javascriptv3/example_code/s3/tests/create-bucket.unit.test.js b/javascriptv3/example_code/s3/tests/create-bucket.unit.test.js index 3f980330cfb..54d56f02dcd 100644 --- a/javascriptv3/example_code/s3/tests/create-bucket.unit.test.js +++ b/javascriptv3/example_code/s3/tests/create-bucket.unit.test.js @@ -27,7 +27,7 @@ describe("create-bucket", () => { const spy = vi.spyOn(console, "log"); - await main({ bucketName: "bucket-name" }); + await main({ bucketName: "amzn-s3-demo-bucket" }); expect(spy).toHaveBeenCalledWith("Bucket created with location foo"); }); @@ -38,10 +38,10 @@ describe("create-bucket", () => { const spy = vi.spyOn(console, "error"); - await main({ bucketName: "bucket-name" }); + await main({ bucketName: "amzn-s3-demo-bucket" }); expect(spy).toHaveBeenCalledWith( - `The bucket "bucket-name" already exists in another AWS account. Bucket names must be globally unique.`, + `The bucket "amzn-s3-demo-bucket" already exists in another AWS account. Bucket names must be globally unique.`, ); }); @@ -51,10 +51,10 @@ describe("create-bucket", () => { const spy = vi.spyOn(console, "error"); - await main({ bucketName: "bucket-name" }); + await main({ bucketName: "amzn-s3-demo-bucket" }); expect(spy).toHaveBeenCalledWith( - `The bucket "bucket-name" already exists in this AWS account.`, + `The bucket "amzn-s3-demo-bucket" already exists in this AWS account.`, ); }); }); diff --git a/javascriptv3/example_code/s3/tests/delete-bucket.unit.test.js b/javascriptv3/example_code/s3/tests/delete-bucket.unit.test.js index 5d2da2c68db..675ae7fc839 100644 --- a/javascriptv3/example_code/s3/tests/delete-bucket.unit.test.js +++ b/javascriptv3/example_code/s3/tests/delete-bucket.unit.test.js @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { S3ServiceException } from "@aws-sdk/client-s3"; import { describe, it, expect, vi } from "vitest"; const send = vi.fn(); @@ -23,18 +24,44 @@ describe("delete-bucket", () => { const spy = vi.spyOn(console, "log"); - await main(); + await main({ bucketName: "amzn-s3-demo-bucket" }); - expect(spy).toHaveBeenCalledWith("foo"); + expect(spy).toHaveBeenCalledWith("Bucket was deleted."); }); - it("should log errors", async () => { - send.mockRejectedValue("foo"); + it("should log a relevant error when the bucket doesn't exist", async () => { + const error = new S3ServiceException("The specified bucket does not exist"); + error.name = "NoSuchBucket"; + send.mockRejectedValueOnce(error); const spy = vi.spyOn(console, "error"); - await main(); + await main({ bucketName: "amzn-s3-demo-bucket" }); - expect(spy).toHaveBeenCalledWith("foo"); + expect(spy).toHaveBeenCalledWith( + `Error from S3 while deleting bucket. The bucket doesn't exist.`, + ); + }); + + it("should indicate a failure came from S3 when the error isn't generic", async () => { + const error = new S3ServiceException("Some S3 service exception."); + error.name = "ServiceException"; + send.mockRejectedValueOnce(error); + + const spy = vi.spyOn(console, "error"); + + await main({ bucketName: "amzn-s3-demo-bucket" }); + + expect(spy).toHaveBeenCalledWith( + `Error from S3 while deleting the bucket. ${error.name}: ${error.message}`, + ); + }); + + it("should throw errors that are not S3 specific", async () => { + send.mockRejectedValueOnce(new Error()); + + await expect(() => + main({ bucketName: "amzn-s3-demo-bucket" }), + ).rejects.toBeTruthy(); }); }); diff --git a/javascriptv3/example_code/s3/tests/get-object-legal-hold.integration.test.js b/javascriptv3/example_code/s3/tests/get-object-legal-hold.integration.test.js index 46c664a3afe..aa4417c9395 100644 --- a/javascriptv3/example_code/s3/tests/get-object-legal-hold.integration.test.js +++ b/javascriptv3/example_code/s3/tests/get-object-legal-hold.integration.test.js @@ -13,7 +13,7 @@ import { main as getObjectLegalHold } from "../actions/get-object-legal-hold.js" import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName("test"); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); const objectKey = "test-object"; describe("get-object-legal-hold.js Integration Test", () => { diff --git a/javascriptv3/example_code/s3/tests/get-object-lock-configuration.integration.test.js b/javascriptv3/example_code/s3/tests/get-object-lock-configuration.integration.test.js index 2bb741806fc..93d796d4c61 100644 --- a/javascriptv3/example_code/s3/tests/get-object-lock-configuration.integration.test.js +++ b/javascriptv3/example_code/s3/tests/get-object-lock-configuration.integration.test.js @@ -15,7 +15,7 @@ import { getUniqueName } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName("code-example"); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); describe("get-object-lock-configuration.js Integration Test", () => { afterAll(async () => { diff --git a/javascriptv3/example_code/s3/tests/get-object-retention.integration.test.js b/javascriptv3/example_code/s3/tests/get-object-retention.integration.test.js index af3dbd698f9..9c6f7bffabd 100644 --- a/javascriptv3/example_code/s3/tests/get-object-retention.integration.test.js +++ b/javascriptv3/example_code/s3/tests/get-object-retention.integration.test.js @@ -14,7 +14,7 @@ import { main as getObjectRetention } from "../actions/get-object-retention.js"; import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName("test-bucket"); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); const objectKey = "test-object"; describe("get-object-retention.js Integration Test", () => { diff --git a/javascriptv3/example_code/s3/tests/multipart-upload.integration.test.js b/javascriptv3/example_code/s3/tests/multipart-upload.integration.test.js new file mode 100644 index 00000000000..325ebb19a22 --- /dev/null +++ b/javascriptv3/example_code/s3/tests/multipart-upload.integration.test.js @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, beforeAll, afterAll } from "vitest"; +import { getUniqueName } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; +import { createBucket, deleteBucket, emptyBucket } from "../libs/s3Utils.js"; +import { main } from "../scenarios/multipart-upload.js"; + +describe("multipart-upload", () => { + const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); + + beforeAll(async () => { + await createBucket(bucketName); + }); + + afterAll(async () => { + await emptyBucket(bucketName); + await deleteBucket(bucketName); + }); + + it("should run", async () => { + await main({ bucketName, key: "big-string.txt" }); + }); +}); diff --git a/javascriptv3/example_code/s3/tests/put-default-object-lock-configuration.integration.test.js b/javascriptv3/example_code/s3/tests/put-default-object-lock-configuration.integration.test.js index 6aec91c67a3..24a01bc5c86 100644 --- a/javascriptv3/example_code/s3/tests/put-default-object-lock-configuration.integration.test.js +++ b/javascriptv3/example_code/s3/tests/put-default-object-lock-configuration.integration.test.js @@ -15,7 +15,7 @@ import { getUniqueName } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName("test-bucket"); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); describe("put-default-object-lock-configuration.js Integration Test", () => { afterAll(async () => { @@ -38,7 +38,7 @@ describe("put-default-object-lock-configuration.js Integration Test", () => { // Execute const spy = vi.spyOn(console, "error"); - await putDefaultObjectLockConfiguration(client, bucketName); + await putDefaultObjectLockConfiguration({ bucketName, retentionDays: 1 }); expect(spy).not.toHaveBeenCalled(); // Verify @@ -49,6 +49,6 @@ describe("put-default-object-lock-configuration.js Integration Test", () => { expect(ObjectLockConfiguration.Rule.DefaultRetention.Mode).toBe( "GOVERNANCE", ); - expect(ObjectLockConfiguration.Rule.DefaultRetention.Years).toBe(3); + expect(ObjectLockConfiguration.Rule.DefaultRetention.Days).toBe(1); }); }); diff --git a/javascriptv3/example_code/s3/tests/put-object-legal-hold.integration.test.js b/javascriptv3/example_code/s3/tests/put-object-legal-hold.integration.test.js index 2ca7989ce2f..bbe9af11922 100644 --- a/javascriptv3/example_code/s3/tests/put-object-legal-hold.integration.test.js +++ b/javascriptv3/example_code/s3/tests/put-object-legal-hold.integration.test.js @@ -13,7 +13,7 @@ import { main as putObjectLegalHold } from "../actions/put-object-legal-hold.js" import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName("code-example"); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); const objectKey = "file.txt"; describe("put-object-legal-hold.js Integration Test", () => { diff --git a/javascriptv3/example_code/s3/tests/put-object-lock-configuration.integration.test.js b/javascriptv3/example_code/s3/tests/put-object-lock-configuration.integration.test.js index 818150d8197..7313a5e6e14 100644 --- a/javascriptv3/example_code/s3/tests/put-object-lock-configuration.integration.test.js +++ b/javascriptv3/example_code/s3/tests/put-object-lock-configuration.integration.test.js @@ -15,9 +15,7 @@ import { getUniqueName } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName( - process.env["S3_BUCKET_NAME"] || "object-lock-integ", -); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); describe("put-object-lock-configuration.js Integration Test", () => { afterAll(async () => { diff --git a/javascriptv3/example_code/s3/tests/put-object-retention.integration.test.js b/javascriptv3/example_code/s3/tests/put-object-retention.integration.test.js index 1c3fee6ebba..d2d267041bb 100644 --- a/javascriptv3/example_code/s3/tests/put-object-retention.integration.test.js +++ b/javascriptv3/example_code/s3/tests/put-object-retention.integration.test.js @@ -13,7 +13,7 @@ import { getUniqueName } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; import { legallyEmptyAndDeleteBuckets } from "../libs/s3Utils.js"; const client = new S3Client({}); -const bucketName = getUniqueName("code-example"); +const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); const objectKey = "test-object"; describe("put-object-retention.js Integration Test", () => { @@ -40,7 +40,7 @@ describe("put-object-retention.js Integration Test", () => { // Execute const spy = vi.spyOn(console, "error"); - await putObjectRetention(client, bucketName, objectKey); + await putObjectRetention({ bucketName, key: objectKey }); expect(spy).not.toHaveBeenCalled(); // Verify diff --git a/javascriptv3/example_code/s3/tests/s3-hello.integration.test.js b/javascriptv3/example_code/s3/tests/s3-hello.integration.test.js index c11571823ce..8d59d2b48b9 100644 --- a/javascriptv3/example_code/s3/tests/s3-hello.integration.test.js +++ b/javascriptv3/example_code/s3/tests/s3-hello.integration.test.js @@ -4,10 +4,13 @@ import { S3Client } from "@aws-sdk/client-s3"; import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { CreateBucketCommand, DeleteBucketCommand } from "@aws-sdk/client-s3"; + +import { getUniqueName } from "@aws-doc-sdk-examples/lib/utils/util-string.js"; + import { helloS3 } from "../hello.js"; describe("helloS3", () => { - const bucketName = `hello-s3-bucket-${Math.ceil(Math.random() * 1000000)}`; + const bucketName = getUniqueName(process.env["S3_BUCKET_NAME_PREFIX"]); const s3Client = new S3Client({}); beforeAll(async () => { diff --git a/javascriptv3/example_code/s3/vite.config.js b/javascriptv3/example_code/s3/vite.config.js index 329cefc16d9..16124a6fd9b 100644 --- a/javascriptv3/example_code/s3/vite.config.js +++ b/javascriptv3/example_code/s3/vite.config.js @@ -1,12 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { defineConfig } from 'vitest/config' +import { defineConfig } from "vitest/config"; export default defineConfig({ test: { testTimeout: 3600000, // 1 hour timeout + hookTimeout: 3600000, // 1 hour timeout sequence: { concurrent: false, // Run tests sequentially }, }, -}) \ No newline at end of file +}); diff --git a/javascriptv3/example_code/web/s3/list-objects/README.md b/javascriptv3/example_code/web/s3/list-objects/README.md new file mode 100644 index 00000000000..fc3e60afaf3 --- /dev/null +++ b/javascriptv3/example_code/web/s3/list-objects/README.md @@ -0,0 +1,2 @@ +# AWS SDK for JavaScript (v3) - Get started in the browser +This is the companion app for the guide found in the [AWS SDK for JavaScript guide](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started-browser.html).