Merge branch 'next' of https://github.com/mbecker20/monitor into next

This commit is contained in:
karamvir
2023-08-11 01:11:25 -07:00
14 changed files with 282 additions and 44 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -1,8 +1,11 @@
use anyhow::Context;
use async_trait::async_trait;
use monitor_types::{
entities::{builder::{Builder, BuilderListItem}, PermissionLevel},
requests::read::*,
entities::{
builder::{Builder, BuilderConfig, BuilderListItem},
PermissionLevel,
},
requests::read::{self, *},
};
use mungos::mongodb::bson::doc;
use resolver_api::Resolve;
@@ -60,3 +63,31 @@ impl Resolve<GetBuildersSummary, RequestUser> for State {
Ok(res)
}
}
#[async_trait]
impl Resolve<GetBuilderAvailableAccounts, RequestUser> for State {
async fn resolve(
&self,
GetBuilderAvailableAccounts { id }: GetBuilderAvailableAccounts,
user: RequestUser,
) -> anyhow::Result<GetBuilderAvailableAccountsResponse> {
let builder: Builder = self
.get_resource_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
match builder.config {
BuilderConfig::Aws(config) => Ok(GetBuilderAvailableAccountsResponse {
github: config.github_accounts,
docker: config.docker_accounts,
}),
BuilderConfig::Server(config) => {
let res = self
.resolve(read::GetServerAvailableAccounts { id: config.id }, user)
.await?;
Ok(GetBuilderAvailableAccountsResponse {
github: res.github,
docker: res.docker,
})
}
}
}
}

View File

@@ -52,7 +52,7 @@ enum ReadRequest {
GetDockerNetworks(GetDockerNetworks),
GetServerActionState(GetServerActionState),
GetHistoricalServerStats(GetHistoricalServerStats),
GetAvailableAccounts(GetAvailableAccounts),
GetServerAvailableAccounts(GetServerAvailableAccounts),
GetAvailableNetworks(GetAvailableNetworks),
// ==== DEPLOYMENT ====
@@ -83,6 +83,7 @@ enum ReadRequest {
GetBuildersSummary(GetBuildersSummary),
GetBuilder(GetBuilder),
ListBuilders(ListBuilders),
GetBuilderAvailableAccounts(GetBuilderAvailableAccounts),
// ==== ALERTER ====
GetAlertersSummary(GetAlertersSummary),

View File

@@ -405,21 +405,21 @@ impl Resolve<GetDockerContainers, RequestUser> for State {
}
#[async_trait]
impl Resolve<GetAvailableAccounts, RequestUser> for State {
impl Resolve<GetServerAvailableAccounts, RequestUser> for State {
async fn resolve(
&self,
GetAvailableAccounts { server_id }: GetAvailableAccounts,
GetServerAvailableAccounts { id }: GetServerAvailableAccounts,
user: RequestUser,
) -> anyhow::Result<GetAvailableAccountsResponse> {
) -> anyhow::Result<GetServerAvailableAccountsResponse> {
let server: Server = self
.get_resource_check_permissions(&server_id, &user, PermissionLevel::Read)
.get_resource_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let GetAccountsResponse { github, docker } = self
.periphery_client(&server)
.request(requests::GetAccounts {})
.await
.context("failed to get accounts from periphery")?;
let res = GetAvailableAccountsResponse { github, docker };
let res = GetServerAvailableAccountsResponse { github, docker };
Ok(res)
}
}

View 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)
}
}

View File

@@ -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),

View File

@@ -28,7 +28,7 @@ export type ReadResponses = {
GetDockerNetworks: Types.DockerNetwork[];
GetServerActionState: Types.ServerActionState;
GetHistoricalServerStats: Types.GetHistoricalServerStatsResponse;
GetAvailableAccounts: Types.GetAvailableAccountsResponse;
GetServerAvailableAccounts: Types.GetServerAvailableAccountsResponse;
GetAvailableNetworks: Types.GetAvailableNetworksResponse;
// ==== DEPLOYMENT ====
@@ -59,6 +59,7 @@ export type ReadResponses = {
GetBuildersSummary: Types.GetBuildersSummaryResponse;
GetBuilder: Types.Builder;
ListBuilders: Types.BuilderListItem[];
GetBuilderAvailableAccounts: Types.GetBuilderAvailableAccountsResponse;
// ==== ALERTER ====
GetAlertersSummary: Types.GetAlertersSummaryResponse;
@@ -100,6 +101,7 @@ export type WriteResponses = {
UpdateDescription: Types.UpdateDescriptionResponse;
// ==== SERVER ====
LaunchServer: Types.Update;
CreateServer: Types.Server;
DeleteServer: Types.Server;
UpdateServer: Types.Server;

View File

@@ -559,6 +559,7 @@ export interface CustomTag {
export enum Operation {
None = "None",
LaunchServer = "LaunchServer",
CreateServer = "CreateServer",
UpdateServer = "UpdateServer",
DeleteServer = "DeleteServer",
@@ -846,6 +847,15 @@ export interface GetBuildersSummaryResponse {
total: number;
}
export interface GetBuilderAvailableAccounts {
id: string;
}
export interface GetBuilderAvailableAccountsResponse {
github: string[];
docker: string[];
}
export interface GetDeployment {
id: string;
}
@@ -1037,11 +1047,11 @@ export interface GetServersSummaryResponse {
disabled: I64;
}
export interface GetAvailableAccounts {
server_id: string;
export interface GetServerAvailableAccounts {
id: string;
}
export interface GetAvailableAccountsResponse {
export interface GetServerAvailableAccountsResponse {
github: string[];
docker: string[];
}
@@ -1173,6 +1183,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;
@@ -1323,7 +1360,7 @@ export type ReadRequest =
| { type: "GetDockerNetworks", params: GetDockerNetworks }
| { type: "GetServerActionState", params: GetServerActionState }
| { type: "GetHistoricalServerStats", params: GetHistoricalServerStats }
| { type: "GetAvailableAccounts", params: GetAvailableAccounts }
| { type: "GetServerAvailableAccounts", params: GetServerAvailableAccounts }
| { type: "GetAvailableNetworks", params: GetAvailableNetworks }
| { type: "GetDeploymentsSummary", params: GetDeploymentsSummary }
| { type: "GetDeployment", params: GetDeployment }
@@ -1346,6 +1383,7 @@ export type ReadRequest =
| { type: "GetBuildersSummary", params: GetBuildersSummary }
| { type: "GetBuilder", params: GetBuilder }
| { type: "ListBuilders", params: ListBuilders }
| { type: "GetBuilderAvailableAccounts", params: GetBuilderAvailableAccounts }
| { type: "GetAlertersSummary", params: GetAlertersSummary }
| { type: "GetAlerter", params: GetAlerter }
| { type: "ListAlerters", params: ListAlerters }
@@ -1369,6 +1407,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 }

View File

@@ -267,6 +267,7 @@ pub enum Operation {
None,
// server
LaunchServer,
CreateServer,
UpdateServer,
DeleteServer,

View File

@@ -34,3 +34,19 @@ pub struct GetBuildersSummary {}
pub struct GetBuildersSummaryResponse {
pub total: u32,
}
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(GetBuilderAvailableAccountsResponse)]
pub struct GetBuilderAvailableAccounts {
pub id: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GetBuilderAvailableAccountsResponse {
pub github: Vec<String>,
pub docker: Vec<String>,
}

View File

@@ -214,14 +214,14 @@ pub struct GetServersSummaryResponse {
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(GetAvailableAccountsResponse)]
pub struct GetAvailableAccounts {
pub server_id: String,
#[response(GetServerAvailableAccountsResponse)]
pub struct GetServerAvailableAccounts {
pub id: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GetAvailableAccountsResponse {
pub struct GetServerAvailableAccountsResponse {
pub github: Vec<String>,
pub docker: Vec<String>,
}

View 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,
}
}
}

View File

@@ -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::*;