From 41da64071e7294d319a041b2c5ab79b25e6524de Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 2 Jul 2025 20:16:33 +0000 Subject: [PATCH 1/8] Add EC2 hello (basics) example for swift --- swift/example_code/ec2/hello/Package.swift | 47 ++++++++ .../ec2/hello/Sources/entry.swift | 101 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 swift/example_code/ec2/hello/Package.swift create mode 100644 swift/example_code/ec2/hello/Sources/entry.swift diff --git a/swift/example_code/ec2/hello/Package.swift b/swift/example_code/ec2/hello/Package.swift new file mode 100644 index 00000000000..4d842c50a26 --- /dev/null +++ b/swift/example_code/ec2/hello/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.glue.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: "hello-ec2", + // 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.0.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: "hello-ec2", + dependencies: [ + .product(name: "AWSEC2", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.glue.scenario.package] diff --git a/swift/example_code/ec2/hello/Sources/entry.swift b/swift/example_code/ec2/hello/Sources/entry.swift new file mode 100644 index 00000000000..7709a48f613 --- /dev/null +++ b/swift/example_code/ec2/hello/Sources/entry.swift @@ -0,0 +1,101 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.glue.scenario] +// An example that shows how to use the AWS SDK for Swift to perform a simple +// operation using Amazon Elastic Compute Cloud (EC2). +// + + +import ArgumentParser +import Foundation + +// snippet-start:[swift.glue.import] + +// import AWSClientRuntime +import AWSEC2 +// snippet-end:[swift.glue.import] + +struct ExampleCommand: ParsableCommand { + @Option(help: "The AWS Region to run AWS API calls in.") + var awsRegion = "us-east-1" + + @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: "hello-ec2", + abstract: """ + Demonstrates a simple operation using Amazon EC2. + """, + discussion: """ + An example showing how to make a call to Amazon EC2 using the AWS SDK for Swift. + """ + ) + + func getSecurityGroupNames(ec2Client: EC2Client) async -> [String] { + let pages = ec2Client.describeSecurityGroupsPaginated( + input: DescribeSecurityGroupsInput() + ) + + var groupNames: [String] = [] + + do { + for try await page in pages { + guard let groups = page.securityGroups else { + print("*** Error: No groups returned.") + continue + } + + for group in groups { + groupNames.append(group.groupName ?? "") + } + } + } catch { + print("*** Error: \(error.localizedDescription)") + } + + return groupNames + } + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let ec2Config = try await EC2Client.EC2ClientConfiguration(region: awsRegion) + let ec2Client = EC2Client(config: ec2Config) + + let groupNames = await getSecurityGroupNames(ec2Client: ec2Client) + + print("Found \(groupNames.count) security group(s):") + + for group in groupNames { + print(" \(group)") + } + } +} + +/// 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.glue.scenario] From b1147c9b437398babb07f89e796928725780a4bc Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 2 Jul 2025 20:39:09 +0000 Subject: [PATCH 2/8] Hello EC2 example for Swift complete Including metadata updates. --- .doc_gen/metadata/ec2_metadata.yaml | 19 ++++ swift/example_code/ec2/README.md | 98 +++++++++++++++++++ swift/example_code/ec2/hello/Package.swift | 4 +- .../ec2/hello/Sources/entry.swift | 21 ++-- 4 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 swift/example_code/ec2/README.md diff --git a/.doc_gen/metadata/ec2_metadata.yaml b/.doc_gen/metadata/ec2_metadata.yaml index 73ed60a9d66..392bd9865c8 100644 --- a/.doc_gen/metadata/ec2_metadata.yaml +++ b/.doc_gen/metadata/ec2_metadata.yaml @@ -76,6 +76,16 @@ ec2_Hello: excerpts: - snippet_tags: - ec2.rust.ec2-helloworld + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: The Package.swift file. + snippet_tags: + - swift.ec2.hello.package + - description: The entry.swift file. + - swift.ec2.hello services: ec2: {DescribeSecurityGroups} ec2_GetPasswordData: @@ -1269,6 +1279,15 @@ ec2_DescribeSecurityGroups: excerpts: - snippet_tags: - ec2.rust.ec2-helloworld + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DescribeSecurityGroupsPaginated services: ec2: {DescribeSecurityGroups} ec2_DeleteSecurityGroup: diff --git a/swift/example_code/ec2/README.md b/swift/example_code/ec2/README.md new file mode 100644 index 00000000000..0acebac56ab --- /dev/null +++ b/swift/example_code/ec2/README.md @@ -0,0 +1,98 @@ +# Amazon EC2 code examples for the SDK for Swift + +## Overview + +Shows how to use the AWS SDK for Swift to work with Amazon Elastic Compute Cloud (Amazon EC2). + + + + +_Amazon EC2 is a web service that provides resizable computing capacity—literally, servers in Amazon's data centers—that you use to build and host your software systems._ + +## ⚠ 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. + + + + + +### Get started + +- [Hello Amazon EC2](hello/Package.swift#L8) (`DescribeSecurityGroups`) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [DescribeSecurityGroups](hello/Sources/entry.swift#L44) + + + + + +## 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. + + + + +#### Hello Amazon EC2 + +This example shows you how to get started using Amazon EC2. + + + +### 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 EC2 User Guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html) +- [Amazon EC2 API Reference](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Welcome.html) +- [SDK for Swift Amazon EC2 reference](https://sdk.amazonaws.com/swift/api/awsec2/latest/documentation/awsec2) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/swift/example_code/ec2/hello/Package.swift b/swift/example_code/ec2/hello/Package.swift index 4d842c50a26..98ab159210b 100644 --- a/swift/example_code/ec2/hello/Package.swift +++ b/swift/example_code/ec2/hello/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.glue.scenario.package] +// snippet-start:[swift.ec2.hello.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.glue.scenario.package] +// snippet-end:[swift.ec2.hello.package] diff --git a/swift/example_code/ec2/hello/Sources/entry.swift b/swift/example_code/ec2/hello/Sources/entry.swift index 7709a48f613..7fda09cfe6b 100644 --- a/swift/example_code/ec2/hello/Sources/entry.swift +++ b/swift/example_code/ec2/hello/Sources/entry.swift @@ -1,20 +1,17 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // -// snippet-start:[swift.glue.scenario] +// snippet-start:[swift.ec2.hello] // An example that shows how to use the AWS SDK for Swift to perform a simple // operation using Amazon Elastic Compute Cloud (EC2). // - import ArgumentParser import Foundation -// snippet-start:[swift.glue.import] - -// import AWSClientRuntime +// snippet-start:[swift.ec2.import] import AWSEC2 -// snippet-end:[swift.glue.import] +// snippet-end:[swift.ec2.import] struct ExampleCommand: ParsableCommand { @Option(help: "The AWS Region to run AWS API calls in.") @@ -44,6 +41,15 @@ struct ExampleCommand: ParsableCommand { """ ) + // snippet-start:[swift.ec2.DescribeSecurityGroupsPaginated] + /// Return an array of strings giving the names of every security group + /// the user is a member of. + /// + /// - Parameter ec2Client: The `EC2Client` to use when calling + /// `describeSecurityGroupsPaginated()`. + /// + /// - Returns: An array of strings giving the names of every security + /// group the user is a member of. func getSecurityGroupNames(ec2Client: EC2Client) async -> [String] { let pages = ec2Client.describeSecurityGroupsPaginated( input: DescribeSecurityGroupsInput() @@ -68,6 +74,7 @@ struct ExampleCommand: ParsableCommand { return groupNames } + // snippet-end:[swift.ec2.DescribeSecurityGroupsPaginated] /// Called by ``main()`` to run the bulk of the example. func runAsync() async throws { @@ -98,4 +105,4 @@ struct Main { } } } -// snippet-end:[swift.glue.scenario] +// snippet-end:[swift.ec2.hello] From 56e5a3eda14969157d58845191e5636ff14a75e5 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Tue, 15 Jul 2025 19:11:52 +0000 Subject: [PATCH 3/8] Initial EC2 scenario checkin --- swift/example_code/ec2/scenario/Package.swift | 48 + .../ec2/scenario/Sources/entry.swift | 1038 +++++++++++++++++ 2 files changed, 1086 insertions(+) create mode 100644 swift/example_code/ec2/scenario/Package.swift create mode 100644 swift/example_code/ec2/scenario/Sources/entry.swift diff --git a/swift/example_code/ec2/scenario/Package.swift b/swift/example_code/ec2/scenario/Package.swift new file mode 100644 index 00000000000..fcedbc6d4f6 --- /dev/null +++ b/swift/example_code/ec2/scenario/Package.swift @@ -0,0 +1,48 @@ +// 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.hello.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: "ec2-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.0.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: "ec2-scenario", + dependencies: [ + .product(name: "AWSEC2", package: "aws-sdk-swift"), + .product(name: "AWSSSM", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.ec2.hello.package] diff --git a/swift/example_code/ec2/scenario/Sources/entry.swift b/swift/example_code/ec2/scenario/Sources/entry.swift new file mode 100644 index 00000000000..3b0a8c036e3 --- /dev/null +++ b/swift/example_code/ec2/scenario/Sources/entry.swift @@ -0,0 +1,1038 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.ec2.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 AWSEC2 + +import class SmithyWaitersAPI.Waiter +import struct SmithyWaitersAPI.WaiterOptions + +import AWSSSM + +struct ExampleCommand: ParsableCommand { + @Option(help: "The AWS Region to run AWS API calls in.") + var awsRegion = "us-east-1" + + @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: "ec2-scenario", + abstract: """ + Performs various operations to demonstrate the use of Amazon EC2 using the + AWS SDK for Swift. + """, + discussion: """ + """ + ) + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let ssmConfig = try await SSMClient.SSMClientConfiguration(region: awsRegion) + let ssmClient = SSMClient(config: ssmConfig) + + let ec2Config = try await EC2Client.EC2ClientConfiguration(region: awsRegion) + let ec2Client = EC2Client(config: ec2Config) + + let example = Example(ec2Client: ec2Client, ssmClient: ssmClient) + + //===================================================================== + // 1. Create an RSA key pair, saving the private key as a `.pem` file. + // Create a `defer` block that will delete the private key when the + // program exits. + //===================================================================== + + print("Creating an RSA key pair...") + + let keyName = example.tempName(prefix: "ExampleKeyName") + let keyUrl = await example.createKeyPair(name: keyName) + + guard let keyUrl else { + print("*** Failed to create the key pair!") + return + } + + print("Created the private key at: \(keyUrl.absoluteString)") + + // Schedule deleting the private key file to occur automatically when + // the program exits, no matter how it exits. + + defer { + do { + try FileManager.default.removeItem(at: keyUrl) + } catch { + print("*** Failed to delete the private key at \(keyUrl.absoluteString)") + } + } + + //===================================================================== + // 2. List the key pairs by calling `DescribeKeyPairs`. + //===================================================================== + + print("Describing available key pairs...") + await example.describeKeyPairs() + + //===================================================================== + // 3. Create a security group for the default VPC, and add an inbound + // rule to allow SSH from the current computer's public IPv4 + // address. + //===================================================================== + + print("Creating the security group...") + + let secGroupName = example.tempName(prefix: "ExampleSecurityGroup") + let ipAddress = example.getMyIPAddress() + + guard let ipAddress else { + print("*** Unable to get the device's IP address.") + return + } + + print("IP address is: \(ipAddress)") + + let groupId = await example.createSecurityGroup( + name: secGroupName, + description: "An example security group created using the AWS SDK for Swift" + ) + + guard let groupId else { + return + } + + print("Created security group: \(groupId)") + + if !(await example.authorizeSecurityGroupIngress(groupId: groupId, ipAddress: ipAddress)) { + return + } + + //===================================================================== + // 4. Display security group information for the new security group + // using DescribeSecurityGroups. + //===================================================================== + + if !(await example.describeSecurityGroups(groupId: groupId)) { + return + } + + //===================================================================== + // 5. Get a list of Amazon Linux 2023 AMIs and pick one (SSM is the + // best practice), using path and then filter the list after the + // fact to include "al2023" in the Name field + // (ssm.GetParametersByPath). Paginate to get all images. + //===================================================================== + + print("Searching available images for Amazon Linux 2023 images...") + + let options = await example.findAMIsMatchingFilter("al2023") + + //===================================================================== + // 6. The information in the AMI options isn't great, so make a list + // of the image IDs (the "Value" field in the AMI options) and get + // more information about them from EC2. Display the Description + // field and select one of them (DescribeImages with ImageIds + // filter). + //===================================================================== + + print("Images for Amazon Linux 2023:") + + var imageIds: [String] = [] + for option in options { + guard let id = option.value else { + continue + } + imageIds.append(id) + } + + let images = await example.describeImages(imageIds) + + // This is where you would normally let the user choose which AMI to + // use. However, for this example, we're just going to use the first + // one, whatever it is. + + let chosenImage = images[0] + + //===================================================================== + // 7. Get a list of instance types that are compatible with the + // selected AMI's architecture (such as "x86_64") and are either + // small or micro. Select one (DescribeInstanceTypes). + //===================================================================== + + print("Getting the instance types compatible with the selected image...") + + guard let arch = chosenImage.architecture else { + print("*** The selected image doesn't have a valid architecture.") + return + } + + let imageTypes = await example.getInstanceTypes(architecture: arch) + + for type in imageTypes { + guard let instanceType = type.instanceType else { + continue + } + print(" \(instanceType.rawValue)") + } + + // This example selects the first returned instance type. A real-world + // application would probably ask the user to select one here. + + let chosenInstanceType = imageTypes[0] + + //===================================================================== + // 8. Create an instance with the key pair, security group, AMI, and + // instance type (RunInstances). + //===================================================================== + + print("Creating an instance...") + + guard let imageId = chosenImage.imageId else { + print("*** Cannot start image without a valid image ID.") + return + } + guard let instanceType = chosenInstanceType.instanceType else { + print("*** Unable to start image without a valid image type.") + return + } + + let instance = await example.createInstance( + imageId: imageId, + instanceType: instanceType, + keyPairName: keyName, + securityGroups: [groupId] + ) + + guard let instance else { + return + } + guard let instanceId = instance.instanceId else { + print("*** Instance is missing an ID. Canceling.") + return + } + + //===================================================================== + // 9. Wait for the instance to be ready and then display its + // information (DescribeInstances). + //===================================================================== + + print("Waiting a few seconds to let the instance come up...") + + /********************************************************************* + *** + *** ADD CODE TO USE WaitUntilInstanceExists and + *** WaitUntilInstanceRunning HERE!!!!!!!! + *** + *********************************************************************/ + + do { + try await Task.sleep(for: .seconds(8)) + } catch { + print("*** Error pausing the task.") + } + print("Success! Your new instance is ready:") + + //===================================================================== + // 10. Display SSH connection info for the instance. + //===================================================================== + + var runningInstance = await example.describeInstance(instanceId: instanceId) + + if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { + print("\nYou can SSH to this instance using the following command:") + print("ssh -i \(keyUrl.path) ec2-user@\(runningInstance!.publicIpAddress!)") + } + + //===================================================================== + // 11. Stop the instance and wait for it to stop (StopInstances). + //===================================================================== + + print("Stopping the instance...") + + if !(await example.stopInstance(instanceId: instanceId, waitUntilStopped: true)) { + return + } + + //===================================================================== + // 12. Start the instance and wait for it to start (StartInstances). + //===================================================================== + + print("Starting the instance again...") + + if !(await example.startInstance(instanceId: instanceId, waitUntilStarted: true)) { + //return + } + + //===================================================================== + // 13. Display SSH connection info for the instance. Note that it's + // changed. + //===================================================================== + + runningInstance = await example.describeInstance(instanceId: instanceId) + if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { + print("\nYou can SSH to this instance using the following command.") + print("This is probably different from when the instance was running before.") + print("ssh -i \(keyUrl.path) ec2-user@\(runningInstance!.publicIpAddress!)") + } + + //===================================================================== + // 14. Allocate an elastic IP and associate it with the instance + // (AllocateAddress and AssociateAddress). + //===================================================================== + + let allocationId = await example.allocateAddress() + + guard let allocationId else { + return + } + + let associationId = await example.associateAddress(instanceId: instanceId, allocationId: allocationId) + + guard let associationId else { + return + } + + //===================================================================== + // 15. Display SSH connection info for the connection. Note that the + // public IP is now the Elastic IP, which stays constant. + //===================================================================== + + runningInstance = await example.describeInstance(instanceId: instanceId) + if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { + print("\nYou can SSH to this instance using the following command.") + print("This has changed again, and is now the Elastic IP.") + print("ssh -i \(keyUrl.path) ec2-user@\(runningInstance!.publicIpAddress!)") + } + + //===================================================================== + // 16. Disassociate and delete the Elastic IP (DisassociateAddress and + // ReleaseAddress). + //===================================================================== + + await example.disassociateAddress(associationId: associationId) + await example.releaseAddress(allocationId: allocationId) + + //===================================================================== + // 17. Terminate the instance and wait for it to terminate + // (TerminateInstances). + //===================================================================== + + if !(await example.terminateInstance(instanceId: instanceId)) { + return + } + + //===================================================================== + // 18. Delete the security group (DeleteSecurityGroup). + //===================================================================== + + if !(await example.deleteSecurityGroup(groupId: groupId)) { + return + } + + //===================================================================== + // 19. Delete the key pair (DeleteKeyPair). + //===================================================================== + + if !(await example.deleteKeyPair(keyPair: keyName)) { + return + } + } +} + +class Example { + let ec2Client: EC2Client + let ssmClient: SSMClient + + init(ec2Client: EC2Client, ssmClient: SSMClient) { + self.ec2Client = ec2Client + self.ssmClient = ssmClient + } + + /// Create a new RSA key pair and save the private key to a randomly-named + /// file in the temporary directory. + /// + /// - Parameter name: The name of the key pair to create. + /// + /// - Returns: The URL of the newly created `.pem` file or `nil` if unable + /// to create the key pair. + func createKeyPair(name: String) async -> URL? { + do { + let output = try await ec2Client.createKeyPair( + input: CreateKeyPairInput( + keyName: name + ) + ) + + guard let keyMaterial = output.keyMaterial else { + return nil + } + + let fileURL = URL.temporaryDirectory + .appendingPathComponent(name) + .appendingPathExtension("pem") + + do { + try keyMaterial.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8) + return fileURL + } catch { + return nil + } + } catch { + return nil + } + } + + /// Describe the key pairs associated with the user by outputting each key + /// pair's name and fingerprint. + func describeKeyPairs() async { + do { + let output = try await ec2Client.describeKeyPairs( + input: DescribeKeyPairsInput() + ) + + guard let keyPairs = output.keyPairs else { + print("*** No key pairs list available.") + return + } + + for keyPair in keyPairs { + print(keyPair.keyName ?? "", ":", keyPair.keyFingerprint ?? "") + } + } catch { + print("*** Error: Unable to obtain a key pair list.") + } + } + + /// Delete an EC2 key pair. + /// + /// - Parameter keyPair: The name of the key pair to delete. + /// + /// - Returns: `true` if the key pair is deleted successfully; otherwise + /// `false`. + func deleteKeyPair(keyPair: String) async -> Bool { + do { + _ = try await ec2Client.deleteKeyPair( + input: DeleteKeyPairInput( + keyName: keyPair + ) + ) + + return true + } catch { + print("*** Error deleting the key pair: \(error.localizedDescription)") + return false + } + } + + /// Return a list of AMI names that contain the specified string. + /// + /// - Parameter filter: A string that must be contained in all returned + /// AMI names. + /// + /// - Returns: An array of the parameters matching the specified substring. + func findAMIsMatchingFilter(_ filter: String) async -> [SSMClientTypes.Parameter] { + var parameterList: [SSMClientTypes.Parameter] = [] + var matchingAMIs: [SSMClientTypes.Parameter] = [] + + do { + let pages = ssmClient.getParametersByPathPaginated( + input: GetParametersByPathInput( + path: "/aws/service/ami-amazon-linux-latest" + ) + ) + + for try await page in pages { + guard let parameters = page.parameters else { + return matchingAMIs + } + + for parameter in parameters { + parameterList.append(parameter) + } + } + + print("Found \(parameterList.count) images total:") + for parameter in parameterList { + guard let name = parameter.name else { + continue + } + print(" \(name)") + + if name.contains(filter) { + matchingAMIs.append(parameter) + } + } + } catch { + return matchingAMIs + } + + return matchingAMIs + } + + /// Return a list of instance types matching the specified architecture + /// and instance sizes. + /// + /// - Parameters: + /// - architecture: The architecture of the instance types to return, as + /// a member of `EC2ClientTypes.ArchitectureValues`. + /// - sizes: An array of one or more strings identifying sizes of + /// instance type to accept. + /// + /// - Returns: An array of `EC2ClientTypes.InstanceTypeInfo` records + /// describing the instance types matching the given requirements. + func getInstanceTypes(architecture: EC2ClientTypes.ArchitectureValues = EC2ClientTypes.ArchitectureValues.x8664, + sizes: [String] = ["*.micro", "*.small"]) async + -> [EC2ClientTypes.InstanceTypeInfo] { + var instanceTypes: [EC2ClientTypes.InstanceTypeInfo] = [] + + let archFilter = EC2ClientTypes.Filter( + name: "processor-info.supported-architecture", + values: [architecture.rawValue] + ) + let sizeFilter = EC2ClientTypes.Filter( + name: "instance-type", + values: sizes + ) + + do { + let pages = ec2Client.describeInstanceTypesPaginated( + input: DescribeInstanceTypesInput( + filters: [archFilter, sizeFilter] + ) + ) + + for try await page in pages { + guard let types = page.instanceTypes else { + return [] + } + + instanceTypes += types + } + } catch { + print("*** Error getting image types: \(error.localizedDescription)") + return [] + } + + return instanceTypes + } + + /// Get the latest information about the specified instance and output it + /// to the screen, returning the instance details to the caller. + /// + /// - Parameters: + /// - instanceId: The ID of the instance to provide details about. + /// - stateFilter: The state to require the instance to be in. + /// + /// - Returns: The instance's details as an `EC2ClientTypes.Instance` object. + func describeInstance(instanceId: String, + stateFilter: EC2ClientTypes.InstanceStateName? = EC2ClientTypes.InstanceStateName.running) async + -> EC2ClientTypes.Instance? { + do { + let pages = ec2Client.describeInstancesPaginated( + input: DescribeInstancesInput( + instanceIds: [instanceId] + ) + ) + + for try await page in pages { + guard let reservations = page.reservations else { + continue + } + + for reservation in reservations { + guard let instances = reservation.instances else { + continue + } + + for instance in instances { + guard let state = instance.state else { + print("*** Instance is missing its state...") + continue + } + let instanceState = state.name + + if stateFilter != nil && (instanceState != stateFilter) { + continue + } + + let instanceTypeName: String + if instance.instanceType == nil { + instanceTypeName = "" + } else { + instanceTypeName = instance.instanceType?.rawValue ?? "" + } + + let instanceStateName: String + if instanceState == nil { + instanceStateName = "" + } else { + instanceStateName = instanceState?.rawValue ?? "" + } + + print(""" + Instance: \(instance.instanceId ?? "") + • Image ID: \(instance.imageId ?? "") + • Instance type: \(instanceTypeName) + • Key name: \(instance.keyName ?? "") + • VPC ID: \(instance.vpcId ?? "") + • Public IP: \(instance.publicIpAddress ?? "N/A") + • State: \(instanceStateName) + """) + + return instance + } + } + } + } catch { + print("*** Error retrieving instance information to display: \(error.localizedDescription)") + return nil + } + + return nil + } + + /// Stop the specified instance. + /// + /// - Parameters: + /// - instanceId: The ID of the instance to stop. + /// - waitUntilStopped: If `true`, execution waits until the instance + /// has stopped. Otherwise, execution continues and the instance stops + /// asynchronously. + /// + /// - Returns: `true` if the image is successfully stopped (or is left to + /// stop asynchronously). `false` if the instance doesn't stop. + func stopInstance(instanceId: String, waitUntilStopped: Bool = false) async -> Bool { + let instanceList = [instanceId] + + do { + _ = try await ec2Client.stopInstances( + input: StopInstancesInput( + instanceIds: instanceList + ) + ) + + if waitUntilStopped { + print("Waiting for the instance to stop. This may take a very long time,") + print("so please be patient!") + + let waitOptions = WaiterOptions(maxWaitTime: 600) + let output = try await ec2Client.waitUntilInstanceStopped( + options: waitOptions, + input: DescribeInstancesInput( + instanceIds: instanceList + ) + ) + + switch output.result { + case .success: + return true + case .failure: + return false + } + } else { + return true + } + } catch { + print("*** Unable to stop the instance: \(error.localizedDescription)") + return false + } + } + + /// Start the specified instance. + /// + /// - Parameters: + /// - instanceId: The ID of the instance to start. + /// - waitUntilStarted: If `true`, execution waits until the instance + /// has started. Otherwise, execution continues and the instance starts + /// asynchronously. + /// + /// - Returns: `true` if the image is successfully started (or is left to + /// start asynchronously). `false` if the instance doesn't start. + func startInstance(instanceId: String, waitUntilStarted: Bool = false) async -> Bool { + let instanceList = [instanceId] + + do { + _ = try await ec2Client.startInstances( + input: StartInstancesInput( + instanceIds: instanceList + ) + ) + + if waitUntilStarted { + print("Waiting for the instance to start...") + + let waitOptions = WaiterOptions(maxWaitTime: 60.0) + let output = try await ec2Client.waitUntilInstanceRunning( + options: waitOptions, + input: DescribeInstancesInput( + instanceIds: instanceList + ) + ) + switch output.result { + case .success: + return true + case .failure: + return false + } + } else { + return true + } + } catch { + print("*** Unable to start the instance: \(error.localizedDescription)") + return false + } + } + + func terminateInstance(instanceId: String, waitUntilTerminated: Bool = false) async -> Bool { + let instanceList = [instanceId] + + do { + _ = try await ec2Client.terminateInstances( + input: TerminateInstancesInput( + instanceIds: instanceList + ) + ) + + if waitUntilTerminated { + print("Waiting for the instance to terminate...") + + let waitOptions = WaiterOptions(maxWaitTime: 60.0) + let output = try await ec2Client.waitUntilInstanceTerminated( + options: waitOptions, + input: DescribeInstancesInput( + instanceIds: instanceList + ) + ) + + switch output.result { + case .success: + return true + case .failure: + return false + } + } else { + return true + } + } catch { + print("*** Unable to terminate the instance: \(error.localizedDescription)") + return false + } + } + + /// Return an array of `EC2ClientTypes.Image` objects describing all of + /// the images in the specified array. + /// + /// - Parameter idList: A list of image ID strings indicating the images + /// to return details about. + /// + /// - Returns: An array of the images. + func describeImages(_ idList: [String]) async -> [EC2ClientTypes.Image] { + do { + let output = try await ec2Client.describeImages( + input: DescribeImagesInput( + imageIds: idList + ) + ) + + guard let images = output.images else { + print("*** No images found.") + return [] + } + + for image in images { + guard let id = image.imageId else { + continue + } + print(" \(id): \(image.description ?? "")") + } + + return images + } catch { + print("*** Error getting image descriptions: \(error.localizedDescription)") + return [] + } + } + + /// Create and return a new EC2 instance. + /// + /// - Parameters: + /// - imageId: The image ID of the AMI to use when creating the instance. + /// - instanceType: The type of instance to create. + /// - keyPairName: The RSA key pair's name to use to secure the instance. + /// - securityGroups: The security group or groups to add the instance + /// to. + /// + /// - Returns: The EC2 instance as an `EC2ClientTypes.Instance` object. + func createInstance(imageId: String, instanceType: EC2ClientTypes.InstanceType, + keyPairName: String, securityGroups: [String]?) async -> EC2ClientTypes.Instance? { + do { + let output = try await ec2Client.runInstances( + input: RunInstancesInput( + imageId: imageId, + instanceType: instanceType, + keyName: keyPairName, + maxCount: 1, + minCount: 1, + securityGroupIds: securityGroups + ) + ) + + guard let instances = output.instances else { + print("*** Unable to create the instance.") + return nil + } + + return instances[0] + } catch { + print("*** Error creating the instance: \(error.localizedDescription)") + return nil + } + } + + /// Return the device's external IP address. + /// + /// - Returns: A string containing the device's IP address. + func getMyIPAddress() -> String? { + guard let url = URL(string: "http://checkip.amazonaws.com") else { + print("Couldn't create the URL") + return nil + } + + do { + print("Getting the IP address...") + return try String(contentsOf: url, encoding: String.Encoding.utf8).trim() + } catch { + print("Got an error!") + return nil + } + } + + /// Create a new security group. + /// + /// - Parameters: + /// - groupName: The name of the group to create. + /// - groupDescription: A description of the new security group. + /// + /// - Returns: The ID string of the new security group. + func createSecurityGroup(name groupName: String, description groupDescription: String) async -> String? { + do { + let output = try await ec2Client.createSecurityGroup( + input: CreateSecurityGroupInput( + description: groupDescription, + groupName: groupName + ) + ) + + return output.groupId + } catch { + print("*** Error creating the security group: \(error.localizedDescription)") + return nil + } + } + + /// Authorize ingress of connections for the security group. + /// + /// - Parameters: + /// - groupId: The group ID of the security group to authorize access for. + /// - ipAddress: The IP address of the device to grant access to. + /// + /// - Returns: `true` if access is successfully granted; otherwise `false`. + func authorizeSecurityGroupIngress(groupId: String, ipAddress: String) async -> Bool { + let ipRange = EC2ClientTypes.IpRange(cidrIp: "\(ipAddress)/0") + let httpPermission = EC2ClientTypes.IpPermission( + fromPort: 80, + ipProtocol: "tcp", + ipRanges: [ipRange], + toPort: 80 + ) + + let sshPermission = EC2ClientTypes.IpPermission( + fromPort: 22, + ipProtocol: "tcp", + ipRanges: [ipRange], + toPort: 22 + ) + + do { + _ = try await ec2Client.authorizeSecurityGroupIngress( + input: AuthorizeSecurityGroupIngressInput( + groupId: groupId, + ipPermissions: [httpPermission, sshPermission] + ) + ) + + return true + } catch { + dump(error) + print("*** Error authorizing ingress for the security group: \(error.localizedDescription)") + return false + } + } + + func describeSecurityGroups(groupId: String) async -> Bool { + do { + let output = try await ec2Client.describeSecurityGroups( + input: DescribeSecurityGroupsInput( + groupIds: [groupId] + ) + ) + + guard let securityGroups = output.securityGroups else { + print("No security groups found.") + return true + } + + for group in securityGroups { + print("Group \(group.groupId ?? "") found with VPC \(group.vpcId ?? "")") + } + return true + } catch { + print("*** Error getting security group details: \(error.localizedDescription)") + return false + } + } + + /// Delete a security group. + /// + /// - Parameter groupId: The ID of the security group to delete. + /// + /// - Returns: `true` on successful deletion; `false` on error. + func deleteSecurityGroup(groupId: String) async -> Bool { + do { + _ = try await ec2Client.deleteSecurityGroup( + input: DeleteSecurityGroupInput( + groupId: groupId + ) + ) + + return true + } catch { + print("*** Error deleting the security group: \(error.localizedDescription)") + return false + } + } + + /// Allocate an Elastic IP address. + /// + /// - Returns: A string containing the ID of the Elastic IP. + func allocateAddress() async -> String? { + do { + let output = try await ec2Client.allocateAddress( + input: AllocateAddressInput( + domain: EC2ClientTypes.DomainType.vpc + ) + ) + + guard let allocationId = output.allocationId else { + return nil + } + + return allocationId + } catch { + print("*** Unable to allocate the IP address: \(error.localizedDescription)") + return nil + } + } + + /// Associate the specified allocated Elastic IP to a given instance. + /// + /// - Parameters: + /// - instanceId: The instance to associate the Elastic IP with. + /// - allocationId: The ID of the allocated Elastic IP to associate with + /// the instance. + /// + /// - Returns: The association ID of the association. + func associateAddress(instanceId: String?, allocationId: String?) async -> String? { + do { + let output = try await ec2Client.associateAddress( + input: AssociateAddressInput( + allocationId: allocationId, + instanceId: instanceId + ) + ) + + return output.associationId + } catch { + print("*** Unable to associate the IP address: \(error.localizedDescription)") + return nil + } + } + + /// Disassociate an Elastic IP. + /// + /// - Parameter associationId: The ID of the association to end. + func disassociateAddress(associationId: String?) async { + do { + _ = try await ec2Client.disassociateAddress( + input: DisassociateAddressInput( + associationId: associationId + ) + ) + } catch { + print("*** Unable to disassociate the IP address: \(error.localizedDescription)") + } + } + + /// Release an allocated Elastic IP. + /// + /// - Parameter allocationId: The allocation ID of the Elastic IP to + /// release. + func releaseAddress(allocationId: String?) async { + do { + _ = try await ec2Client.releaseAddress( + input: ReleaseAddressInput( + allocationId: allocationId + ) + ) + } catch { + print("*** Unable to release the IP address: \(error.localizedDescription)") + } + } + + /// 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))" + } +} + +/// 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.ec2.scenario] From c0987c2b74d836d80a09987b7ae779a06bb6f99c Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 16 Jul 2025 14:11:54 +0000 Subject: [PATCH 4/8] Working but needs cleanup --- swift/example_code/ec2/scenario/Package.swift | 2 +- .../ec2/scenario/Sources/entry.swift | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/swift/example_code/ec2/scenario/Package.swift b/swift/example_code/ec2/scenario/Package.swift index fcedbc6d4f6..5fa31fb679e 100644 --- a/swift/example_code/ec2/scenario/Package.swift +++ b/swift/example_code/ec2/scenario/Package.swift @@ -24,7 +24,7 @@ let package = Package( // Dependencies declare other packages that this package depends on. .package( url: "https://github.com/awslabs/aws-sdk-swift", - from: "1.0.0"), + from: "1.4.0"), .package( url: "https://github.com/apple/swift-argument-parser.git", branch: "main" diff --git a/swift/example_code/ec2/scenario/Sources/entry.swift b/swift/example_code/ec2/scenario/Sources/entry.swift index 3b0a8c036e3..12686af735a 100644 --- a/swift/example_code/ec2/scenario/Sources/entry.swift +++ b/swift/example_code/ec2/scenario/Sources/entry.swift @@ -232,16 +232,9 @@ struct ExampleCommand: ParsableCommand { //===================================================================== print("Waiting a few seconds to let the instance come up...") - - /********************************************************************* - *** - *** ADD CODE TO USE WaitUntilInstanceExists and - *** WaitUntilInstanceRunning HERE!!!!!!!! - *** - *********************************************************************/ - + do { - try await Task.sleep(for: .seconds(8)) + try await Task.sleep(for: .seconds(20)) } catch { print("*** Error pausing the task.") } @@ -332,7 +325,9 @@ struct ExampleCommand: ParsableCommand { // (TerminateInstances). //===================================================================== - if !(await example.terminateInstance(instanceId: instanceId)) { + print("Terminating the instance...") + + if !(await example.terminateInstance(instanceId: instanceId, waitUntilTerminated: true)) { return } @@ -340,6 +335,8 @@ struct ExampleCommand: ParsableCommand { // 18. Delete the security group (DeleteSecurityGroup). //===================================================================== + print("Deleting the security group...") + if !(await example.deleteSecurityGroup(groupId: groupId)) { return } @@ -348,6 +345,8 @@ struct ExampleCommand: ParsableCommand { // 19. Delete the key pair (DeleteKeyPair). //===================================================================== + print("Deleting the key pair...") + if !(await example.deleteKeyPair(keyPair: keyName)) { return } @@ -627,8 +626,7 @@ class Example { ) if waitUntilStopped { - print("Waiting for the instance to stop. This may take a very long time,") - print("so please be patient!") + print("Waiting for the instance to stop. Please be patient!") let waitOptions = WaiterOptions(maxWaitTime: 600) let output = try await ec2Client.waitUntilInstanceStopped( @@ -698,6 +696,15 @@ class Example { } } + /// Terminate the specified instance. + /// + /// - Parameters: + /// - instanceId: The instance to terminate. + /// - waitUntilTerminated: Whether or not to wait until the instance is + /// terminated before returning. + /// + /// - Returns: `true` if terminated successfully. `false` if not or if an + /// error occurs. func terminateInstance(instanceId: String, waitUntilTerminated: Bool = false) async -> Bool { let instanceList = [instanceId] @@ -711,7 +718,7 @@ class Example { if waitUntilTerminated { print("Waiting for the instance to terminate...") - let waitOptions = WaiterOptions(maxWaitTime: 60.0) + let waitOptions = WaiterOptions(maxWaitTime: 600.0) let output = try await ec2Client.waitUntilInstanceTerminated( options: waitOptions, input: DescribeInstancesInput( @@ -878,7 +885,6 @@ class Example { return true } catch { - dump(error) print("*** Error authorizing ingress for the security group: \(error.localizedDescription)") return false } From f35e13f40220ea79cb1f2e20ed71095cee6db036 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 16 Jul 2025 15:42:41 +0000 Subject: [PATCH 5/8] More cleanup --- .../ec2/scenario/Sources/entry.swift | 102 ++++++++++++------ 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/swift/example_code/ec2/scenario/Sources/entry.swift b/swift/example_code/ec2/scenario/Sources/entry.swift index 12686af735a..f99e9533e5f 100644 --- a/swift/example_code/ec2/scenario/Sources/entry.swift +++ b/swift/example_code/ec2/scenario/Sources/entry.swift @@ -53,6 +53,20 @@ struct ExampleCommand: ParsableCommand { let example = Example(ec2Client: ec2Client, ssmClient: ssmClient) + await example.run() + } +} + +class Example { + let ec2Client: EC2Client + let ssmClient: SSMClient + + init(ec2Client: EC2Client, ssmClient: SSMClient) { + self.ec2Client = ec2Client + self.ssmClient = ssmClient + } + + func run() async { //===================================================================== // 1. Create an RSA key pair, saving the private key as a `.pem` file. // Create a `defer` block that will delete the private key when the @@ -61,8 +75,8 @@ struct ExampleCommand: ParsableCommand { print("Creating an RSA key pair...") - let keyName = example.tempName(prefix: "ExampleKeyName") - let keyUrl = await example.createKeyPair(name: keyName) + let keyName = self.tempName(prefix: "ExampleKeyName") + let keyUrl = await self.createKeyPair(name: keyName) guard let keyUrl else { print("*** Failed to create the key pair!") @@ -87,7 +101,7 @@ struct ExampleCommand: ParsableCommand { //===================================================================== print("Describing available key pairs...") - await example.describeKeyPairs() + await self.describeKeyPairs() //===================================================================== // 3. Create a security group for the default VPC, and add an inbound @@ -97,8 +111,8 @@ struct ExampleCommand: ParsableCommand { print("Creating the security group...") - let secGroupName = example.tempName(prefix: "ExampleSecurityGroup") - let ipAddress = example.getMyIPAddress() + let secGroupName = self.tempName(prefix: "ExampleSecurityGroup") + let ipAddress = self.getMyIPAddress() guard let ipAddress else { print("*** Unable to get the device's IP address.") @@ -107,18 +121,18 @@ struct ExampleCommand: ParsableCommand { print("IP address is: \(ipAddress)") - let groupId = await example.createSecurityGroup( + let securityGroupId = await self.createSecurityGroup( name: secGroupName, description: "An example security group created using the AWS SDK for Swift" ) - guard let groupId else { + guard let securityGroupId else { return } - print("Created security group: \(groupId)") + print("Created security group: \(securityGroupId)") - if !(await example.authorizeSecurityGroupIngress(groupId: groupId, ipAddress: ipAddress)) { + if !(await self.authorizeSecurityGroupIngress(groupId: securityGroupId, ipAddress: ipAddress)) { return } @@ -127,7 +141,7 @@ struct ExampleCommand: ParsableCommand { // using DescribeSecurityGroups. //===================================================================== - if !(await example.describeSecurityGroups(groupId: groupId)) { + if !(await self.describeSecurityGroups(groupId: securityGroupId)) { return } @@ -140,7 +154,7 @@ struct ExampleCommand: ParsableCommand { print("Searching available images for Amazon Linux 2023 images...") - let options = await example.findAMIsMatchingFilter("al2023") + let options = await self.findAMIsMatchingFilter("al2023") //===================================================================== // 6. The information in the AMI options isn't great, so make a list @@ -160,7 +174,7 @@ struct ExampleCommand: ParsableCommand { imageIds.append(id) } - let images = await example.describeImages(imageIds) + let images = await self.describeImages(imageIds) // This is where you would normally let the user choose which AMI to // use. However, for this example, we're just going to use the first @@ -181,7 +195,7 @@ struct ExampleCommand: ParsableCommand { return } - let imageTypes = await example.getInstanceTypes(architecture: arch) + let imageTypes = await self.getInstanceTypes(architecture: arch) for type in imageTypes { guard let instanceType = type.instanceType else { @@ -211,11 +225,11 @@ struct ExampleCommand: ParsableCommand { return } - let instance = await example.createInstance( + let instance = await self.createInstance( imageId: imageId, instanceType: instanceType, keyPairName: keyName, - securityGroups: [groupId] + securityGroups: [securityGroupId] ) guard let instance else { @@ -244,7 +258,7 @@ struct ExampleCommand: ParsableCommand { // 10. Display SSH connection info for the instance. //===================================================================== - var runningInstance = await example.describeInstance(instanceId: instanceId) + var runningInstance = await self.describeInstance(instanceId: instanceId) if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { print("\nYou can SSH to this instance using the following command:") @@ -257,7 +271,7 @@ struct ExampleCommand: ParsableCommand { print("Stopping the instance...") - if !(await example.stopInstance(instanceId: instanceId, waitUntilStopped: true)) { + if !(await self.stopInstance(instanceId: instanceId, waitUntilStopped: true)) { return } @@ -267,7 +281,7 @@ struct ExampleCommand: ParsableCommand { print("Starting the instance again...") - if !(await example.startInstance(instanceId: instanceId, waitUntilStarted: true)) { + if !(await self.startInstance(instanceId: instanceId, waitUntilStarted: true)) { //return } @@ -276,7 +290,7 @@ struct ExampleCommand: ParsableCommand { // changed. //===================================================================== - runningInstance = await example.describeInstance(instanceId: instanceId) + runningInstance = await self.describeInstance(instanceId: instanceId) if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { print("\nYou can SSH to this instance using the following command.") print("This is probably different from when the instance was running before.") @@ -288,13 +302,13 @@ struct ExampleCommand: ParsableCommand { // (AllocateAddress and AssociateAddress). //===================================================================== - let allocationId = await example.allocateAddress() + let allocationId = await self.allocateAddress() guard let allocationId else { return } - let associationId = await example.associateAddress(instanceId: instanceId, allocationId: allocationId) + let associationId = await self.associateAddress(instanceId: instanceId, allocationId: allocationId) guard let associationId else { return @@ -305,7 +319,7 @@ struct ExampleCommand: ParsableCommand { // public IP is now the Elastic IP, which stays constant. //===================================================================== - runningInstance = await example.describeInstance(instanceId: instanceId) + runningInstance = await self.describeInstance(instanceId: instanceId) if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { print("\nYou can SSH to this instance using the following command.") print("This has changed again, and is now the Elastic IP.") @@ -317,8 +331,8 @@ struct ExampleCommand: ParsableCommand { // ReleaseAddress). //===================================================================== - await example.disassociateAddress(associationId: associationId) - await example.releaseAddress(allocationId: allocationId) + await self.disassociateAddress(associationId: associationId) + await self.releaseAddress(allocationId: allocationId) //===================================================================== // 17. Terminate the instance and wait for it to terminate @@ -327,7 +341,7 @@ struct ExampleCommand: ParsableCommand { print("Terminating the instance...") - if !(await example.terminateInstance(instanceId: instanceId, waitUntilTerminated: true)) { + if !(await self.terminateInstance(instanceId: instanceId, waitUntilTerminated: true)) { return } @@ -337,7 +351,7 @@ struct ExampleCommand: ParsableCommand { print("Deleting the security group...") - if !(await example.deleteSecurityGroup(groupId: groupId)) { + if !(await self.deleteSecurityGroup(groupId: securityGroupId)) { return } @@ -347,19 +361,39 @@ struct ExampleCommand: ParsableCommand { print("Deleting the key pair...") - if !(await example.deleteKeyPair(keyPair: keyName)) { + if !(await self.deleteKeyPair(keyPair: keyName)) { return } } -} -class Example { - let ec2Client: EC2Client - let ssmClient: SSMClient + func cleanUp(keyName: String?, + securityGroupId: String?, + instanceId: String?, + allocationId: String?, + associationId: String?) async { - init(ec2Client: EC2Client, ssmClient: SSMClient) { - self.ec2Client = ec2Client - self.ssmClient = ssmClient + if associationId != nil { + await self.disassociateAddress(associationId: associationId) + } + + if allocationId != nil { + await self.releaseAddress(allocationId: allocationId) + } + + if instanceId != nil { + print("Terminating the instance...") + _ = await self.terminateInstance(instanceId: instanceId, waitUntilTerminateed: true) + } + + if securityGroupId != nil { + print("Deleting the security group...") + _ = await self.deleteSecurityGroup(groupId: securityGroupId) + } + + if keyPair != nil { + print("Deleting the key pair...") + _ = await self.deleteKeyPair(keyPair: keyName) + } } /// Create a new RSA key pair and save the private key to a randomly-named From 526d741c917dd0d58af28c73f7ba14763aefdb9d Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 16 Jul 2025 19:57:17 +0000 Subject: [PATCH 6/8] SoS Tags in place --- .doc_gen/metadata/ec2_metadata.yaml | 150 +++++++++++++- swift/example_code/ec2/README.md | 18 +- .../ec2/scenario/Sources/entry.swift | 189 +++++++++++------- 3 files changed, 287 insertions(+), 70 deletions(-) diff --git a/.doc_gen/metadata/ec2_metadata.yaml b/.doc_gen/metadata/ec2_metadata.yaml index 392bd9865c8..17033f206ba 100644 --- a/.doc_gen/metadata/ec2_metadata.yaml +++ b/.doc_gen/metadata/ec2_metadata.yaml @@ -198,6 +198,15 @@ ec2_CreateKeyPair: - description: A function that calls the create_key impl and securely saves the PEM private key. snippet_tags: - ec2.rust.create_key.wrapper + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.CreateKeyPair services: ec2: {CreateKeyPair} ec2_DescribeKeyPairs: @@ -284,6 +293,15 @@ ec2_DescribeKeyPairs: excerpts: - snippet_tags: - ec2.rust.list_keys.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DescribeKeyPairs services: ec2: {DescribeKeyPairs} @@ -380,6 +398,15 @@ ec2_CreateSecurityGroup: excerpts: - snippet_tags: - ec2.rust.create_security_group.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.CreateSecurityGroup services: ec2: {CreateSecurityGroup} ec2_RunInstances: @@ -467,6 +494,15 @@ ec2_RunInstances: excerpts: - snippet_tags: - ec2.rust.create_instance.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.RunInstances services: ec2: {RunInstances} ec2_StartInstances: @@ -567,6 +603,15 @@ ec2_StartInstances: snippet_tags: - aws-cli.bash-linux.ec2.errecho - aws-cli.bash-linux.ec2.aws_cli_error_log + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.StartInstances services: ec2: {StartInstances} ec2_StopInstances: @@ -666,6 +711,15 @@ ec2_StopInstances: snippet_tags: - aws-cli.bash-linux.ec2.errecho - aws-cli.bash-linux.ec2.aws_cli_error_log + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.StopInstances services: ec2: {StopInstances} ec2_AllocateAddress: @@ -762,6 +816,15 @@ ec2_AllocateAddress: excerpts: - snippet_tags: - ec2.rust.allocate_address.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.AllocateAddress services: ec2: {AllocateAddress} ec2_AssociateAddress: @@ -858,6 +921,15 @@ ec2_AssociateAddress: excerpts: - snippet_tags: - ec2.rust.associate_address.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.AssociateAddress services: ec2: {AssociateAddress} ec2_DisassociateAddress: @@ -928,6 +1000,15 @@ ec2_DisassociateAddress: excerpts: - snippet_tags: - ec2.rust.disassociate_address.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DisassociateAddress services: ec2: {DisassociateAddress} ec2_ReleaseAddress: @@ -1023,6 +1104,15 @@ ec2_ReleaseAddress: excerpts: - snippet_tags: - ec2.rust.deallocate_address.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.ReleaseAddress services: ec2: {ReleaseAddress} ec2_AuthorizeSecurityGroupIngress: @@ -1104,6 +1194,15 @@ ec2_AuthorizeSecurityGroupIngress: excerpts: - snippet_tags: - ec2.rust.authorize_security_group_ssh_ingress.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.AuthorizeSecurityGroupIngress services: ec2: {AuthorizeSecurityGroupIngress} ec2_DeleteKeyPair: @@ -1193,6 +1292,15 @@ ec2_DeleteKeyPair: - ec2.rust.delete_key.wrapper - snippet_tags: - ec2.rust.delete_key.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DeleteKeyPair services: ec2: {DeleteKeyPair} ec2_DescribeSecurityGroups: @@ -1284,10 +1392,14 @@ ec2_DescribeSecurityGroups: - sdk_version: 1 github: swift/example_code/ec2 excerpts: - - description: + - description: Using pagination with describeSecurityGroupsPaginated(). snippet_tags: - swift.ec2.import - swift.ec2.DescribeSecurityGroupsPaginated + - description: Without pagination. + snippet_tags: + - swift.ec2.import + - swift.ec2.DescribeSecurityGroups services: ec2: {DescribeSecurityGroups} ec2_DeleteSecurityGroup: @@ -1374,6 +1486,15 @@ ec2_DeleteSecurityGroup: excerpts: - snippet_tags: - ec2.rust.delete_security_group.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DeleteSecurityGroup services: ec2: {DeleteSecurityGroup} ec2_DeleteSnapshot: @@ -1492,6 +1613,15 @@ ec2_TerminateInstances: - description: Wait for an instance to be in the terminted state, using the Waiters API. Using the Waiters API requires `use aws_sdk_ec2::client::Waiters` in the rust file. snippet_tags: - ec2.rust.wait_for_instance_terminated.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.TerminateInstances services: ec2: {TerminateInstances} ec2_DescribeInstances: @@ -1826,6 +1956,15 @@ ec2_DescribeImages: snippet_tags: - aws-cli.bash-linux.ec2.errecho - aws-cli.bash-linux.ec2.aws_cli_error_log + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DescribeImages services: ec2: {DescribeImages} ec2_DescribeInstanceTypes: @@ -1896,6 +2035,15 @@ ec2_DescribeInstanceTypes: excerpts: - snippet_tags: - ec2.rust.list_instance_types.impl + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: + snippet_tags: + - swift.ec2.import + - swift.ec2.DescribeInstanceTypes services: ec2: {DescribeInstanceTypes} ec2_DescribeAddresses: diff --git a/swift/example_code/ec2/README.md b/swift/example_code/ec2/README.md index 0acebac56ab..d352c340a16 100644 --- a/swift/example_code/ec2/README.md +++ b/swift/example_code/ec2/README.md @@ -31,14 +31,30 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `swift ### Get started -- [Hello Amazon EC2](hello/Package.swift#L8) (`DescribeSecurityGroups`) +- [Hello Amazon EC2](scenario/Package.swift#L8) (`DescribeSecurityGroups`) ### Single actions Code excerpts that show you how to call individual service functions. +- [AllocateAddress](scenario/Sources/entry.swift#L1015) +- [AssociateAddress](scenario/Sources/entry.swift#L1039) +- [AuthorizeSecurityGroupIngress](scenario/Sources/entry.swift#L928) +- [CreateKeyPair](scenario/Sources/entry.swift#L410) +- [CreateSecurityGroup](scenario/Sources/entry.swift#L903) +- [DeleteKeyPair](scenario/Sources/entry.swift#L469) +- [DeleteSecurityGroup](scenario/Sources/entry.swift#L993) +- [DescribeImages](scenario/Sources/entry.swift#L809) +- [DescribeInstanceTypes](scenario/Sources/entry.swift#L537) +- [DescribeKeyPairs](scenario/Sources/entry.swift#L446) - [DescribeSecurityGroups](hello/Sources/entry.swift#L44) +- [DisassociateAddress](scenario/Sources/entry.swift#L1065) +- [ReleaseAddress](scenario/Sources/entry.swift#L1082) +- [RunInstances](scenario/Sources/entry.swift#L845) +- [StartInstances](scenario/Sources/entry.swift#L711) +- [StopInstances](scenario/Sources/entry.swift#L661) +- [TerminateInstances](scenario/Sources/entry.swift#L760) diff --git a/swift/example_code/ec2/scenario/Sources/entry.swift b/swift/example_code/ec2/scenario/Sources/entry.swift index f99e9533e5f..82fdfa2f2c5 100644 --- a/swift/example_code/ec2/scenario/Sources/entry.swift +++ b/swift/example_code/ec2/scenario/Sources/entry.swift @@ -10,6 +10,8 @@ import ArgumentParser import Foundation import AWSEC2 +// Allow waiters to be used. + import class SmithyWaitersAPI.Waiter import struct SmithyWaitersAPI.WaiterOptions @@ -61,11 +63,20 @@ class Example { let ec2Client: EC2Client let ssmClient: SSMClient + // Storage for AWS EC2 properties. + + var keyName: String? = nil + var securityGroupId: String? = nil + var instanceId: String? = nil + var allocationId: String? = nil + var associationId: String? = nil + init(ec2Client: EC2Client, ssmClient: SSMClient) { self.ec2Client = ec2Client self.ssmClient = ssmClient } + /// The example's main body. func run() async { //===================================================================== // 1. Create an RSA key pair, saving the private key as a `.pem` file. @@ -75,8 +86,8 @@ class Example { print("Creating an RSA key pair...") - let keyName = self.tempName(prefix: "ExampleKeyName") - let keyUrl = await self.createKeyPair(name: keyName) + keyName = self.tempName(prefix: "ExampleKeyName") + let keyUrl = await self.createKeyPair(name: keyName!) guard let keyUrl else { print("*** Failed to create the key pair!") @@ -121,18 +132,20 @@ class Example { print("IP address is: \(ipAddress)") - let securityGroupId = await self.createSecurityGroup( + securityGroupId = await self.createSecurityGroup( name: secGroupName, description: "An example security group created using the AWS SDK for Swift" ) - guard let securityGroupId else { + if securityGroupId == nil { + await cleanUp() return } - print("Created security group: \(securityGroupId)") + print("Created security group: \(securityGroupId ?? "")") - if !(await self.authorizeSecurityGroupIngress(groupId: securityGroupId, ipAddress: ipAddress)) { + if !(await self.authorizeSecurityGroupIngress(groupId: securityGroupId!, ipAddress: ipAddress)) { + await cleanUp() return } @@ -141,7 +154,8 @@ class Example { // using DescribeSecurityGroups. //===================================================================== - if !(await self.describeSecurityGroups(groupId: securityGroupId)) { + if !(await self.describeSecurityGroups(groupId: securityGroupId!)) { + await cleanUp() return } @@ -164,7 +178,7 @@ class Example { // filter). //===================================================================== - print("Images for Amazon Linux 2023:") + print("Images matching Amazon Linux 2023:") var imageIds: [String] = [] for option in options { @@ -192,10 +206,11 @@ class Example { guard let arch = chosenImage.architecture else { print("*** The selected image doesn't have a valid architecture.") + await cleanUp() return } - let imageTypes = await self.getInstanceTypes(architecture: arch) + let imageTypes = await self.getMatchingInstanceTypes(architecture: arch) for type in imageTypes { guard let instanceType = type.instanceType else { @@ -218,25 +233,31 @@ class Example { guard let imageId = chosenImage.imageId else { print("*** Cannot start image without a valid image ID.") + await cleanUp() return } guard let instanceType = chosenInstanceType.instanceType else { print("*** Unable to start image without a valid image type.") + await cleanUp() return } - let instance = await self.createInstance( + let instance = await self.runInstance( imageId: imageId, instanceType: instanceType, - keyPairName: keyName, - securityGroups: [securityGroupId] + keyPairName: keyName!, + securityGroups: [securityGroupId!] ) guard let instance else { + await cleanUp() return } - guard let instanceId = instance.instanceId else { + + instanceId = instance.instanceId + if instanceId == nil { print("*** Instance is missing an ID. Canceling.") + await cleanUp() return } @@ -258,7 +279,7 @@ class Example { // 10. Display SSH connection info for the instance. //===================================================================== - var runningInstance = await self.describeInstance(instanceId: instanceId) + var runningInstance = await self.describeInstance(instanceId: instanceId!) if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { print("\nYou can SSH to this instance using the following command:") @@ -271,7 +292,8 @@ class Example { print("Stopping the instance...") - if !(await self.stopInstance(instanceId: instanceId, waitUntilStopped: true)) { + if !(await self.stopInstance(instanceId: instanceId!, waitUntilStopped: true)) { + await cleanUp() return } @@ -281,8 +303,9 @@ class Example { print("Starting the instance again...") - if !(await self.startInstance(instanceId: instanceId, waitUntilStarted: true)) { - //return + if !(await self.startInstance(instanceId: instanceId!, waitUntilStarted: true)) { + await cleanUp() + return } //===================================================================== @@ -290,7 +313,7 @@ class Example { // changed. //===================================================================== - runningInstance = await self.describeInstance(instanceId: instanceId) + runningInstance = await self.describeInstance(instanceId: instanceId!) if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { print("\nYou can SSH to this instance using the following command.") print("This is probably different from when the instance was running before.") @@ -302,15 +325,17 @@ class Example { // (AllocateAddress and AssociateAddress). //===================================================================== - let allocationId = await self.allocateAddress() + allocationId = await self.allocateAddress() - guard let allocationId else { + if allocationId == nil { + await cleanUp() return } - let associationId = await self.associateAddress(instanceId: instanceId, allocationId: allocationId) + associationId = await self.associateAddress(instanceId: instanceId!, allocationId: allocationId) - guard let associationId else { + if associationId == nil { + await cleanUp() return } @@ -319,83 +344,70 @@ class Example { // public IP is now the Elastic IP, which stays constant. //===================================================================== - runningInstance = await self.describeInstance(instanceId: instanceId) + runningInstance = await self.describeInstance(instanceId: instanceId!) if (runningInstance != nil) && (runningInstance!.publicIpAddress != nil) { print("\nYou can SSH to this instance using the following command.") print("This has changed again, and is now the Elastic IP.") print("ssh -i \(keyUrl.path) ec2-user@\(runningInstance!.publicIpAddress!)") } + //===================================================================== + // Handle all cleanup tasks + //===================================================================== + + await cleanUp() + } + + /// Clean up by discarding and closing down all allocated EC2 items: + /// + /// * Elastic IP allocation and association + /// * Terminate the instance + /// * Delete the security group + /// * Delete the key pair + func cleanUp() async { //===================================================================== // 16. Disassociate and delete the Elastic IP (DisassociateAddress and // ReleaseAddress). //===================================================================== - await self.disassociateAddress(associationId: associationId) - await self.releaseAddress(allocationId: allocationId) + if associationId != nil { + await self.disassociateAddress(associationId: associationId!) + } + + if allocationId != nil { + await self.releaseAddress(allocationId: allocationId!) + } //===================================================================== // 17. Terminate the instance and wait for it to terminate // (TerminateInstances). //===================================================================== - print("Terminating the instance...") - - if !(await self.terminateInstance(instanceId: instanceId, waitUntilTerminated: true)) { - return + if instanceId != nil { + print("Terminating the instance...") + _ = await self.terminateInstance(instanceId: instanceId!, waitUntilTerminated: true) } //===================================================================== // 18. Delete the security group (DeleteSecurityGroup). //===================================================================== - print("Deleting the security group...") - - if !(await self.deleteSecurityGroup(groupId: securityGroupId)) { - return + if securityGroupId != nil { + print("Deleting the security group...") + _ = await self.deleteSecurityGroup(groupId: securityGroupId!) } //===================================================================== // 19. Delete the key pair (DeleteKeyPair). //===================================================================== - print("Deleting the key pair...") - - if !(await self.deleteKeyPair(keyPair: keyName)) { - return - } - } - - func cleanUp(keyName: String?, - securityGroupId: String?, - instanceId: String?, - allocationId: String?, - associationId: String?) async { - - if associationId != nil { - await self.disassociateAddress(associationId: associationId) - } - - if allocationId != nil { - await self.releaseAddress(allocationId: allocationId) - } - - if instanceId != nil { - print("Terminating the instance...") - _ = await self.terminateInstance(instanceId: instanceId, waitUntilTerminateed: true) - } - - if securityGroupId != nil { - print("Deleting the security group...") - _ = await self.deleteSecurityGroup(groupId: securityGroupId) - } - - if keyPair != nil { + if keyName != nil { print("Deleting the key pair...") - _ = await self.deleteKeyPair(keyPair: keyName) + _ = await self.deleteKeyPair(keyPair: keyName!) } } + // snippet-start:[swift.ec2.CreateKeyPair] /// Create a new RSA key pair and save the private key to a randomly-named /// file in the temporary directory. /// @@ -429,7 +441,9 @@ class Example { return nil } } + // snippet-end:[swift.ec2.CreateKeyPair] + // snippet-start:[swift.ec2.DescribeKeyPairs] /// Describe the key pairs associated with the user by outputting each key /// pair's name and fingerprint. func describeKeyPairs() async { @@ -450,7 +464,9 @@ class Example { print("*** Error: Unable to obtain a key pair list.") } } + // snippet-end:[swift.ec2.DescribeKeyPairs] + // snippet-start:[swift.ec2.DeleteKeyPair] /// Delete an EC2 key pair. /// /// - Parameter keyPair: The name of the key pair to delete. @@ -471,6 +487,7 @@ class Example { return false } } + // snippet-end:[swift.ec2.DeleteKeyPair] /// Return a list of AMI names that contain the specified string. /// @@ -517,6 +534,7 @@ class Example { return matchingAMIs } + // snippet-start:[swift.ec2.DescribeInstanceTypes] /// Return a list of instance types matching the specified architecture /// and instance sizes. /// @@ -528,7 +546,7 @@ class Example { /// /// - Returns: An array of `EC2ClientTypes.InstanceTypeInfo` records /// describing the instance types matching the given requirements. - func getInstanceTypes(architecture: EC2ClientTypes.ArchitectureValues = EC2ClientTypes.ArchitectureValues.x8664, + func getMatchingInstanceTypes(architecture: EC2ClientTypes.ArchitectureValues = EC2ClientTypes.ArchitectureValues.x8664, sizes: [String] = ["*.micro", "*.small"]) async -> [EC2ClientTypes.InstanceTypeInfo] { var instanceTypes: [EC2ClientTypes.InstanceTypeInfo] = [] @@ -563,6 +581,7 @@ class Example { return instanceTypes } + // snippet-end:[swift.ec2.DescribeInstanceTypes] /// Get the latest information about the specified instance and output it /// to the screen, returning the instance details to the caller. @@ -639,6 +658,8 @@ class Example { return nil } + // snippet-start:[swift.ec2.StopInstances] + // snippet-start:[swift.ec2.WaitUntilInstanceStopped] /// Stop the specified instance. /// /// - Parameters: @@ -684,7 +705,11 @@ class Example { return false } } + // snippet-end:[swift.ec2.WaitUntilInstanceStopped] + // snippet-end:[swift.ec2.StopInstances] + // snippet-start:[swift.ec2.StartInstances] + // snippet-start:[swift.ec2.WaitUntilInstanceRunning] /// Start the specified instance. /// /// - Parameters: @@ -729,7 +754,11 @@ class Example { return false } } + // snippet-end:[swift.ec2.WaitUntilInstanceRunning] + // snippet-end:[swift.ec2.StartInstances] + // snippet-start:[swift.ec2.TerminateInstances] + // snippet-start:[swift.ec2.WaitUntilInstanceTerminated] /// Terminate the specified instance. /// /// - Parameters: @@ -774,7 +803,10 @@ class Example { return false } } + // snippet-end:[swift.ec2.WaitUntilInstanceTerminated] + // snippet-end:[swift.ec2.TerminateInstances] + // snippet-start:[swift.ec2.DescribeImages] /// Return an array of `EC2ClientTypes.Image` objects describing all of /// the images in the specified array. /// @@ -808,7 +840,9 @@ class Example { return [] } } + // snippet-end:[swift.ec2.DescribeImages] + // snippet-start:[swift.ec2.RunInstances] /// Create and return a new EC2 instance. /// /// - Parameters: @@ -819,7 +853,7 @@ class Example { /// to. /// /// - Returns: The EC2 instance as an `EC2ClientTypes.Instance` object. - func createInstance(imageId: String, instanceType: EC2ClientTypes.InstanceType, + func runInstance(imageId: String, instanceType: EC2ClientTypes.InstanceType, keyPairName: String, securityGroups: [String]?) async -> EC2ClientTypes.Instance? { do { let output = try await ec2Client.runInstances( @@ -844,7 +878,9 @@ class Example { return nil } } + // snippet-end:[swift.ec2.RunInstances] + // snippet-start:[swift.ec2.getMyIPAddress] /// Return the device's external IP address. /// /// - Returns: A string containing the device's IP address. @@ -862,7 +898,9 @@ class Example { return nil } } + // snippet-end:[swift.ec2.getMyIPAddress] + // snippet-start:[swift.ec2.CreateSecurityGroup] /// Create a new security group. /// /// - Parameters: @@ -885,7 +923,9 @@ class Example { return nil } } + // snippet-end:[swift.ec2.CreateSecurityGroup] + // snippet-start:[swift.ec2.AuthorizeSecurityGroupIngress] /// Authorize ingress of connections for the security group. /// /// - Parameters: @@ -923,7 +963,9 @@ class Example { return false } } + // snippet-end:[swift.ec2.AuthorizeSecurityGroupIngress] + // snippet-start:[swift.ec2.DescribeSecurityGroups] func describeSecurityGroups(groupId: String) async -> Bool { do { let output = try await ec2Client.describeSecurityGroups( @@ -946,7 +988,9 @@ class Example { return false } } + // snippet-end:[swift.ec2.DescribeSecurityGroups] + // snippet-start:[swift.ec2.DeleteSecurityGroup] /// Delete a security group. /// /// - Parameter groupId: The ID of the security group to delete. @@ -966,7 +1010,9 @@ class Example { return false } } + // snippet-end:[swift.ec2.DeleteSecurityGroup] + // snippet-start:[swift.ec2.AllocateAddress] /// Allocate an Elastic IP address. /// /// - Returns: A string containing the ID of the Elastic IP. @@ -988,7 +1034,9 @@ class Example { return nil } } + // snippet-end:[swift.ec2.AllocateAddress] + // snippet-start:[swift.ec2.AssociateAddress] /// Associate the specified allocated Elastic IP to a given instance. /// /// - Parameters: @@ -1012,7 +1060,9 @@ class Example { return nil } } + // snippet-end:[swift.ec2.AssociateAddress] + // snippet-start:[swift.ec2.DisassociateAddress] /// Disassociate an Elastic IP. /// /// - Parameter associationId: The ID of the association to end. @@ -1027,7 +1077,9 @@ class Example { print("*** Unable to disassociate the IP address: \(error.localizedDescription)") } } + // snippet-end:[swift.ec2.DisassociateAddress] + // snippet-start:[swift.ec2.ReleaseAddress] /// Release an allocated Elastic IP. /// /// - Parameter allocationId: The allocation ID of the Elastic IP to @@ -1043,6 +1095,7 @@ class Example { print("*** Unable to release the IP address: \(error.localizedDescription)") } } + // snippet-end:[swift.ec2.ReleaseAddress] /// Generate and return a unique file name that begins with the specified /// string. From 229a277e7847724c23be6a9b57046b29c64f8d10 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Wed, 16 Jul 2025 20:52:09 +0000 Subject: [PATCH 7/8] Add a couple error messages --- swift/example_code/ec2/scenario/Sources/entry.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/swift/example_code/ec2/scenario/Sources/entry.swift b/swift/example_code/ec2/scenario/Sources/entry.swift index 82fdfa2f2c5..243cacc3d84 100644 --- a/swift/example_code/ec2/scenario/Sources/entry.swift +++ b/swift/example_code/ec2/scenario/Sources/entry.swift @@ -427,6 +427,8 @@ class Example { return nil } + // Build the URL of the temporary private key file. + let fileURL = URL.temporaryDirectory .appendingPathComponent(name) .appendingPathExtension("pem") @@ -435,9 +437,11 @@ class Example { try keyMaterial.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8) return fileURL } catch { + print("*** Failed to write the private key.") return nil } } catch { + print("*** Unable to create the key pair.") return nil } } @@ -894,7 +898,7 @@ class Example { print("Getting the IP address...") return try String(contentsOf: url, encoding: String.Encoding.utf8).trim() } catch { - print("Got an error!") + print("*** Unable to get your public IP address.") return nil } } From 9216d1db9a8c540f394e2c792a76f43c6312c497 Mon Sep 17 00:00:00 2001 From: Eric Shepherd Date: Fri, 18 Jul 2025 14:34:15 +0000 Subject: [PATCH 8/8] Update metadata --- .doc_gen/metadata/ec2_metadata.yaml | 12 ++++ swift/example_code/ec2/README.md | 56 +++++++++++++------ swift/example_code/ec2/scenario/Package.swift | 4 +- 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/.doc_gen/metadata/ec2_metadata.yaml b/.doc_gen/metadata/ec2_metadata.yaml index 17033f206ba..97b32d325d6 100644 --- a/.doc_gen/metadata/ec2_metadata.yaml +++ b/.doc_gen/metadata/ec2_metadata.yaml @@ -85,6 +85,7 @@ ec2_Hello: snippet_tags: - swift.ec2.hello.package - description: The entry.swift file. + snippet_tags: - swift.ec2.hello services: ec2: {DescribeSecurityGroups} @@ -2586,6 +2587,17 @@ ec2_Scenario_GetStartedInstances: - description: The main entry point for the scenario. snippet_files: - rustv1/examples/ec2/src/bin/getting-started.rs + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - description: The Package.swift file. + snippet_tags: + - swift.ec2.scenario.package + - description: The entry.swift file. + snippet_tags: + - swift.ec2.scenario services: ec2: { diff --git a/swift/example_code/ec2/README.md b/swift/example_code/ec2/README.md index d352c340a16..41fc4793ddd 100644 --- a/swift/example_code/ec2/README.md +++ b/swift/example_code/ec2/README.md @@ -31,30 +31,37 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `swift ### Get started -- [Hello Amazon EC2](scenario/Package.swift#L8) (`DescribeSecurityGroups`) +- [Hello Amazon EC2](hello/Package.swift#L8) (`DescribeSecurityGroups`) + + +### 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. -- [AllocateAddress](scenario/Sources/entry.swift#L1015) -- [AssociateAddress](scenario/Sources/entry.swift#L1039) -- [AuthorizeSecurityGroupIngress](scenario/Sources/entry.swift#L928) +- [AllocateAddress](scenario/Sources/entry.swift#L1019) +- [AssociateAddress](scenario/Sources/entry.swift#L1043) +- [AuthorizeSecurityGroupIngress](scenario/Sources/entry.swift#L932) - [CreateKeyPair](scenario/Sources/entry.swift#L410) -- [CreateSecurityGroup](scenario/Sources/entry.swift#L903) -- [DeleteKeyPair](scenario/Sources/entry.swift#L469) -- [DeleteSecurityGroup](scenario/Sources/entry.swift#L993) -- [DescribeImages](scenario/Sources/entry.swift#L809) -- [DescribeInstanceTypes](scenario/Sources/entry.swift#L537) -- [DescribeKeyPairs](scenario/Sources/entry.swift#L446) +- [CreateSecurityGroup](scenario/Sources/entry.swift#L907) +- [DeleteKeyPair](scenario/Sources/entry.swift#L473) +- [DeleteSecurityGroup](scenario/Sources/entry.swift#L997) +- [DescribeImages](scenario/Sources/entry.swift#L813) +- [DescribeInstanceTypes](scenario/Sources/entry.swift#L541) +- [DescribeKeyPairs](scenario/Sources/entry.swift#L450) - [DescribeSecurityGroups](hello/Sources/entry.swift#L44) -- [DisassociateAddress](scenario/Sources/entry.swift#L1065) -- [ReleaseAddress](scenario/Sources/entry.swift#L1082) -- [RunInstances](scenario/Sources/entry.swift#L845) -- [StartInstances](scenario/Sources/entry.swift#L711) -- [StopInstances](scenario/Sources/entry.swift#L661) -- [TerminateInstances](scenario/Sources/entry.swift#L760) +- [DisassociateAddress](scenario/Sources/entry.swift#L1069) +- [ReleaseAddress](scenario/Sources/entry.swift#L1086) +- [RunInstances](scenario/Sources/entry.swift#L849) +- [StartInstances](scenario/Sources/entry.swift#L715) +- [StopInstances](scenario/Sources/entry.swift#L665) +- [TerminateInstances](scenario/Sources/entry.swift#L764) @@ -84,6 +91,23 @@ and run commands. This example shows you how to get started using Amazon EC2. +#### Learn the basics + +This example shows you how to do the following: + +- Create a key pair and security group. +- Select an Amazon Machine Image (AMI) and compatible instance type, then create an instance. +- Stop and restart the instance. +- Associate an Elastic IP address with your instance. +- Connect to your instance with SSH, then clean up resources. + + + + + + + + ### Tests diff --git a/swift/example_code/ec2/scenario/Package.swift b/swift/example_code/ec2/scenario/Package.swift index 5fa31fb679e..777f85e941f 100644 --- a/swift/example_code/ec2/scenario/Package.swift +++ b/swift/example_code/ec2/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.hello.package] +// snippet-start:[swift.ec2.scenario.package] // swift-tools-version: 5.9 // // The swift-tools-version declares the minimum version of Swift required to @@ -45,4 +45,4 @@ let package = Package( ] ) -// snippet-end:[swift.ec2.hello.package] +// snippet-end:[swift.ec2.scenario.package]