Skip to content

Commit f95a1e7

Browse files
committed
Checksums in S3 example updates
Added a SHA256 checksum to the multipart upload example. Added a new basic example showing the use of a checksum while doing a basic PutObject. Other cleanup.
1 parent dd2f7bd commit f95a1e7

File tree

6 files changed

+212
-31
lines changed

6 files changed

+212
-31
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// swift-tools-version: 5.9
2+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// The swift-tools-version declares the minimum version of Swift required to
6+
// build this package.
7+
8+
import PackageDescription
9+
10+
let package = Package(
11+
name: "checksums",
12+
// Let Xcode know the minimum Apple platforms supported.
13+
platforms: [
14+
.macOS(.v13),
15+
.iOS(.v15)
16+
],
17+
dependencies: [
18+
// Dependencies declare other packages that this package depends on.
19+
.package(
20+
url: "https://github.com/awslabs/aws-sdk-swift",
21+
from: "1.0.0"),
22+
.package(
23+
url: "https://github.com/apple/swift-argument-parser.git",
24+
branch: "main"
25+
)
26+
],
27+
targets: [
28+
// Targets are the basic building blocks of a package, defining a module or a test suite.
29+
// Targets can depend on other targets in this package and products
30+
// from dependencies.
31+
.executableTarget(
32+
name: "checksums",
33+
dependencies: [
34+
.product(name: "AWSS3", package: "aws-sdk-swift"),
35+
.product(name: "ArgumentParser", package: "swift-argument-parser")
36+
],
37+
path: "Sources")
38+
39+
]
40+
)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/// Errors thrown by the example's functions.
5+
enum TransferError: Error {
6+
/// The destination directory for a download is missing or inaccessible.
7+
case directoryError
8+
/// An error occurred while downloading a file from Amazon S3.
9+
case downloadError(_ message: String = "")
10+
/// An error occurred moving the file to its final destination.
11+
case fileMoveError
12+
/// An error occurred when completing a multi-part upload to Amazon S3.
13+
case multipartFinishError
14+
/// An error occurred when starting a multi-part upload to Amazon S3.
15+
case multipartStartError
16+
/// An error occurred while uploading a file to Amazon S3.
17+
case uploadError(_ message: String = "")
18+
/// An error occurred while reading the file's contents.
19+
case readError
20+
/// An error occurred while presigning the URL.
21+
case signingError
22+
/// An error occurred while writing the file's contents.
23+
case writeError
24+
25+
var errorDescription: String? {
26+
switch self {
27+
case .directoryError:
28+
return "The destination directory could not be located or created"
29+
case .downloadError(message: let message):
30+
return "An error occurred attempting to download the file: \(message)"
31+
case .fileMoveError:
32+
return "The file couldn't be moved to the destination directory"
33+
case .multipartFinishError:
34+
return "An error occurred when completing a multi-part upload to Amazon S3."
35+
case .multipartStartError:
36+
return "An error occurred when starting a multi-part upload to Amazon S3."
37+
case .uploadError(message: let message):
38+
return "An error occurred attempting to upload the file: \(message)"
39+
case .readError:
40+
return "An error occurred while reading the file data"
41+
case .signingError:
42+
return "An error occurred while pre-signing the URL"
43+
case .writeError:
44+
return "An error occurred while writing the file data"
45+
}
46+
}
47+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
/// An example demonstrating how to configure checksums when uploading to
5+
/// Amazon S3.
6+
7+
// snippet-start:[swift.s3.checksums-upload.imports]
8+
import ArgumentParser
9+
import AWSClientRuntime
10+
import AWSS3
11+
import Foundation
12+
import Smithy
13+
// snippet-end:[swift.s3.checksums-upload.imports]
14+
15+
// -MARK: - Async command line tool
16+
17+
struct ExampleCommand: ParsableCommand {
18+
// -MARK: Command arguments
19+
@Option(help: "Path of local file to upload to Amazon S3")
20+
var source: String
21+
@Option(help: "Name of the Amazon S3 bucket to upload to")
22+
var bucket: String
23+
@Option(help: "Destination file path within the bucket")
24+
var dest: String?
25+
@Option(help: "Name of the Amazon S3 Region to use (default: us-east-1)")
26+
var region = "us-east-1"
27+
28+
static var configuration = CommandConfiguration(
29+
commandName: "checksums",
30+
abstract: """
31+
This example shows how to configure checksums when uploading to Amazon S3.
32+
""",
33+
discussion: """
34+
"""
35+
)
36+
37+
// -MARK: - File upload
38+
39+
func uploadFile(sourcePath: String, bucket: String, key: String?) async throws {
40+
let fileURL = URL(fileURLWithPath: sourcePath)
41+
let fileName: String
42+
43+
// If no key was provided, use the last component of the filename.
44+
45+
if key == nil {
46+
fileName = fileURL.lastPathComponent
47+
} else {
48+
fileName = key!
49+
}
50+
51+
// Create an Amazon S3 client in the desired Region.
52+
53+
let config = try await S3Client.S3ClientConfiguration(region: region)
54+
let s3Client = S3Client(config: config)
55+
56+
print("Uploading file from \(fileURL.path) to \(bucket)/\(fileName).")
57+
58+
let fileData = try Data(contentsOf: fileURL)
59+
let dataStream = ByteStream.data(fileData)
60+
61+
// Use PutObject to send the file to Amazon S3. The checksum is
62+
// specified by setting the `checksumAlgorithm` property. In this
63+
// example, SHA256 is used.
64+
65+
do {
66+
// snippet-start:[swift.s3.checksums.upload-file]
67+
_ = try await s3Client.putObject(
68+
input: PutObjectInput(
69+
body: dataStream,
70+
bucket: bucket,
71+
checksumAlgorithm: .sha256,
72+
key: fileName
73+
)
74+
)
75+
// snippet-end:[swift.s3.checksums.upload-file]
76+
} catch {
77+
throw TransferError.uploadError("Error uploading file: \(error.localizedDescription)")
78+
}
79+
print("Uploaded \(sourcePath) to \(bucket)/\(fileName).")
80+
}
81+
82+
// -MARK: - Asynchronous main code
83+
84+
/// Called by ``main()`` to run the bulk of the example.
85+
func runAsync() async throws {
86+
try await uploadFile(sourcePath: source, bucket: bucket, key: dest)
87+
}
88+
}
89+
90+
// -MARK: - Entry point
91+
92+
/// The program's asynchronous entry point.
93+
@main
94+
struct Main {
95+
static func main() async {
96+
let args = Array(CommandLine.arguments.dropFirst())
97+
98+
do {
99+
let command = try ExampleCommand.parse(args)
100+
try await command.runAsync()
101+
} catch let error as TransferError {
102+
print("ERROR: \(error.errorDescription ?? "Unknown error")")
103+
} catch {
104+
ExampleCommand.exit(withError: error)
105+
}
106+
}
107+
}

swift/example_code/s3/multipart-upload/Sources/TransferError.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
/// Errors thrown by the example's functions.
55
enum TransferError: Error {
6+
/// The checksum is missing or erroneous.
7+
case checksumError
68
/// An error occurred when completing a multi-part upload to Amazon S3.
79
case multipartFinishError(_ message: String = "")
810
/// An error occurred when starting a multi-part upload to Amazon S3.
@@ -14,6 +16,8 @@ enum TransferError: Error {
1416

1517
var errorDescription: String? {
1618
switch self {
19+
case .checksumError:
20+
return "The checksum is missing or incorrect"
1721
case .multipartFinishError(message: let message):
1822
return "An error occurred when completing a multi-part upload to Amazon S3. \(message)"
1923
case .multipartStartError:

swift/example_code/s3/multipart-upload/Sources/entry.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,13 @@ struct ExampleCommand: ParsableCommand {
126126
func startMultipartUpload(client: S3Client, bucket: String, key: String) async throws -> String {
127127
let multiPartUploadOutput: CreateMultipartUploadOutput
128128

129-
// First, create the multi-part upload.
129+
// First, create the multi-part upload, using SHA256 checksums.
130130

131131
do {
132132
multiPartUploadOutput = try await client.createMultipartUpload(
133133
input: CreateMultipartUploadInput(
134134
bucket: bucket,
135+
checksumAlgorithm: .sha256,
135136
key: key
136137
)
137138
)
@@ -170,18 +171,28 @@ struct ExampleCommand: ParsableCommand {
170171
let uploadPartInput = UploadPartInput(
171172
body: ByteStream.data(data),
172173
bucket: bucket,
174+
checksumAlgorithm: .sha256,
173175
key: key,
174176
partNumber: partNumber,
175177
uploadId: uploadID
176178
)
177179

180+
// Upload the part with a SHA256 checksum.
178181
do {
179-
let uploadPartOutput = try await client.uploadPart(input: uploadPartInput)
182+
let uploadPartOutput = try await client.uploadPart(input: uploadPartInput)
183+
180184
guard let eTag = uploadPartOutput.eTag else {
181185
throw TransferError.uploadError("Missing eTag")
182186
}
187+
guard let checksum = uploadPartOutput.checksumSHA256 else {
188+
throw TransferError.checksumError
189+
}
183190

184-
return S3ClientTypes.CompletedPart(eTag: eTag, partNumber: partNumber)
191+
return S3ClientTypes.CompletedPart(
192+
checksumSHA256: checksum,
193+
eTag: eTag,
194+
partNumber: partNumber
195+
)
185196
} catch {
186197
throw TransferError.uploadError(error.localizedDescription)
187198
}

swift/example_code/s3/presigned-urls/Sources/presigned-upload/TransferError.swift

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,17 @@
33

44
/// Errors thrown by the example's functions.
55
enum TransferError: Error {
6-
/// The destination directory for a download is missing or inaccessible.
7-
case directoryError
8-
/// An error occurred while downloading a file from Amazon S3.
9-
case downloadError(_ message: String = "")
10-
/// An error occurred moving the file to its final destination.
11-
case fileMoveError
12-
/// An error occurred when completing a multi-part upload to Amazon S3.
13-
case multipartFinishError
14-
/// An error occurred when starting a multi-part upload to Amazon S3.
15-
case multipartStartError
166
/// An error occurred while uploading a file to Amazon S3.
177
case uploadError(_ message: String = "")
188
/// An error occurred while reading the file's contents.
199
case readError
20-
/// An error occurred while presigning the URL.
21-
case signingError
22-
/// An error occurred while writing the file's contents.
23-
case writeError
2410

2511
var errorDescription: String? {
2612
switch self {
27-
case .directoryError:
28-
return "The destination directory could not be located or created"
29-
case .downloadError(message: let message):
30-
return "An error occurred attempting to download the file: \(message)"
31-
case .fileMoveError:
32-
return "The file couldn't be moved to the destination directory"
33-
case .multipartFinishError:
34-
return "An error occurred when completing a multi-part upload to Amazon S3."
35-
case .multipartStartError:
36-
return "An error occurred when starting a multi-part upload to Amazon S3."
3713
case .uploadError(message: let message):
3814
return "An error occurred attempting to upload the file: \(message)"
3915
case .readError:
4016
return "An error occurred while reading the file data"
41-
case .signingError:
42-
return "An error occurred while pre-signing the URL"
43-
case .writeError:
44-
return "An error occurred while writing the file data"
4517
}
4618
}
4719
}

0 commit comments

Comments
 (0)