diff --git a/.doc_gen/metadata/ec2_metadata.yaml b/.doc_gen/metadata/ec2_metadata.yaml index 73ed60a9d66..97b32d325d6 100644 --- a/.doc_gen/metadata/ec2_metadata.yaml +++ b/.doc_gen/metadata/ec2_metadata.yaml @@ -76,6 +76,17 @@ 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. + snippet_tags: + - swift.ec2.hello services: ec2: {DescribeSecurityGroups} ec2_GetPasswordData: @@ -188,6 +199,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: @@ -274,6 +294,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} @@ -370,6 +399,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: @@ -457,6 +495,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: @@ -557,6 +604,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: @@ -656,6 +712,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: @@ -752,6 +817,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: @@ -848,6 +922,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: @@ -918,6 +1001,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: @@ -1013,6 +1105,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: @@ -1094,6 +1195,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: @@ -1183,6 +1293,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: @@ -1269,6 +1388,19 @@ ec2_DescribeSecurityGroups: excerpts: - snippet_tags: - ec2.rust.ec2-helloworld + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/ec2 + excerpts: + - 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: @@ -1355,6 +1487,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: @@ -1473,6 +1614,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: @@ -1807,6 +1957,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: @@ -1877,6 +2036,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: @@ -2419,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 new file mode 100644 index 00000000000..41fc4793ddd --- /dev/null +++ b/swift/example_code/ec2/README.md @@ -0,0 +1,138 @@ +# 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`) + + +### 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#L1019) +- [AssociateAddress](scenario/Sources/entry.swift#L1043) +- [AuthorizeSecurityGroupIngress](scenario/Sources/entry.swift#L932) +- [CreateKeyPair](scenario/Sources/entry.swift#L410) +- [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#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) + + + + + +## 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. + + +#### 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 + +⚠ 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 new file mode 100644 index 00000000000..98ab159210b --- /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.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: "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.ec2.hello.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..7fda09cfe6b --- /dev/null +++ b/swift/example_code/ec2/hello/Sources/entry.swift @@ -0,0 +1,108 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// 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.ec2.import] +import AWSEC2 +// snippet-end:[swift.ec2.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. + """ + ) + + // 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() + ) + + 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 + } + // snippet-end:[swift.ec2.DescribeSecurityGroupsPaginated] + + /// 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.ec2.hello] diff --git a/swift/example_code/ec2/scenario/Package.swift b/swift/example_code/ec2/scenario/Package.swift new file mode 100644 index 00000000000..777f85e941f --- /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.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: "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.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: "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.scenario.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..243cacc3d84 --- /dev/null +++ b/swift/example_code/ec2/scenario/Sources/entry.swift @@ -0,0 +1,1135 @@ +// 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 + +// Allow waiters to be used. + +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) + + await example.run() + } +} + +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. + // Create a `defer` block that will delete the private key when the + // program exits. + //===================================================================== + + print("Creating an RSA key pair...") + + keyName = self.tempName(prefix: "ExampleKeyName") + let keyUrl = await self.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 self.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 = self.tempName(prefix: "ExampleSecurityGroup") + let ipAddress = self.getMyIPAddress() + + guard let ipAddress else { + print("*** Unable to get the device's IP address.") + return + } + + print("IP address is: \(ipAddress)") + + securityGroupId = await self.createSecurityGroup( + name: secGroupName, + description: "An example security group created using the AWS SDK for Swift" + ) + + if securityGroupId == nil { + await cleanUp() + return + } + + print("Created security group: \(securityGroupId ?? "")") + + if !(await self.authorizeSecurityGroupIngress(groupId: securityGroupId!, ipAddress: ipAddress)) { + await cleanUp() + return + } + + //===================================================================== + // 4. Display security group information for the new security group + // using DescribeSecurityGroups. + //===================================================================== + + if !(await self.describeSecurityGroups(groupId: securityGroupId!)) { + await cleanUp() + 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 self.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 matching Amazon Linux 2023:") + + var imageIds: [String] = [] + for option in options { + guard let id = option.value else { + continue + } + imageIds.append(id) + } + + 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 + // 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.") + await cleanUp() + return + } + + let imageTypes = await self.getMatchingInstanceTypes(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.") + 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.runInstance( + imageId: imageId, + instanceType: instanceType, + keyPairName: keyName!, + securityGroups: [securityGroupId!] + ) + + guard let instance else { + await cleanUp() + return + } + + instanceId = instance.instanceId + if instanceId == nil { + print("*** Instance is missing an ID. Canceling.") + await cleanUp() + 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...") + + do { + try await Task.sleep(for: .seconds(20)) + } catch { + print("*** Error pausing the task.") + } + print("Success! Your new instance is ready:") + + //===================================================================== + // 10. Display SSH connection info for the instance. + //===================================================================== + + var runningInstance = await self.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 self.stopInstance(instanceId: instanceId!, waitUntilStopped: true)) { + await cleanUp() + return + } + + //===================================================================== + // 12. Start the instance and wait for it to start (StartInstances). + //===================================================================== + + print("Starting the instance again...") + + if !(await self.startInstance(instanceId: instanceId!, waitUntilStarted: true)) { + await cleanUp() + return + } + + //===================================================================== + // 13. Display SSH connection info for the instance. Note that it's + // changed. + //===================================================================== + + 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.") + print("ssh -i \(keyUrl.path) ec2-user@\(runningInstance!.publicIpAddress!)") + } + + //===================================================================== + // 14. Allocate an elastic IP and associate it with the instance + // (AllocateAddress and AssociateAddress). + //===================================================================== + + allocationId = await self.allocateAddress() + + if allocationId == nil { + await cleanUp() + return + } + + associationId = await self.associateAddress(instanceId: instanceId!, allocationId: allocationId) + + if associationId == nil { + await cleanUp() + return + } + + //===================================================================== + // 15. Display SSH connection info for the connection. Note that the + // public IP is now the Elastic IP, which stays constant. + //===================================================================== + + 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). + //===================================================================== + + 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). + //===================================================================== + + if instanceId != nil { + print("Terminating the instance...") + _ = await self.terminateInstance(instanceId: instanceId!, waitUntilTerminated: true) + } + + //===================================================================== + // 18. Delete the security group (DeleteSecurityGroup). + //===================================================================== + + if securityGroupId != nil { + print("Deleting the security group...") + _ = await self.deleteSecurityGroup(groupId: securityGroupId!) + } + + //===================================================================== + // 19. Delete the key pair (DeleteKeyPair). + //===================================================================== + + if keyName != nil { + print("Deleting the key pair...") + _ = 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. + /// + /// - 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 + } + + // Build the URL of the temporary private key file. + + let fileURL = URL.temporaryDirectory + .appendingPathComponent(name) + .appendingPathExtension("pem") + + do { + 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 + } + } + // 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 { + 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.") + } + } + // 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. + /// + /// - 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 + } + } + // snippet-end:[swift.ec2.DeleteKeyPair] + + /// 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 + } + + // snippet-start:[swift.ec2.DescribeInstanceTypes] + /// 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 getMatchingInstanceTypes(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 + } + // 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. + /// + /// - 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 + } + + // snippet-start:[swift.ec2.StopInstances] + // snippet-start:[swift.ec2.WaitUntilInstanceStopped] + /// 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. 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 + } + } + // 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: + /// - 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 + } + } + // 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: + /// - 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] + + do { + _ = try await ec2Client.terminateInstances( + input: TerminateInstancesInput( + instanceIds: instanceList + ) + ) + + if waitUntilTerminated { + print("Waiting for the instance to terminate...") + + let waitOptions = WaiterOptions(maxWaitTime: 600.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 + } + } + // 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. + /// + /// - 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 [] + } + } + // snippet-end:[swift.ec2.DescribeImages] + + // snippet-start:[swift.ec2.RunInstances] + /// 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 runInstance(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 + } + } + // 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. + 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("*** Unable to get your public IP address.") + return nil + } + } + // snippet-end:[swift.ec2.getMyIPAddress] + + // snippet-start:[swift.ec2.CreateSecurityGroup] + /// 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 + } + } + // snippet-end:[swift.ec2.CreateSecurityGroup] + + // snippet-start:[swift.ec2.AuthorizeSecurityGroupIngress] + /// 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 { + print("*** Error authorizing ingress for the security group: \(error.localizedDescription)") + 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( + 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 + } + } + // snippet-end:[swift.ec2.DescribeSecurityGroups] + + // snippet-start:[swift.ec2.DeleteSecurityGroup] + /// 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 + } + } + // 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. + 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 + } + } + // snippet-end:[swift.ec2.AllocateAddress] + + // snippet-start:[swift.ec2.AssociateAddress] + /// 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 + } + } + // snippet-end:[swift.ec2.AssociateAddress] + + // snippet-start:[swift.ec2.DisassociateAddress] + /// 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)") + } + } + // 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 + /// 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)") + } + } + // snippet-end:[swift.ec2.ReleaseAddress] + + /// 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]