Skip to content

Commit

Permalink
Added support for T3 instances. I had to create a new AMI (works for …
Browse files Browse the repository at this point in the history
…both T2 and T3, not just T3) so I updated the AMI IDs. I also removed `--instance-type` from the arguments in favour of `--size <SIZE>` and a `--t3` flag.
  • Loading branch information
Tom1380 committed Sep 25, 2022
1 parent 1d8de8e commit 35105ee
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 56 deletions.
5 changes: 4 additions & 1 deletion server_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ sysctl -p
# Have the command run on boot as iptables isn't persistent.
# Reference https://askubuntu.com/a/290107
# I'm using single quotes to prevent bash from interpreting the exclamation point as an event.
echo '#!/bin/bash' > /etc/init.d/iptables_forwarding
echo '#!/bin/bash' > /etc/init.d/iptables_forwarding
# For T2 instances.
echo "iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE" >> /etc/init.d/iptables_forwarding
# For T3 instances.
echo "iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o ens5 -j MASQUERADE" >> /etc/init.d/iptables_forwarding
chmod 755 /etc/init.d/iptables_forwarding
ln -s /etc/init.d/iptables_forwarding /etc/rc2.d/S01iptables_forwarding

Expand Down
72 changes: 46 additions & 26 deletions src/ami.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,50 @@
// TODO put all the AMIs in and change the Option to a &str.
pub const REGION_AMIS: [(&str, Option<&str>); 23] = [
("eu-central-1", Some("ami-06e97680b8bf6528e")),
("eu-west-1", None),
("eu-west-2", Some("ami-061e7843348aee109")),
("eu-south-1", None),
("eu-west-3", None),
("eu-north-1", None),
("us-east-1", Some("ami-09b8d4b4d39180649")),
("us-east-2", None),
("us-west-1", None),
("us-west-2", None),
("ca-central-1", None),
("sa-east-1", Some("ami-0d5990429ef5c2da8")),
("ap-southeast-2", Some("ami-081d9e6c6f75745e3")),
("ap-east-1", None),
("ap-southeast-3", None),
("ap-south-1", None),
("ap-northeast-3", None),
("ap-northeast-2", None),
("ap-southeast-1", None),
("ap-northeast-1", None),
("af-south-1", None),
("me-south-1", None),
("me-central-1", None),
pub const REGION_INFO: [RegionInfo; 23] = [
RegionInfo::new("eu-central-1", Some("ami-0163e22b06e9d08d4"), true),
RegionInfo::new("eu-west-1", None, true),
RegionInfo::new("eu-west-2", None, true),
RegionInfo::new("eu-south-1", Some("ami-09412495bdfcff6e0"), false),
RegionInfo::new("eu-west-3", None, true),
RegionInfo::new("eu-north-1", None, true),
RegionInfo::new("us-east-1", None, true),
RegionInfo::new("us-east-2", None, true),
RegionInfo::new("us-west-1", None, true),
RegionInfo::new("us-west-2", None, true),
RegionInfo::new("ca-central-1", None, true),
RegionInfo::new("sa-east-1", None, true),
RegionInfo::new("ap-southeast-2", None, true),
RegionInfo::new("ap-east-1", None, true),
RegionInfo::new("ap-southeast-3", None, true),
RegionInfo::new("ap-south-1", None, true),
RegionInfo::new("ap-northeast-3", None, true),
RegionInfo::new("ap-northeast-2", None, true),
RegionInfo::new("ap-southeast-1", None, true),
RegionInfo::new("ap-northeast-1", None, true),
RegionInfo::new("af-south-1", None, true),
RegionInfo::new("me-south-1", None, true),
RegionInfo::new("me-central-1", None, true),
];

pub fn get_ami(region: &str) -> Option<&str> {
REGION_AMIS.into_iter().find(|(r, _ami)| r == &region)?.1
pub fn get_region_info(region: &str) -> RegionInfo {
REGION_INFO
.into_iter()
.find(|info| info.region == region)
// Shouldn't happen as clap ensures that the region is in REGION_INFO.
.expect("Couldn't find specified region.")
}

pub struct RegionInfo<'a> {
pub region: &'a str,
pub ami: Option<&'a str>,
pub has_t2: bool,
}

impl<'a> RegionInfo<'a> {
const fn new(region: &'a str, ami: Option<&'a str>, has_t2: bool) -> Self {
Self {
region,
ami,
has_t2,
}
}
}
37 changes: 16 additions & 21 deletions src/arguments.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use crate::ami::REGION_AMIS;
use aws_sdk_ec2::model::InstanceType;
use crate::ami::REGION_INFO;
use clap::{Parser, ValueEnum};

/// Extract an array of regions only from the array of region-ami tuples.
const fn regions() -> [&'static str; REGION_AMIS.len()] {
let mut regions = [""; REGION_AMIS.len()];
const fn regions() -> [&'static str; REGION_INFO.len()] {
let mut regions = [""; REGION_INFO.len()];

let mut i = 0;

// Circumnavigates the ban on for loops in constant functions.
while i < regions.len() {
regions[i] = REGION_AMIS[i].0;
regions[i] = REGION_INFO[i].region;
i += 1;
}

Expand All @@ -24,25 +23,21 @@ pub struct Args {
#[clap(help = "Where you will appear from when establishing connections.")]
pub region: String,

#[clap(short, long, value_enum, default_value_t=AcceptedInstanceType::T2Micro)]
#[clap(short, long, value_enum, default_value_t=InstanceSize::Micro)]
#[clap(
help = "Choose which instance type to spin up. T2Nano is cheaper, but AWS offers 750 free hours of T2Micro per month for the first year."
help = "Choose which instance size to spin up. Nano is cheaper, but AWS offers 750 free hours per month of the lowest available generation of Micro for the first year."
)]
pub instance_type: AcceptedInstanceType,
}
pub size: InstanceSize,

#[derive(Debug, Copy, Clone, ValueEnum)]
pub enum AcceptedInstanceType {
T2Nano,
T2Micro,
#[clap(long, action)]
#[clap(
help = "Chooses T3 over T2 even when both are available. Keep in mind that AWS' offer only applies to the lowest available generation of Micro."
)]
pub t3: bool,
}

impl Into<InstanceType> for AcceptedInstanceType {
fn into(self) -> InstanceType {
use InstanceType::*;
match self {
Self::T2Nano => T2Nano,
Self::T2Micro => T2Micro,
}
}
#[derive(Debug, Copy, Clone, ValueEnum)]
pub enum InstanceSize {
Nano,
Micro,
}
4 changes: 2 additions & 2 deletions src/ec2_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use tokio::time::sleep;
/// Launches a new EC2 instance and returns its instance ID.
pub async fn launch_ec2_instance(
client: &Client,
instance_type: impl Into<InstanceType>,
instance_type: InstanceType,
ami: &str,
key_name: &str,
) -> String {
let run_instances_output = client
.run_instances()
.instance_type(instance_type.into())
.instance_type(instance_type)
.image_id(ami)
.key_name(key_name)
.security_groups("globevpn")
Expand Down
34 changes: 28 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ mod openvpn;
mod security_groups;

use self::{
ami::get_ami,
arguments::Args,
ami::{get_region_info, RegionInfo},
arguments::{Args, InstanceSize},
ec2_instance::{get_public_ip, launch_ec2_instance, terminate_ec2_instance},
key_pairs::create_key_pair_if_necessary,
manage_directory::change_directory,
openvpn::{preshare_openvpn_key, run_openvpn},
security_groups::configure_security_group,
};
use aws_config::meta::region::RegionProviderChain;
use aws_sdk_ec2::{Client, Error, Region};
use aws_sdk_ec2::{model::InstanceType, Client, Error, Region};
use clap::Parser;
use std::time::Instant;

Expand All @@ -25,9 +25,14 @@ async fn main() -> Result<(), Error> {
let start = Instant::now();

let args = Args::parse();
let ri = get_region_info(&args.region);
let ami = ri
.ami
.expect("Couldn't find the AMI for the requested region.");
let instance_type = get_instance_type(&args, &ri);

// TODO this is a temporary fix.
let key_name = &args.region;
let ami = get_ami(&args.region).expect("Couldn't find the AMI for the requested region.");

let client = new_client(&args.region).await;

Expand All @@ -36,11 +41,11 @@ async fn main() -> Result<(), Error> {
create_key_pair_if_necessary(&client, key_name).await;
configure_security_group(&client).await;

let instance_id = launch_ec2_instance(&client, args.instance_type, &ami, key_name).await;
let instance_id = launch_ec2_instance(&client, instance_type.clone(), &ami, key_name).await;

println!(
"Launched a {:?} EC2 instance in {}.",
args.instance_type, args.region
&instance_type, args.region
);

let ctrl_c = tokio::spawn(ctrl_c_listener(client.clone(), instance_id.clone()));
Expand All @@ -58,6 +63,23 @@ async fn main() -> Result<(), Error> {
Ok(())
}

/// Check the arguments and T2 availability in the requested region to determine which instance type to launch.
fn get_instance_type(args: &Args, region_info: &RegionInfo) -> InstanceType {
use InstanceSize::*;
// If the user wants T3 explicitly or T2 isn't available, opt for T3.
if args.t3 || !region_info.has_t2 {
match args.size {
Nano => InstanceType::T3Nano,
Micro => InstanceType::T3Micro,
}
} else {
match args.size {
Nano => InstanceType::T2Nano,
Micro => InstanceType::T2Micro,
}
}
}

async fn new_client(region: &str) -> Client {
let region = Region::new(region.to_string());
let region_provider = RegionProviderChain::first_try(region);
Expand Down

0 comments on commit 35105ee

Please sign in to comment.