From ba499fae55d507ec1cabfbfc2ed1f729cfcea4f6 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Thu, 7 Aug 2025 20:17:46 +0000 Subject: [PATCH 01/12] Initial commit --- swift/example_code/rds/scenario/Package.swift | 47 + .../rds/scenario/Sources/entry.swift | 848 ++++++++++++++++++ 2 files changed, 895 insertions(+) create mode 100644 swift/example_code/rds/scenario/Package.swift create mode 100644 swift/example_code/rds/scenario/Sources/entry.swift diff --git a/swift/example_code/rds/scenario/Package.swift b/swift/example_code/rds/scenario/Package.swift new file mode 100644 index 00000000000..f434262bb71 --- /dev/null +++ b/swift/example_code/rds/scenario/Package.swift @@ -0,0 +1,47 @@ +// swift-tools-version: 5.9 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// (swift-tools-version has two lines here because it needs to be the first +// line in the file, but it should also appear in the snippet below) +// +// snippet-start:[swift.ec2.scenario.package] +// swift-tools-version: 5.9 +// +// The swift-tools-version declares the minimum version of Swift required to +// build this package. + +import PackageDescription + +let package = Package( + name: "rds-scenario", + // Let Xcode know the minimum Apple platforms supported. + platforms: [ + .macOS(.v13), + .iOS(.v15) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package( + url: "https://github.com/awslabs/aws-sdk-swift", + from: "1.4.0"), + .package( + url: "https://github.com/apple/swift-argument-parser.git", + branch: "main" + ) + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products + // from dependencies. + .executableTarget( + name: "rds-scenario", + dependencies: [ + .product(name: "AWSRDS", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.ec2.scenario.package] diff --git a/swift/example_code/rds/scenario/Sources/entry.swift b/swift/example_code/rds/scenario/Sources/entry.swift new file mode 100644 index 00000000000..7169cdc9d5e --- /dev/null +++ b/swift/example_code/rds/scenario/Sources/entry.swift @@ -0,0 +1,848 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.rds.scenario] +// An example that shows how to use the AWS SDK for Swift to perform a variety +// of operations using Amazon Elastic Compute Cloud (EC2). +// + +import ArgumentParser +import Foundation +import AWSRDS + +struct ExampleCommand: ParsableCommand { + @Option(help: "The AWS Region to run AWS API calls in.") + var awsRegion = "us-east-1" + @Option(help: "The username to use for the database administrator.") + var dbUsername = "admin" + @Option(help: "The password to use for the database administrator.") + var dbPassword: String + + @Option( + help: ArgumentHelp("The level of logging for the Swift SDK to perform."), + completion: .list([ + "critical", + "debug", + "error", + "info", + "notice", + "trace", + "warning" + ]) + ) + var logLevel: String = "error" + + static var configuration = CommandConfiguration( + commandName: "rds-scenario", + abstract: """ + Performs various operations to demonstrate the use of Amazon RDS Instances + using the AWS SDK for Swift. + """, + discussion: """ + """ + ) + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let example = try await Example(region: awsRegion, username: dbUsername, password: dbPassword) + + await example.run() + } +} + +class Example { + let rdsClient: RDSClient + + // Storage for AWS RDS properties + + let dbUsername: String + let dbPassword: String + var dbInstanceIdentifier: String + var dbSnapshotIdentifier: String + var dbParameterGroupName: String + var dbParameterGroup: RDSClientTypes.DBParameterGroup? + var selectedEngineVersion: String? + + init(region: String, username: String, password: String) async throws{ + let rdsConfig = try await RDSClient.RDSClientConfiguration(region: region) + self.rdsClient = RDSClient(config: rdsConfig) + + self.dbUsername = username + self.dbPassword = password + dbParameterGroupName = "" + dbInstanceIdentifier = "" + dbSnapshotIdentifier = "" + } + + /// The example's main body. + func run() async { + var parameterGroupFamilies: Set = [] + + //===================================================================== + // 1. Get available database engine families for MySQL. + //===================================================================== + + let engineVersions = await getDBEngineVersions(engineName: "mysql") + + for version in engineVersions { + if version.dbParameterGroupFamily != nil { + parameterGroupFamilies.insert(version.dbParameterGroupFamily!) + } + } + + if engineVersions.count > 0 { + selectedEngineVersion = engineVersions.last!.engineVersion + } else { + print("*** Unable to find a valid database engine version. Canceling operations.") + await cleanUp() + return + } + + print("Found \(parameterGroupFamilies.count) parameter group families:") + for family in parameterGroupFamilies { + print(" \(family)") + } + + //===================================================================== + // 2. Select an engine family and create a custom DB parameter group. + // We select a family by sorting the set of family names, then + // choosing the last one. + //===================================================================== + + let sortedFamilies = parameterGroupFamilies.sorted() + + guard let selectedFamily = sortedFamilies.last else { + print("*** Unable to find a database engine family. Canceling operations.") + await cleanUp() + return + } + + print("Selected database engine family \(selectedFamily)") + + dbParameterGroupName = tempName(prefix: "rds-example") + print("Creating a database parameter group named \(dbParameterGroupName) using \(selectedFamily)") + dbParameterGroup = await createDBParameterGroup(groupName: dbParameterGroupName, + familyName: selectedFamily) + + //===================================================================== + // 3. Get the parameter group's details. + //===================================================================== + + print("Getting the database parameter group list...") + let dbParameterGroupList = await describeDBParameterGroups(groupName: dbParameterGroupName) + guard let dbParameterGroupList else { + await cleanUp() + return + } + + print("Found \(dbParameterGroupList.count) parameter groups...") + for group in dbParameterGroupList { + print(" \(group.dbParameterGroupName ?? "")") + } + print() + + //===================================================================== + // 4. Get a list of the parameter group's parameters. This list is + // likely to be long, so use pagination. Find the + // auto_increment_offset and auto_increment_increment parameters. + //===================================================================== + + let parameters = await describeDBParameters(groupName: dbParameterGroupName) + + //===================================================================== + // 5. Parse and display each parameter's name, description, and + // allowed values. + //===================================================================== + + for parameter in parameters { + let name = parameter.parameterName + guard let name else { + print("*** Unable to get parameter name!") + continue + } + + if name == "auto_increment_offset" || name == "auto_increment_increment" { + print("Parameter \(name):") + print(" Value: \(parameter.parameterValue ?? "")") + print(" Data type: \(parameter.dataType ?? "")") + print(" Description: \(parameter.description ?? "")") + print(" Allowed values: \(parameter.allowedValues ?? "")") + print(" Data type: \(parameter.dataType ?? "")") + print(" Description: \(parameter.description ?? "")") + print(" Allowed values: \(parameter.allowedValues ?? " [RDSClientTypes.DBEngineVersion] { + do { + let output = try await rdsClient.describeDBEngineVersions( + input: DescribeDBEngineVersionsInput( + engine: engineName + ) + ) + + return output.dbEngineVersions ?? [] + } catch { + return [] + } + } + // snippet-end:[swift.rds.DescribeDBEngineVersions] + + // snippet-start:[swift.rds.CreateDBParameterGroup] + /// Create a new database parameter group with the specified name. + /// + /// - Parameters: + /// - groupName: The name of the new parameter group. + /// - familyName: The name of the parameter group family. + /// - Returns: + func createDBParameterGroup(groupName: String, familyName: String) async -> RDSClientTypes.DBParameterGroup? { + do { + let output = try await rdsClient.createDBParameterGroup( + input: CreateDBParameterGroupInput( + dbParameterGroupFamily: familyName, + dbParameterGroupName: groupName, + description: "Created using the AWS SDK for Swift" + ) + ) + return output.dbParameterGroup + } catch { + print("*** Error creating the parameter group: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.rds.CreateDBParameterGroup] + + // snippet-start:[swift.rds.DescribeDBParameterGroups] + /// Get descriptions of the database parameter groups matching the given + /// name. + /// + /// - Parameter groupName: The name of the parameter group to describe. + /// + /// - Returns: An array of [RDSClientTypes.DBParameterGroup] objects + /// describing the parameter group. + func describeDBParameterGroups(groupName: String) async -> [RDSClientTypes.DBParameterGroup]? { + do { + let output = try await rdsClient.describeDBParameterGroups( + input: DescribeDBParameterGroupsInput( + dbParameterGroupName: groupName + ) + ) + return output.dbParameterGroups + } catch { + print("*** Error getting the database parameter group's details: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.rds.DescribeDBParameterGroups] + + // snippet-start:[swift.rds.DescribeDBParametersPaginated] + // snippet-start:[swift.rds.DescribeDBParameters] + /// Returns the detailed parameter list for the specified database + /// parameter group. + /// + /// - Parameters: + /// - groupName: The name of the parameter group to return parameters for. + /// - source: The types of parameters to return (`user`, `system`, or + /// `engine-default`). + /// + /// - Returns: An array of `RdSClientTypes.Parameter` objects, each + /// describing one of the group's parameters. + func describeDBParameters(groupName: String, source: String? = nil) async -> [RDSClientTypes.Parameter] { + var parameterList: [RDSClientTypes.Parameter] = [] + + do { + let pages = rdsClient.describeDBParametersPaginated( + input: DescribeDBParametersInput( + dbParameterGroupName: groupName, + source: source + ) + ) + + for try await page in pages { + guard let parameters = page.parameters else { + return [] + } + + parameterList += parameters + } + } catch { + print("*** Error getting database parameters: \(error.localizedDescription)") + return [] + } + + return parameterList + } + // snippet-end:[swift.rds.DescribeDBParameters] + // snippet-end:[swift.rds.DescribeDBParametersPaginated] + + // snippet-start:[swift.rds.ModifyDBParameterGroup] + /// Demonstrates modifying two of the specified database parameter group's + /// parameters. + /// + /// - Parameter groupName: The name of the parameter group to change + /// parameters for. + func modifyDBParameters(groupName: String) async { + let parameter1 = RDSClientTypes.Parameter( + applyMethod: RDSClientTypes.ApplyMethod.immediate, + parameterName: "auto_increment_offset", + parameterValue: "5" + ) + let parameter2 = RDSClientTypes.Parameter( + applyMethod: RDSClientTypes.ApplyMethod.immediate, + parameterName: "auto_increment_increment", + parameterValue: "5" + ) + + let parameterList = [parameter1, parameter2] + + do { + _ = try await rdsClient.modifyDBParameterGroup( + input: ModifyDBParameterGroupInput( + dbParameterGroupName: groupName, + parameters: parameterList + ) + ) + + print("Successfully modified the parameter group \(groupName).") + } catch { + print("*** Error modifying the parameter group \(groupName): \(error.localizedDescription)") + } + } + // snippet-end:[swift.rds.ModifyDBParameterGroup] + + // snippet-start:[swift.rds.DescribeDBEngineVersions] + /// Output a list of the database engine versions supported by the + /// specified family. + /// + /// - Parameter family: The family for which to list allowed database + /// engines. + func listAllowedEngines(family: String?) async { + do { + let output = try await rdsClient.describeDBEngineVersions( + input: DescribeDBEngineVersionsInput( + dbParameterGroupFamily: family, + engine: "mysql" + ) + ) + + guard let engineVersions = output.dbEngineVersions else { + print("No engine versions returned.") + return + } + + print("Found \(engineVersions.count) database engine versions:") + for version in engineVersions { + print(" \(version.engineVersion ?? ""): \(version.dbEngineDescription ?? "")") + } + } catch { + print("*** Error getting database engine version list: \(error.localizedDescription)") + return + } + } + // snippet-end:[swift.rds.DescribeDBEngineVersions] + + // snippet-start:[swift.rds.DescribeOrderedDBInstanceOptionsPaginated] + // snippet-start:[swift.rds.DescribeOrderableDBInstanceOptions] + /// Print a list of available database instances with "micro" in the class + /// name, then return one of them to be used by other code. + /// + /// - Parameters: + /// - engine: The database engine for which to list database instance + /// classes. + /// - engineVersion: The database version for which to list instances. + /// + /// - Returns: An `RDSClientTypes.OrderableDBInstanceOption` describing + /// the selected instance type. + func chooseMicroInstance(engine: String = "mysql", engineVersion: String? = nil) async -> RDSClientTypes.OrderableDBInstanceOption? { + do { + let pages = rdsClient.describeOrderableDBInstanceOptionsPaginated( + input: DescribeOrderableDBInstanceOptionsInput( + engine: engine, + engineVersion: engineVersion + ) + ) + + var optionsList: [RDSClientTypes.OrderableDBInstanceOption] = [] + + for try await page in pages { + guard let orderableDBInstanceOptions = page.orderableDBInstanceOptions else { + continue + } + + for dbInstanceOption in orderableDBInstanceOptions { + guard let className = dbInstanceOption.dbInstanceClass else { + continue + } + if className.contains("micro") { + optionsList.append(dbInstanceOption) + } + } + } + + print("Found \(optionsList.count) database instances of 'micro' class types:") + for dbInstanceOption in optionsList { + print(" \(dbInstanceOption.engine ?? "") \(dbInstanceOption.engineVersion ?? "") (\(dbInstanceOption.dbInstanceClass ?? ""))") + } + + return optionsList[0] + } catch { + print("*** Error getting a list of orderable instance options: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.rds.DescribeOrderableDBInstanceOptions] + // snippet-end:[swift.rds.DescribeOrderedDBInstanceOptionsPaginated] + + // snippet-start:[swift.rds.CreateDBInstance] + /// Create a new database instance. + /// + /// - Parameters: + /// - name: The name of the database to create. + /// - instanceIdentifier: The identifier to give the new database + /// instance. + /// - parameterGroupName: The name of the parameter group to associate + /// with the new database instance. + /// - engine: The database engine to use. + /// - engineVersion: The version of the database given by `engine` to + /// use. + /// - instanceClass: The memory and compute capacity of the database + /// instance, such as `db.m5.large``. + /// - username: The admin user's username to establish for the new + /// instance. + /// - password: The password to use for the specified user's access. + /// + /// - Returns: A string indicating the ARN of the newly created database + /// instance, or nil if the instance couldn't be created. + func createDBInstance(name: String, instanceIdentifier: String, parameterGroupName: String, + engine: String, engineVersion: String, instanceClass: String, + username: String, password: String) async -> String? { + do { + let output = try await rdsClient.createDBInstance( + input: CreateDBInstanceInput( + allocatedStorage: 100, + dbInstanceClass: instanceClass, + dbInstanceIdentifier: instanceIdentifier, + dbName: name, + dbParameterGroupName: parameterGroupName, + engine: engine, + engineVersion: engineVersion, + masterUserPassword: password, + masterUsername: username, + storageType: "gp2" + ) + ) + + guard let dbInstance = output.dbInstance else { + print("*** Unable to get the database instance.") + return nil + } + + return dbInstance.dbInstanceArn + } catch { + print("*** An error occurred while creating the database instance: \(error.localizedDescription)") + return nil + } + } + // snippet-end:[swift.rds.CreateDBInstance] + + // snippet-start:[swift.rds.DescribeDBInstances] + /// Wait until the specified database is available to use. + /// + /// - Parameter instanceIdentifier: The database instance identifier of the + /// database to wait for. + func waitUntilDBInstanceReady(instanceIdentifier: String) async { + do { + var instanceReady = false + + putString("Waiting for the database instance to be ready to use. This may take 10 minutes or more...") + while !instanceReady { + let output = try await rdsClient.describeDBInstances( + input: DescribeDBInstancesInput( + dbInstanceIdentifier: instanceIdentifier + ) + ) + + guard let instanceList = output.dbInstances else { + continue + } + + for instance in instanceList { + let status = instance.dbInstanceStatus + + guard let status else { + print("\nUnable to determine the status.") + continue + } + + if status.contains("available") { + guard let instanceEndpoint = instance.endpoint else { + print("\n*** Instance is available but no endpoint returned!") + return + } + + guard let endpointAddress = instanceEndpoint.address else { + print("\nNo endpoint address returned.") + return + } + guard let endpointPort = instanceEndpoint.port else { + print("\nNo endpoint port returned.") + return + } + guard let username = instance.masterUsername else { + print("\nNo main username returned.") + return + } + + //===================================================================== + // 12. Display connection information for the database instance. + //===================================================================== + + print("\nTo connect to the new database instance using 'mysql' from the shell:") + print(" mysql -h \(endpointAddress) -P \(endpointPort) -u \(username)") + + instanceReady = true + } else { + putString(".") + do { + try await Task.sleep(for: .seconds(15)) + } catch { + print("*** Error pausing the task!") + } + } + } + } + } catch { + print("*** Unable to wait until the database is ready: \(error.localizedDescription)") + } + } + // snippet-end:[swift.rds.DescribeDBInstances] + + // snippet-start:[swift.rds.CreateDBSnapshot] + /// Create a snapshot of the specified name. + /// + /// - Parameters: + /// - instanceIdentifier: The identifier of the database instance to + /// snapshot. + /// - snapshotIdentifier: A unique identifier to give the newly-created + /// snapshot. + func createDBSnapshot(instanceIdentifier: String, snapshotIdentifier: String) async { + do { + let output = try await rdsClient.createDBSnapshot( + input: CreateDBSnapshotInput( + dbInstanceIdentifier: instanceIdentifier, + dbSnapshotIdentifier: snapshotIdentifier + ) + ) + + guard let snapshot = output.dbSnapshot else { + print("No snapshot returned.") + return + } + + print("The snapshot has been created with ID \(snapshot.dbiResourceId ?? "")") + } catch { + print("*** Unable to create the database snapshot named \(snapshotIdentifier): \(error.localizedDescription)") + } + } + // snippet-end:[swift.rds.CreateDBSnapshot] + + // snippet-start:[swift.rds.DescribeDBSnapshots] + /// Wait until the specified database snapshot is available to use. + /// + /// - Parameters: + /// - instanceIdentifier: The identifier of the database for which the + /// snapshot was taken. + /// - snapshotIdentifier: The identifier of the snapshot to wait for. + func waitUntilDBSnapshotReady(instanceIdentifier: String, snapshotIdentifier: String) async { + var snapshotReady = false + + putString("Waiting for the snapshot to be ready...") + + do { + while !snapshotReady { + let output = try await rdsClient.describeDBSnapshots( + input: DescribeDBSnapshotsInput( + dbInstanceIdentifier: instanceIdentifier, + dbSnapshotIdentifier: snapshotIdentifier + ) + ) + + guard let snapshotList = output.dbSnapshots else { + return + } + + for snapshot in snapshotList { + guard let snapshotReadyStr = snapshot.status else { + return + } + + if snapshotReadyStr.contains("available") { + snapshotReady = true + print() + } else { + putString(".") + do { + try await Task.sleep(for: .seconds(15)) + } catch { + print("\n*** Error pausing the task!") + } + } + } + } + } catch { + print("\n*** Unable to wait for the database snapshot to be ready: \(error.localizedDescription)") + } + } + // snippet-end:[swift.rds.DescribeDBSnapshots] + + // snippet-start:[swift.rds.DeleteDBInstance] + /// Delete the specified database instance. + /// + /// - Parameter instanceIdentifier: The identifier of the database + /// instance to delete. + func deleteDBInstance(instanceIdentifier: String) async { + do { + _ = try await rdsClient.deleteDBInstance( + input: DeleteDBInstanceInput( + dbInstanceIdentifier: instanceIdentifier, + deleteAutomatedBackups: true, + skipFinalSnapshot: true + ) + ) + } catch { + print("*** Error deleting the database instance \(instanceIdentifier): \(error.localizedDescription)") + } + } + // snippet-end:[swift.rds.DeleteDBInstance] + + /// Wait until the specified database instance has been deleted. + /// + /// - Parameter instanceIdentifier: The identifier of the database + /// instance to wait for. + func waitUntilDBInstanceDeleted(instanceIdentifier: String) async { + putString("Waiting for the database instance to be deleted. This may take a few minutes...") + do { + var isDatabaseDeleted = false + var foundInstance = false + + while !isDatabaseDeleted { + let output = try await rdsClient.describeDBInstances(input: DescribeDBInstancesInput()) + guard let instanceList = output.dbInstances else { + return + } + + foundInstance = false + + for instance in instanceList { + guard let foundInstanceIdentifier = instance.dbInstanceIdentifier else { + continue + } + + if instanceIdentifier == foundInstanceIdentifier { + foundInstance = true + break + } else { + putString(".") + do { + try await Task.sleep(for: .seconds(15)) + } catch { + print("\n*** Error pausing the task!") + } + } + } + if !foundInstance { + isDatabaseDeleted = true + print() + } + } + } catch { + print("\n*** Error waiting for the database instance to be deleted: \(error.localizedDescription)") + } + } + + // snippet-start:[swift.rds.DeleteDBParameterGroup] + /// Delete the specified database parameter group. + /// + /// - Parameter groupName: The name of the parameter group to delete. + func deleteDBParameterGroup(groupName: String) async { + do { + _ = try await rdsClient.deleteDBParameterGroup( + input: DeleteDBParameterGroupInput( + dbParameterGroupName: groupName + ) + ) + } catch { + print("*** Error deleting the database parameter group \(groupName): \(error.localizedDescription)") + } + } + // snippet-end:[swift.rds.DeleteDBParameterGroup] + + /// Generate and return a unique file name that begins with the specified + /// string. + /// + /// - Parameters: + /// - prefix: Text to use at the beginning of the returned name. + /// + /// - Returns: A string containing a unique filename that begins with the + /// specified `prefix`. + /// + /// The returned name uses a random number between 1 million and 1 billion to + /// provide reasonable certainty of uniqueness for the purposes of this + /// example. + func tempName(prefix: String) -> String { + return "\(prefix)-\(Int.random(in: 1000000..<1000000000))" + } + + /// Print a string to standard output without a trailing newline, and + /// without buffering. + /// + /// - Parameter str: The string to output. + func putString(_ str: String = "") { + if str.length >= 1 { + let data = str.data(using: .utf8) + guard let data else { + return + } + FileHandle.standardOutput.write(data) + } + } +} + +/// The program's asynchronous entry point. +@main +struct Main { + static func main() async { + let args = Array(CommandLine.arguments.dropFirst()) + + do { + let command = try ExampleCommand.parse(args) + try await command.runAsync() + } catch { + ExampleCommand.exit(withError: error) + } + } +} +// snippet-end:[swift.rds.scenario] From c1e679e7b8b9922678761624e7e7e738b6efe44e Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 8 Aug 2025 16:46:02 +0000 Subject: [PATCH 02/12] Code and metadata completed --- .doc_gen/metadata/rds_metadata.yaml | 119 +++++++++++++++++ .../cognito-identity-provider/README.md | 2 +- swift/example_code/rds/README.md | 123 ++++++++++++++++++ swift/example_code/rds/scenario/Package.swift | 4 +- .../rds/scenario/Sources/entry.swift | 70 +++++----- 5 files changed, 279 insertions(+), 39 deletions(-) create mode 100644 swift/example_code/rds/README.md diff --git a/.doc_gen/metadata/rds_metadata.yaml b/.doc_gen/metadata/rds_metadata.yaml index c52096a9f6c..6a7c2e3be93 100644 --- a/.doc_gen/metadata/rds_metadata.yaml +++ b/.doc_gen/metadata/rds_metadata.yaml @@ -126,6 +126,15 @@ rds_CreateDBInstance: - description: snippet_tags: - php.example_code.rds.createDBInstance.complete + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.CreateDBInstance services: rds: {CreateDBInstance} rds_DeleteDBInstance: @@ -194,6 +203,15 @@ rds_DeleteDBInstance: - description: snippet_tags: - php.example_code.rds.deleteDBInstance.complete + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DeleteDBInstance services: rds: {DeleteDBInstance} rds_DescribeAccountAttributes: @@ -292,6 +310,15 @@ rds_DescribeDBInstances: - description: snippet_tags: - php.example_code.rds.describeDBInstances.complete + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DescribeDBInstances services: rds: {DescribeDBInstances} rds_ModifyDBInstance: @@ -389,6 +416,15 @@ rds_DescribeDBParameterGroups: - cpp.example_code.rds.client - cpp.example_code.rds.DescribeDBParameterGroups1 - cpp.example_code.rds.DescribeDBParameterGroups2 + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DescribeDBParameterGroups services: rds: {DescribeDBParameterGroups} rds_CreateDBParameterGroup: @@ -441,6 +477,15 @@ rds_CreateDBParameterGroup: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.CreateDBParameterGroup + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.CreateDBParameterGroup services: rds: {CreateDBParameterGroup} rds_DeleteDBParameterGroup: @@ -493,6 +538,15 @@ rds_DeleteDBParameterGroup: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.DeleteDBParameterGroup + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DeleteDBParameterGroup services: rds: {DeleteDBParameterGroup} rds_DescribeDBParameters: @@ -554,6 +608,15 @@ rds_DescribeDBParameters: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.DescribeDBParameters + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DescribeDBParameters services: rds: {DescribeDBParameters} rds_ModifyDBParameterGroup: @@ -606,6 +669,15 @@ rds_ModifyDBParameterGroup: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.ModifyDBParameterGroup + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.ModifyDBParameterGroup services: rds: {ModifyDBParameterGroup} rds_CreateDBSnapshot: @@ -675,6 +747,15 @@ rds_CreateDBSnapshot: - description: snippet_tags: - php.example_code.rds.createDBSnapshot.complete + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.CreateDBSnapshot services: rds: {CreateDBSnapshot} rds_DescribeDBSnapshots: @@ -727,6 +808,15 @@ rds_DescribeDBSnapshots: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.DescribeDBSnapshots + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DescribeDBSnapshots services: rds: {DescribeDBSnapshots} rds_DescribeDBEngineVersions: @@ -779,6 +869,15 @@ rds_DescribeDBEngineVersions: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.DescribeDBEngineVersions + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DescribeDBEngineVersions services: rds: {DescribeDBEngineVersions} rds_DescribeOrderableDBInstanceOptions: @@ -822,6 +921,15 @@ rds_DescribeOrderableDBInstanceOptions: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.client - cpp.example_code.rds.DescribeOrderableDBInstanceOptions + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: + snippet_tags: + - swift.rds.import + - swift.rds.DescribeOrderableDBInstanceOptions services: rds: {DescribeOrderableDBInstanceOptions} rds_GenerateRDSAuthToken: @@ -916,6 +1024,17 @@ rds_Scenario_GetStartedInstances: snippet_tags: - cpp.example_code.rds.client_configuration - cpp.example_code.rds.get_started_instances + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/rds + excerpts: + - description: The Package.swift file. + snippet_tags: + - swift.rds.scenario.package + - description: The Swift code file, entry.swift. + snippet_tags: + - swift.rds.scenario services: rds: {CreateDBInstance, CreateDBParameterGroup, CreateDBSnapshot, DeleteDBInstance, DeleteDBParameterGroup, DescribeDBInstances, DescribeDBEngineVersions, DescribeDBParameterGroups, DescribeDBParameters, DescribeDBSnapshots, DescribeOrderableDBInstanceOptions, diff --git a/swift/example_code/cognito-identity-provider/README.md b/swift/example_code/cognito-identity-provider/README.md index 026dfbafde7..d8cc1639667 100644 --- a/swift/example_code/cognito-identity-provider/README.md +++ b/swift/example_code/cognito-identity-provider/README.md @@ -48,7 +48,7 @@ Code excerpts that show you how to call individual service functions. Code examples that show you how to accomplish a specific task by calling multiple functions within the same service. -- [Sign up a user with a user pool that requires MFA](scenario/Package.swift) +- [Sign up a user with a user pool that requires MFA](../swift-sdk/http-config/Package.swift) diff --git a/swift/example_code/rds/README.md b/swift/example_code/rds/README.md new file mode 100644 index 00000000000..dec47f0dfdd --- /dev/null +++ b/swift/example_code/rds/README.md @@ -0,0 +1,123 @@ +# Amazon RDS code examples for the SDK for Swift + +## Overview + +Shows how to use the AWS SDK for Swift to work with Amazon Relational Database Service (Amazon RDS). + + + + +_Amazon RDS is a web service that makes it easier to set up, operate, and scale a relational database in the cloud._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `swift` folder. + + + + + +### Basics + +Code examples that show you how to perform the essential operations within a service. + +- [Learn the basics](scenario/Package.swift) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreateDBInstance](scenario/Sources/entry.swift#L543) +- [CreateDBParameterGroup](scenario/Sources/entry.swift#L340) +- [CreateDBSnapshot](scenario/Sources/entry.swift#L641) +- [DeleteDBInstance](scenario/Sources/entry.swift#L719) +- [DeleteDBParameterGroup](scenario/Sources/entry.swift#L784) +- [DescribeDBEngineVersions](scenario/Sources/entry.swift#L317) +- [DescribeDBInstances](scenario/Sources/entry.swift#L595) +- [DescribeDBParameterGroups](scenario/Sources/entry.swift#L364) +- [DescribeDBParameters](scenario/Sources/entry.swift#L388) +- [DescribeDBSnapshots](scenario/Sources/entry.swift#L670) +- [DescribeOrderableDBInstanceOptions](scenario/Sources/entry.swift#L492) +- [ModifyDBParameterGroup](scenario/Sources/entry.swift#L427) + + + + + +## Run the examples + +### Instructions + +To build any of these examples from a terminal window, navigate into its +directory, then use the following command: + +``` +$ swift build +``` + +To build one of these examples in Xcode, navigate to the example's directory +(such as the `ListUsers` directory, to build that example). Then type `xed.` +to open the example directory in Xcode. You can then use standard Xcode build +and run commands. + + + + + +#### Learn the basics + +This example shows you how to do the following: + +- Create a custom DB parameter group and set parameter values. +- Create a DB instance that's configured to use the parameter group. The DB instance also contains a database. +- Take a snapshot of the instance. +- Delete the instance and parameter group. + + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `swift` folder. + + + + + + +## Additional resources + +- [Amazon RDS User Guide](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html) +- [Amazon RDS API Reference](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/Welcome.html) +- [SDK for Swift Amazon RDS reference](https://sdk.amazonaws.com/swift/api/awsrds/latest/documentation/awsrds) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/swift/example_code/rds/scenario/Package.swift b/swift/example_code/rds/scenario/Package.swift index f434262bb71..63b70f5494e 100644 --- a/swift/example_code/rds/scenario/Package.swift +++ b/swift/example_code/rds/scenario/Package.swift @@ -5,7 +5,7 @@ // (swift-tools-version has two lines here because it needs to be the first // line in the file, but it should also appear in the snippet below) // -// snippet-start:[swift.ec2.scenario.package] +// snippet-start:[swift.rds.scenario.package] // swift-tools-version: 5.9 // // The swift-tools-version declares the minimum version of Swift required to @@ -44,4 +44,4 @@ let package = Package( ] ) -// snippet-end:[swift.ec2.scenario.package] +// snippet-end:[swift.rds.scenario.package] diff --git a/swift/example_code/rds/scenario/Sources/entry.swift b/swift/example_code/rds/scenario/Sources/entry.swift index 7169cdc9d5e..39e45def10d 100644 --- a/swift/example_code/rds/scenario/Sources/entry.swift +++ b/swift/example_code/rds/scenario/Sources/entry.swift @@ -8,7 +8,9 @@ import ArgumentParser import Foundation +// snippet-start:[swift.rds.import] import AWSRDS +// snippet-end:[swift.rds.import] struct ExampleCommand: ParsableCommand { @Option(help: "The AWS Region to run AWS API calls in.") @@ -259,7 +261,29 @@ class Example { // minutes, let the user know that. //===================================================================== - await waitUntilDBInstanceReady(instanceIdentifier: dbInstanceIdentifier) + guard let endpoint = await waitUntilDBInstanceReady(instanceIdentifier: dbInstanceIdentifier) else { + print("\nDid not get a valid endpoint from AWS RDS.") + await cleanUp() + return + } + + guard let endpointAddress = endpoint.address else { + print("\nNo endpoint address returned.") + await cleanUp() + return + } + guard let endpointPort = endpoint.port else { + print("\nNo endpoint port returned.") + await cleanUp() + return + } + + //===================================================================== + // 12. Display connection information for the database instance. + //===================================================================== + + print("\nTo connect to the new database instance using 'mysql' from the shell:") + print(" mysql -h \(endpointAddress) -P \(endpointPort) -u \(self.dbUsername)") //===================================================================== // 13. Create a snapshot of the database instance. @@ -274,6 +298,9 @@ class Example { await waitUntilDBSnapshotReady(instanceIdentifier: dbInstanceIdentifier, snapshotIdentifier: dbSnapshotIdentifier) + // That's it! Clean up and exit! + + print("Example complete! Cleaning up...") await cleanUp() } @@ -281,7 +308,6 @@ class Example { func cleanUp() async { print("Deleting the database instance \(dbInstanceIdentifier)...") await deleteDBInstance(instanceIdentifier: dbInstanceIdentifier) - await waitUntilDBInstanceDeleted(instanceIdentifier: dbInstanceIdentifier) print("Deleting the database parameter group \(dbParameterGroupName)...") @@ -433,7 +459,6 @@ class Example { } // snippet-end:[swift.rds.ModifyDBParameterGroup] - // snippet-start:[swift.rds.DescribeDBEngineVersions] /// Output a list of the database engine versions supported by the /// specified family. /// @@ -462,9 +487,8 @@ class Example { return } } - // snippet-end:[swift.rds.DescribeDBEngineVersions] - // snippet-start:[swift.rds.DescribeOrderedDBInstanceOptionsPaginated] + // snippet-start:[swift.rds.DescribeOrderableDBInstanceOptionsPaginated] // snippet-start:[swift.rds.DescribeOrderableDBInstanceOptions] /// Print a list of available database instances with "micro" in the class /// name, then return one of them to be used by other code. @@ -514,7 +538,7 @@ class Example { } } // snippet-end:[swift.rds.DescribeOrderableDBInstanceOptions] - // snippet-end:[swift.rds.DescribeOrderedDBInstanceOptionsPaginated] + // snippet-end:[swift.rds.DescribeOrderableDBInstanceOptionsPaginated] // snippet-start:[swift.rds.CreateDBInstance] /// Create a new database instance. @@ -573,12 +597,10 @@ class Example { /// /// - Parameter instanceIdentifier: The database instance identifier of the /// database to wait for. - func waitUntilDBInstanceReady(instanceIdentifier: String) async { + func waitUntilDBInstanceReady(instanceIdentifier: String) async -> RDSClientTypes.Endpoint? { do { - var instanceReady = false - putString("Waiting for the database instance to be ready to use. This may take 10 minutes or more...") - while !instanceReady { + while true { let output = try await rdsClient.describeDBInstances( input: DescribeDBInstancesInput( dbInstanceIdentifier: instanceIdentifier @@ -598,32 +620,7 @@ class Example { } if status.contains("available") { - guard let instanceEndpoint = instance.endpoint else { - print("\n*** Instance is available but no endpoint returned!") - return - } - - guard let endpointAddress = instanceEndpoint.address else { - print("\nNo endpoint address returned.") - return - } - guard let endpointPort = instanceEndpoint.port else { - print("\nNo endpoint port returned.") - return - } - guard let username = instance.masterUsername else { - print("\nNo main username returned.") - return - } - - //===================================================================== - // 12. Display connection information for the database instance. - //===================================================================== - - print("\nTo connect to the new database instance using 'mysql' from the shell:") - print(" mysql -h \(endpointAddress) -P \(endpointPort) -u \(username)") - - instanceReady = true + return instance.endpoint } else { putString(".") do { @@ -636,6 +633,7 @@ class Example { } } catch { print("*** Unable to wait until the database is ready: \(error.localizedDescription)") + return nil } } // snippet-end:[swift.rds.DescribeDBInstances] From c406e6dfff6a8233ac445b48198eef8bf6d3ee95 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 8 Aug 2025 17:56:48 +0000 Subject: [PATCH 03/12] Replace broken readme --- swift/example_code/cognito-identity-provider/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/example_code/cognito-identity-provider/README.md b/swift/example_code/cognito-identity-provider/README.md index d8cc1639667..026dfbafde7 100644 --- a/swift/example_code/cognito-identity-provider/README.md +++ b/swift/example_code/cognito-identity-provider/README.md @@ -48,7 +48,7 @@ Code excerpts that show you how to call individual service functions. Code examples that show you how to accomplish a specific task by calling multiple functions within the same service. -- [Sign up a user with a user pool that requires MFA](../swift-sdk/http-config/Package.swift) +- [Sign up a user with a user pool that requires MFA](scenario/Package.swift) From 11b995eb01640f3e36fe48d9c9dfe174ce6fe8ca Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 8 Aug 2025 20:51:48 +0000 Subject: [PATCH 04/12] Remove dead code --- .../example_code/rds/scenario/Sources/entry.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/swift/example_code/rds/scenario/Sources/entry.swift b/swift/example_code/rds/scenario/Sources/entry.swift index 39e45def10d..0dd497f52fc 100644 --- a/swift/example_code/rds/scenario/Sources/entry.swift +++ b/swift/example_code/rds/scenario/Sources/entry.swift @@ -20,20 +20,6 @@ struct ExampleCommand: ParsableCommand { @Option(help: "The password to use for the database administrator.") var dbPassword: String - @Option( - help: ArgumentHelp("The level of logging for the Swift SDK to perform."), - completion: .list([ - "critical", - "debug", - "error", - "info", - "notice", - "trace", - "warning" - ]) - ) - var logLevel: String = "error" - static var configuration = CommandConfiguration( commandName: "rds-scenario", abstract: """ From b7567e903737b96657e506c5af477d898d34cdf3 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Mon, 11 Aug 2025 14:38:56 +0000 Subject: [PATCH 05/12] Fix readme problem --- swift/example_code/rds/README.md | 24 +++++++++---------- .../swift-sdk/http-config/Package.swift | 2 -- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/swift/example_code/rds/README.md b/swift/example_code/rds/README.md index dec47f0dfdd..f324bcc0462 100644 --- a/swift/example_code/rds/README.md +++ b/swift/example_code/rds/README.md @@ -40,18 +40,18 @@ Code examples that show you how to perform the essential operations within a ser Code excerpts that show you how to call individual service functions. -- [CreateDBInstance](scenario/Sources/entry.swift#L543) -- [CreateDBParameterGroup](scenario/Sources/entry.swift#L340) -- [CreateDBSnapshot](scenario/Sources/entry.swift#L641) -- [DeleteDBInstance](scenario/Sources/entry.swift#L719) -- [DeleteDBParameterGroup](scenario/Sources/entry.swift#L784) -- [DescribeDBEngineVersions](scenario/Sources/entry.swift#L317) -- [DescribeDBInstances](scenario/Sources/entry.swift#L595) -- [DescribeDBParameterGroups](scenario/Sources/entry.swift#L364) -- [DescribeDBParameters](scenario/Sources/entry.swift#L388) -- [DescribeDBSnapshots](scenario/Sources/entry.swift#L670) -- [DescribeOrderableDBInstanceOptions](scenario/Sources/entry.swift#L492) -- [ModifyDBParameterGroup](scenario/Sources/entry.swift#L427) +- [CreateDBInstance](scenario/Sources/entry.swift#L529) +- [CreateDBParameterGroup](scenario/Sources/entry.swift#L326) +- [CreateDBSnapshot](scenario/Sources/entry.swift#L627) +- [DeleteDBInstance](scenario/Sources/entry.swift#L705) +- [DeleteDBParameterGroup](scenario/Sources/entry.swift#L770) +- [DescribeDBEngineVersions](scenario/Sources/entry.swift#L303) +- [DescribeDBInstances](scenario/Sources/entry.swift#L581) +- [DescribeDBParameterGroups](scenario/Sources/entry.swift#L350) +- [DescribeDBParameters](scenario/Sources/entry.swift#L374) +- [DescribeDBSnapshots](scenario/Sources/entry.swift#L656) +- [DescribeOrderableDBInstanceOptions](scenario/Sources/entry.swift#L478) +- [ModifyDBParameterGroup](scenario/Sources/entry.swift#L413) diff --git a/swift/example_code/swift-sdk/http-config/Package.swift b/swift/example_code/swift-sdk/http-config/Package.swift index 6c70287af94..b0317651016 100644 --- a/swift/example_code/swift-sdk/http-config/Package.swift +++ b/swift/example_code/swift-sdk/http-config/Package.swift @@ -5,7 +5,6 @@ // (swift-tools-version has two lines here because it needs to be the first // line in the file, but it should also appear in the snippet below) // -// snippet-start:[swift.cognito-identity-provider.scenario.package] // swift-tools-version: 5.9 // // The swift-tools-version declares the minimum version of Swift required to @@ -44,4 +43,3 @@ let package = Package( ] ) -// snippet-end:[swift.cognito-identity-provider.scenario.package] From dda8d154ce7402f31986cfe84a691854552e8ea7 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Mon, 11 Aug 2025 17:04:16 +0000 Subject: [PATCH 06/12] Fix copy-paste error in comments --- swift/example_code/rds/scenario/Sources/entry.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/example_code/rds/scenario/Sources/entry.swift b/swift/example_code/rds/scenario/Sources/entry.swift index 0dd497f52fc..a0e5349465b 100644 --- a/swift/example_code/rds/scenario/Sources/entry.swift +++ b/swift/example_code/rds/scenario/Sources/entry.swift @@ -3,7 +3,7 @@ // // snippet-start:[swift.rds.scenario] // An example that shows how to use the AWS SDK for Swift to perform a variety -// of operations using Amazon Elastic Compute Cloud (EC2). +// of operations using Amazon Relational Database Service (RDS). // import ArgumentParser From e825d3fbaa65eb38e16540053790b75d6443fc59 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Mon, 11 Aug 2025 19:27:58 +0000 Subject: [PATCH 07/12] Consistency tweaks --- swift/example_code/rds/scenario/Sources/entry.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swift/example_code/rds/scenario/Sources/entry.swift b/swift/example_code/rds/scenario/Sources/entry.swift index a0e5349465b..b1cb2f90905 100644 --- a/swift/example_code/rds/scenario/Sources/entry.swift +++ b/swift/example_code/rds/scenario/Sources/entry.swift @@ -53,10 +53,10 @@ class Example { init(region: String, username: String, password: String) async throws{ let rdsConfig = try await RDSClient.RDSClientConfiguration(region: region) - self.rdsClient = RDSClient(config: rdsConfig) + rdsClient = RDSClient(config: rdsConfig) - self.dbUsername = username - self.dbPassword = password + dbUsername = username + dbPassword = password dbParameterGroupName = "" dbInstanceIdentifier = "" dbSnapshotIdentifier = "" From 82e73addf1c3c5ec3d6ea419ca1e6073e84586ef Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Fri, 1 Aug 2025 05:22:32 -0400 Subject: [PATCH 08/12] Dotnetv4: Add CreatePresignedPost in S3 (#7536) * Add CreatePresignedPost examples for dotnetv4 --- .doc_gen/metadata/s3_metadata.yaml | 20 ++ .../Actions/HelloBedrockRuntime.cs | 3 +- dotnetv4/DotNetV4Examples.sln | 21 +- dotnetv4/S3/README.md | 97 ++++++ dotnetv4/S3/S3Examples.sln | 38 +++ .../S3_CreatePresignedPost/Basics.csproj | 18 ++ .../CreatePresignedPostBasics.cs | 296 ++++++++++++++++++ .../S3_CreatePresignedPost/S3Wrapper.cs | 174 ++++++++++ .../S3_CreatePresignedPost/UiMethods.cs | 72 +++++ .../S3_CreatePresignedPost/Usings.cs | 11 + .../Tests/CreatePresignedPostBasicsTests.cs | 77 +++++ dotnetv4/S3/Tests/S3Tests.csproj | 30 ++ dotnetv4/S3/Tests/Usings.cs | 8 + 13 files changed, 862 insertions(+), 3 deletions(-) create mode 100644 dotnetv4/S3/README.md create mode 100644 dotnetv4/S3/S3Examples.sln create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs create mode 100644 dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs create mode 100644 dotnetv4/S3/Tests/S3Tests.csproj create mode 100644 dotnetv4/S3/Tests/Usings.cs diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index 462f07881e0..b3e0532a61e 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -2776,6 +2776,12 @@ s3_Scenario_PresignedUrl: - description: Generate a presigned URL and perform an upload using that URL. snippet_tags: - S3.dotnetv3.UploadUsingPresignedURLExample + - sdk_version: 4 + github: dotnetv4/S3/Scenarios/S3_CreatePresignedPost + excerpts: + - description: Create and use presigned POST URLs for direct browser uploads. + snippet_tags: + - S3.dotnetv4.CreatePresignedPostBasics Java: versions: - sdk_version: 2 @@ -3705,3 +3711,17 @@ s3_Scenario_DoesBucketExist: - s3.java2.does-bucket-exist-main services: s3: {GetBucketAcl} + +s3_CreatePresignedPost: + languages: + .NET: + versions: + - sdk_version: 4 + github: dotnetv4/S3 + excerpts: + - description: Create a presigned POST URL. + genai: most + snippet_tags: + - S3.dotnetv4.Scenario_CreatePresignedPostAsync + services: + s3: {CreatePresignedPost} diff --git a/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs b/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs index 1ee5a0bf9ea..e29cb38778f 100644 --- a/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs +++ b/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs @@ -28,7 +28,8 @@ private static async Task Invoke(string modelId, string prompt) default: Console.WriteLine($"Unknown model ID: {modelId}. Valid model IDs are: {CLAUDE}."); break; - }; + } + ; } } } \ No newline at end of file diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln index 9c3a74c6cc2..8b243f70231 100644 --- a/dotnetv4/DotNetV4Examples.sln +++ b/dotnetv4/DotNetV4Examples.sln @@ -149,7 +149,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "DynamoDB\Scenario EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBActions", "DynamoDB\Actions\DynamoDBActions.csproj", "{B0F91FE2-6AC5-4FA8-B321-54623A516D4D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "S3", "S3", "{F929DB74-DD0E-B0EF-AA66-D8703D547BBD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Tests", "S3\Tests\S3Tests.csproj", "{11497EB7-B702-B537-3CBE-BA2F4F85F313}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{A65C33EA-4F2E-DE85-7501-4389A2100813}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "S3\Scenarios\S3_CreatePresignedPost\Basics.csproj", "{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -365,6 +371,14 @@ Global {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|Any CPU.Build.0 = Release|Any CPU + {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|Any CPU.Build.0 = Release|Any CPU + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -425,6 +439,7 @@ Global {3F159C49-3DE7-42F5-AF14-E64C03AF19E8} = {EE6D1933-1E38-406A-B691-446326310D1F} {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F} = {EE6D1933-1E38-406A-B691-446326310D1F} {7485EAED-F81C-4119-BABC-E009A21ACE46} = {EE6D1933-1E38-406A-B691-446326310D1F} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} {43C5E98B-5EC4-9F2B-2676-8F1E34969855} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {6BE1D9A4-1832-49F5-8682-6DEE4A7D6232} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6} = {6BE1D9A4-1832-49F5-8682-6DEE4A7D6232} @@ -432,7 +447,9 @@ Global {F578CA07-E74F-4F47-9203-C67777D9BB78} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} {E10920BB-6409-41BB-9A9D-813BC37CC3C0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {B0F91FE2-6AC5-4FA8-B321-54623A516D4D} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} - {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {11497EB7-B702-B537-3CBE-BA2F4F85F313} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} + {A65C33EA-4F2E-DE85-7501-4389A2100813} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320} = {A65C33EA-4F2E-DE85-7501-4389A2100813} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA} diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md new file mode 100644 index 00000000000..cf1e626503e --- /dev/null +++ b/dotnetv4/S3/README.md @@ -0,0 +1,97 @@ +# Amazon S3 code examples for the SDK for .NET (v4) + +## Overview + +Shows how to use the AWS SDK for .NET (v4) to work with Amazon Simple Storage Service (Amazon S3). + + + + +_Amazon S3 is storage for the internet. You can use Amazon S3 to store and retrieve any amount of data at any time, from anywhere on the web._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv4` folder. + + + + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreatePresignedPost](Scenarios/S3_CreatePresignedPost/S3Wrapper.cs#L35) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +- [Create a presigned URL](Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs) + + + + + +## Run the examples + +### Instructions + + + + + + + +#### Create a presigned URL + +This example shows you how to create a presigned URL for Amazon S3 and upload an object. + + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../README.md#Tests) +in the `dotnetv4` folder. + + + + + + +## Additional resources + +- [Amazon S3 User Guide](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) +- [Amazon S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) +- [SDK for .NET (v4) Amazon S3 reference](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/NS3.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/dotnetv4/S3/S3Examples.sln b/dotnetv4/S3/S3Examples.sln new file mode 100644 index 00000000000..10c6bc5d832 --- /dev/null +++ b/dotnetv4/S3/S3Examples.sln @@ -0,0 +1,38 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33414.496 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{C13DDD1A-438D-4E52-90FB-A496A54516C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C51625C8-3B42-4810-BF1B-0E3C6C716FA6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "Scenarios\S3_CreatePresignedPost\Basics.csproj", "{22C217CC-E2D9-9F79-EE15-24F9D262655E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Tests", "Tests\S3Tests.csproj", "{FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Release|Any CPU.Build.0 = Release|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {22C217CC-E2D9-9F79-EE15-24F9D262655E} = {C13DDD1A-438D-4E52-90FB-A496A54516C7} + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C} = {C51625C8-3B42-4810-BF1B-0E3C6C716FA6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5ACB5B08-E8F8-453C-B63B-6C0C9DE67780} + EndGlobalSection +EndGlobal diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj new file mode 100644 index 00000000000..75694d45011 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs new file mode 100644 index 00000000000..5d8d70ead1e --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -0,0 +1,296 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Scenarios; + +// snippet-start:[S3.dotnetv4.CreatePresignedPostBasics] +/// +/// Scenario demonstrating the complete workflow for presigned POST URLs: +/// 1. Create an S3 bucket +/// 2. Create a presigned POST URL +/// 3. Upload a file using the presigned POST URL +/// 4. Clean up resources +/// +public class CreatePresignedPostBasics +{ + public static ILogger _logger = null!; + public static S3Wrapper _s3Wrapper = null!; + public static UiMethods _uiMethods = null!; + public static IHttpClientFactory _httpClientFactory = null!; + public static bool _isInteractive = true; + public static string? _bucketName; + public static string? _objectKey; + + /// + /// Set up the services and logging. + /// + /// The IHost instance. + public static void SetUpServices(IHost host) + { + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + _logger = new Logger(loggerFactory); + + _s3Wrapper = host.Services.GetRequiredService(); + _httpClientFactory = host.Services.GetRequiredService(); + _uiMethods = new UiMethods(); + } + + /// + /// Perform the actions defined for the Amazon S3 Presigned POST scenario. + /// + /// Command line arguments. + /// A Task object. + public static async Task Main(string[] args) + { + _isInteractive = !args.Contains("--non-interactive"); + + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + .AddHttpClient() + ) + .Build(); + + SetUpServices(host); + + try + { + // Display overview + _uiMethods.DisplayOverview(); + _uiMethods.PressEnter(_isInteractive); + + // Step 1: Create bucket + await CreateBucketAsync(); + _uiMethods.PressEnter(_isInteractive); + + // Step 2: Create presigned URL + _uiMethods.DisplayTitle("Step 2: Create presigned POST URL"); + var response = await CreatePresignedPostAsync(); + _uiMethods.PressEnter(_isInteractive); + + // Step 3: Display URL and fields + _uiMethods.DisplayTitle("Step 3: Presigned POST URL details"); + DisplayPresignedPostFields(response); + _uiMethods.PressEnter(_isInteractive); + + // Step 4: Upload file + _uiMethods.DisplayTitle("Step 4: Upload test file using presigned POST URL"); + await UploadFileAsync(response); + _uiMethods.PressEnter(_isInteractive); + + // Step 5: Verify file exists + await VerifyFileExistsAsync(); + _uiMethods.PressEnter(_isInteractive); + + // Step 6: Cleanup + _uiMethods.DisplayTitle("Step 6: Clean up resources"); + await CleanupAsync(); + + _uiMethods.DisplayTitle("S3 Presigned POST Scenario completed successfully!"); + _uiMethods.PressEnter(_isInteractive); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in scenario"); + Console.WriteLine($"Error: {ex.Message}"); + + // Attempt cleanup if there was an error + if (!string.IsNullOrEmpty(_bucketName)) + { + _uiMethods.DisplayTitle("Cleaning up resources after error"); + await _s3Wrapper.DeleteBucketAsync(_bucketName); + Console.WriteLine($"Cleaned up bucket: {_bucketName}"); + } + } + } + + /// + /// Create an S3 bucket for the scenario. + /// + private static async Task CreateBucketAsync() + { + _uiMethods.DisplayTitle("Step 1: Create an S3 bucket"); + + // Generate a default bucket name for the scenario + var defaultBucketName = $"presigned-post-demo-{DateTime.Now:yyyyMMddHHmmss}".ToLower(); + + // Prompt user for bucket name or use default in non-interactive mode + _bucketName = _uiMethods.GetUserInput( + $"Enter S3 bucket name (or press Enter for '{defaultBucketName}'): ", + defaultBucketName, + _isInteractive); + + // Basic validation to ensure bucket name is not empty + if (string.IsNullOrWhiteSpace(_bucketName)) + { + _bucketName = defaultBucketName; + } + + Console.WriteLine($"Creating bucket: {_bucketName}"); + + await _s3Wrapper.CreateBucketAsync(_bucketName); + + Console.WriteLine($"Successfully created bucket: {_bucketName}"); + } + + + /// + /// Create a presigned POST URL. + /// + private static async Task CreatePresignedPostAsync() + { + _objectKey = "example-upload.txt"; + var expiration = DateTime.UtcNow.AddMinutes(10); // Short expiration for the demo + + Console.WriteLine($"Creating presigned POST URL for {_bucketName}/{_objectKey}"); + Console.WriteLine($"Expiration: {expiration} UTC"); + + var s3Client = _s3Wrapper.GetS3Client(); + + var response = await _s3Wrapper.CreatePresignedPostAsync( + s3Client, _bucketName!, _objectKey, expiration); + + Console.WriteLine("Successfully created presigned POST URL"); + return response; + } + + /// + /// Upload a file using the presigned POST URL. + /// + private static async Task UploadFileAsync(CreatePresignedPostResponse response) + { + + // Create a temporary test file to upload + string testFilePath = Path.GetTempFileName(); + string testContent = "This is a test file for the S3 presigned POST scenario."; + + await File.WriteAllTextAsync(testFilePath, testContent); + Console.WriteLine($"Created test file at: {testFilePath}"); + + // Upload the file using the presigned POST URL + Console.WriteLine("\nUploading file using the presigned POST URL..."); + var uploadResult = await UploadFileWithPresignedPostAsync(response, testFilePath); + + // Display the upload result + if (uploadResult.Success) + { + Console.WriteLine($"Upload successful! Status code: {uploadResult.StatusCode}"); + } + else + { + Console.WriteLine($"Upload failed with status code: {uploadResult.StatusCode}"); + Console.WriteLine($"Error: {uploadResult.Response}"); + throw new Exception("File upload failed"); + } + + // Clean up the temporary file + File.Delete(testFilePath); + Console.WriteLine("Temporary file deleted"); + } + + /// + /// Helper method to upload a file using a presigned POST URL. + /// + private static async Task<(bool Success, HttpStatusCode StatusCode, string Response)> UploadFileWithPresignedPostAsync( + CreatePresignedPostResponse response, + string filePath) + { + try + { + _logger.LogInformation("Uploading file {filePath} using presigned POST URL", filePath); + + using var httpClient = _httpClientFactory.CreateClient(); + using var formContent = new MultipartFormDataContent(); + + // Add all the fields from the presigned POST response + foreach (var field in response.Fields) + { + formContent.Add(new StringContent(field.Value), field.Key); + } + + // Add the file content + var fileStream = File.OpenRead(filePath); + var fileName = Path.GetFileName(filePath); + var fileContent = new StreamContent(fileStream); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); + formContent.Add(fileContent, "file", fileName); + + // Send the POST request + var httpResponse = await httpClient.PostAsync(response.Url, formContent); + var responseContent = await httpResponse.Content.ReadAsStringAsync(); + + // Log and return the result + _logger.LogInformation("Upload completed with status code {statusCode}", httpResponse.StatusCode); + + return (httpResponse.IsSuccessStatusCode, httpResponse.StatusCode, responseContent); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error uploading file"); + return (false, HttpStatusCode.InternalServerError, ex.Message); + } + } + + /// + /// Verify that the uploaded file exists in the S3 bucket. + /// + private static async Task VerifyFileExistsAsync() + { + _uiMethods.DisplayTitle("Step 5: Verify uploaded file exists"); + + Console.WriteLine($"Checking if file exists at {_bucketName}/{_objectKey}..."); + + try + { + var metadata = await _s3Wrapper.GetObjectMetadataAsync(_bucketName!, _objectKey!); + + Console.WriteLine($"File verification successful! File exists in the bucket."); + Console.WriteLine($"File size: {metadata.ContentLength} bytes"); + Console.WriteLine($"File type: {metadata.Headers.ContentType}"); + Console.WriteLine($"Last modified: {metadata.LastModified}"); + } + catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + Console.WriteLine($"Error: File was not found in the bucket."); + throw; + } + } + + private static void DisplayPresignedPostFields(CreatePresignedPostResponse response) + { + Console.WriteLine($"Presigned POST URL: {response.Url}"); + Console.WriteLine("Form fields to include:"); + + foreach (var field in response.Fields) + { + Console.WriteLine($" {field.Key}: {field.Value}"); + } + } + + /// + /// Clean up resources created by the scenario. + /// + private static async Task CleanupAsync() + { + if (!string.IsNullOrEmpty(_bucketName)) + { + Console.WriteLine($"Deleting bucket {_bucketName} and its contents..."); + bool result = await _s3Wrapper.DeleteBucketAsync(_bucketName); + + if (result) + { + Console.WriteLine("Bucket deleted successfully"); + } + else + { + Console.WriteLine("Failed to delete bucket - it may have been already deleted"); + } + } + } +} +// snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] \ No newline at end of file diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs new file mode 100644 index 00000000000..12e629ed827 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs @@ -0,0 +1,174 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Scenarios; + +/// +/// Wrapper methods for common Amazon Simple Storage Service (Amazon S3) +/// operations. +/// +public class S3Wrapper +{ + private readonly IAmazonS3 _s3Client; + private readonly ILogger _logger; + + /// + /// Constructor for the wrapper class. + /// + /// The injected S3 client. + /// The injected logger for use with this class. + public S3Wrapper(IAmazonS3 s3Client, ILogger logger) + { + _s3Client = s3Client; + _logger = logger; + } + + /// + /// Get the Amazon S3 client. + /// + /// The Amazon S3 client. + public IAmazonS3 GetS3Client() + { + return _s3Client; + } + + // snippet-start:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] + /// + /// Create a presigned POST URL with conditions. + /// + /// The Amazon S3 client. + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// Dictionary of fields to add to the form. + /// List of conditions to apply. + /// A CreatePresignedPostResponse object with URL and form fields. + public async Task CreatePresignedPostAsync( + IAmazonS3 s3Client, + string bucketName, + string objectKey, + DateTime expires, + Dictionary? fields = null, + List? conditions = null) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + // Add custom fields if provided + if (fields != null) + { + foreach (var field in fields) + { + request.Fields.Add(field.Key, field.Value); + } + } + + // Add conditions if provided + if (conditions != null) + { + foreach (var condition in conditions) + { + request.Conditions.Add(condition); + } + } + + return await s3Client.CreatePresignedPostAsync(request); + } + // snippet-end:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] + + /// + /// Create a bucket and wait until it's ready to use. + /// + /// The name of the bucket to create. + /// The name of the newly created bucket. + public async Task CreateBucketAsync(string bucketName) + { + _logger.LogInformation("Creating bucket {bucket}", bucketName); + + var request = new PutBucketRequest + { + BucketName = bucketName + }; + + var response = await _s3Client.PutBucketAsync(request); + + _logger.LogInformation("Created bucket {bucket} with status {status}", + bucketName, response.HttpStatusCode); + + // Wait for the bucket to be available + var exist = await Amazon.S3.Util.AmazonS3Util.DoesS3BucketExistV2Async(_s3Client, bucketName); + + if (!exist) + { + _logger.LogInformation("Waiting for bucket {bucket} to be ready", bucketName); + + while (!exist) + { + await Task.Delay(2000); + exist = await Amazon.S3.Util.AmazonS3Util.DoesS3BucketExistV2Async(_s3Client, bucketName); + } + } + + return bucketName; + } + + /// + /// Delete an object from an S3 bucket. + /// + /// The name of the bucket. + /// The object key to delete. + /// The response from the DeleteObjectAsync call. + public async Task DeleteObjectAsync( + string bucketName, string objectKey) + { + var request = new DeleteObjectRequest + { + BucketName = bucketName, + Key = objectKey + }; + + return await _s3Client.DeleteObjectAsync(request); + } + + /// + /// Delete an S3 bucket and all its objects. + /// + /// The name of the bucket to delete. + /// A boolean value indicating the success of the operation. + public async Task DeleteBucketAsync(string bucketName) + { + try + { + // Delete all objects in the bucket + await AmazonS3Util.DeleteS3BucketWithObjectsAsync(_s3Client, bucketName); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting bucket {bucket}", bucketName); + return false; + } + } + + /// + /// Get object metadata. + /// + /// The name of the bucket. + /// The object key. + /// Object metadata. + public async Task GetObjectMetadataAsync( + string bucketName, string objectKey) + { + var request = new GetObjectMetadataRequest + { + BucketName = bucketName, + Key = objectKey + }; + + return await _s3Client.GetObjectMetadataAsync(request); + } +} \ No newline at end of file diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs new file mode 100644 index 00000000000..fdedb6b30d5 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs @@ -0,0 +1,72 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Scenarios; + +/// +/// UI helper methods for the S3 presigned POST scenario. +/// +public class UiMethods +{ + public readonly string SepBar = new string('-', 88); + + /// + /// Show information about the scenario. + /// + public void DisplayOverview() + { + DisplayTitle("Welcome to the Amazon S3 Presigned POST URL Scenario"); + + Console.WriteLine("This example application does the following:"); + Console.WriteLine("\t 1. Creates an S3 bucket with a unique name"); + Console.WriteLine("\t 2. Creates a presigned POST URL for the bucket"); + Console.WriteLine("\t 3. Displays the URL and form fields needed for browser uploads"); + Console.WriteLine("\t 4. Uploads a test file using the presigned POST URL"); + Console.WriteLine("\t 5. Verifies the file was successfully uploaded to S3"); + Console.WriteLine("\t 6. Cleans up the resources (bucket and test file)"); + } + + /// + /// Display a message and wait until the user presses enter if in interactive mode. + /// + public void PressEnter(bool interactive) + { + Console.Write("\nPlease press to continue. "); + if (interactive) + _ = Console.ReadLine(); + } + + /// + /// Display a line of hyphens, the text of the title and another + /// line of hyphens. + /// + /// The string to be displayed. + public void DisplayTitle(string strTitle) + { + Console.WriteLine(SepBar); + Console.WriteLine(strTitle); + Console.WriteLine(SepBar); + } + + /// + /// Get user input with an optional default value. + /// + /// The prompt to display to the user. + /// The default value to use if user doesn't provide input. + /// Whether to wait for user input or use default. + /// The user input or default value. + public string GetUserInput(string prompt, string? defaultValue = null, bool isInteractive = true) + { + Console.Write(prompt); + if (isInteractive) + { + var input = Console.ReadLine(); + return string.IsNullOrWhiteSpace(input) ? defaultValue ?? "" : input.Trim(); + } + else + { + Console.WriteLine(defaultValue ?? ""); + return defaultValue ?? ""; + } + } +} \ No newline at end of file diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs new file mode 100644 index 00000000000..f3e8a913cc9 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using System.Net; +global using System.Net.Http.Headers; +global using Amazon.S3; +global using Amazon.S3.Model; +global using Amazon.S3.Util; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; \ No newline at end of file diff --git a/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs b/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs new file mode 100644 index 00000000000..2c44f6ff5d7 --- /dev/null +++ b/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs @@ -0,0 +1,77 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Tests; + +/// +/// Integration tests for the Amazon Simple Storage Service (Amazon S3) +/// presigned POST URL scenario (CreatePresignedPostBasics). +/// +public class CreatePresignedPostBasicsTests +{ + private AmazonS3Client _client = null!; + private S3Wrapper _s3Wrapper = null!; + + /// + /// Verifies the presigned POST URL scenario with an integration test. No errors should be logged. + /// + /// Async task. + [Fact] + [Trait("Category", "Integration")] + public async Task TestScenario() + { + // Arrange. + var loggerScenarioMock = new Mock>(); + var loggerWrapperMock = new Mock>(); + var uiMethods = new S3Scenarios.UiMethods(); + + _client = new AmazonS3Client(); + _s3Wrapper = new S3Wrapper(_client, loggerWrapperMock.Object); + + // Set up the static fields directly + S3Scenarios.CreatePresignedPostBasics._logger = loggerScenarioMock.Object; + S3Scenarios.CreatePresignedPostBasics._s3Wrapper = _s3Wrapper; + S3Scenarios.CreatePresignedPostBasics._uiMethods = uiMethods; + S3Scenarios.CreatePresignedPostBasics._isInteractive = false; + + // Set up verification for error logging. + loggerScenarioMock.Setup(logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>() + )); + + loggerWrapperMock.Setup(logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>() + )); + + // Act. + // Call the static Main method with --non-interactive flag to match previous behavior + await S3Scenarios.CreatePresignedPostBasics.Main(new string[] { "--non-interactive" }); + + // Assert no exceptions or errors logged. + loggerScenarioMock.Verify( + logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>()), + Times.Never); + + loggerWrapperMock.Verify( + logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>()), + Times.Never); + } +} \ No newline at end of file diff --git a/dotnetv4/S3/Tests/S3Tests.csproj b/dotnetv4/S3/Tests/S3Tests.csproj new file mode 100644 index 00000000000..81cfde53213 --- /dev/null +++ b/dotnetv4/S3/Tests/S3Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/dotnetv4/S3/Tests/Usings.cs b/dotnetv4/S3/Tests/Usings.cs new file mode 100644 index 00000000000..7fa523be4c4 --- /dev/null +++ b/dotnetv4/S3/Tests/Usings.cs @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using Amazon.S3; +global using Microsoft.Extensions.Logging; +global using Moq; +global using S3Scenarios; +global using Xunit; \ No newline at end of file From 71d7fe45b9019d0f53b1cf23aa54893b8acb4362 Mon Sep 17 00:00:00 2001 From: Max Esser Date: Tue, 5 Aug 2025 07:38:43 -0500 Subject: [PATCH 09/12] Python: Fix type annotations in KMS client methods (#7520) * Update key_encryption.py --- python/example_code/kms/key_encryption.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/example_code/kms/key_encryption.py b/python/example_code/kms/key_encryption.py index d2faa2a40ec..ed036458b72 100644 --- a/python/example_code/kms/key_encryption.py +++ b/python/example_code/kms/key_encryption.py @@ -35,7 +35,7 @@ def from_client(cls) -> "KeyEncrypt": # snippet-end:[python.example_code.kms.KeyEncrypt.decl] # snippet-start:[python.example_code.kms.Encrypt] - def encrypt(self, key_id: str, text: str) -> str: + def encrypt(self, key_id: str, text: str) -> bytes: """ Encrypts text by using the specified key. @@ -64,7 +64,7 @@ def encrypt(self, key_id: str, text: str) -> str: # snippet-end:[python.example_code.kms.Encrypt] # snippet-start:[python.example_code.kms.Decrypt] - def decrypt(self, key_id: str, cipher_text: str) -> bytes: + def decrypt(self, key_id: str, cipher_text: bytes) -> str: """ Decrypts text previously encrypted with a key. @@ -75,7 +75,7 @@ def decrypt(self, key_id: str, cipher_text: str) -> bytes: try: return self.kms_client.decrypt(KeyId=key_id, CiphertextBlob=cipher_text)[ "Plaintext" - ] + ].decode() except ClientError as err: logger.error( "Couldn't decrypt your ciphertext. Here's why: %s", @@ -198,7 +198,7 @@ def key_encryption(kms_client): answer = input("Ready to decrypt your ciphertext (y/n)? ") if answer.lower() == "y": decrypted_text = key_encrypt.decrypt(key_id, cipher_text) - print(f"Your plaintext is {decrypted_text.decode()}") + print(f"Your plaintext is {decrypted_text}") print("-" * 88) key_encrypt.re_encrypt(key_id, cipher_text) else: From be62f59dfbe401fce12dae10e653ccaac38a8f7a Mon Sep 17 00:00:00 2001 From: Ping Liu <118313457+ypliu-AWS@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:00:41 -0500 Subject: [PATCH 10/12] Updated metadata for Entity Resolution and Location services to fix broken links in code examples (#7538) --- .../metadata/entityresolution_metadata.yaml | 26 +++---------------- .doc_gen/metadata/location_metadata.yaml | 2 +- .../example_code/entityresolution/README.md | 5 ++-- javascriptv3/example_code/location/README.md | 2 +- .../example_code/entityresolution/README.md | 5 ++-- javav2/example_code/location/README.md | 2 +- kotlin/services/location/README.md | 2 +- 7 files changed, 11 insertions(+), 33 deletions(-) diff --git a/.doc_gen/metadata/entityresolution_metadata.yaml b/.doc_gen/metadata/entityresolution_metadata.yaml index a992fe8359e..141cd4427b5 100644 --- a/.doc_gen/metadata/entityresolution_metadata.yaml +++ b/.doc_gen/metadata/entityresolution_metadata.yaml @@ -21,7 +21,7 @@ entityresolution_Hello: snippet_tags: - javascript.v3.entity-resolution.hello services: - entityresolution: {listMatchingWorkflows} + entityresolution: {ListMatchingWorkflows} entityresolution_DeleteSchemaMapping: languages: Java: @@ -42,7 +42,7 @@ entityresolution_DeleteSchemaMapping: - entity-resolution.JavaScriptv3.delete.schema-mapping services: entityresolution: {DeleteSchemaMapping} -entityresolution_TagEntityResource: +entityresolution_TagResource: languages: Java: versions: @@ -61,7 +61,7 @@ entityresolution_TagEntityResource: snippet_tags: - entity-resolution.JavaScriptv3.tag.entity-resource services: - entityresolution: {TagEntityResource} + entityresolution: {TagResource} entityresolution_CreateMatchingWorkflow: languages: Java: @@ -82,26 +82,6 @@ entityresolution_CreateMatchingWorkflow: - entity-resolution.JavaScriptv3.create-matching-workflow services: entityresolution: {CreateMatchingWorkflow} -entityresolution_CheckWorkflowStatus: - languages: - Java: - versions: - - sdk_version: 2 - github: javav2/example_code/entityresolution - excerpts: - - description: - snippet_tags: - - entityres.java2_check_matching_workflow.main - JavaScript: - versions: - - sdk_version: 3 - github: javascriptv3/example_code/entityresolution - excerpts: - - description: - snippet_tags: - - entity-resolution.JavaScriptv3.check-workflow-status - services: - entityresolution: {CheckWorkflowStatus} entityresolution_StartMatchingJob: languages: Java: diff --git a/.doc_gen/metadata/location_metadata.yaml b/.doc_gen/metadata/location_metadata.yaml index 8185fa9d04f..f87c5430df0 100644 --- a/.doc_gen/metadata/location_metadata.yaml +++ b/.doc_gen/metadata/location_metadata.yaml @@ -33,7 +33,7 @@ location_Hello: snippet_tags: - javascript.v3.location.hello services: - location: {ListGeofencesPaginator} + location: {ListGeofences, ListGeofenceCollections} location_CreateMap: languages: Kotlin: diff --git a/javascriptv3/example_code/entityresolution/README.md b/javascriptv3/example_code/entityresolution/README.md index 891d420c4b3..a7071382bcf 100644 --- a/javascriptv3/example_code/entityresolution/README.md +++ b/javascriptv3/example_code/entityresolution/README.md @@ -31,14 +31,13 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `javas ### Get started -- [Hello AWS Entity Resolution](hello.js#L4) (`listMatchingWorkflows`) +- [Hello AWS Entity Resolution](hello.js#L4) (`ListMatchingWorkflows`) ### Single actions Code excerpts that show you how to call individual service functions. -- [CheckWorkflowStatus](actions/check-workflow-status.js#L4) - [CreateMatchingWorkflow](actions/create-matching-workflow.js#L4) - [CreateSchemaMapping](actions/create-schema-mapping.js#L4) - [DeleteMatchingWorkflow](actions/delete-matching-workflow.js#L4) @@ -47,7 +46,7 @@ Code excerpts that show you how to call individual service functions. - [GetSchemaMapping](actions/get-schema-mapping.js#L4) - [ListSchemaMappings](actions/list-schema-mappings.js#L4) - [StartMatchingJob](actions/start-matching-job.js#L4) -- [TagEntityResource](actions/tag-entity-resource.js#L4) +- [TagResource](actions/tag-entity-resource.js#L4) diff --git a/javascriptv3/example_code/location/README.md b/javascriptv3/example_code/location/README.md index 3e8886e1c14..9b4661cc455 100644 --- a/javascriptv3/example_code/location/README.md +++ b/javascriptv3/example_code/location/README.md @@ -31,7 +31,7 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `javas ### Get started -- [Hello Amazon Location](hello.js#L4) (`ListGeofencesPaginator`) +- [Hello Amazon Location](hello.js#L4) (`ListGeofenceCollections`) ### Basics diff --git a/javav2/example_code/entityresolution/README.md b/javav2/example_code/entityresolution/README.md index 70ff18acb90..4e352c7f6e0 100644 --- a/javav2/example_code/entityresolution/README.md +++ b/javav2/example_code/entityresolution/README.md @@ -31,7 +31,7 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `javav ### Get started -- [Hello AWS Entity Resolution](src/main/java/com/example/entity/HelloEntityResoultion.java#L19) (`listMatchingWorkflows`) +- [Hello AWS Entity Resolution](src/main/java/com/example/entity/HelloEntityResoultion.java#L19) (`ListMatchingWorkflows`) ### Basics @@ -45,7 +45,6 @@ Code examples that show you how to perform the essential operations within a ser Code excerpts that show you how to call individual service functions. -- [CheckWorkflowStatus](src/main/java/com/example/entity/scenario/EntityResActions.java#L391) - [CreateMatchingWorkflow](src/main/java/com/example/entity/scenario/EntityResActions.java#L429) - [CreateSchemaMapping](src/main/java/com/example/entity/scenario/EntityResActions.java#L230) - [DeleteMatchingWorkflow](src/main/java/com/example/entity/scenario/EntityResActions.java#L196) @@ -54,7 +53,7 @@ Code excerpts that show you how to call individual service functions. - [GetSchemaMapping](src/main/java/com/example/entity/scenario/EntityResActions.java#L280) - [ListSchemaMappings](src/main/java/com/example/entity/scenario/EntityResActions.java#L173) - [StartMatchingJob](src/main/java/com/example/entity/scenario/EntityResActions.java#L354) -- [TagEntityResource](src/main/java/com/example/entity/scenario/EntityResActions.java#L516) +- [TagResource](src/main/java/com/example/entity/scenario/EntityResActions.java#L516) diff --git a/javav2/example_code/location/README.md b/javav2/example_code/location/README.md index 25861abec23..15a2a647c5c 100644 --- a/javav2/example_code/location/README.md +++ b/javav2/example_code/location/README.md @@ -31,7 +31,7 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `javav ### Get started -- [Hello Amazon Location](src/main/java/com/example/location/HelloLocation.java#L18) (`ListGeofencesPaginator`) +- [Hello Amazon Location](src/main/java/com/example/location/HelloLocation.java#L18) (`ListGeofenceCollections`) ### Basics diff --git a/kotlin/services/location/README.md b/kotlin/services/location/README.md index ce8de6da3f5..7435b58436e 100644 --- a/kotlin/services/location/README.md +++ b/kotlin/services/location/README.md @@ -31,7 +31,7 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `kotli ### Get started -- [Hello Amazon Location](src/main/java/location/HelloLocation.kt#L10) (`ListGeofencesPaginator`) +- [Hello Amazon Location](src/main/java/location/HelloLocation.kt#L10) (`ListGeofenceCollections`) ### Basics From 3401294e21676bb85daf6f59abac524bf5b82b95 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Thu, 14 Aug 2025 07:37:39 -0500 Subject: [PATCH 11/12] Multi: Clean up duplicate snippet tags (#7550) Fixes for duplicate snippets. --- python/example_code/bedrock-agent-runtime/README.md | 4 ++-- python/example_code/bedrock-agent/flows/flow-conversation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/example_code/bedrock-agent-runtime/README.md b/python/example_code/bedrock-agent-runtime/README.md index 70e07c6c8a7..9bc0ffdd17d 100644 --- a/python/example_code/bedrock-agent-runtime/README.md +++ b/python/example_code/bedrock-agent-runtime/README.md @@ -38,7 +38,7 @@ python -m pip install -r requirements.txt Code examples that show you how to perform the essential operations within a service. -- [Learn the basics](../bedrock-agent/flows/flow-conversation.py) +- [Learn the basics](flows/flow-conversation.py) ### Single actions @@ -79,7 +79,7 @@ This example shows you how to use InvokeFlow to converse with an Amazon Bedrock Start the example by running the following at a command prompt: ``` -python ../bedrock-agent/flows/flow-conversation.py +python flows/flow-conversation.py ``` diff --git a/python/example_code/bedrock-agent/flows/flow-conversation.py b/python/example_code/bedrock-agent/flows/flow-conversation.py index d8e08fbad63..0713141ee8a 100644 --- a/python/example_code/bedrock-agent/flows/flow-conversation.py +++ b/python/example_code/bedrock-agent/flows/flow-conversation.py @@ -1,7 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -# snippet-start:[python.example_code.bedrock-agent-runtime.flow_conversation.complete] +# snippet-start:[python.example_code.bedrock-agent.flow_conversation.complete] """ @@ -179,4 +179,4 @@ def main(): if __name__ == "__main__": main() - # snippet-end:[python.example_code.bedrock-agent-runtime.flow_conversation.complete] + # snippet-end:[python.example_code.bedrock-agent.flow_conversation.complete] From ab6bcdc772f6cf87533fb539b3c876e8a9c55fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20M=2E=20K=C3=B6hler?= Date: Thu, 14 Aug 2025 16:46:56 +0200 Subject: [PATCH 12/12] deps: update react to fix Can't resolve 'react/jsx-dev-runtime' error (#7551) --- .../example_code/cross-services/textract-react/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascriptv3/example_code/cross-services/textract-react/package.json b/javascriptv3/example_code/cross-services/textract-react/package.json index b500f6952a9..981d066d8dc 100644 --- a/javascriptv3/example_code/cross-services/textract-react/package.json +++ b/javascriptv3/example_code/cross-services/textract-react/package.json @@ -11,8 +11,8 @@ "@aws-sdk/client-textract": "^3.9.0", "@aws-sdk/credential-provider-cognito-identity": "^3.9.0", "bootstrap": "^5.0.0-beta3", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-scripts": "5.0.0", "web-vitals": "^1.1.1" },