Skip to content

Commit

Permalink
add serialize/deserialize functionality to Ec2Instance
Browse files Browse the repository at this point in the history
  • Loading branch information
rukai committed Feb 6, 2024
1 parent 1844878 commit 99ca38e
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 9 deletions.
2 changes: 0 additions & 2 deletions aws-throwaway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ anyhow = "1.0.42"
uuid = { version = "1.0.0", features = ["serde", "v4"] }
tracing = "0.1.15"
async-trait = "0.1.30"
# TODO: avoid pulling in these dependencies when use_sdk is enabled,
# will need to introduce a use_cli feature to do so
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
futures = "0.3.30"
Expand Down
46 changes: 42 additions & 4 deletions aws-throwaway/src/ec2_instance.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
use crate::ssh::SshConnection;
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
use std::time::Duration;
use tokio::{net::TcpStream, time::Instant};

/// Represents a currently running EC2 instance and provides various methods for interacting with the instance.
///
/// This type implements serde Serialize/Deserialize to allow you to save and restore the instance from disk.
/// After restoring Ec2Instance in this way you need to call the [`Ec2Instance::init`] method.
#[derive(Serialize, Deserialize)]
pub struct Ec2Instance {
connect_ip: IpAddr,
public_ip: Option<IpAddr>,
private_ip: IpAddr,
client_private_key: String,
host_public_key_bytes: Vec<u8>,
host_public_key: String,
ssh: SshConnection,
#[serde(skip)]
ssh: Option<SshConnection>,
network_interfaces: Vec<NetworkInterface>,
}

#[derive(Serialize, Deserialize)]
pub struct NetworkInterface {
pub private_ipv4: Ipv4Addr,
pub device_index: i32,
Expand All @@ -32,7 +40,7 @@ impl Ec2Instance {
}

/// Use this address to get the private or public IP that aws-throwaway is using to ssh to the instance.
/// Whether or not this is public is decided by AwsBuilder::use_public_addresses.
/// Whether or not this is public is decided by [`crate::AwsBuilder::use_public_addresses`].
///
/// You should use this address if you want to connect to the instance from your local machine
pub fn connect_ip(&self) -> IpAddr {
Expand Down Expand Up @@ -62,7 +70,9 @@ impl Ec2Instance {

/// Returns an object that allows commands to be sent over ssh
pub fn ssh(&self) -> &SshConnection {
&self.ssh
self.ssh
.as_ref()
.expect("Make sure to call `Ec2Instance::init` after deserializing `Ec2Instance`")
}

/// Get a list of commands that the user can paste into bash to manually open an ssh connection to this instance.
Expand Down Expand Up @@ -132,7 +142,7 @@ TERM=xterm ssh -i key ubuntu@{} -o "UserKnownHostsFile known_hosts"
Ok(ssh) => {
break Ec2Instance {
connect_ip,
ssh,
ssh: Some(ssh),
public_ip,
private_ip,
host_public_key_bytes,
Expand All @@ -146,4 +156,32 @@ TERM=xterm ssh -i key ubuntu@{} -o "UserKnownHostsFile known_hosts"
};
}
}

/// After deserializing [`Ec2Instance`] this method must be called to recreate the ssh connection.
///
/// No need to call after creating via [`crate::Aws::create_ec2_instance`]
pub async fn init(&mut self) -> Result<()> {
let connect_ip = self.connect_ip;

// We use a drastically simplifed initialization approach here compared to `Ec2Instance::new`.
// Since we can assume that the server has either already started up or is now terminated we
// avoid retries and tailor our error messages in order to provide better error reporting.
let stream =
tokio::time::timeout(Duration::from_secs(5), TcpStream::connect((connect_ip, 22)))
.await
.map_err(|_| anyhow!("Timed out connecting to {connect_ip}:22"))?
.with_context(|| format!("Failed to connect to {connect_ip}:22"))?;

let ssh = SshConnection::new(
stream,
connect_ip,
self.host_public_key_bytes.clone(),
&self.client_private_key,
)
.await
.with_context(|| format!("Failed to make ssh connection to {connect_ip}:22"))?;
self.ssh = Some(ssh);

Ok(())
}
}
2 changes: 1 addition & 1 deletion aws-throwaway/src/ec2_instance_definition.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::InstanceType;

/// Defines an instance that can be launched via [`Aws::create_ec2_instance`]
/// Defines an instance that can be launched via [`crate::Aws::create_ec2_instance`]
pub struct Ec2InstanceDefinition {
pub(crate) instance_type: InstanceType,
pub(crate) volume_size_gb: u32,
Expand Down
2 changes: 1 addition & 1 deletion aws-throwaway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,6 @@ pub enum CleanupResources {
/// Cleanup resources created by all [`Aws`] instances that use [`CleanupResources::WithAppTag`] of the same tag.
/// It is highly reccomended that this tag is hardcoded, generating this tag could easily lead to forgotten resources.
WithAppTag(String),
/// Cleanup resources created by all [`Aws`] instances regardless of whether it was created via [`CleanupResources::AllResources`] or [`CleanupResources::ResourcesMatchingTag`]
/// Cleanup resources created by all [`Aws`] instances regardless of whether it was created via [`CleanupResources::AllResources`] or [`CleanupResources::WithAppTag`]
AllResources,
}
5 changes: 4 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ aws-throwaway makes it trivial to spin up an instance, interact with it, and the
```rust
let aws = Aws::builder(CleanupResources::AllResources).build().await;

let instance = aws.create_ec2_instance(Ec2InstanceDefinition::new(InstanceType::T2Micro)).await;
let instance = aws.create_ec2_instance(
Ec2InstanceDefinition::new(InstanceType::T2Micro)
).await;

let output = instance.ssh().shell("echo 'Hello world!'").await;
println!("output from ec2 instance: {}", output.stdout);

Expand Down

0 comments on commit 99ca38e

Please sign in to comment.