forked from github-starred/komodo
LaunchServer
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::time::Duration;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use aws_sdk_ec2::{
|
||||
@@ -6,11 +6,11 @@ use aws_sdk_ec2::{
|
||||
types::{
|
||||
BlockDeviceMapping, EbsBlockDevice, InstanceNetworkInterfaceSpecification,
|
||||
InstanceStateChange, InstanceStateName, InstanceStatus, InstanceType, ResourceType, Tag,
|
||||
TagSpecification,
|
||||
TagSpecification, VolumeType,
|
||||
},
|
||||
Client,
|
||||
};
|
||||
use monitor_types::entities::builder::AwsBuilderConfig;
|
||||
use monitor_types::requests::write::LaunchAwsServerConfig;
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
@@ -32,45 +32,34 @@ impl State {
|
||||
Client::new(&config)
|
||||
}
|
||||
|
||||
pub async fn create_ec2_instance(
|
||||
pub async fn launch_ec2_instance(
|
||||
&self,
|
||||
instance_name: &str,
|
||||
AwsBuilderConfig {
|
||||
name: &str,
|
||||
config: impl Into<LaunchAwsServerConfig>,
|
||||
) -> anyhow::Result<Ec2Instance> {
|
||||
let LaunchAwsServerConfig {
|
||||
region,
|
||||
instance_type,
|
||||
volume_gb,
|
||||
volumes,
|
||||
ami_id,
|
||||
subnet_id,
|
||||
security_group_ids,
|
||||
key_pair_name,
|
||||
assign_public_ip,
|
||||
..
|
||||
}: &AwsBuilderConfig,
|
||||
) -> anyhow::Result<Ec2Instance> {
|
||||
} = config.into();
|
||||
let instance_type = InstanceType::from(instance_type.as_str());
|
||||
if let InstanceType::Unknown(t) = instance_type {
|
||||
return Err(anyhow!("unknown instance type {t:?}"));
|
||||
}
|
||||
let client = self.create_ec2_client(region.clone()).await;
|
||||
let res = client
|
||||
let mut req = client
|
||||
.run_instances()
|
||||
.image_id(ami_id)
|
||||
.instance_type(instance_type)
|
||||
.block_device_mappings(
|
||||
BlockDeviceMapping::builder()
|
||||
.set_device_name(String::from("/dev/sda1").into())
|
||||
.set_ebs(
|
||||
EbsBlockDevice::builder()
|
||||
.volume_size(*volume_gb)
|
||||
.build()
|
||||
.into(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.network_interfaces(
|
||||
InstanceNetworkInterfaceSpecification::builder()
|
||||
.subnet_id(subnet_id)
|
||||
.associate_public_ip_address(*assign_public_ip)
|
||||
.associate_public_ip_address(assign_public_ip)
|
||||
.set_groups(security_group_ids.to_vec().into())
|
||||
.device_index(0)
|
||||
.build(),
|
||||
@@ -78,28 +67,50 @@ impl State {
|
||||
.key_name(key_pair_name)
|
||||
.tag_specifications(
|
||||
TagSpecification::builder()
|
||||
.tags(Tag::builder().key("Name").value(instance_name).build())
|
||||
.tags(Tag::builder().key("Name").value(name).build())
|
||||
.resource_type(ResourceType::Instance)
|
||||
.build(),
|
||||
)
|
||||
.min_count(1)
|
||||
.max_count(1)
|
||||
.max_count(1);
|
||||
|
||||
for volume in volumes {
|
||||
let mut ebs = EbsBlockDevice::builder()
|
||||
.volume_size(volume.size_gb)
|
||||
.set_iops(volume.iops)
|
||||
.set_throughput(volume.throughput);
|
||||
if let Some(volume_type) = &volume.volume_type {
|
||||
ebs = ebs
|
||||
.volume_type(VolumeType::from_str(volume_type).context("invalid volume type")?);
|
||||
}
|
||||
req = req.block_device_mappings(
|
||||
BlockDeviceMapping::builder()
|
||||
.set_device_name(volume.device_name.clone().into())
|
||||
.set_ebs(ebs.build().into())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
let res = req
|
||||
.send()
|
||||
.await
|
||||
.context("failed to start builder ec2 instance")?;
|
||||
|
||||
let instance = res
|
||||
.instances()
|
||||
.ok_or(anyhow!("got None for created instances"))?
|
||||
.get(0)
|
||||
.ok_or(anyhow!("instances array is empty"))?;
|
||||
|
||||
let instance_id = instance
|
||||
.instance_id()
|
||||
.ok_or(anyhow!("instance does not have instance_id"))?
|
||||
.to_string();
|
||||
|
||||
for _ in 0..MAX_POLL_TRIES {
|
||||
let state_name = get_ec2_instance_state_name(&client, &instance_id).await?;
|
||||
if state_name == Some(InstanceStateName::Running) {
|
||||
let ip = if *assign_public_ip {
|
||||
let ip = if assign_public_ip {
|
||||
get_ec2_instance_public_ip(&client, &instance_id).await?
|
||||
} else {
|
||||
instance
|
||||
|
||||
@@ -254,7 +254,7 @@ impl State {
|
||||
build.config.version.to_string()
|
||||
);
|
||||
let Ec2Instance { instance_id, ip } =
|
||||
self.create_ec2_instance(&instance_name, &config).await?;
|
||||
self.launch_ec2_instance(&instance_name, &config).await?;
|
||||
|
||||
let readable_sec_group_ids = config.security_group_ids.join(", ");
|
||||
let AwsBuilderConfig {
|
||||
|
||||
69
bin/core/src/requests/write/launch.rs
Normal file
69
bin/core/src/requests/write/launch.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
server::PartialServerConfig,
|
||||
update::{ResourceTarget, Update},
|
||||
Operation,
|
||||
},
|
||||
requests::write::{self, LaunchServer, LaunchServerConfig},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, helpers::make_update, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<LaunchServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
LaunchServer { name, config }: LaunchServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if !user.is_admin {
|
||||
return Err(anyhow!("only admins can launch servers"));
|
||||
}
|
||||
let mut update = make_update(
|
||||
ResourceTarget::System("system".to_string()),
|
||||
Operation::LaunchServer,
|
||||
&user,
|
||||
);
|
||||
update.push_simple_log("launching server", format!("{:#?}", config));
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
match config {
|
||||
LaunchServerConfig::Aws(config) => {
|
||||
let region = config.region.clone();
|
||||
let instance = self.launch_ec2_instance(&name, config).await;
|
||||
if let Err(e) = &instance {
|
||||
update.push_error_log(
|
||||
"launch server",
|
||||
format!("failed to launch aws instance\n\n{e:#?}"),
|
||||
);
|
||||
update.finalize();
|
||||
self.update_update(update.clone()).await?;
|
||||
return Ok(update);
|
||||
}
|
||||
let instance = instance.unwrap();
|
||||
update.push_simple_log(
|
||||
"launch server",
|
||||
format!("successfully launched server {name} on ip {}", instance.ip),
|
||||
);
|
||||
let _ = self
|
||||
.resolve(
|
||||
write::CreateServer {
|
||||
name,
|
||||
config: PartialServerConfig {
|
||||
address: format!("http://{}:8000", instance.ip).into(),
|
||||
region: region.into(),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
user,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
update.finalize();
|
||||
self.update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
mod description;
|
||||
mod launch;
|
||||
mod permissions;
|
||||
mod repo;
|
||||
mod secret;
|
||||
@@ -49,6 +50,7 @@ enum WriteRequest {
|
||||
UpdateDescription(UpdateDescription),
|
||||
|
||||
// ==== SERVER ====
|
||||
LaunchServer(LaunchServer),
|
||||
CreateServer(CreateServer),
|
||||
DeleteServer(DeleteServer),
|
||||
UpdateServer(UpdateServer),
|
||||
|
||||
@@ -100,6 +100,7 @@ export type WriteResponses = {
|
||||
UpdateDescription: Types.UpdateDescriptionResponse;
|
||||
|
||||
// ==== SERVER ====
|
||||
LaunchServer: Types.Update;
|
||||
CreateServer: Types.Server;
|
||||
DeleteServer: Types.Server;
|
||||
UpdateServer: Types.Server;
|
||||
|
||||
@@ -559,6 +559,7 @@ export interface CustomTag {
|
||||
|
||||
export enum Operation {
|
||||
None = "None",
|
||||
LaunchServer = "LaunchServer",
|
||||
CreateServer = "CreateServer",
|
||||
UpdateServer = "UpdateServer",
|
||||
DeleteServer = "DeleteServer",
|
||||
@@ -1173,6 +1174,33 @@ export interface UpdateDescription {
|
||||
export interface UpdateDescriptionResponse {
|
||||
}
|
||||
|
||||
export type LaunchServerConfig =
|
||||
| { type: "Aws", params: LaunchAwsServerConfig };
|
||||
|
||||
export interface LaunchServer {
|
||||
name: string;
|
||||
config: LaunchServerConfig;
|
||||
}
|
||||
|
||||
export interface AwsVolume {
|
||||
device_name: string;
|
||||
size_gb: number;
|
||||
volume_type?: string;
|
||||
iops?: number;
|
||||
throughput?: number;
|
||||
}
|
||||
|
||||
export interface LaunchAwsServerConfig {
|
||||
region: string;
|
||||
instance_type: string;
|
||||
volumes: AwsVolume[];
|
||||
ami_id: string;
|
||||
subnet_id: string;
|
||||
security_group_ids: string[];
|
||||
key_pair_name: string;
|
||||
assign_public_ip: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateUserPermissionsOnTarget {
|
||||
user_id: string;
|
||||
permission: PermissionLevel;
|
||||
@@ -1369,6 +1397,7 @@ export type WriteRequest =
|
||||
| { type: "UpdateUserPerimissions", params: UpdateUserPermissions }
|
||||
| { type: "UpdateUserPermissionsOnTarget", params: UpdateUserPermissionsOnTarget }
|
||||
| { type: "UpdateDescription", params: UpdateDescription }
|
||||
| { type: "LaunchServer", params: LaunchServer }
|
||||
| { type: "CreateServer", params: CreateServer }
|
||||
| { type: "DeleteServer", params: DeleteServer }
|
||||
| { type: "UpdateServer", params: UpdateServer }
|
||||
|
||||
@@ -267,6 +267,7 @@ pub enum Operation {
|
||||
None,
|
||||
|
||||
// server
|
||||
LaunchServer,
|
||||
CreateServer,
|
||||
UpdateServer,
|
||||
DeleteServer,
|
||||
|
||||
64
lib/types/src/requests/write/launch.rs
Normal file
64
lib/types/src/requests/write/launch.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{builder::AwsBuilderConfig, update::Update};
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct LaunchServer {
|
||||
pub name: String,
|
||||
pub config: LaunchServerConfig,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
pub enum LaunchServerConfig {
|
||||
Aws(LaunchAwsServerConfig),
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct LaunchAwsServerConfig {
|
||||
pub region: String,
|
||||
pub instance_type: String,
|
||||
pub volumes: Vec<AwsVolume>,
|
||||
pub ami_id: String,
|
||||
pub subnet_id: String,
|
||||
pub security_group_ids: Vec<String>,
|
||||
pub key_pair_name: String,
|
||||
pub assign_public_ip: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AwsVolume {
|
||||
pub device_name: String,
|
||||
pub size_gb: i32,
|
||||
pub volume_type: Option<String>,
|
||||
pub iops: Option<i32>,
|
||||
pub throughput: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<&AwsBuilderConfig> for LaunchAwsServerConfig {
|
||||
fn from(value: &AwsBuilderConfig) -> Self {
|
||||
Self {
|
||||
region: value.region.clone(),
|
||||
instance_type: value.instance_type.clone(),
|
||||
volumes: vec![AwsVolume {
|
||||
size_gb: value.volume_gb,
|
||||
device_name: "/dev/sda1".to_string(),
|
||||
volume_type: None,
|
||||
iops: None,
|
||||
throughput: None,
|
||||
}],
|
||||
ami_id: value.ami_id.clone(),
|
||||
subnet_id: value.subnet_id.clone(),
|
||||
security_group_ids: value.security_group_ids.clone(),
|
||||
key_pair_name: value.key_pair_name.clone(),
|
||||
assign_public_ip: value.assign_public_ip,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
mod description;
|
||||
mod launch;
|
||||
mod permissions;
|
||||
mod repo;
|
||||
mod secret;
|
||||
@@ -15,6 +16,7 @@ pub use build::*;
|
||||
pub use builder::*;
|
||||
pub use deployment::*;
|
||||
pub use description::*;
|
||||
pub use launch::*;
|
||||
pub use permissions::*;
|
||||
pub use repo::*;
|
||||
pub use secret::*;
|
||||
|
||||
Reference in New Issue
Block a user