Amazon EC2 examples using SDK for Rust - AWS SDK for Rust

Amazon EC2 examples using SDK for Rust

The following code examples show you how to perform actions and implement common scenarios by using the AWS SDK for Rust with Amazon EC2.

Basics are code examples that show you how to perform the essential operations within a service.

Actions are code excerpts from larger programs and must be run in context. While actions show you how to call individual service functions, you can see actions in context in their related scenarios.

Each example includes a link to the complete source code, where you can find instructions on how to set up and run the code in context.

Get started

The following code examples show how to get started using Amazon EC2.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

async fn show_security_groups(client: &aws_sdk_ec2::Client, group_ids: Vec<String>) { let response = client .describe_security_groups() .set_group_ids(Some(group_ids)) .send() .await; match response { Ok(output) => { for group in output.security_groups() { println!( "Found Security Group {} ({}), vpc id {} and description {}", group.group_name().unwrap_or("unknown"), group.group_id().unwrap_or("id-unknown"), group.vpc_id().unwrap_or("vpcid-unknown"), group.description().unwrap_or("(none)") ); } } Err(err) => { let err = err.into_service_error(); let meta = err.meta(); let message = meta.message().unwrap_or("unknown"); let code = meta.code().unwrap_or("unknown"); eprintln!("Error listing EC2 Security Groups: ({code}) {message}"); } } }

Basics

The following code example shows how to:

  • 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.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

The EC2InstanceScenario implementation contains logic to run the example as a whole.

//! Scenario that uses the AWS SDK for Rust (the SDK) with Amazon Elastic Compute Cloud //! (Amazon EC2) to do the following: //! //! * Create a key pair that is used to secure SSH communication between your computer and //! an EC2 instance. //! * Create a security group that acts as a virtual firewall for your EC2 instances to //! control incoming and outgoing traffic. //! * Find an Amazon Machine Image (AMI) and a compatible instance type. //! * Create an instance that is created from the instance type and AMI you select, and //! is configured to use the security group and key pair created in this example. //! * Stop and restart the instance. //! * Create an Elastic IP address and associate it as a consistent IP address for your instance. //! * Connect to your instance with SSH, using both its public IP address and your Elastic IP //! address. //! * Clean up all of the resources created by this example. use std::net::Ipv4Addr; use crate::{ ec2::{EC2Error, EC2}, getting_started::{key_pair::KeyPairManager, util::Util}, ssm::SSM, }; use aws_sdk_ssm::types::Parameter; use super::{ elastic_ip::ElasticIpManager, instance::InstanceManager, security_group::SecurityGroupManager, util::ScenarioImage, }; pub struct Ec2InstanceScenario { ec2: EC2, ssm: SSM, util: Util, key_pair_manager: KeyPairManager, security_group_manager: SecurityGroupManager, instance_manager: InstanceManager, elastic_ip_manager: ElasticIpManager, } impl Ec2InstanceScenario { pub fn new(ec2: EC2, ssm: SSM, util: Util) -> Self { Ec2InstanceScenario { ec2, ssm, util, key_pair_manager: Default::default(), security_group_manager: Default::default(), instance_manager: Default::default(), elastic_ip_manager: Default::default(), } } pub async fn run(&mut self) -> Result<(), EC2Error> { self.create_and_list_key_pairs().await?; self.create_security_group().await?; self.create_instance().await?; self.stop_and_start_instance().await?; self.associate_elastic_ip().await?; self.stop_and_start_instance().await?; Ok(()) } /// 1. Creates an RSA key pair and saves its private key data as a .pem file in secure /// temporary storage. The private key data is deleted after the example completes. /// 2. Optionally, lists the first five key pairs for the current account. pub async fn create_and_list_key_pairs(&mut self) -> Result<(), EC2Error> { println!( "Let's create an RSA key pair that you can be use to securely connect to your EC2 instance."); let key_name = self.util.prompt_key_name()?; self.key_pair_manager .create(&self.ec2, &self.util, key_name) .await?; println!( "Created a key pair {} and saved the private key to {:?}.", self.key_pair_manager .key_pair() .key_name() .ok_or_else(|| EC2Error::new("No key name after creating key"))?, self.key_pair_manager .key_file_path() .ok_or_else(|| EC2Error::new("No key file after creating key"))? ); if self.util.should_list_key_pairs()? { for pair in self.key_pair_manager.list(&self.ec2).await? { println!( "Found {:?} key {} with fingerprint:\t{:?}", pair.key_type(), pair.key_name().unwrap_or("Unknown"), pair.key_fingerprint() ); } } Ok(()) } /// 1. Creates a security group for the default VPC. /// 2. Adds an inbound rule to allow SSH. The SSH rule allows only /// inbound traffic from the current computer’s public IPv4 address. /// 3. Displays information about the security group. /// /// This function uses <http://checkip.amazonaws.com> to get the current public IP /// address of the computer that is running the example. This method works in most /// cases. However, depending on how your computer connects to the internet, you /// might have to manually add your public IP address to the security group by using /// the AWS Management Console. pub async fn create_security_group(&mut self) -> Result<(), EC2Error> { println!("Let's create a security group to manage access to your instance."); let group_name = self.util.prompt_security_group_name()?; self.security_group_manager .create( &self.ec2, &group_name, "Security group for example: get started with instances.", ) .await?; println!( "Created security group {} in your default VPC {}.", self.security_group_manager.group_name(), self.security_group_manager .vpc_id() .unwrap_or("(unknown vpc)") ); let check_ip = self.util.do_get("https://checkip.amazonaws.com").await?; let current_ip_address: Ipv4Addr = check_ip.trim().parse().map_err(|e| { EC2Error::new(format!( "Failed to convert response {} to IP Address: {e:?}", check_ip )) })?; println!("Your public IP address seems to be {current_ip_address}"); if self.util.should_add_to_security_group() { match self .security_group_manager .authorize_ingress(&self.ec2, current_ip_address) .await { Ok(_) => println!("Security group rules updated"), Err(err) => eprintln!("Couldn't update security group rules: {err:?}"), } } println!("{}", self.security_group_manager); Ok(()) } /// 1. Gets a list of Amazon Linux 2 AMIs from AWS Systems Manager. Specifying the /// '/aws/service/ami-amazon-linux-latest' path returns only the latest AMIs. /// 2. Gets and displays information about the available AMIs and lets you select one. /// 3. Gets a list of instance types that are compatible with the selected AMI and /// lets you select one. /// 4. Creates an instance with the previously created key pair and security group, /// and the selected AMI and instance type. /// 5. Waits for the instance to be running and then displays its information. pub async fn create_instance(&mut self) -> Result<(), EC2Error> { let ami = self.find_image().await?; let instance_types = self .ec2 .list_instance_types(&ami.0) .await .map_err(|e| e.add_message("Could not find instance types"))?; println!( "There are several instance types that support the {} architecture of the image.", ami.0 .architecture .as_ref() .ok_or_else(|| EC2Error::new(format!("Missing architecture in {:?}", ami.0)))? ); let instance_type = self.util.select_instance_type(instance_types)?; println!("Creating your instance and waiting for it to start..."); self.instance_manager .create( &self.ec2, ami.0 .image_id() .ok_or_else(|| EC2Error::new("Could not find image ID"))?, instance_type, self.key_pair_manager.key_pair(), self.security_group_manager .security_group() .map(|sg| vec![sg]) .ok_or_else(|| EC2Error::new("Could not find security group"))?, ) .await .map_err(|e| e.add_message("Scenario failed to create instance"))?; while let Err(err) = self .ec2 .wait_for_instance_ready(self.instance_manager.instance_id(), None) .await { println!("{err}"); if !self.util.should_continue_waiting() { return Err(err); } } println!("Your instance is ready:\n{}", self.instance_manager); self.display_ssh_info(); Ok(()) } async fn find_image(&mut self) -> Result<ScenarioImage, EC2Error> { let params: Vec<Parameter> = self .ssm .list_path("/aws/service/ami-amazon-linux-latest") .await .map_err(|e| e.add_message("Could not find parameters for available images"))? .into_iter() .filter(|param| param.name().is_some_and(|name| name.contains("amzn2"))) .collect(); let amzn2_images: Vec<ScenarioImage> = self .ec2 .list_images(params) .await .map_err(|e| e.add_message("Could not find images"))? .into_iter() .map(ScenarioImage::from) .collect(); println!("We will now create an instance from an Amazon Linux 2 AMI"); let ami = self.util.select_scenario_image(amzn2_images)?; Ok(ami) } // 1. Stops the instance and waits for it to stop. // 2. Starts the instance and waits for it to start. // 3. Displays information about the instance. // 4. Displays an SSH connection string. When an Elastic IP address is associated // with the instance, the IP address stays consistent when the instance stops // and starts. pub async fn stop_and_start_instance(&self) -> Result<(), EC2Error> { println!("Let's stop and start your instance to see what changes."); println!("Stopping your instance and waiting until it's stopped..."); self.instance_manager.stop(&self.ec2).await?; println!("Your instance is stopped. Restarting..."); self.instance_manager.start(&self.ec2).await?; println!("Your instance is running."); println!("{}", self.instance_manager); if self.elastic_ip_manager.public_ip() == "0.0.0.0" { println!("Every time your instance is restarted, its public IP address changes."); } else { println!( "Because you have associated an Elastic IP with your instance, you can connect by using a consistent IP address after the instance restarts." ); } self.display_ssh_info(); Ok(()) } /// 1. Allocates an Elastic IP address and associates it with the instance. /// 2. Displays an SSH connection string that uses the Elastic IP address. async fn associate_elastic_ip(&mut self) -> Result<(), EC2Error> { self.elastic_ip_manager.allocate(&self.ec2).await?; println!( "Allocated static Elastic IP address: {}", self.elastic_ip_manager.public_ip() ); self.elastic_ip_manager .associate(&self.ec2, self.instance_manager.instance_id()) .await?; println!("Associated your Elastic IP with your instance."); println!("You can now use SSH to connect to your instance by using the Elastic IP."); self.display_ssh_info(); Ok(()) } /// Displays an SSH connection string that can be used to connect to a running /// instance. fn display_ssh_info(&self) { let ip_addr = if self.elastic_ip_manager.has_allocation() { self.elastic_ip_manager.public_ip() } else { self.instance_manager.instance_ip() }; let key_file_path = self.key_pair_manager.key_file_path().unwrap(); println!("To connect, open another command prompt and run the following command:"); println!("\nssh -i {} ec2-user@{ip_addr}\n", key_file_path.display()); let _ = self.util.enter_to_continue(); } /// 1. Disassociate and delete the previously created Elastic IP. /// 2. Terminate the previously created instance. /// 3. Delete the previously created security group. /// 4. Delete the previously created key pair. pub async fn clean_up(self) { println!("Let's clean everything up. This example created these resources:"); println!( "\tKey pair: {}", self.key_pair_manager .key_pair() .key_name() .unwrap_or("(unknown key pair)") ); println!( "\tSecurity group: {}", self.security_group_manager.group_name() ); println!( "\tInstance: {}", self.instance_manager.instance_display_name() ); if self.util.should_clean_resources() { if let Err(err) = self.elastic_ip_manager.remove(&self.ec2).await { eprintln!("{err}") } if let Err(err) = self.instance_manager.delete(&self.ec2).await { eprintln!("{err}") } if let Err(err) = self.security_group_manager.delete(&self.ec2).await { eprintln!("{err}"); } if let Err(err) = self.key_pair_manager.delete(&self.ec2, &self.util).await { eprintln!("{err}"); } } else { println!("Ok, not cleaning up any resources!"); } } } pub async fn run(mut scenario: Ec2InstanceScenario) { println!("--------------------------------------------------------------------------------"); println!( "Welcome to the Amazon Elastic Compute Cloud (Amazon EC2) get started with instances demo." ); println!("--------------------------------------------------------------------------------"); if let Err(err) = scenario.run().await { eprintln!("There was an error running the scenario: {err}") } println!("--------------------------------------------------------------------------------"); scenario.clean_up().await; println!("Thanks for running!"); println!("--------------------------------------------------------------------------------"); }

The EC2Impl struct serves as a an automock point for testing, and its functions wrap the EC2 SDK calls.

use std::{net::Ipv4Addr, time::Duration}; use aws_sdk_ec2::{ client::Waiters, error::ProvideErrorMetadata, operation::{ allocate_address::AllocateAddressOutput, associate_address::AssociateAddressOutput, }, types::{ DomainType, Filter, Image, Instance, InstanceType, IpPermission, IpRange, KeyPairInfo, SecurityGroup, Tag, }, Client as EC2Client, }; use aws_sdk_ssm::types::Parameter; use aws_smithy_runtime_api::client::waiters::error::WaiterError; #[cfg(test)] use mockall::automock; #[cfg(not(test))] pub use EC2Impl as EC2; #[cfg(test)] pub use MockEC2Impl as EC2; #[derive(Clone)] pub struct EC2Impl { pub client: EC2Client, } #[cfg_attr(test, automock)] impl EC2Impl { pub fn new(client: EC2Client) -> Self { EC2Impl { client } } pub async fn create_key_pair(&self, name: String) -> Result<(KeyPairInfo, String), EC2Error> { tracing::info!("Creating key pair {name}"); let output = self.client.create_key_pair().key_name(name).send().await?; let info = KeyPairInfo::builder() .set_key_name(output.key_name) .set_key_fingerprint(output.key_fingerprint) .set_key_pair_id(output.key_pair_id) .build(); let material = output .key_material .ok_or_else(|| EC2Error::new("Create Key Pair has no key material"))?; Ok((info, material)) } pub async fn list_key_pair(&self) -> Result<Vec<KeyPairInfo>, EC2Error> { let output = self.client.describe_key_pairs().send().await?; Ok(output.key_pairs.unwrap_or_default()) } pub async fn delete_key_pair(&self, key_name: &str) -> Result<(), EC2Error> { let key_name: String = key_name.into(); tracing::info!("Deleting key pair {key_name}"); self.client .delete_key_pair() .key_name(key_name) .send() .await?; Ok(()) } pub async fn create_security_group( &self, name: &str, description: &str, ) -> Result<SecurityGroup, EC2Error> { tracing::info!("Creating security group {name}"); let create_output = self .client .create_security_group() .group_name(name) .description(description) .send() .await .map_err(EC2Error::from)?; let group_id = create_output .group_id .ok_or_else(|| EC2Error::new("Missing security group id after creation"))?; let group = self .describe_security_group(&group_id) .await? .ok_or_else(|| { EC2Error::new(format!("Could not find security group with id {group_id}")) })?; tracing::info!("Created security group {name} as {group_id}"); Ok(group) } /// Find a single security group, by ID. Returns Err if multiple groups are found. pub async fn describe_security_group( &self, group_id: &str, ) -> Result<Option<SecurityGroup>, EC2Error> { let group_id: String = group_id.into(); let describe_output = self .client .describe_security_groups() .group_ids(&group_id) .send() .await?; let mut groups = describe_output.security_groups.unwrap_or_default(); match groups.len() { 0 => Ok(None), 1 => Ok(Some(groups.remove(0))), _ => Err(EC2Error::new(format!( "Expected single group for {group_id}" ))), } } /// Add an ingress rule to a security group explicitly allowing IPv4 address /// as {ip}/32 over TCP port 22. pub async fn authorize_security_group_ssh_ingress( &self, group_id: &str, ingress_ips: Vec<Ipv4Addr>, ) -> Result<(), EC2Error> { tracing::info!("Authorizing ingress for security group {group_id}"); self.client .authorize_security_group_ingress() .group_id(group_id) .set_ip_permissions(Some( ingress_ips .into_iter() .map(|ip| { IpPermission::builder() .ip_protocol("tcp") .from_port(22) .to_port(22) .ip_ranges(IpRange::builder().cidr_ip(format!("{ip}/32")).build()) .build() }) .collect(), )) .send() .await?; Ok(()) } pub async fn delete_security_group(&self, group_id: &str) -> Result<(), EC2Error> { tracing::info!("Deleting security group {group_id}"); self.client .delete_security_group() .group_id(group_id) .send() .await?; Ok(()) } pub async fn list_images(&self, ids: Vec<Parameter>) -> Result<Vec<Image>, EC2Error> { let image_ids = ids.into_iter().filter_map(|p| p.value).collect(); let output = self .client .describe_images() .set_image_ids(Some(image_ids)) .send() .await?; let images = output.images.unwrap_or_default(); if images.is_empty() { Err(EC2Error::new("No images for selected AMIs")) } else { Ok(images) } } /// List instance types that match an image's architecture and are free tier eligible. pub async fn list_instance_types(&self, image: &Image) -> Result<Vec<InstanceType>, EC2Error> { let architecture = format!( "{}", image.architecture().ok_or_else(|| EC2Error::new(format!( "Image {:?} does not have a listed architecture", image.image_id() )))? ); let free_tier_eligible_filter = Filter::builder() .name("free-tier-eligible") .values("false") .build(); let supported_architecture_filter = Filter::builder() .name("processor-info.supported-architecture") .values(architecture) .build(); let response = self .client .describe_instance_types() .filters(free_tier_eligible_filter) .filters(supported_architecture_filter) .send() .await?; Ok(response .instance_types .unwrap_or_default() .into_iter() .filter_map(|iti| iti.instance_type) .collect()) } pub async fn create_instance<'a>( &self, image_id: &'a str, instance_type: InstanceType, key_pair: &'a KeyPairInfo, security_groups: Vec<&'a SecurityGroup>, ) -> Result<String, EC2Error> { let run_instances = self .client .run_instances() .image_id(image_id) .instance_type(instance_type) .key_name( key_pair .key_name() .ok_or_else(|| EC2Error::new("Missing key name when launching instance"))?, ) .set_security_group_ids(Some( security_groups .iter() .filter_map(|sg| sg.group_id.clone()) .collect(), )) .min_count(1) .max_count(1) .send() .await?; if run_instances.instances().is_empty() { return Err(EC2Error::new("Failed to create instance")); } let instance_id = run_instances.instances()[0].instance_id().unwrap(); let response = self .client .create_tags() .resources(instance_id) .tags( Tag::builder() .key("Name") .value("From SDK Examples") .build(), ) .send() .await; match response { Ok(_) => tracing::info!("Created {instance_id} and applied tags."), Err(err) => { tracing::info!("Error applying tags to {instance_id}: {err:?}"); return Err(err.into()); } } tracing::info!("Instance is created."); Ok(instance_id.to_string()) } /// Wait for an instance to be ready and status ok (default wait 60 seconds) pub async fn wait_for_instance_ready( &self, instance_id: &str, duration: Option<Duration>, ) -> Result<(), EC2Error> { self.client .wait_until_instance_status_ok() .instance_ids(instance_id) .wait(duration.unwrap_or(Duration::from_secs(60))) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to start.", exceeded.max_wait().as_secs() )), _ => EC2Error::from(err), })?; Ok(()) } pub async fn describe_instance(&self, instance_id: &str) -> Result<Instance, EC2Error> { let response = self .client .describe_instances() .instance_ids(instance_id) .send() .await?; let instance = response .reservations() .first() .ok_or_else(|| EC2Error::new(format!("No instance reservations for {instance_id}")))? .instances() .first() .ok_or_else(|| { EC2Error::new(format!("No instances in reservation for {instance_id}")) })?; Ok(instance.clone()) } pub async fn start_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Starting instance {instance_id}"); self.client .start_instances() .instance_ids(instance_id) .send() .await?; tracing::info!("Started instance."); Ok(()) } pub async fn stop_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Stopping instance {instance_id}"); self.client .stop_instances() .instance_ids(instance_id) .send() .await?; self.wait_for_instance_stopped(instance_id, None).await?; tracing::info!("Stopped instance."); Ok(()) } pub async fn reboot_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Rebooting instance {instance_id}"); self.client .reboot_instances() .instance_ids(instance_id) .send() .await?; Ok(()) } pub async fn wait_for_instance_stopped( &self, instance_id: &str, duration: Option<Duration>, ) -> Result<(), EC2Error> { self.client .wait_until_instance_stopped() .instance_ids(instance_id) .wait(duration.unwrap_or(Duration::from_secs(60))) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to stop.", exceeded.max_wait().as_secs(), )), _ => EC2Error::from(err), })?; Ok(()) } pub async fn delete_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Deleting instance with id {instance_id}"); self.stop_instance(instance_id).await?; self.client .terminate_instances() .instance_ids(instance_id) .send() .await?; self.wait_for_instance_terminated(instance_id).await?; tracing::info!("Terminated instance with id {instance_id}"); Ok(()) } async fn wait_for_instance_terminated(&self, instance_id: &str) -> Result<(), EC2Error> { self.client .wait_until_instance_terminated() .instance_ids(instance_id) .wait(Duration::from_secs(60)) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to terminate.", exceeded.max_wait().as_secs(), )), _ => EC2Error::from(err), })?; Ok(()) } pub async fn allocate_ip_address(&self) -> Result<AllocateAddressOutput, EC2Error> { self.client .allocate_address() .domain(DomainType::Vpc) .send() .await .map_err(EC2Error::from) } pub async fn deallocate_ip_address(&self, allocation_id: &str) -> Result<(), EC2Error> { self.client .release_address() .allocation_id(allocation_id) .send() .await?; Ok(()) } pub async fn associate_ip_address( &self, allocation_id: &str, instance_id: &str, ) -> Result<AssociateAddressOutput, EC2Error> { let response = self .client .associate_address() .allocation_id(allocation_id) .instance_id(instance_id) .send() .await?; Ok(response) } pub async fn disassociate_ip_address(&self, association_id: &str) -> Result<(), EC2Error> { self.client .disassociate_address() .association_id(association_id) .send() .await?; Ok(()) } } #[derive(Debug)] pub struct EC2Error(String); impl EC2Error { pub fn new(value: impl Into<String>) -> Self { EC2Error(value.into()) } pub fn add_message(self, message: impl Into<String>) -> Self { EC2Error(format!("{}: {}", message.into(), self.0)) } } impl<T: ProvideErrorMetadata> From<T> for EC2Error { fn from(value: T) -> Self { EC2Error(format!( "{}: {}", value .code() .map(String::from) .unwrap_or("unknown code".into()), value .message() .map(String::from) .unwrap_or("missing reason".into()), )) } } impl std::error::Error for EC2Error {} impl std::fmt::Display for EC2Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } }

The SSM struct serves as a an automock point for testing, and its functions wraps SSM SDK calls.

use aws_sdk_ssm::{types::Parameter, Client}; use aws_smithy_async::future::pagination_stream::TryFlatMap; use crate::ec2::EC2Error; #[cfg(test)] use mockall::automock; #[cfg(not(test))] pub use SSMImpl as SSM; #[cfg(test)] pub use MockSSMImpl as SSM; pub struct SSMImpl { inner: Client, } #[cfg_attr(test, automock)] impl SSMImpl { pub fn new(inner: Client) -> Self { SSMImpl { inner } } pub async fn list_path(&self, path: &str) -> Result<Vec<Parameter>, EC2Error> { let maybe_params: Vec<Result<Parameter, _>> = TryFlatMap::new( self.inner .get_parameters_by_path() .path(path) .into_paginator() .send(), ) .flat_map(|item| item.parameters.unwrap_or_default()) .collect() .await; // Fail on the first error let params = maybe_params .into_iter() .collect::<Result<Vec<Parameter>, _>>()?; Ok(params) } }

The scenario uses several "Manager"-style structs to handle access to resources that are created and deleted throughout the scenario.

use aws_sdk_ec2::operation::{ allocate_address::AllocateAddressOutput, associate_address::AssociateAddressOutput, }; use crate::ec2::{EC2Error, EC2}; /// ElasticIpManager tracks the lifecycle of a public IP address, including its /// allocation from the global pool and association with a specific instance. #[derive(Debug, Default)] pub struct ElasticIpManager { elastic_ip: Option<AllocateAddressOutput>, association: Option<AssociateAddressOutput>, } impl ElasticIpManager { pub fn has_allocation(&self) -> bool { self.elastic_ip.is_some() } pub fn public_ip(&self) -> &str { if let Some(allocation) = &self.elastic_ip { if let Some(addr) = allocation.public_ip() { return addr; } } "0.0.0.0" } pub async fn allocate(&mut self, ec2: &EC2) -> Result<(), EC2Error> { let allocation = ec2.allocate_ip_address().await?; self.elastic_ip = Some(allocation); Ok(()) } pub async fn associate(&mut self, ec2: &EC2, instance_id: &str) -> Result<(), EC2Error> { if let Some(allocation) = &self.elastic_ip { if let Some(allocation_id) = allocation.allocation_id() { let association = ec2.associate_ip_address(allocation_id, instance_id).await?; self.association = Some(association); return Ok(()); } } Err(EC2Error::new("No ip address allocation to associate")) } pub async fn remove(mut self, ec2: &EC2) -> Result<(), EC2Error> { if let Some(association) = &self.association { if let Some(association_id) = association.association_id() { ec2.disassociate_ip_address(association_id).await?; } } self.association = None; if let Some(allocation) = &self.elastic_ip { if let Some(allocation_id) = allocation.allocation_id() { ec2.deallocate_ip_address(allocation_id).await?; } } self.elastic_ip = None; Ok(()) } } use std::fmt::Display; use aws_sdk_ec2::types::{Instance, InstanceType, KeyPairInfo, SecurityGroup}; use crate::ec2::{EC2Error, EC2}; /// InstanceManager wraps the lifecycle of an EC2 Instance. #[derive(Debug, Default)] pub struct InstanceManager { instance: Option<Instance>, } impl InstanceManager { pub fn instance_id(&self) -> &str { if let Some(instance) = &self.instance { if let Some(id) = instance.instance_id() { return id; } } "Unknown" } pub fn instance_name(&self) -> &str { if let Some(instance) = &self.instance { if let Some(tag) = instance.tags().iter().find(|e| e.key() == Some("Name")) { if let Some(value) = tag.value() { return value; } } } "Unknown" } pub fn instance_ip(&self) -> &str { if let Some(instance) = &self.instance { if let Some(public_ip_address) = instance.public_ip_address() { return public_ip_address; } } "0.0.0.0" } pub fn instance_display_name(&self) -> String { format!("{} ({})", self.instance_name(), self.instance_id()) } /// Create an EC2 instance with the given ID on a given type, using a /// generated KeyPair and applying a list of security groups. pub async fn create( &mut self, ec2: &EC2, image_id: &str, instance_type: InstanceType, key_pair: &KeyPairInfo, security_groups: Vec<&SecurityGroup>, ) -> Result<(), EC2Error> { let instance_id = ec2 .create_instance(image_id, instance_type, key_pair, security_groups) .await?; let instance = ec2.describe_instance(&instance_id).await?; self.instance = Some(instance); Ok(()) } /// Start the managed EC2 instance, if present. pub async fn start(&self, ec2: &EC2) -> Result<(), EC2Error> { if self.instance.is_some() { ec2.start_instance(self.instance_id()).await?; } Ok(()) } /// Stop the managed EC2 instance, if present. pub async fn stop(&self, ec2: &EC2) -> Result<(), EC2Error> { if self.instance.is_some() { ec2.stop_instance(self.instance_id()).await?; } Ok(()) } pub async fn reboot(&self, ec2: &EC2) -> Result<(), EC2Error> { if self.instance.is_some() { ec2.reboot_instance(self.instance_id()).await?; ec2.wait_for_instance_stopped(self.instance_id(), None) .await?; ec2.wait_for_instance_ready(self.instance_id(), None) .await?; } Ok(()) } /// Terminate and delete the managed EC2 instance, if present. pub async fn delete(self, ec2: &EC2) -> Result<(), EC2Error> { if self.instance.is_some() { ec2.delete_instance(self.instance_id()).await?; } Ok(()) } } impl Display for InstanceManager { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(instance) = &self.instance { writeln!(f, "\tID: {}", instance.instance_id().unwrap_or("(Unknown)"))?; writeln!( f, "\tImage ID: {}", instance.image_id().unwrap_or("(Unknown)") )?; writeln!( f, "\tInstance type: {}", instance .instance_type() .map(|it| format!("{it}")) .unwrap_or("(Unknown)".to_string()) )?; writeln!( f, "\tKey name: {}", instance.key_name().unwrap_or("(Unknown)") )?; writeln!(f, "\tVPC ID: {}", instance.vpc_id().unwrap_or("(Unknown)"))?; writeln!( f, "\tPublic IP: {}", instance.public_ip_address().unwrap_or("(Unknown)") )?; let instance_state = instance .state .as_ref() .map(|is| { is.name() .map(|isn| format!("{isn}")) .unwrap_or("(Unknown)".to_string()) }) .unwrap_or("(Unknown)".to_string()); writeln!(f, "\tState: {instance_state}")?; } else { writeln!(f, "\tNo loaded instance")?; } Ok(()) } } use std::{env, path::PathBuf}; use aws_sdk_ec2::types::KeyPairInfo; use crate::ec2::{EC2Error, EC2}; use super::util::Util; /// KeyPairManager tracks a KeyPairInfo and the path the private key has been /// written to, if it's been created. #[derive(Debug)] pub struct KeyPairManager { key_pair: KeyPairInfo, key_file_path: Option<PathBuf>, key_file_dir: PathBuf, } impl KeyPairManager { pub fn new() -> Self { Self::default() } pub fn key_pair(&self) -> &KeyPairInfo { &self.key_pair } pub fn key_file_path(&self) -> Option<&PathBuf> { self.key_file_path.as_ref() } pub fn key_file_dir(&self) -> &PathBuf { &self.key_file_dir } /// Creates a key pair that can be used to securely connect to an EC2 instance. /// The returned key pair contains private key information that cannot be retrieved /// again. The private key data is stored as a .pem file. /// /// :param key_name: The name of the key pair to create. pub async fn create( &mut self, ec2: &EC2, util: &Util, key_name: String, ) -> Result<KeyPairInfo, EC2Error> { let (key_pair, material) = ec2.create_key_pair(key_name.clone()).await.map_err(|e| { self.key_pair = KeyPairInfo::builder().key_name(key_name.clone()).build(); e.add_message(format!("Couldn't create key {key_name}")) })?; let path = self.key_file_dir.join(format!("{key_name}.pem")); // Save the key_pair information immediately, so it can get cleaned up if write_secure fails. self.key_file_path = Some(path.clone()); self.key_pair = key_pair.clone(); util.write_secure(&key_name, &path, material)?; Ok(key_pair) } pub async fn delete(self, ec2: &EC2, util: &Util) -> Result<(), EC2Error> { if let Some(key_name) = self.key_pair.key_name() { ec2.delete_key_pair(key_name).await?; if let Some(key_path) = self.key_file_path() { if let Err(err) = util.remove(key_path) { eprintln!("Failed to remove {key_path:?} ({err:?})"); } } } Ok(()) } pub async fn list(&self, ec2: &EC2) -> Result<Vec<KeyPairInfo>, EC2Error> { ec2.list_key_pair().await } } impl Default for KeyPairManager { fn default() -> Self { KeyPairManager { key_pair: KeyPairInfo::builder().build(), key_file_path: Default::default(), key_file_dir: env::temp_dir(), } } } use std::net::Ipv4Addr; use aws_sdk_ec2::types::SecurityGroup; use crate::ec2::{EC2Error, EC2}; /// SecurityGroupManager tracks the lifecycle of a SecurityGroup for an instance, /// including adding a rule to allow SSH from a public IP address. #[derive(Debug, Default)] pub struct SecurityGroupManager { group_name: String, group_description: String, security_group: Option<SecurityGroup>, } impl SecurityGroupManager { pub async fn create( &mut self, ec2: &EC2, group_name: &str, group_description: &str, ) -> Result<(), EC2Error> { self.group_name = group_name.into(); self.group_description = group_description.into(); self.security_group = Some( ec2.create_security_group(group_name, group_description) .await .map_err(|e| e.add_message("Couldn't create security group"))?, ); Ok(()) } pub async fn authorize_ingress(&self, ec2: &EC2, ip_address: Ipv4Addr) -> Result<(), EC2Error> { if let Some(sg) = &self.security_group { ec2.authorize_security_group_ssh_ingress( sg.group_id() .ok_or_else(|| EC2Error::new("Missing security group ID"))?, vec![ip_address], ) .await?; }; Ok(()) } pub async fn delete(self, ec2: &EC2) -> Result<(), EC2Error> { if let Some(sg) = &self.security_group { ec2.delete_security_group( sg.group_id() .ok_or_else(|| EC2Error::new("Missing security group ID"))?, ) .await?; }; Ok(()) } pub fn group_name(&self) -> &str { &self.group_name } pub fn vpc_id(&self) -> Option<&str> { self.security_group.as_ref().and_then(|sg| sg.vpc_id()) } pub fn security_group(&self) -> Option<&SecurityGroup> { self.security_group.as_ref() } } impl std::fmt::Display for SecurityGroupManager { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.security_group { Some(sg) => { writeln!( f, "Security group: {}", sg.group_name().unwrap_or("(unknown group)") )?; writeln!(f, "\tID: {}", sg.group_id().unwrap_or("(unknown group id)"))?; writeln!(f, "\tVPC: {}", sg.vpc_id().unwrap_or("(unknown group vpc)"))?; if !sg.ip_permissions().is_empty() { writeln!(f, "\tInbound Permissions:")?; for permission in sg.ip_permissions() { writeln!(f, "\t\t{permission:?}")?; } } Ok(()) } None => writeln!(f, "No security group loaded."), } } }

The main entry point for the scenario.

use ec2_code_examples::{ ec2::EC2, getting_started::{ scenario::{run, Ec2InstanceScenario}, util::UtilImpl, }, ssm::SSM, }; #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); let sdk_config = aws_config::load_from_env().await; let ec2 = EC2::new(aws_sdk_ec2::Client::new(&sdk_config)); let ssm = SSM::new(aws_sdk_ssm::Client::new(&sdk_config)); let util = UtilImpl {}; let scenario = Ec2InstanceScenario::new(ec2, ssm, util); run(scenario).await; }

Actions

The following code example shows how to use AllocateAddress.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn allocate_ip_address(&self) -> Result<AllocateAddressOutput, EC2Error> { self.client .allocate_address() .domain(DomainType::Vpc) .send() .await .map_err(EC2Error::from) }

The following code example shows how to use AssociateAddress.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn associate_ip_address( &self, allocation_id: &str, instance_id: &str, ) -> Result<AssociateAddressOutput, EC2Error> { let response = self .client .associate_address() .allocation_id(allocation_id) .instance_id(instance_id) .send() .await?; Ok(response) }

The following code example shows how to use AuthorizeSecurityGroupIngress.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

/// Add an ingress rule to a security group explicitly allowing IPv4 address /// as {ip}/32 over TCP port 22. pub async fn authorize_security_group_ssh_ingress( &self, group_id: &str, ingress_ips: Vec<Ipv4Addr>, ) -> Result<(), EC2Error> { tracing::info!("Authorizing ingress for security group {group_id}"); self.client .authorize_security_group_ingress() .group_id(group_id) .set_ip_permissions(Some( ingress_ips .into_iter() .map(|ip| { IpPermission::builder() .ip_protocol("tcp") .from_port(22) .to_port(22) .ip_ranges(IpRange::builder().cidr_ip(format!("{ip}/32")).build()) .build() }) .collect(), )) .send() .await?; Ok(()) }

The following code example shows how to use CreateKeyPair.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

Rust implementation that calls the EC2 Client's create_key_pair and extracts the returned material.

pub async fn create_key_pair(&self, name: String) -> Result<(KeyPairInfo, String), EC2Error> { tracing::info!("Creating key pair {name}"); let output = self.client.create_key_pair().key_name(name).send().await?; let info = KeyPairInfo::builder() .set_key_name(output.key_name) .set_key_fingerprint(output.key_fingerprint) .set_key_pair_id(output.key_pair_id) .build(); let material = output .key_material .ok_or_else(|| EC2Error::new("Create Key Pair has no key material"))?; Ok((info, material)) }

A function that calls the create_key impl and securely saves the PEM private key.

/// Creates a key pair that can be used to securely connect to an EC2 instance. /// The returned key pair contains private key information that cannot be retrieved /// again. The private key data is stored as a .pem file. /// /// :param key_name: The name of the key pair to create. pub async fn create( &mut self, ec2: &EC2, util: &Util, key_name: String, ) -> Result<KeyPairInfo, EC2Error> { let (key_pair, material) = ec2.create_key_pair(key_name.clone()).await.map_err(|e| { self.key_pair = KeyPairInfo::builder().key_name(key_name.clone()).build(); e.add_message(format!("Couldn't create key {key_name}")) })?; let path = self.key_file_dir.join(format!("{key_name}.pem")); // Save the key_pair information immediately, so it can get cleaned up if write_secure fails. self.key_file_path = Some(path.clone()); self.key_pair = key_pair.clone(); util.write_secure(&key_name, &path, material)?; Ok(key_pair) }
  • For API details, see CreateKeyPair in AWS SDK for Rust API reference.

The following code example shows how to use CreateSecurityGroup.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn create_security_group( &self, name: &str, description: &str, ) -> Result<SecurityGroup, EC2Error> { tracing::info!("Creating security group {name}"); let create_output = self .client .create_security_group() .group_name(name) .description(description) .send() .await .map_err(EC2Error::from)?; let group_id = create_output .group_id .ok_or_else(|| EC2Error::new("Missing security group id after creation"))?; let group = self .describe_security_group(&group_id) .await? .ok_or_else(|| { EC2Error::new(format!("Could not find security group with id {group_id}")) })?; tracing::info!("Created security group {name} as {group_id}"); Ok(group) }

The following code example shows how to use CreateTags.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

This example applies the Name tag After creating an instance.

pub async fn create_instance<'a>( &self, image_id: &'a str, instance_type: InstanceType, key_pair: &'a KeyPairInfo, security_groups: Vec<&'a SecurityGroup>, ) -> Result<String, EC2Error> { let run_instances = self .client .run_instances() .image_id(image_id) .instance_type(instance_type) .key_name( key_pair .key_name() .ok_or_else(|| EC2Error::new("Missing key name when launching instance"))?, ) .set_security_group_ids(Some( security_groups .iter() .filter_map(|sg| sg.group_id.clone()) .collect(), )) .min_count(1) .max_count(1) .send() .await?; if run_instances.instances().is_empty() { return Err(EC2Error::new("Failed to create instance")); } let instance_id = run_instances.instances()[0].instance_id().unwrap(); let response = self .client .create_tags() .resources(instance_id) .tags( Tag::builder() .key("Name") .value("From SDK Examples") .build(), ) .send() .await; match response { Ok(_) => tracing::info!("Created {instance_id} and applied tags."), Err(err) => { tracing::info!("Error applying tags to {instance_id}: {err:?}"); return Err(err.into()); } } tracing::info!("Instance is created."); Ok(instance_id.to_string()) }
  • For API details, see CreateTags in AWS SDK for Rust API reference.

The following code example shows how to use DeleteKeyPair.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

Wrapper around delete_key that also removes the backing private PEM key.

pub async fn delete(self, ec2: &EC2, util: &Util) -> Result<(), EC2Error> { if let Some(key_name) = self.key_pair.key_name() { ec2.delete_key_pair(key_name).await?; if let Some(key_path) = self.key_file_path() { if let Err(err) = util.remove(key_path) { eprintln!("Failed to remove {key_path:?} ({err:?})"); } } } Ok(()) }
pub async fn delete_key_pair(&self, key_name: &str) -> Result<(), EC2Error> { let key_name: String = key_name.into(); tracing::info!("Deleting key pair {key_name}"); self.client .delete_key_pair() .key_name(key_name) .send() .await?; Ok(()) }
  • For API details, see DeleteKeyPair in AWS SDK for Rust API reference.

The following code example shows how to use DeleteSecurityGroup.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn delete_security_group(&self, group_id: &str) -> Result<(), EC2Error> { tracing::info!("Deleting security group {group_id}"); self.client .delete_security_group() .group_id(group_id) .send() .await?; Ok(()) }

The following code example shows how to use DeleteSnapshot.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

async fn delete_snapshot(client: &Client, id: &str) -> Result<(), Error> { client.delete_snapshot().snapshot_id(id).send().await?; println!("Deleted"); Ok(()) }
  • For API details, see DeleteSnapshot in AWS SDK for Rust API reference.

The following code example shows how to use DescribeImages.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn list_images(&self, ids: Vec<Parameter>) -> Result<Vec<Image>, EC2Error> { let image_ids = ids.into_iter().filter_map(|p| p.value).collect(); let output = self .client .describe_images() .set_image_ids(Some(image_ids)) .send() .await?; let images = output.images.unwrap_or_default(); if images.is_empty() { Err(EC2Error::new("No images for selected AMIs")) } else { Ok(images) } }

Using the list_images function with SSM to limit based on your environment. For more details on SSM, see https://docs.aws.amazon.com/systems-manager/latest/userguide/example_ssm_GetParameters_section.html.

async fn find_image(&mut self) -> Result<ScenarioImage, EC2Error> { let params: Vec<Parameter> = self .ssm .list_path("/aws/service/ami-amazon-linux-latest") .await .map_err(|e| e.add_message("Could not find parameters for available images"))? .into_iter() .filter(|param| param.name().is_some_and(|name| name.contains("amzn2"))) .collect(); let amzn2_images: Vec<ScenarioImage> = self .ec2 .list_images(params) .await .map_err(|e| e.add_message("Could not find images"))? .into_iter() .map(ScenarioImage::from) .collect(); println!("We will now create an instance from an Amazon Linux 2 AMI"); let ami = self.util.select_scenario_image(amzn2_images)?; Ok(ami) }
  • For API details, see DescribeImages in AWS SDK for Rust API reference.

The following code example shows how to use DescribeInstanceStatus.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

async fn show_all_events(client: &Client) -> Result<(), Error> { let resp = client.describe_regions().send().await.unwrap(); for region in resp.regions.unwrap_or_default() { let reg: &'static str = Box::leak(Box::from(region.region_name().unwrap())); let region_provider = RegionProviderChain::default_provider().or_else(reg); let config = aws_config::from_env().region(region_provider).load().await; let new_client = Client::new(&config); let resp = new_client.describe_instance_status().send().await; println!("Instances in region {}:", reg); println!(); for status in resp.unwrap().instance_statuses() { println!( " Events scheduled for instance ID: {}", status.instance_id().unwrap_or_default() ); for event in status.events() { println!(" Event ID: {}", event.instance_event_id().unwrap()); println!(" Description: {}", event.description().unwrap()); println!(" Event code: {}", event.code().unwrap().as_ref()); println!(); } } } Ok(()) }

The following code example shows how to use DescribeInstanceTypes.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

/// List instance types that match an image's architecture and are free tier eligible. pub async fn list_instance_types(&self, image: &Image) -> Result<Vec<InstanceType>, EC2Error> { let architecture = format!( "{}", image.architecture().ok_or_else(|| EC2Error::new(format!( "Image {:?} does not have a listed architecture", image.image_id() )))? ); let free_tier_eligible_filter = Filter::builder() .name("free-tier-eligible") .values("false") .build(); let supported_architecture_filter = Filter::builder() .name("processor-info.supported-architecture") .values(architecture) .build(); let response = self .client .describe_instance_types() .filters(free_tier_eligible_filter) .filters(supported_architecture_filter) .send() .await?; Ok(response .instance_types .unwrap_or_default() .into_iter() .filter_map(|iti| iti.instance_type) .collect()) }

The following code example shows how to use DescribeInstances.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

Retrieve details for an EC2 Instance.

pub async fn describe_instance(&self, instance_id: &str) -> Result<Instance, EC2Error> { let response = self .client .describe_instances() .instance_ids(instance_id) .send() .await?; let instance = response .reservations() .first() .ok_or_else(|| EC2Error::new(format!("No instance reservations for {instance_id}")))? .instances() .first() .ok_or_else(|| { EC2Error::new(format!("No instances in reservation for {instance_id}")) })?; Ok(instance.clone()) }

After creating an EC2 instance, retrieve and store its details.

/// Create an EC2 instance with the given ID on a given type, using a /// generated KeyPair and applying a list of security groups. pub async fn create( &mut self, ec2: &EC2, image_id: &str, instance_type: InstanceType, key_pair: &KeyPairInfo, security_groups: Vec<&SecurityGroup>, ) -> Result<(), EC2Error> { let instance_id = ec2 .create_instance(image_id, instance_type, key_pair, security_groups) .await?; let instance = ec2.describe_instance(&instance_id).await?; self.instance = Some(instance); Ok(()) }

The following code example shows how to use DescribeKeyPairs.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn list_key_pair(&self) -> Result<Vec<KeyPairInfo>, EC2Error> { let output = self.client.describe_key_pairs().send().await?; Ok(output.key_pairs.unwrap_or_default()) }

The following code example shows how to use DescribeRegions.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

async fn show_regions(client: &Client) -> Result<(), Error> { let rsp = client.describe_regions().send().await?; println!("Regions:"); for region in rsp.regions() { println!(" {}", region.region_name().unwrap()); } Ok(()) }

The following code example shows how to use DescribeSecurityGroups.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

async fn show_security_groups(client: &aws_sdk_ec2::Client, group_ids: Vec<String>) { let response = client .describe_security_groups() .set_group_ids(Some(group_ids)) .send() .await; match response { Ok(output) => { for group in output.security_groups() { println!( "Found Security Group {} ({}), vpc id {} and description {}", group.group_name().unwrap_or("unknown"), group.group_id().unwrap_or("id-unknown"), group.vpc_id().unwrap_or("vpcid-unknown"), group.description().unwrap_or("(none)") ); } } Err(err) => { let err = err.into_service_error(); let meta = err.meta(); let message = meta.message().unwrap_or("unknown"); let code = meta.code().unwrap_or("unknown"); eprintln!("Error listing EC2 Security Groups: ({code}) {message}"); } } }

The following code example shows how to use DescribeSnapshots.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

Shows the state of a snapshot.

async fn show_state(client: &Client, id: &str) -> Result<(), Error> { let resp = client .describe_snapshots() .filters(Filter::builder().name("snapshot-id").values(id).build()) .send() .await?; println!( "State: {}", resp.snapshots().first().unwrap().state().unwrap().as_ref() ); Ok(()) }
async fn show_snapshots(client: &Client) -> Result<(), Error> { // "self" represents your account ID. // You can list the snapshots for any account by replacing // "self" with that account ID. let resp = client.describe_snapshots().owner_ids("self").send().await?; let snapshots = resp.snapshots(); let length = snapshots.len(); for snapshot in snapshots { println!( "ID: {}", snapshot.snapshot_id().unwrap_or_default() ); println!( "Description: {}", snapshot.description().unwrap_or_default() ); println!("State: {}", snapshot.state().unwrap().as_ref()); println!(); } println!(); println!("Found {} snapshot(s)", length); println!(); Ok(()) }

The following code example shows how to use DisassociateAddress.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn disassociate_ip_address(&self, association_id: &str) -> Result<(), EC2Error> { self.client .disassociate_address() .association_id(association_id) .send() .await?; Ok(()) }

The following code example shows how to use RebootInstances.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn reboot(&self, ec2: &EC2) -> Result<(), EC2Error> { if self.instance.is_some() { ec2.reboot_instance(self.instance_id()).await?; ec2.wait_for_instance_stopped(self.instance_id(), None) .await?; ec2.wait_for_instance_ready(self.instance_id(), None) .await?; } Ok(()) }
pub async fn reboot_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Rebooting instance {instance_id}"); self.client .reboot_instances() .instance_ids(instance_id) .send() .await?; Ok(()) }

Waiters for instance to be in the stopped and ready states, using the Waiters API. Using the Waiters API requires `use aws_sdk_ec2::client::Waiters` in the rust file.

/// Wait for an instance to be ready and status ok (default wait 60 seconds) pub async fn wait_for_instance_ready( &self, instance_id: &str, duration: Option<Duration>, ) -> Result<(), EC2Error> { self.client .wait_until_instance_status_ok() .instance_ids(instance_id) .wait(duration.unwrap_or(Duration::from_secs(60))) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to start.", exceeded.max_wait().as_secs() )), _ => EC2Error::from(err), })?; Ok(()) } pub async fn wait_for_instance_stopped( &self, instance_id: &str, duration: Option<Duration>, ) -> Result<(), EC2Error> { self.client .wait_until_instance_stopped() .instance_ids(instance_id) .wait(duration.unwrap_or(Duration::from_secs(60))) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to stop.", exceeded.max_wait().as_secs(), )), _ => EC2Error::from(err), })?; Ok(()) }

The following code example shows how to use ReleaseAddress.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn deallocate_ip_address(&self, allocation_id: &str) -> Result<(), EC2Error> { self.client .release_address() .allocation_id(allocation_id) .send() .await?; Ok(()) }
  • For API details, see ReleaseAddress in AWS SDK for Rust API reference.

The following code example shows how to use RunInstances.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn create_instance<'a>( &self, image_id: &'a str, instance_type: InstanceType, key_pair: &'a KeyPairInfo, security_groups: Vec<&'a SecurityGroup>, ) -> Result<String, EC2Error> { let run_instances = self .client .run_instances() .image_id(image_id) .instance_type(instance_type) .key_name( key_pair .key_name() .ok_or_else(|| EC2Error::new("Missing key name when launching instance"))?, ) .set_security_group_ids(Some( security_groups .iter() .filter_map(|sg| sg.group_id.clone()) .collect(), )) .min_count(1) .max_count(1) .send() .await?; if run_instances.instances().is_empty() { return Err(EC2Error::new("Failed to create instance")); } let instance_id = run_instances.instances()[0].instance_id().unwrap(); let response = self .client .create_tags() .resources(instance_id) .tags( Tag::builder() .key("Name") .value("From SDK Examples") .build(), ) .send() .await; match response { Ok(_) => tracing::info!("Created {instance_id} and applied tags."), Err(err) => { tracing::info!("Error applying tags to {instance_id}: {err:?}"); return Err(err.into()); } } tracing::info!("Instance is created."); Ok(instance_id.to_string()) }
  • For API details, see RunInstances in AWS SDK for Rust API reference.

The following code example shows how to use StartInstances.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

Start an EC2 Instance by instance ID.

pub async fn start_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Starting instance {instance_id}"); self.client .start_instances() .instance_ids(instance_id) .send() .await?; tracing::info!("Started instance."); Ok(()) }

Wait for an instance to be in the ready and status ok states, using the Waiters API. Using the Waiters API requires `use aws_sdk_ec2::client::Waiters` in the rust file.

/// Wait for an instance to be ready and status ok (default wait 60 seconds) pub async fn wait_for_instance_ready( &self, instance_id: &str, duration: Option<Duration>, ) -> Result<(), EC2Error> { self.client .wait_until_instance_status_ok() .instance_ids(instance_id) .wait(duration.unwrap_or(Duration::from_secs(60))) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to start.", exceeded.max_wait().as_secs() )), _ => EC2Error::from(err), })?; Ok(()) }
  • For API details, see StartInstances in AWS SDK for Rust API reference.

The following code example shows how to use StopInstances.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn stop_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Stopping instance {instance_id}"); self.client .stop_instances() .instance_ids(instance_id) .send() .await?; self.wait_for_instance_stopped(instance_id, None).await?; tracing::info!("Stopped instance."); Ok(()) }

Wait for an instance to be in the stopped state, using the Waiters API. Using the Waiters API requires `use aws_sdk_ec2::client::Waiters` in the rust file.

pub async fn stop_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Stopping instance {instance_id}"); self.client .stop_instances() .instance_ids(instance_id) .send() .await?; self.wait_for_instance_stopped(instance_id, None).await?; tracing::info!("Stopped instance."); Ok(()) }
  • For API details, see StopInstances in AWS SDK for Rust API reference.

The following code example shows how to use TerminateInstances.

SDK for Rust
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository.

pub async fn delete_instance(&self, instance_id: &str) -> Result<(), EC2Error> { tracing::info!("Deleting instance with id {instance_id}"); self.stop_instance(instance_id).await?; self.client .terminate_instances() .instance_ids(instance_id) .send() .await?; self.wait_for_instance_terminated(instance_id).await?; tracing::info!("Terminated instance with id {instance_id}"); Ok(()) }

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.

async fn wait_for_instance_terminated(&self, instance_id: &str) -> Result<(), EC2Error> { self.client .wait_until_instance_terminated() .instance_ids(instance_id) .wait(Duration::from_secs(60)) .await .map_err(|err| match err { WaiterError::ExceededMaxWait(exceeded) => EC2Error(format!( "Exceeded max time ({}s) waiting for instance to terminate.", exceeded.max_wait().as_secs(), )), _ => EC2Error::from(err), })?; Ok(()) }