1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
mod backend;
mod ec2_instance;
mod ec2_instance_definition;
mod ssh;

pub use backend::{Aws, InstanceType, PlacementStrategy};
pub use ec2_instance::{Ec2Instance, NetworkInterface};
pub use ec2_instance_definition::{Ec2InstanceDefinition, InstanceOs};

// include a magic number in the keyname to avoid collisions
// This can never change or we may fail to cleanup resources.
const USER_TAG_NAME: &str = "aws-throwaway-23c2d22c-d929-43fc-b2a4-c1c72f0b733f:user";
const APP_TAG_NAME: &str = "aws-throwaway-23c2d22c-d929-43fc-b2a4-c1c72f0b733f:app";

pub struct AwsBuilder {
    cleanup: CleanupResources,
    use_public_addresses: bool,
    vpc_id: Option<String>,
    subnet_id: Option<String>,
    placement_strategy: PlacementStrategy,
    security_group_id: Option<String>,
}

/// The default configuration will succeed for an AMI user with sufficient access and unmodified default vpcs/subnets
/// Consider altering the configuration if:
/// * you want to reduce the amount of access required by the user
/// * you want to connect directly from within the VPC
/// * you have already created a specific VPC, subnet or security group that you want aws-throwaway to make use of.
///
/// All resources will be created in us-east-1c.
/// This is hardcoded so that aws-throawaway only has to look into one region when cleaning up.
/// All instances are created in a single spread placement group in a single AZ to ensure consistent latency between instances.
// TODO: document minimum required access for default configuration.
impl AwsBuilder {
    fn new(cleanup: CleanupResources) -> Self {
        AwsBuilder {
            cleanup,
            use_public_addresses: true,
            vpc_id: None,
            subnet_id: None,
            // refer to: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html
            // I believe Spread is the best default since it is the easiest for amazon to fulfill and gives the most realistic results in benchmarks.
            placement_strategy: PlacementStrategy::Spread,
            security_group_id: None,
        }
    }

    /// When set to:
    /// * true => aws-throwaway will connect to the public ip of the instances that it creates.
    ///     + The subnet must have the property MapPublicIpOnLaunch set to true (the unmodified default subnet meets this requirement)
    ///     + Elastic IPs will be created for instances with multiple network interfaces because AWS does not assign a public IP in that scenario
    /// * false => aws-throwaway will connect to the private ip of the instances that it creates.
    ///     + aws-throwaway must be running on a machine within the VPC used by aws-throwaaway or a VPN must be used to connect to the VPC or another similar setup.
    ///
    /// If the subnet used has MapPublicIpOnLaunch=true then all instances will be publically accessible regardless of this use_public_addresses field.
    ///
    /// The default is `true`.
    pub fn use_public_addresses(mut self, use_public_addresses: bool) -> Self {
        self.use_public_addresses = use_public_addresses;
        self
    }

    /// * Some(_) => All resources will go into the specified vpc
    /// * None => All resources will go into the default vpc
    ///
    /// The default is `None`
    pub fn use_vpc_id(mut self, vpc_id: Option<String>) -> Self {
        self.vpc_id = vpc_id;
        self
    }

    /// * Some(_) => All instances will go into the specified subnet
    /// * None => All instances will go into the default subnet for the specified or default vpc
    ///
    /// The default is `None`
    pub fn use_subnet_id(mut self, subnet_id: Option<String>) -> Self {
        self.subnet_id = subnet_id;
        self
    }

    /// All EC2 instances are created within a single placement group with the specified strategy.
    ///
    /// The default is `PlacementStrategy::Spread`
    pub fn use_placement_strategy(mut self, placement_strategy: PlacementStrategy) -> Self {
        self.placement_strategy = placement_strategy;
        self
    }

    /// * Some(_) => All instances will use the specified security group
    /// * None => A single security group will be created for all instances to use. It will allow:
    ///      + ssh traffic in from the internet
    ///      + all traffic out to the internet
    ///      + all traffic in+out between instances in the security group, i.e. all ec2 instances created by this [`Aws`] instance
    ///
    /// The default is `None`
    pub fn use_security_group_id(mut self, security_group_id: Option<String>) -> Self {
        self.security_group_id = security_group_id;
        self
    }

    pub async fn build(self) -> Aws {
        Aws::new(self).await
    }
}

/// Specify the cleanup process to use.
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`]
    AllResources,
}