Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 85 additions & 36 deletions hydro_deploy/core/src/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,32 @@ impl LaunchedSshHost for LaunchedEc2Instance {
}
}

#[derive(Debug, Clone)]
pub struct NetworkResources {
vpc: String,
subnet: String,
security_group: String,
}

#[derive(Debug)]
pub struct AwsNetwork {
pub region: String,
pub existing_vpc: OnceLock<String>,
pub existing_network_key: OnceLock<NetworkResources>,
pub existing_network_id: OnceLock<NetworkResources>,
id: String,
}

impl AwsNetwork {
pub fn new(region: impl Into<String>, existing_vpc: Option<String>) -> Arc<Self> {
pub fn new(region: impl Into<String>, existing_vpc: Option<NetworkResources>) -> Arc<Self> {
Arc::new(Self {
region: region.into(),
existing_vpc: existing_vpc.map(From::from).unwrap_or_default(),
existing_network_key: OnceLock::new(),
existing_network_id: existing_vpc.map(From::from).unwrap_or_default(),
id: nanoid!(8, &TERRAFORM_ALPHABET),
})
}

fn collect_resources(&self, resource_batch: &mut ResourceBatch) -> String {
fn collect_resources(&self, resource_batch: &mut ResourceBatch) -> NetworkResources {
resource_batch
.terraform
.terraform
Expand All @@ -77,29 +86,30 @@ impl AwsNetwork {
);

let vpc_network = format!("hydro-vpc-network-{}", self.id);
let subnet_key = format!("{vpc_network}-subnet");
let sg_key = format!("{vpc_network}-default-sg");

if let Some(existing) = self.existing_vpc.get() {
if resource_batch
.terraform
.resource
.get("aws_vpc")
.is_some_and(|map| map.contains_key(existing))
{
format!("aws_vpc.{existing}")
} else {
if let Some(existing) = self.existing_network_id.get() {
let mut resolve = |resource_type: &str, existing_id: &str, data_key: String| {
resource_batch
.terraform
.data
.entry("aws_vpc".to_owned())
.entry(resource_type.to_owned())
.or_default()
.insert(
vpc_network.clone(),
json!({
"id": existing,
}),
);

format!("data.aws_vpc.{vpc_network}")
.insert(data_key.clone(), json!({ "id": existing_id }));
format!("data.{resource_type}.{data_key}")
};

NetworkResources {
vpc: resolve("aws_vpc", &existing.vpc, vpc_network),
subnet: resolve("aws_subnet", &existing.subnet, subnet_key),
security_group: resolve("aws_security_group", &existing.security_group, sg_key),
}
} else if let Some(existing) = self.existing_network_key.get() {
NetworkResources {
vpc: format!("aws_vpc.{}", existing.vpc),
subnet: format!("aws_subnet.{}", existing.subnet),
security_group: format!("aws_security_group.{}", existing.security_group),
}
} else {
resource_batch
Expand Down Expand Up @@ -137,7 +147,6 @@ impl AwsNetwork {
);

// Create subnet
let subnet_key = format!("{vpc_network}-subnet");
resource_batch
.terraform
.resource
Expand Down Expand Up @@ -202,14 +211,13 @@ impl AwsNetwork {
);

// Create security group that allows internal communication
let sg_key = format!("{vpc_network}-default-sg");
resource_batch
.terraform
.resource
.entry("aws_security_group".to_owned())
.or_default()
.insert(
sg_key,
sg_key.clone(),
json!({
"name": format!("{vpc_network}-default-allow-internal"),
"description": "Allow internal communication between instances",
Expand Down Expand Up @@ -265,9 +273,53 @@ impl AwsNetwork {
}),
);

let out = format!("aws_vpc.{vpc_network}");
self.existing_vpc.set(vpc_network).unwrap();
out
let resources = NetworkResources {
vpc: format!("aws_vpc.{vpc_network}"),
subnet: format!("aws_subnet.{subnet_key}"),
security_group: format!("aws_security_group.{sg_key}"),
};

// Add outputs so we can retrieve actual AWS IDs after apply
resource_batch.terraform.output.insert(
format!("hydro-network-{}-vpc-id", self.id),
TerraformOutput {
value: format!("${{aws_vpc.{vpc_network}.id}}"),
},
);
resource_batch.terraform.output.insert(
format!("hydro-network-{}-subnet-id", self.id),
TerraformOutput {
value: format!("${{aws_subnet.{subnet_key}.id}}"),
},
);
resource_batch.terraform.output.insert(
format!("hydro-network-{}-sg-id", self.id),
TerraformOutput {
value: format!("${{aws_security_group.{sg_key}.id}}"),
},
);

let _ = self.existing_network_key.set(NetworkResources {
vpc: vpc_network,
subnet: subnet_key,
security_group: sg_key,
});
resources
}
}

pub fn update_from_outputs(&self, resource_result: &ResourceResult) {
let outputs = &resource_result.terraform.outputs;
if let (Some(vpc), Some(subnet), Some(sg)) = (
outputs.get(&format!("hydro-network-{}-vpc-id", self.id)),
outputs.get(&format!("hydro-network-{}-subnet-id", self.id)),
outputs.get(&format!("hydro-network-{}-sg-id", self.id)),
) {
let _ = self.existing_network_id.set(NetworkResources {
vpc: vpc.value.clone(),
subnet: subnet.value.clone(),
security_group: sg.value.clone(),
});
}
}
}
Expand Down Expand Up @@ -612,7 +664,7 @@ impl Host for AwsEc2Host {
return;
}

let vpc_path = self.network.collect_resources(resource_batch);
let network_resources = self.network.collect_resources(resource_batch);

let iam_instance_profile = self
.iam_instance_profile
Expand Down Expand Up @@ -705,12 +757,8 @@ impl Host for AwsEc2Host {
instance_name.push_str(&display_name);
}

let network_id = self.network.id.clone();
let vpc_ref = format!("${{{}.id}}", vpc_path);
let default_sg_ref = format!(
"${{aws_security_group.hydro-vpc-network-{}-default-sg.id}}",
network_id
);
let vpc_ref = format!("${{{}.id}}", network_resources.vpc);
let default_sg_ref = format!("${{{}.id}}", network_resources.security_group);

// Create additional security group for external ports if needed
let mut security_groups = vec![default_sg_ref];
Expand Down Expand Up @@ -764,7 +812,7 @@ impl Host for AwsEc2Host {
}
drop(external_ports);

let subnet_ref = format!("${{aws_subnet.hydro-vpc-network-{}-subnet.id}}", network_id);
let subnet_ref = format!("${{{}.id}}", network_resources.subnet);
let iam_instance_profile_ref = iam_instance_profile.map(|key| format!("${{{key}.name}}"));

// Write the CloudWatch Agent config file.
Expand Down Expand Up @@ -894,6 +942,7 @@ echo -e "{cwa_config_esc}" > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwa
.get_or_init(|| {
let id = self.id;

self.network.update_from_outputs(resource_result);
let internal_ip = resource_result
.terraform
.outputs
Expand Down
Loading