mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-28 11:49:39 -05:00
split api into read, write, execute
This commit is contained in:
@@ -120,10 +120,10 @@ impl MonitorClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn api<T: HasResponse>(&self, request: T) -> anyhow::Result<T::Response> {
|
||||
pub async fn auth<T: HasResponse>(&self, request: T) -> anyhow::Result<T::Response> {
|
||||
let req_type = T::req_type();
|
||||
self.post(
|
||||
"/api",
|
||||
"/auth",
|
||||
json!({
|
||||
"type": req_type,
|
||||
"params": request
|
||||
@@ -132,10 +132,34 @@ impl MonitorClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn auth<T: HasResponse>(&self, request: T) -> anyhow::Result<T::Response> {
|
||||
pub async fn read<T: HasResponse>(&self, request: T) -> anyhow::Result<T::Response> {
|
||||
let req_type = T::req_type();
|
||||
self.post(
|
||||
"/auth",
|
||||
"/read",
|
||||
json!({
|
||||
"type": req_type,
|
||||
"params": request
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn write<T: HasResponse>(&self, request: T) -> anyhow::Result<T::Response> {
|
||||
let req_type = T::req_type();
|
||||
self.post(
|
||||
"/write",
|
||||
json!({
|
||||
"type": req_type,
|
||||
"params": request
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn execute<T: HasResponse>(&self, request: T) -> anyhow::Result<T::Response> {
|
||||
let req_type = T::req_type();
|
||||
self.post(
|
||||
"/execute",
|
||||
json!({
|
||||
"type": req_type,
|
||||
"params": request
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context};
|
||||
use axum::{extract::Path, http::HeaderMap, routing::post, Router};
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use monitor_types::requests::api;
|
||||
use monitor_types::requests::execute;
|
||||
use resolver_api::Resolve;
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
@@ -77,7 +77,7 @@ impl State {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
self.resolve(
|
||||
api::RunBuild { build_id },
|
||||
execute::RunBuild { build_id },
|
||||
InnerRequestUser {
|
||||
id: String::from("github"),
|
||||
is_admin: true,
|
||||
@@ -103,7 +103,7 @@ impl State {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
self.resolve(
|
||||
api::CloneRepo { id: repo_id },
|
||||
execute::CloneRepo { id: repo_id },
|
||||
InnerRequestUser {
|
||||
id: String::from("github"),
|
||||
is_admin: true,
|
||||
@@ -129,7 +129,7 @@ impl State {
|
||||
return Err(anyhow!("request branch does not match expected"));
|
||||
}
|
||||
self.resolve(
|
||||
api::PullRepo { id: repo_id },
|
||||
execute::PullRepo { id: repo_id },
|
||||
InnerRequestUser {
|
||||
id: String::from("github"),
|
||||
is_admin: true,
|
||||
|
||||
@@ -26,7 +26,9 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/auth", auth::router(&state))
|
||||
.nest("/api", requests::api::router())
|
||||
.nest("/read", requests::read::router())
|
||||
.nest("/write", requests::write::router())
|
||||
.nest("/execute", requests::execute::router())
|
||||
.nest("/listener", listener::router())
|
||||
.nest("/ws", ws::router())
|
||||
.layer(Extension(state));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,726 +0,0 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
deployment::BasicContainerInfo,
|
||||
server::{
|
||||
docker_image::ImageSummary, docker_network::DockerNetwork, stats::SystemInformation,
|
||||
Server, ServerActionState,
|
||||
},
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
permissioned::Permissioned,
|
||||
requests::api::*,
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::{Resolve, ResolveToString};
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetPeripheryVersion, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: GetPeripheryVersion,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<GetPeripheryVersionResponse> {
|
||||
self.get_server_check_permissions(&req.server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let version = self
|
||||
.server_status_cache
|
||||
.get(&req.server_id)
|
||||
.await
|
||||
.map(|s| s.version.clone())
|
||||
.unwrap_or(String::from("unknown"));
|
||||
Ok(GetPeripheryVersionResponse { version })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetServer, RequestUser> for State {
|
||||
async fn resolve(&self, req: GetServer, user: RequestUser) -> anyhow::Result<Server> {
|
||||
self.get_server_check_permissions(&req.id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListServers, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListServers { query }: ListServers,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Server>> {
|
||||
let servers = self
|
||||
.db
|
||||
.servers
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull servers from mongo")?;
|
||||
|
||||
let servers = if user.is_admin {
|
||||
servers
|
||||
} else {
|
||||
servers
|
||||
.into_iter()
|
||||
.filter(|server| server.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(servers)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetServerActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetServerActionState { id }: GetServerActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<ServerActionState> {
|
||||
self.get_server_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self.action_states.server.get(&id).await.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateServer, RequestUser> for State {
|
||||
async fn resolve(&self, req: CreateServer, user: RequestUser) -> anyhow::Result<Server> {
|
||||
if !user.is_admin && !user.create_server_permissions {
|
||||
return Err(anyhow!("user does not have create server permissions"));
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let server = Server {
|
||||
id: Default::default(),
|
||||
name: req.name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description: Default::default(),
|
||||
config: req.config.into(),
|
||||
};
|
||||
let server_id = self
|
||||
.db
|
||||
.servers
|
||||
.create_one(&server)
|
||||
.await
|
||||
.context("failed to add server to db")?;
|
||||
let server = self.get_server(&server_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Server(server_id),
|
||||
operation: Operation::CreateServer,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create server",
|
||||
format!("created server\nid: {}\nname: {}", server.id, server.name),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", server.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
self.update_cache_for_server(&server).await;
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<DeleteServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteServer { id }: DeleteServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Server> {
|
||||
if self.action_states.server.busy(&id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let server = self
|
||||
.get_server_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
self.db
|
||||
.builds
|
||||
.update_many(
|
||||
doc! { "config.builder.params.server_id": &id },
|
||||
doc! { "$set": { "config.builder.params.server_id": "" } },
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from builds")?;
|
||||
|
||||
self.db
|
||||
.deployments
|
||||
.update_many(
|
||||
doc! { "config.server_id": &id },
|
||||
doc! { "$set": { "config.server_id": "" } },
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from deployments")?;
|
||||
|
||||
self.db
|
||||
.repos
|
||||
.update_many(
|
||||
doc! { "config.server_id": &id },
|
||||
doc! { "$set": { "config.server_id": "" } },
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from repos")?;
|
||||
|
||||
self.db
|
||||
.servers
|
||||
.delete_one(&id)
|
||||
.await
|
||||
.context("failed to delete server from mongo")?;
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(id.clone()),
|
||||
operation: Operation::DeleteServer,
|
||||
start_ts,
|
||||
operator: user.id.clone(),
|
||||
logs: vec![Log::simple(
|
||||
"delete server",
|
||||
format!("deleted server {}", server.name),
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
self.add_update(update).await?;
|
||||
|
||||
self.server_status_cache.remove(&id).await;
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<UpdateServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateServer { id, config }: UpdateServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Server> {
|
||||
if self.action_states.server.busy(&id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
self.get_server_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
self.db
|
||||
.servers
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "config": to_bson(&config)? }),
|
||||
)
|
||||
.await
|
||||
.context("failed to update server on mongo")?;
|
||||
let update = Update {
|
||||
operation: Operation::UpdateServer,
|
||||
target: ResourceTarget::Server(id.clone()),
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
status: UpdateStatus::Complete,
|
||||
logs: vec![Log::simple(
|
||||
"server update",
|
||||
serde_json::to_string_pretty(&config).unwrap(),
|
||||
)],
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let new_server = self.get_server(&id).await?;
|
||||
|
||||
self.update_cache_for_server(&new_server).await;
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(new_server)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<RenameServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RenameServer { id, name }: RenameServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
let start_ts = monitor_timestamp();
|
||||
let server = self
|
||||
.get_server_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
self.db
|
||||
.updates
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "name": &name, "updated_at": monitor_timestamp() }),
|
||||
)
|
||||
.await?;
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(id.clone()),
|
||||
operation: Operation::RenameServer,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
logs: vec![Log::simple(
|
||||
"rename server",
|
||||
format!("renamed server {id} from {} to {name}", server.name),
|
||||
)],
|
||||
status: UpdateStatus::Complete,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetSystemInformation, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetSystemInformation { server_id }: GetSystemInformation,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<SystemInformation> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetSystemInformation {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetAllSystemStats, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetAllSystemStats { server_id }: GetAllSystemStats,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetBasicSystemStats, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetBasicSystemStats { server_id }: GetBasicSystemStats,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.basic)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetCpuUsage, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetCpuUsage { server_id }: GetCpuUsage,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.cpu)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetDiskUsage, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetDiskUsage { server_id }: GetDiskUsage,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.disk)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetNetworkUsage, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetNetworkUsage { server_id }: GetNetworkUsage,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.network)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetSystemProcesses, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetSystemProcesses { server_id }: GetSystemProcesses,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.processes)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetSystemComponents, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetSystemComponents { server_id }: GetSystemComponents,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.components)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDockerImages, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerImages { server_id }: GetDockerImages,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<ImageSummary>> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetImageList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PruneDockerImages, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneDockerImages { server_id }: PruneDockerImages,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.server.busy(&server_id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let inner = || async {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(server_id.to_owned()),
|
||||
operation: Operation::PruneImagesServer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PruneImages {})
|
||||
.await
|
||||
.context(format!("failed to prune images on server {}", server.name))
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("prune images", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.logs.push(log);
|
||||
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_images = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_images = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDockerNetworks, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerNetworks { server_id }: GetDockerNetworks,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<DockerNetwork>> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetNetworkList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PruneDockerNetworks, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneDockerNetworks { server_id }: PruneDockerNetworks,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.server.busy(&server_id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let inner = || async {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(server_id.to_owned()),
|
||||
operation: Operation::PruneNetworksServer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PruneNetworks {})
|
||||
.await
|
||||
.context(format!(
|
||||
"failed to prune networks on server {}",
|
||||
server.name
|
||||
)) {
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("prune networks", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.logs.push(log);
|
||||
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_networks = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_networks = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDockerContainers, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerContainers { server_id }: GetDockerContainers,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<BasicContainerInfo>> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetContainerList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PruneDockerContainers, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneDockerContainers { server_id }: PruneDockerContainers,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.server.busy(&server_id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let inner = || async {
|
||||
let start_ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(server_id),
|
||||
operation: Operation::PruneContainersServer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PruneNetworks {})
|
||||
.await
|
||||
.context(format!(
|
||||
"failed to prune containers on server {}",
|
||||
server.name
|
||||
)) {
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("prune containers", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.logs.push(log);
|
||||
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server.id.to_string(), |entry| {
|
||||
entry.pruning_containers = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server.id, |entry| {
|
||||
entry.pruning_containers = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -3,381 +3,12 @@ use std::time::Duration;
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use futures::future::join_all;
|
||||
use monitor_types::{
|
||||
all_logs_success,
|
||||
entities::{
|
||||
build::{Build, BuildActionState, BuildBuilderConfig},
|
||||
builder::{AwsBuilder, BuilderConfig},
|
||||
deployment::DockerContainerState,
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
permissioned::Permissioned,
|
||||
requests::api::*,
|
||||
};
|
||||
use monitor_types::{requests::execute::{RunBuild, Deploy}, entities::{update::{Update, ResourceTarget, UpdateStatus, Log}, PermissionLevel, Operation, build::{Build, BuildBuilderConfig}, builder::{BuilderConfig, AwsBuilder}, deployment::DockerContainerState}, monitor_timestamp, all_logs_success};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use periphery_client::{
|
||||
requests::{self, GetVersionResponse},
|
||||
PeripheryClient,
|
||||
};
|
||||
use periphery_client::{requests::{self, GetVersionResponse}, PeripheryClient};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
auth::{InnerRequestUser, RequestUser},
|
||||
cloud::{aws::Ec2Instance, BuildCleanupData},
|
||||
helpers::empty_or_only_spaces,
|
||||
state::State,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetBuild, RequestUser> for State {
|
||||
async fn resolve(&self, GetBuild { id }: GetBuild, user: RequestUser) -> anyhow::Result<Build> {
|
||||
self.get_build_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListBuilds, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListBuilds { query }: ListBuilds,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Build>> {
|
||||
let builds = self
|
||||
.db
|
||||
.builds
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull builds from mongo")?;
|
||||
|
||||
let builds = if user.is_admin {
|
||||
builds
|
||||
} else {
|
||||
builds
|
||||
.into_iter()
|
||||
.filter(|build| build.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(builds)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetBuildActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuildActionState { id }: GetBuildActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<BuildActionState> {
|
||||
self.get_build_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self.action_states.build.get(&id).await.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateBuild { name, config }: CreateBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
if let Some(builder) = &config.builder {
|
||||
match builder {
|
||||
BuildBuilderConfig::Server { server_id } => {
|
||||
self.get_server_check_permissions(
|
||||
server_id,
|
||||
&user,
|
||||
PermissionLevel::Update,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build on this server. user must have update permissions on the server.")?;
|
||||
}
|
||||
BuildBuilderConfig::Builder { builder_id } => {
|
||||
self.get_builder_check_permissions(
|
||||
builder_id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let build = Build {
|
||||
id: Default::default(),
|
||||
name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
last_built_at: 0,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description: Default::default(),
|
||||
config: config.into(),
|
||||
};
|
||||
let build_id = self
|
||||
.db
|
||||
.builds
|
||||
.create_one(build)
|
||||
.await
|
||||
.context("failed to add build to db")?;
|
||||
let build = self.get_build(&build_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Build(build_id),
|
||||
operation: Operation::CreateBuild,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create build",
|
||||
format!("created build\nid: {}\nname: {}", build.id, build.name),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", build.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CopyBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CopyBuild { name, id }: CopyBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
let Build {
|
||||
config,
|
||||
description,
|
||||
..
|
||||
} = self
|
||||
.get_build_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
match &config.builder {
|
||||
BuildBuilderConfig::Server { server_id } => {
|
||||
self.get_server_check_permissions(
|
||||
server_id,
|
||||
&user,
|
||||
PermissionLevel::Update,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build on this server. user must have update permissions on the server.")?;
|
||||
}
|
||||
BuildBuilderConfig::Builder { builder_id } => {
|
||||
self.get_builder_check_permissions(
|
||||
builder_id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
|
||||
}
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let build = Build {
|
||||
id: Default::default(),
|
||||
name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
last_built_at: 0,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description,
|
||||
config,
|
||||
};
|
||||
let build_id = self
|
||||
.db
|
||||
.builds
|
||||
.create_one(build)
|
||||
.await
|
||||
.context("failed to add build to db")?;
|
||||
let build = self.get_build(&build_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Build(build_id),
|
||||
operation: Operation::CreateBuild,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create build",
|
||||
format!("created build\nid: {}\nname: {}", build.id, build.name),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", build.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<DeleteBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteBuild { id }: DeleteBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
if self.action_states.build.busy(&id).await {
|
||||
return Err(anyhow!("build busy"));
|
||||
}
|
||||
|
||||
let build = self
|
||||
.get_build_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Build(id.clone()),
|
||||
operation: Operation::DeleteBuild,
|
||||
start_ts,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
status: UpdateStatus::InProgress,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let res = self
|
||||
.db
|
||||
.builds
|
||||
.delete_one(&id)
|
||||
.await
|
||||
.context("failed to delete build from database");
|
||||
|
||||
let log = match res {
|
||||
Ok(_) => Log::simple("delete build", format!("deleted build {}", build.name)),
|
||||
Err(e) => Log::error("delete build", format!("failed to delete build\n{e:#?}")),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
self.update_update(update).await?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<UpdateBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateBuild { id, mut config }: UpdateBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
if self.action_states.build.busy(&id).await {
|
||||
return Err(anyhow!("build busy"));
|
||||
}
|
||||
|
||||
let build = self
|
||||
.get_build_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if let Some(builder) = &config.builder {
|
||||
match builder {
|
||||
BuildBuilderConfig::Server { server_id } => {
|
||||
self.get_server_check_permissions(
|
||||
server_id,
|
||||
&user,
|
||||
PermissionLevel::Update,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build on this server. user must have update permissions on the server.")?;
|
||||
}
|
||||
BuildBuilderConfig::Builder { builder_id } => {
|
||||
self.get_builder_check_permissions(
|
||||
builder_id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(build_args) = &mut config.build_args {
|
||||
build_args.retain(|v| {
|
||||
!empty_or_only_spaces(&v.variable) && !empty_or_only_spaces(&v.value)
|
||||
})
|
||||
}
|
||||
if let Some(extra_args) = &mut config.extra_args {
|
||||
extra_args.retain(|v| !empty_or_only_spaces(v))
|
||||
}
|
||||
|
||||
self.db
|
||||
.builds
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "config": to_bson(&config)? }),
|
||||
)
|
||||
.await
|
||||
.context("failed to update build on database")?;
|
||||
|
||||
let update = Update {
|
||||
operation: Operation::UpdateBuild,
|
||||
target: ResourceTarget::Build(id.clone()),
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
status: UpdateStatus::Complete,
|
||||
logs: vec![Log::simple(
|
||||
"build update",
|
||||
serde_json::to_string_pretty(&config).unwrap(),
|
||||
)],
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
version: config.version.unwrap_or_default(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
let build = self.get_build(&id).await?;
|
||||
|
||||
anyhow::Ok(build)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.build
|
||||
.update_entry(build.id.clone(), |entry| {
|
||||
entry.updating = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.build
|
||||
.update_entry(build.id, |entry| {
|
||||
entry.updating = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
use crate::{auth::{RequestUser, InnerRequestUser}, state::State, cloud::{BuildCleanupData, aws::Ec2Instance}};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<RunBuild, RequestUser> for State {
|
||||
@@ -728,4 +359,4 @@ impl State {
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
391
core/src/requests/execute/deployment.rs
Normal file
391
core/src/requests/execute/deployment.rs
Normal file
@@ -0,0 +1,391 @@
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
deployment::DeploymentImage,
|
||||
server::ServerStatus,
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel, Version,
|
||||
},
|
||||
get_image_name, monitor_timestamp,
|
||||
requests::execute::*,
|
||||
};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<Deploy, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
Deploy {
|
||||
deployment_id,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
}: Deploy,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.deployment.busy(&deployment_id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let mut deployment = self
|
||||
.get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
if deployment.config.server_id.is_empty() {
|
||||
return Err(anyhow!("deployment has no server configured"));
|
||||
}
|
||||
|
||||
let (server, status) = self
|
||||
.get_server_with_status(&deployment.config.server_id)
|
||||
.await?;
|
||||
if status != ServerStatus::Ok {
|
||||
return Err(anyhow!(
|
||||
"cannot send action when server is unreachable or disabled"
|
||||
));
|
||||
}
|
||||
|
||||
let periphery = self.periphery_client(&server);
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let version = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = self.get_build(&build_id).await?;
|
||||
let image_name = get_image_name(&build);
|
||||
let version = if version.is_none() {
|
||||
build.config.version
|
||||
} else {
|
||||
version
|
||||
};
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{}", version.to_string()),
|
||||
};
|
||||
if deployment.config.docker_account.is_empty() {
|
||||
deployment.config.docker_account = build.config.docker_account;
|
||||
}
|
||||
version
|
||||
}
|
||||
DeploymentImage::Image { .. } => Version::default(),
|
||||
};
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
operation: Operation::DeployContainer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
version,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match periphery
|
||||
.request(requests::Deploy {
|
||||
deployment,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("deploy container", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
self.update_cache_for_server(&server).await;
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id.to_string(), |entry| {
|
||||
entry.deploying = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id, |entry| {
|
||||
entry.deploying = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<StartContainer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
StartContainer { deployment_id }: StartContainer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.deployment.busy(&deployment_id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let deployment = self
|
||||
.get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
if deployment.config.server_id.is_empty() {
|
||||
return Err(anyhow!("deployment has no server configured"));
|
||||
}
|
||||
|
||||
let (server, status) = self
|
||||
.get_server_with_status(&deployment.config.server_id)
|
||||
.await?;
|
||||
if status != ServerStatus::Ok {
|
||||
return Err(anyhow!(
|
||||
"cannot send action when server is unreachable or disabled"
|
||||
));
|
||||
}
|
||||
|
||||
let periphery = self.periphery_client(&server);
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
operation: Operation::StartContainer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match periphery
|
||||
.request(requests::StartContainer {
|
||||
name: deployment.name.clone(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("start container", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
self.update_cache_for_server(&server).await;
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id.to_string(), |entry| {
|
||||
entry.starting = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id, |entry| {
|
||||
entry.starting = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<StopContainer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
StopContainer {
|
||||
deployment_id,
|
||||
signal,
|
||||
time,
|
||||
}: StopContainer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.deployment.busy(&deployment_id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let deployment = self
|
||||
.get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
if deployment.config.server_id.is_empty() {
|
||||
return Err(anyhow!("deployment has no server configured"));
|
||||
}
|
||||
|
||||
let (server, status) = self
|
||||
.get_server_with_status(&deployment.config.server_id)
|
||||
.await?;
|
||||
if status != ServerStatus::Ok {
|
||||
return Err(anyhow!(
|
||||
"cannot send action when server is unreachable or disabled"
|
||||
));
|
||||
}
|
||||
|
||||
let periphery = self.periphery_client(&server);
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
operation: Operation::StopContainer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match periphery
|
||||
.request(requests::StopContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
.into(),
|
||||
time: time.unwrap_or(deployment.config.termination_timeout).into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("stop container", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
self.update_cache_for_server(&server).await;
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id.to_string(), |entry| {
|
||||
entry.stopping = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id, |entry| {
|
||||
entry.stopping = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<RemoveContainer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RemoveContainer {
|
||||
deployment_id,
|
||||
signal,
|
||||
time,
|
||||
}: RemoveContainer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.deployment.busy(&deployment_id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let deployment = self
|
||||
.get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
if deployment.config.server_id.is_empty() {
|
||||
return Err(anyhow!("deployment has no server configured"));
|
||||
}
|
||||
|
||||
let (server, status) = self
|
||||
.get_server_with_status(&deployment.config.server_id)
|
||||
.await?;
|
||||
if status != ServerStatus::Ok {
|
||||
return Err(anyhow!(
|
||||
"cannot send action when server is unreachable or disabled"
|
||||
));
|
||||
}
|
||||
|
||||
let periphery = self.periphery_client(&server);
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
operation: Operation::RemoveContainer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match periphery
|
||||
.request(requests::RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: signal
|
||||
.unwrap_or(deployment.config.termination_signal)
|
||||
.into(),
|
||||
time: time.unwrap_or(deployment.config.termination_timeout).into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("stop container", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
self.update_cache_for_server(&server).await;
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id.to_string(), |entry| {
|
||||
entry.removing = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment_id, |entry| {
|
||||
entry.removing = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
84
core/src/requests/execute/mod.rs
Normal file
84
core/src/requests/execute/mod.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use axum::{
|
||||
headers::ContentType, http::StatusCode, middleware, routing::post, Extension, Json, Router,
|
||||
TypedHeader,
|
||||
};
|
||||
use monitor_types::requests::execute::*;
|
||||
use resolver_api::{derive::Resolver, Resolve, Resolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{auth_request, RequestUser, RequestUserExtension},
|
||||
state::{State, StateExtension},
|
||||
};
|
||||
|
||||
mod build;
|
||||
mod deployment;
|
||||
mod repo;
|
||||
mod server;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
|
||||
#[resolver_target(State)]
|
||||
#[resolver_args(RequestUser)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
|
||||
pub enum ExecuteRequest {
|
||||
// ==== SERVER ====
|
||||
PruneContainers(PruneDockerContainers),
|
||||
PruneImages(PruneDockerImages),
|
||||
PruneNetworks(PruneDockerNetworks),
|
||||
|
||||
// ==== DEPLOYMENT ====
|
||||
Deploy(Deploy),
|
||||
StartContainer(StartContainer),
|
||||
StopContainer(StopContainer),
|
||||
RemoveContainer(RemoveContainer),
|
||||
|
||||
// ==== BUILD ====
|
||||
RunBuild(RunBuild),
|
||||
|
||||
// ==== REPO ====
|
||||
CloneRepo(CloneRepo),
|
||||
PullRepo(PullRepo),
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
"/",
|
||||
post(
|
||||
|state: StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Json(request): Json<ExecuteRequest>| async move {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
info!("/execute request {req_id} | {request:?}");
|
||||
let res = tokio::spawn(async move {
|
||||
state
|
||||
.resolve_request(request, user)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")));
|
||||
if let Err(e) = &res {
|
||||
info!("/execute request {req_id} SPAWN ERROR: {e:?}");
|
||||
}
|
||||
let res = res?;
|
||||
if let Err(e) = &res {
|
||||
info!("/execute request {req_id} ERROR: {e:?}");
|
||||
}
|
||||
let res = res?;
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/execute request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/execute request {req_id} RESPONSE: {res}");
|
||||
Result::<_, (StatusCode, String)>::Ok((TypedHeader(ContentType::json()), res))
|
||||
},
|
||||
),
|
||||
)
|
||||
.layer(middleware::from_fn(auth_request))
|
||||
}
|
||||
162
core/src/requests/execute/repo.rs
Normal file
162
core/src/requests/execute/repo.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp, optional_string,
|
||||
requests::execute::*,
|
||||
};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CloneRepo, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CloneRepo { id }: CloneRepo,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
let repo = self
|
||||
.get_repo_check_permissions(&id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if repo.config.server_id.is_empty() {
|
||||
return Err(anyhow!("repo has no server attached"));
|
||||
}
|
||||
|
||||
let server = self.get_server(&repo.config.server_id).await?;
|
||||
|
||||
let mut update = Update {
|
||||
operation: Operation::CloneRepo,
|
||||
target: ResourceTarget::Repo(repo.id.clone()),
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::CloneRepo {
|
||||
args: (&repo).into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(logs) => update.logs.extend(logs),
|
||||
Err(e) => update
|
||||
.logs
|
||||
.push(Log::error("clone repo", format!("{e:#?}"))),
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
self.update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
if self.action_states.repo.busy(&id).await {
|
||||
return Err(anyhow!("repo busy"));
|
||||
}
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(&id, |entry| {
|
||||
entry.cloning = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(id, |entry| {
|
||||
entry.cloning = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PullRepo, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PullRepo { id }: PullRepo,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
let repo = self
|
||||
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if repo.config.server_id.is_empty() {
|
||||
return Err(anyhow!("repo has no server attached"));
|
||||
}
|
||||
|
||||
let server = self.get_server(&repo.config.server_id).await?;
|
||||
|
||||
let mut update = Update {
|
||||
operation: Operation::PullRepo,
|
||||
target: ResourceTarget::Repo(repo.id.clone()),
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PullRepo {
|
||||
name: repo.name,
|
||||
branch: optional_string(&repo.config.branch),
|
||||
on_pull: repo.config.on_pull.into_option(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(logs) => update.logs.extend(logs),
|
||||
Err(e) => update.logs.push(Log::error("pull repo", format!("{e:#?}"))),
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
self.update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
if self.action_states.repo.busy(&id).await {
|
||||
return Err(anyhow!("repo busy"));
|
||||
}
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(id.clone(), |entry| {
|
||||
entry.pulling = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(id, |entry| {
|
||||
entry.pulling = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
222
core/src/requests/execute/server.rs
Normal file
222
core/src/requests/execute/server.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
requests::execute::*,
|
||||
};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PruneDockerContainers, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneDockerContainers { server_id }: PruneDockerContainers,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.server.busy(&server_id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let inner = || async {
|
||||
let start_ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(server_id),
|
||||
operation: Operation::PruneContainersServer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PruneNetworks {})
|
||||
.await
|
||||
.context(format!(
|
||||
"failed to prune containers on server {}",
|
||||
server.name
|
||||
)) {
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("prune containers", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.logs.push(log);
|
||||
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server.id.to_string(), |entry| {
|
||||
entry.pruning_containers = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server.id, |entry| {
|
||||
entry.pruning_containers = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PruneDockerNetworks, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneDockerNetworks { server_id }: PruneDockerNetworks,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.server.busy(&server_id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let inner = || async {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(server_id.to_owned()),
|
||||
operation: Operation::PruneNetworksServer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PruneNetworks {})
|
||||
.await
|
||||
.context(format!(
|
||||
"failed to prune networks on server {}",
|
||||
server.name
|
||||
)) {
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("prune networks", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.logs.push(log);
|
||||
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_networks = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_networks = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PruneDockerImages, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneDockerImages { server_id }: PruneDockerImages,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.server.busy(&server_id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let inner = || async {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(server_id.to_owned()),
|
||||
operation: Operation::PruneImagesServer,
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let log = match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PruneImages {})
|
||||
.await
|
||||
.context(format!("failed to prune images on server {}", server.name))
|
||||
{
|
||||
Ok(log) => log,
|
||||
Err(e) => Log::error("prune images", format!("{e:#?}")),
|
||||
};
|
||||
|
||||
update.success = log.success;
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.logs.push(log);
|
||||
|
||||
self.update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_images = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.server
|
||||
.update_entry(server_id.to_string(), |entry| {
|
||||
entry.pruning_images = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod api;
|
||||
pub mod auth;
|
||||
pub mod execute;
|
||||
pub mod read;
|
||||
pub mod write;
|
||||
|
||||
61
core/src/requests/read/build.rs
Normal file
61
core/src/requests/read/build.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
build::{Build, BuildActionState},
|
||||
PermissionLevel,
|
||||
},
|
||||
requests::read::*, permissioned::Permissioned,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetBuild, RequestUser> for State {
|
||||
async fn resolve(&self, GetBuild { id }: GetBuild, user: RequestUser) -> anyhow::Result<Build> {
|
||||
self.get_build_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListBuilds, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListBuilds { query }: ListBuilds,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Build>> {
|
||||
let builds = self
|
||||
.db
|
||||
.builds
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull builds from mongo")?;
|
||||
|
||||
let builds = if user.is_admin {
|
||||
builds
|
||||
} else {
|
||||
builds
|
||||
.into_iter()
|
||||
.filter(|build| build.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(builds)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetBuildActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuildActionState { id }: GetBuildActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<BuildActionState> {
|
||||
self.get_build_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self.action_states.build.get(&id).await.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
49
core/src/requests/read/builder.rs
Normal file
49
core/src/requests/read/builder.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{builder::Builder, PermissionLevel},
|
||||
permissioned::Permissioned,
|
||||
requests::read::*,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetBuilder, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuilder { id }: GetBuilder,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Builder> {
|
||||
self.get_builder_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListBuilders, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListBuilders { query }: ListBuilders,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Builder>> {
|
||||
let builders = self
|
||||
.db
|
||||
.builders
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull builders from mongo")?;
|
||||
|
||||
let builders = if user.is_admin {
|
||||
builders
|
||||
} else {
|
||||
builders
|
||||
.into_iter()
|
||||
.filter(|builder| builder.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(builders)
|
||||
}
|
||||
}
|
||||
194
core/src/requests/read/deployment.rs
Normal file
194
core/src/requests/read/deployment.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use std::cmp;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
deployment::{
|
||||
Deployment, DeploymentActionState, DeploymentConfig, DeploymentImage,
|
||||
DockerContainerStats,
|
||||
},
|
||||
update::{Log, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
permissioned::Permissioned,
|
||||
requests::read::*,
|
||||
};
|
||||
use mungos::mongodb::{bson::doc, options::FindOneOptions};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDeployment, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDeployment { id }: GetDeployment,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Deployment> {
|
||||
self.get_deployment_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListDeployments, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListDeployments { query }: ListDeployments,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Deployment>> {
|
||||
let deployments = self
|
||||
.db
|
||||
.deployments
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull deployments from mongo")?;
|
||||
|
||||
let deployments = if user.is_admin {
|
||||
deployments
|
||||
} else {
|
||||
deployments
|
||||
.into_iter()
|
||||
.filter(|deployment| {
|
||||
deployment.get_user_permissions(&user.id) > PermissionLevel::None
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(deployments)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LOG_LENGTH: u64 = 5000;
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetLog, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetLog {
|
||||
deployment_id,
|
||||
tail,
|
||||
}: GetLog,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Log> {
|
||||
let Deployment {
|
||||
name,
|
||||
config: DeploymentConfig { server_id, .. },
|
||||
..
|
||||
} = self
|
||||
.get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
if server_id.is_empty() {
|
||||
return Ok(Log::default());
|
||||
}
|
||||
let server = self.get_server(&server_id).await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetContainerLog {
|
||||
name,
|
||||
tail: cmp::min(tail, MAX_LOG_LENGTH),
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDeployedVersion, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDeployedVersion { deployment_id }: GetDeployedVersion,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<GetDeployedVersionResponse> {
|
||||
let Deployment {
|
||||
config: DeploymentConfig { image, .. },
|
||||
..
|
||||
} = self
|
||||
.get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let version = match image {
|
||||
DeploymentImage::Build { .. } => {
|
||||
let latest_deploy_update = self
|
||||
.db
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target": {
|
||||
"type": "Deployment",
|
||||
"id": deployment_id
|
||||
},
|
||||
"operation": Operation::DeployContainer.to_string(),
|
||||
"status": UpdateStatus::Complete.to_string(),
|
||||
"success": true,
|
||||
},
|
||||
FindOneOptions::builder().sort(doc! { "_id": -1 }).build(),
|
||||
)
|
||||
.await
|
||||
.context("failed at query to get latest deploy update from mongo")?;
|
||||
if let Some(update) = latest_deploy_update {
|
||||
if !update.version.is_none() {
|
||||
update.version.to_string()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
DeploymentImage::Image { image } => {
|
||||
let split = image.split(':').collect::<Vec<&str>>();
|
||||
if let Some(version) = split.get(1) {
|
||||
version.to_string()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(GetDeployedVersionResponse { version })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDeploymentStats, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDeploymentStats { id }: GetDeploymentStats,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<DockerContainerStats> {
|
||||
let Deployment {
|
||||
name,
|
||||
config: DeploymentConfig { server_id, .. },
|
||||
..
|
||||
} = self
|
||||
.get_deployment_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
if server_id.is_empty() {
|
||||
return Err(anyhow!("deployment has no server attached"));
|
||||
}
|
||||
let server = self.get_server(&server_id).await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetContainerStats { name })
|
||||
.await
|
||||
.context("failed to get stats from periphery")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDeploymentActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDeploymentActionState { id }: GetDeploymentActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<DeploymentActionState> {
|
||||
self.get_deployment_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self
|
||||
.action_states
|
||||
.deployment
|
||||
.get(&id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use axum::{
|
||||
headers::ContentType, http::StatusCode, middleware, routing::post, Extension, Json, Router,
|
||||
TypedHeader,
|
||||
};
|
||||
use monitor_types::requests::api::*;
|
||||
use monitor_types::requests::read::*;
|
||||
use resolver_api::{derive::Resolver, Resolve, ResolveToString, Resolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
@@ -18,9 +18,7 @@ use crate::{
|
||||
mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
mod permissions;
|
||||
mod repo;
|
||||
mod secret;
|
||||
mod server;
|
||||
|
||||
#[typeshare]
|
||||
@@ -29,15 +27,7 @@ mod server;
|
||||
#[resolver_args(RequestUser)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
|
||||
pub enum ApiRequest {
|
||||
// ==== SECRET ====
|
||||
CreateLoginSecret(CreateLoginSecret),
|
||||
DeleteLoginSecret(DeleteLoginSecret),
|
||||
|
||||
// ==== PERMISSIONS ====
|
||||
UpdateUserPerimissions(UpdateUserPermissions),
|
||||
UpdateUserPermissionsOnTarget(UpdateUserPermissionsOnTarget),
|
||||
|
||||
pub enum ReadRequest {
|
||||
// ==== SERVER ====
|
||||
GetPeripheryVersion(GetPeripheryVersion),
|
||||
GetSystemInformation(GetSystemInformation),
|
||||
@@ -46,11 +36,7 @@ pub enum ApiRequest {
|
||||
GetDockerNetworks(GetDockerNetworks),
|
||||
GetServer(GetServer),
|
||||
ListServers(ListServers),
|
||||
// CRUD
|
||||
CreateServer(CreateServer),
|
||||
DeleteServer(DeleteServer),
|
||||
UpdateServer(UpdateServer),
|
||||
RenameServer(RenameServer),
|
||||
GetServerActionState(GetServerActionState),
|
||||
// STATS
|
||||
#[to_string_resolver]
|
||||
GetAllSystemStats(GetAllSystemStats),
|
||||
@@ -66,53 +52,25 @@ pub enum ApiRequest {
|
||||
GetSystemProcesses(GetSystemProcesses),
|
||||
#[to_string_resolver]
|
||||
GetSystemComponents(GetSystemComponents),
|
||||
// ACTIONS
|
||||
PruneContainers(PruneDockerContainers),
|
||||
PruneImages(PruneDockerImages),
|
||||
PruneNetworks(PruneDockerNetworks),
|
||||
|
||||
// ==== DEPLOYMENT ====
|
||||
GetDeployment(GetDeployment),
|
||||
ListDeployments(ListDeployments),
|
||||
// CRUD
|
||||
CreateDeployment(CreateDeployment),
|
||||
DeleteDeployment(DeleteDeployment),
|
||||
UpdateDeployment(UpdateDeployment),
|
||||
RenameDeployment(RenameDeployment),
|
||||
// ACTIONS
|
||||
Deploy(Deploy),
|
||||
StartContainer(StartContainer),
|
||||
StopContainer(StopContainer),
|
||||
RemoveContainer(RemoveContainer),
|
||||
GetDeploymentActionState(GetDeploymentActionState),
|
||||
|
||||
// ==== BUILD ====
|
||||
GetBuild(GetBuild),
|
||||
ListBuilds(ListBuilds),
|
||||
// CRUD
|
||||
CreateBuild(CreateBuild),
|
||||
DeleteBuild(DeleteBuild),
|
||||
UpdateBuild(UpdateBuild),
|
||||
// ACTIONS
|
||||
RunBuild(RunBuild),
|
||||
GetBuildActionState(GetBuildActionState),
|
||||
|
||||
// ==== BUILDER ====
|
||||
GetBuilder(GetBuilder),
|
||||
ListBuilders(ListBuilders),
|
||||
// CRUD
|
||||
CreateBuilder(CreateBuilder),
|
||||
DeleteBuilder(DeleteBuilder),
|
||||
UpdateBuilder(UpdateBuilder),
|
||||
|
||||
// ==== REPO ====
|
||||
GetRepo(GetRepo),
|
||||
ListRepos(ListRepos),
|
||||
// CRUD
|
||||
CreateRepo(CreateRepo),
|
||||
UpdateRepo(UpdateRepo),
|
||||
DeleteRepo(DeleteRepo),
|
||||
// ACTIONS
|
||||
CloneRepo(CloneRepo),
|
||||
PullRepo(PullRepo),
|
||||
GetRepoActionState(GetRepoActionState),
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
@@ -122,10 +80,10 @@ pub fn router() -> Router {
|
||||
post(
|
||||
|state: StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Json(request): Json<ApiRequest>| async move {
|
||||
Json(request): Json<ReadRequest>| async move {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
info!("/api request {req_id} | {request:?}");
|
||||
info!("/read request {req_id} | {request:?}");
|
||||
let res = tokio::spawn(async move {
|
||||
state
|
||||
.resolve_request(request, user)
|
||||
@@ -135,16 +93,16 @@ pub fn router() -> Router {
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")));
|
||||
if let Err(e) = &res {
|
||||
info!("/api request {req_id} SPAWN ERROR: {e:?}");
|
||||
info!("/read request {req_id} SPAWN ERROR: {e:?}");
|
||||
}
|
||||
let res = res?;
|
||||
if let Err(e) = &res {
|
||||
info!("/api request {req_id} ERROR: {e:?}");
|
||||
info!("/read request {req_id} ERROR: {e:?}");
|
||||
}
|
||||
let res = res?;
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/api request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/api request {req_id} RESPONSE: {res}");
|
||||
info!("/read request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/read request {req_id} RESPONSE: {res}");
|
||||
Result::<_, (StatusCode, String)>::Ok((TypedHeader(ContentType::json()), res))
|
||||
},
|
||||
),
|
||||
62
core/src/requests/read/repo.rs
Normal file
62
core/src/requests/read/repo.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
repo::{Repo, RepoActionState},
|
||||
PermissionLevel,
|
||||
},
|
||||
permissioned::Permissioned,
|
||||
requests::read::*,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetRepo, RequestUser> for State {
|
||||
async fn resolve(&self, GetRepo { id }: GetRepo, user: RequestUser) -> anyhow::Result<Repo> {
|
||||
self.get_repo_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListRepos, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListRepos { query }: ListRepos,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Repo>> {
|
||||
let repos = self
|
||||
.db
|
||||
.repos
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull repos from mongo")?;
|
||||
|
||||
let repos = if user.is_admin {
|
||||
repos
|
||||
} else {
|
||||
repos
|
||||
.into_iter()
|
||||
.filter(|repo| repo.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(repos)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetRepoActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetRepoActionState { id }: GetRepoActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<RepoActionState> {
|
||||
self.get_repo_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self.action_states.repo.get(&id).await.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
311
core/src/requests/read/server.rs
Normal file
311
core/src/requests/read/server.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
deployment::BasicContainerInfo,
|
||||
server::{
|
||||
docker_image::ImageSummary, docker_network::DockerNetwork, stats::SystemInformation,
|
||||
Server, ServerActionState,
|
||||
},
|
||||
PermissionLevel,
|
||||
},
|
||||
permissioned::Permissioned,
|
||||
requests::read::*,
|
||||
};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::{Resolve, ResolveToString};
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetPeripheryVersion, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: GetPeripheryVersion,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<GetPeripheryVersionResponse> {
|
||||
self.get_server_check_permissions(&req.server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let version = self
|
||||
.server_status_cache
|
||||
.get(&req.server_id)
|
||||
.await
|
||||
.map(|s| s.version.clone())
|
||||
.unwrap_or(String::from("unknown"));
|
||||
Ok(GetPeripheryVersionResponse { version })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetServer, RequestUser> for State {
|
||||
async fn resolve(&self, req: GetServer, user: RequestUser) -> anyhow::Result<Server> {
|
||||
self.get_server_check_permissions(&req.id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListServers, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListServers { query }: ListServers,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Server>> {
|
||||
let servers = self
|
||||
.db
|
||||
.servers
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull servers from mongo")?;
|
||||
|
||||
let servers = if user.is_admin {
|
||||
servers
|
||||
} else {
|
||||
servers
|
||||
.into_iter()
|
||||
.filter(|server| server.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(servers)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetServerActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetServerActionState { id }: GetServerActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<ServerActionState> {
|
||||
self.get_server_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self.action_states.server.get(&id).await.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetSystemInformation, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetSystemInformation { server_id }: GetSystemInformation,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<SystemInformation> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetSystemInformation {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetAllSystemStats, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetAllSystemStats { server_id }: GetAllSystemStats,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetBasicSystemStats, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetBasicSystemStats { server_id }: GetBasicSystemStats,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.basic)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetCpuUsage, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetCpuUsage { server_id }: GetCpuUsage,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.cpu)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetDiskUsage, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetDiskUsage { server_id }: GetDiskUsage,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.disk)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetNetworkUsage, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetNetworkUsage { server_id }: GetNetworkUsage,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.network)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetSystemProcesses, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetSystemProcesses { server_id }: GetSystemProcesses,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.processes)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResolveToString<GetSystemComponents, RequestUser> for State {
|
||||
async fn resolve_to_string(
|
||||
&self,
|
||||
GetSystemComponents { server_id }: GetSystemComponents,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<String> {
|
||||
self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let status = self
|
||||
.server_status_cache
|
||||
.get(&server_id)
|
||||
.await
|
||||
.ok_or(anyhow!("did not find status for server at {server_id}"))?;
|
||||
let stats = status
|
||||
.stats
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("server not reachable"))?;
|
||||
let stats = serde_json::to_string(&stats.components)?;
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDockerImages, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerImages { server_id }: GetDockerImages,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<ImageSummary>> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetImageList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDockerNetworks, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerNetworks { server_id }: GetDockerNetworks,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<DockerNetwork>> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetNetworkList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetDockerContainers, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetDockerContainers { server_id }: GetDockerContainers,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<BasicContainerInfo>> {
|
||||
let server = self
|
||||
.get_server_check_permissions(&server_id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
self.periphery_client(&server)
|
||||
.request(requests::GetContainerList {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
315
core/src/requests/write/build.rs
Normal file
315
core/src/requests/write/build.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
build::{Build, BuildBuilderConfig},
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
requests::write::*,
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, helpers::empty_or_only_spaces, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateBuild { name, config }: CreateBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
if let Some(builder) = &config.builder {
|
||||
match builder {
|
||||
BuildBuilderConfig::Server { server_id } => {
|
||||
self.get_server_check_permissions(
|
||||
server_id,
|
||||
&user,
|
||||
PermissionLevel::Update,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build on this server. user must have update permissions on the server.")?;
|
||||
}
|
||||
BuildBuilderConfig::Builder { builder_id } => {
|
||||
self.get_builder_check_permissions(
|
||||
builder_id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let build = Build {
|
||||
id: Default::default(),
|
||||
name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
last_built_at: 0,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description: Default::default(),
|
||||
config: config.into(),
|
||||
};
|
||||
let build_id = self
|
||||
.db
|
||||
.builds
|
||||
.create_one(build)
|
||||
.await
|
||||
.context("failed to add build to db")?;
|
||||
let build = self.get_build(&build_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Build(build_id),
|
||||
operation: Operation::CreateBuild,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create build",
|
||||
format!("created build\nid: {}\nname: {}", build.id, build.name),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", build.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CopyBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CopyBuild { name, id }: CopyBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
let Build {
|
||||
config,
|
||||
description,
|
||||
..
|
||||
} = self
|
||||
.get_build_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
match &config.builder {
|
||||
BuildBuilderConfig::Server { server_id } => {
|
||||
self.get_server_check_permissions(
|
||||
server_id,
|
||||
&user,
|
||||
PermissionLevel::Update,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build on this server. user must have update permissions on the server.")?;
|
||||
}
|
||||
BuildBuilderConfig::Builder { builder_id } => {
|
||||
self.get_builder_check_permissions(
|
||||
builder_id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
|
||||
}
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let build = Build {
|
||||
id: Default::default(),
|
||||
name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
last_built_at: 0,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description,
|
||||
config,
|
||||
};
|
||||
let build_id = self
|
||||
.db
|
||||
.builds
|
||||
.create_one(build)
|
||||
.await
|
||||
.context("failed to add build to db")?;
|
||||
let build = self.get_build(&build_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Build(build_id),
|
||||
operation: Operation::CreateBuild,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create build",
|
||||
format!("created build\nid: {}\nname: {}", build.id, build.name),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", build.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<DeleteBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteBuild { id }: DeleteBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
if self.action_states.build.busy(&id).await {
|
||||
return Err(anyhow!("build busy"));
|
||||
}
|
||||
|
||||
let build = self
|
||||
.get_build_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Build(id.clone()),
|
||||
operation: Operation::DeleteBuild,
|
||||
start_ts,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
status: UpdateStatus::InProgress,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
let res = self
|
||||
.db
|
||||
.builds
|
||||
.delete_one(&id)
|
||||
.await
|
||||
.context("failed to delete build from database");
|
||||
|
||||
let log = match res {
|
||||
Ok(_) => Log::simple("delete build", format!("deleted build {}", build.name)),
|
||||
Err(e) => Log::error("delete build", format!("failed to delete build\n{e:#?}")),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.finalize();
|
||||
self.update_update(update).await?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<UpdateBuild, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateBuild { id, mut config }: UpdateBuild,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Build> {
|
||||
if self.action_states.build.busy(&id).await {
|
||||
return Err(anyhow!("build busy"));
|
||||
}
|
||||
|
||||
let build = self
|
||||
.get_build_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if let Some(builder) = &config.builder {
|
||||
match builder {
|
||||
BuildBuilderConfig::Server { server_id } => {
|
||||
self.get_server_check_permissions(
|
||||
server_id,
|
||||
&user,
|
||||
PermissionLevel::Update,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build on this server. user must have update permissions on the server.")?;
|
||||
}
|
||||
BuildBuilderConfig::Builder { builder_id } => {
|
||||
self.get_builder_check_permissions(
|
||||
builder_id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
.context("cannot create build using this builder. user must have at least read permissions on the builder.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(build_args) = &mut config.build_args {
|
||||
build_args.retain(|v| {
|
||||
!empty_or_only_spaces(&v.variable) && !empty_or_only_spaces(&v.value)
|
||||
})
|
||||
}
|
||||
if let Some(extra_args) = &mut config.extra_args {
|
||||
extra_args.retain(|v| !empty_or_only_spaces(v))
|
||||
}
|
||||
|
||||
self.db
|
||||
.builds
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "config": to_bson(&config)? }),
|
||||
)
|
||||
.await
|
||||
.context("failed to update build on database")?;
|
||||
|
||||
let update = Update {
|
||||
operation: Operation::UpdateBuild,
|
||||
target: ResourceTarget::Build(id.clone()),
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
status: UpdateStatus::Complete,
|
||||
logs: vec![Log::simple(
|
||||
"build update",
|
||||
serde_json::to_string_pretty(&config).unwrap(),
|
||||
)],
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
version: config.version.unwrap_or_default(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
let build = self.get_build(&id).await?;
|
||||
|
||||
anyhow::Ok(build)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.build
|
||||
.update_entry(build.id.clone(), |entry| {
|
||||
entry.updating = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.build
|
||||
.update_entry(build.id, |entry| {
|
||||
entry.updating = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -7,53 +7,13 @@ use monitor_types::{
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
permissioned::Permissioned,
|
||||
requests::api::*,
|
||||
requests::write::*,
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetBuilder, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetBuilder { id }: GetBuilder,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Builder> {
|
||||
self.get_builder_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListBuilders, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListBuilders { query }: ListBuilders,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Builder>> {
|
||||
let builders = self
|
||||
.db
|
||||
.builders
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull builders from mongo")?;
|
||||
|
||||
let builders = if user.is_admin {
|
||||
builders
|
||||
} else {
|
||||
builders
|
||||
.into_iter()
|
||||
.filter(|builder| builder.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(builders)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateBuilder, RequestUser> for State {
|
||||
async fn resolve(
|
||||
467
core/src/requests/write/deployment.rs
Normal file
467
core/src/requests/write/deployment.rs
Normal file
@@ -0,0 +1,467 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
all_logs_success,
|
||||
entities::{
|
||||
deployment::{Deployment, DeploymentImage, DockerContainerState},
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
requests::write::*,
|
||||
to_monitor_name,
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use periphery_client::requests;
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, helpers::empty_or_only_spaces, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateDeployment, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateDeployment { name, config }: CreateDeployment,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Deployment> {
|
||||
if let Some(server_id) = &config.server_id {
|
||||
if !server_id.is_empty() {
|
||||
self.get_server_check_permissions(server_id, &user, PermissionLevel::Update)
|
||||
.await
|
||||
.context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?;
|
||||
}
|
||||
}
|
||||
if let Some(DeploymentImage::Build { build_id, .. }) = &config.image {
|
||||
if !build_id.is_empty() {
|
||||
self.get_build_check_permissions(build_id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
.context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?;
|
||||
}
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let deployment = Deployment {
|
||||
id: Default::default(),
|
||||
name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description: Default::default(),
|
||||
config: config.into(),
|
||||
};
|
||||
let deployment_id = self
|
||||
.db
|
||||
.deployments
|
||||
.create_one(&deployment)
|
||||
.await
|
||||
.context("failed to add deployment to db")?;
|
||||
let deployment = self.get_deployment(&deployment_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Deployment(deployment_id),
|
||||
operation: Operation::CreateDeployment,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create deployment",
|
||||
format!(
|
||||
"created deployment\nid: {}\nname: {}",
|
||||
deployment.id, deployment.name
|
||||
),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", deployment.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(deployment)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CopyDeployment, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CopyDeployment { name, id }: CopyDeployment,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Deployment> {
|
||||
let Deployment {
|
||||
config,
|
||||
description,
|
||||
..
|
||||
} = self
|
||||
.get_deployment_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
if !config.server_id.is_empty() {
|
||||
self.get_server_check_permissions(&config.server_id, &user, PermissionLevel::Update)
|
||||
.await
|
||||
.context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?;
|
||||
}
|
||||
if let DeploymentImage::Build { build_id, .. } = &config.image {
|
||||
if !build_id.is_empty() {
|
||||
self.get_build_check_permissions(build_id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
.context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?;
|
||||
}
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let deployment = Deployment {
|
||||
id: Default::default(),
|
||||
name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description,
|
||||
config,
|
||||
};
|
||||
let deployment_id = self
|
||||
.db
|
||||
.deployments
|
||||
.create_one(&deployment)
|
||||
.await
|
||||
.context("failed to add deployment to db")?;
|
||||
let deployment = self.get_deployment(&deployment_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Deployment(deployment_id),
|
||||
operation: Operation::CreateDeployment,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create deployment",
|
||||
format!(
|
||||
"created deployment\nid: {}\nname: {}",
|
||||
deployment.id, deployment.name
|
||||
),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", deployment.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(deployment)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<DeleteDeployment, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteDeployment { id }: DeleteDeployment,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Deployment> {
|
||||
if self.action_states.deployment.busy(&id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let deployment = self
|
||||
.get_deployment_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let state = self
|
||||
.get_deployment_state(&deployment)
|
||||
.await
|
||||
.context("failed to get container state")?;
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(deployment.id.clone()),
|
||||
operation: Operation::DeleteDeployment,
|
||||
start_ts,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
status: UpdateStatus::InProgress,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
if !matches!(
|
||||
state,
|
||||
DockerContainerState::NotDeployed | DockerContainerState::Unknown
|
||||
) {
|
||||
// container needs to be destroyed
|
||||
let server = self.get_server(&deployment.config.server_id).await;
|
||||
if let Err(e) = server {
|
||||
update.logs.push(Log::error(
|
||||
"remove container",
|
||||
format!(
|
||||
"failed to retrieve server at {} from mongo | {e:#?}",
|
||||
deployment.config.server_id
|
||||
),
|
||||
));
|
||||
} else {
|
||||
let server = server.unwrap();
|
||||
match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: deployment.config.termination_signal.into(),
|
||||
time: deployment.config.termination_timeout.into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.logs.push(Log::error(
|
||||
"remove container",
|
||||
format!("failed to remove container on periphery | {e:#?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = self
|
||||
.db
|
||||
.deployments
|
||||
.delete_one(&deployment.id)
|
||||
.await
|
||||
.context("failed to delete deployment from mongo");
|
||||
|
||||
let log = match res {
|
||||
Ok(_) => Log::simple(
|
||||
"delete deployment",
|
||||
format!("deleted deployment {}", deployment.name),
|
||||
),
|
||||
Err(e) => Log::error(
|
||||
"delete deployment",
|
||||
format!("failed to delete deployment\n{e:#?}"),
|
||||
),
|
||||
};
|
||||
|
||||
update.logs.push(log);
|
||||
update.end_ts = Some(monitor_timestamp());
|
||||
update.status = UpdateStatus::Complete;
|
||||
update.success = all_logs_success(&update.logs);
|
||||
|
||||
self.update_update(update).await?;
|
||||
|
||||
Ok(deployment)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(id.clone(), |entry| {
|
||||
entry.deleting = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(id, |entry| {
|
||||
entry.deleting = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<UpdateDeployment, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateDeployment { id, mut config }: UpdateDeployment,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Deployment> {
|
||||
if self.action_states.deployment.busy(&id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let deployment = self
|
||||
.get_deployment_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if let Some(server_id) = &config.server_id {
|
||||
self.get_server_check_permissions(server_id, &user, PermissionLevel::Update)
|
||||
.await
|
||||
.context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?;
|
||||
}
|
||||
if let Some(DeploymentImage::Build { build_id, .. }) = &config.image {
|
||||
self.get_build_check_permissions(build_id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
.context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?;
|
||||
}
|
||||
|
||||
if let Some(volumes) = &mut config.volumes {
|
||||
volumes.retain(|v| {
|
||||
!empty_or_only_spaces(&v.local) && !empty_or_only_spaces(&v.container)
|
||||
})
|
||||
}
|
||||
if let Some(ports) = &mut config.ports {
|
||||
ports.retain(|v| {
|
||||
!empty_or_only_spaces(&v.local) && !empty_or_only_spaces(&v.container)
|
||||
})
|
||||
}
|
||||
if let Some(environment) = &mut config.environment {
|
||||
environment.retain(|v| {
|
||||
!empty_or_only_spaces(&v.variable) && !empty_or_only_spaces(&v.value)
|
||||
})
|
||||
}
|
||||
if let Some(extra_args) = &mut config.extra_args {
|
||||
extra_args.retain(|v| !empty_or_only_spaces(v))
|
||||
}
|
||||
|
||||
self.db
|
||||
.deployments
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "config": to_bson(&config)? }),
|
||||
)
|
||||
.await
|
||||
.context("failed to update server on mongo")?;
|
||||
|
||||
let update = Update {
|
||||
operation: Operation::UpdateDeployment,
|
||||
target: ResourceTarget::Deployment(id.clone()),
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
status: UpdateStatus::Complete,
|
||||
logs: vec![Log::simple(
|
||||
"deployment update",
|
||||
serde_json::to_string_pretty(&config).unwrap(),
|
||||
)],
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
let deployment = self.get_deployment(&id).await?;
|
||||
|
||||
anyhow::Ok(deployment)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment.id.clone(), |entry| {
|
||||
entry.updating = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(deployment.id, |entry| {
|
||||
entry.updating = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<RenameDeployment, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RenameDeployment { id, name }: RenameDeployment,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
if self.action_states.deployment.busy(&id).await {
|
||||
return Err(anyhow!("deployment busy"));
|
||||
}
|
||||
|
||||
let deployment = self
|
||||
.get_deployment_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
let mut logs = Vec::new();
|
||||
let name = to_monitor_name(&name);
|
||||
|
||||
let container_state = self.get_deployment_state(&deployment).await?;
|
||||
|
||||
if container_state == DockerContainerState::Unknown {
|
||||
return Err(anyhow!(
|
||||
"cannot rename deployment when container status is unknown"
|
||||
));
|
||||
}
|
||||
|
||||
if container_state != DockerContainerState::NotDeployed {
|
||||
let server = self.get_server(&deployment.config.server_id).await?;
|
||||
match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::RenameContainer {
|
||||
curr_name: deployment.name.clone(),
|
||||
new_name: name.clone(),
|
||||
})
|
||||
.await
|
||||
.context("failed to rename container on server")
|
||||
{
|
||||
Ok(log) => logs.push(log),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
}
|
||||
|
||||
self.db
|
||||
.deployments
|
||||
.update_one(
|
||||
&deployment.id,
|
||||
mungos::Update::Set(doc! { "name": &name, "updated_at": monitor_timestamp() }),
|
||||
)
|
||||
.await
|
||||
.context("failed to update deployment name on mongo")?;
|
||||
|
||||
logs.push(Log::simple(
|
||||
"rename deployment",
|
||||
format!("renamed deployment from {} to {}", deployment.name, name),
|
||||
));
|
||||
|
||||
let update = Update {
|
||||
target: ResourceTarget::Deployment(deployment.id),
|
||||
operation: Operation::RenameDeployment,
|
||||
start_ts,
|
||||
end_ts: monitor_timestamp().into(),
|
||||
status: UpdateStatus::InProgress,
|
||||
success: all_logs_success(&logs),
|
||||
operator: user.id.clone(),
|
||||
logs,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(id.clone(), |entry| {
|
||||
entry.renaming = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.deployment
|
||||
.update_entry(id, |entry| {
|
||||
entry.renaming = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
104
core/src/requests/write/mod.rs
Normal file
104
core/src/requests/write/mod.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use axum::{
|
||||
headers::ContentType, http::StatusCode, middleware, routing::post, Extension, Json, Router,
|
||||
TypedHeader,
|
||||
};
|
||||
use monitor_types::requests::write::*;
|
||||
use resolver_api::{derive::Resolver, Resolve, Resolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{auth_request, RequestUser, RequestUserExtension},
|
||||
state::{State, StateExtension},
|
||||
};
|
||||
|
||||
mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
mod permissions;
|
||||
mod repo;
|
||||
mod secret;
|
||||
mod server;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
|
||||
#[resolver_target(State)]
|
||||
#[resolver_args(RequestUser)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
|
||||
pub enum WriteRequest {
|
||||
// ==== SECRET ====
|
||||
CreateLoginSecret(CreateLoginSecret),
|
||||
DeleteLoginSecret(DeleteLoginSecret),
|
||||
|
||||
// ==== PERMISSIONS ====
|
||||
UpdateUserPerimissions(UpdateUserPermissions),
|
||||
UpdateUserPermissionsOnTarget(UpdateUserPermissionsOnTarget),
|
||||
|
||||
// ==== SERVER ====
|
||||
CreateServer(CreateServer),
|
||||
DeleteServer(DeleteServer),
|
||||
UpdateServer(UpdateServer),
|
||||
RenameServer(RenameServer),
|
||||
|
||||
// ==== DEPLOYMENT ====
|
||||
CreateDeployment(CreateDeployment),
|
||||
DeleteDeployment(DeleteDeployment),
|
||||
UpdateDeployment(UpdateDeployment),
|
||||
RenameDeployment(RenameDeployment),
|
||||
|
||||
// ==== BUILD ====
|
||||
CreateBuild(CreateBuild),
|
||||
DeleteBuild(DeleteBuild),
|
||||
UpdateBuild(UpdateBuild),
|
||||
|
||||
// ==== BUIDLER ====
|
||||
CreateBuilder(CreateBuilder),
|
||||
DeleteBuilder(DeleteBuilder),
|
||||
UpdateBuilder(UpdateBuilder),
|
||||
|
||||
// ==== REPO ====
|
||||
CreateRepo(CreateRepo),
|
||||
UpdateRepo(UpdateRepo),
|
||||
DeleteRepo(DeleteRepo),
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
"/",
|
||||
post(
|
||||
|state: StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Json(request): Json<WriteRequest>| async move {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
info!("/write request {req_id} | {request:?}");
|
||||
let res = tokio::spawn(async move {
|
||||
state
|
||||
.resolve_request(request, user)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")));
|
||||
if let Err(e) = &res {
|
||||
info!("/write request {req_id} SPAWN ERROR: {e:?}");
|
||||
}
|
||||
let res = res?;
|
||||
if let Err(e) = &res {
|
||||
info!("/write request {req_id} ERROR: {e:?}");
|
||||
}
|
||||
let res = res?;
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/write request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/write request {req_id} RESPONSE: {res}");
|
||||
Result::<_, (StatusCode, String)>::Ok((TypedHeader(ContentType::json()), res))
|
||||
},
|
||||
),
|
||||
)
|
||||
.layer(middleware::from_fn(auth_request))
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use monitor_types::{
|
||||
Operation,
|
||||
},
|
||||
monitor_timestamp,
|
||||
requests::api::{UpdateUserPermissions, UpdateUserPermissionsOnTarget},
|
||||
requests::write::{UpdateUserPermissions, UpdateUserPermissionsOnTarget},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, Document};
|
||||
use resolver_api::Resolve;
|
||||
@@ -2,13 +2,12 @@ use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
repo::{Repo, RepoActionState},
|
||||
repo::Repo,
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp, optional_string,
|
||||
permissioned::Permissioned,
|
||||
requests::api::*,
|
||||
monitor_timestamp,
|
||||
requests::{execute, write::*},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use periphery_client::requests;
|
||||
@@ -16,55 +15,6 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetRepo, RequestUser> for State {
|
||||
async fn resolve(&self, GetRepo { id }: GetRepo, user: RequestUser) -> anyhow::Result<Repo> {
|
||||
self.get_repo_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListRepos, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListRepos { query }: ListRepos,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Vec<Repo>> {
|
||||
let repos = self
|
||||
.db
|
||||
.repos
|
||||
.get_some(query, None)
|
||||
.await
|
||||
.context("failed to pull repos from mongo")?;
|
||||
|
||||
let repos = if user.is_admin {
|
||||
repos
|
||||
} else {
|
||||
repos
|
||||
.into_iter()
|
||||
.filter(|repo| repo.get_user_permissions(&user.id) > PermissionLevel::None)
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(repos)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<GetRepoActionState, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetRepoActionState { id }: GetRepoActionState,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<RepoActionState> {
|
||||
self.get_repo_check_permissions(&id, &user, PermissionLevel::Read)
|
||||
.await?;
|
||||
let action_state = self.action_states.repo.get(&id).await.unwrap_or_default();
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateRepo, RequestUser> for State {
|
||||
async fn resolve(
|
||||
@@ -126,7 +76,7 @@ impl Resolve<CreateRepo, RequestUser> for State {
|
||||
if !repo.config.repo.is_empty() && !repo.config.server_id.is_empty() {
|
||||
let _ = self
|
||||
.resolve(
|
||||
CloneRepo {
|
||||
execute::CloneRepo {
|
||||
id: repo.id.clone(),
|
||||
},
|
||||
user,
|
||||
@@ -360,7 +310,7 @@ impl Resolve<UpdateRepo, RequestUser> for State {
|
||||
// clone on new server
|
||||
let _ = self
|
||||
.resolve(
|
||||
CloneRepo {
|
||||
execute::CloneRepo {
|
||||
id: repo.id.clone(),
|
||||
},
|
||||
user,
|
||||
@@ -398,151 +348,3 @@ impl Resolve<UpdateRepo, RequestUser> for State {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CloneRepo, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
CloneRepo { id }: CloneRepo,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
let repo = self
|
||||
.get_repo_check_permissions(&id, &user, PermissionLevel::Execute)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if repo.config.server_id.is_empty() {
|
||||
return Err(anyhow!("repo has no server attached"));
|
||||
}
|
||||
|
||||
let server = self.get_server(&repo.config.server_id).await?;
|
||||
|
||||
let mut update = Update {
|
||||
operation: Operation::CloneRepo,
|
||||
target: ResourceTarget::Repo(repo.id.clone()),
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::CloneRepo {
|
||||
args: (&repo).into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(logs) => update.logs.extend(logs),
|
||||
Err(e) => update
|
||||
.logs
|
||||
.push(Log::error("clone repo", format!("{e:#?}"))),
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
self.update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
if self.action_states.repo.busy(&id).await {
|
||||
return Err(anyhow!("repo busy"));
|
||||
}
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(&id, |entry| {
|
||||
entry.cloning = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(id, |entry| {
|
||||
entry.cloning = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<PullRepo, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
PullRepo { id }: PullRepo,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
let repo = self
|
||||
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let inner = || async move {
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
if repo.config.server_id.is_empty() {
|
||||
return Err(anyhow!("repo has no server attached"));
|
||||
}
|
||||
|
||||
let server = self.get_server(&repo.config.server_id).await?;
|
||||
|
||||
let mut update = Update {
|
||||
operation: Operation::PullRepo,
|
||||
target: ResourceTarget::Repo(repo.id.clone()),
|
||||
start_ts,
|
||||
status: UpdateStatus::InProgress,
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
|
||||
match self
|
||||
.periphery_client(&server)
|
||||
.request(requests::PullRepo {
|
||||
name: repo.name,
|
||||
branch: optional_string(&repo.config.branch),
|
||||
on_pull: repo.config.on_pull.into_option(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(logs) => update.logs.extend(logs),
|
||||
Err(e) => update.logs.push(Log::error("pull repo", format!("{e:#?}"))),
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
self.update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
};
|
||||
|
||||
if self.action_states.repo.busy(&id).await {
|
||||
return Err(anyhow!("repo busy"));
|
||||
}
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(id.clone(), |entry| {
|
||||
entry.pulling = true;
|
||||
})
|
||||
.await;
|
||||
|
||||
let res = inner().await;
|
||||
|
||||
self.action_states
|
||||
.repo
|
||||
.update_entry(id, |entry| {
|
||||
entry.pulling = false;
|
||||
})
|
||||
.await;
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::user::ApiSecret,
|
||||
monitor_timestamp,
|
||||
requests::api::{CreateLoginSecret, CreateLoginSecretResponse, DeleteLoginSecret},
|
||||
requests::write::*,
|
||||
};
|
||||
use mungos::{
|
||||
mongodb::bson::{doc, to_bson},
|
||||
219
core/src/requests/write/server.rs
Normal file
219
core/src/requests/write/server.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use monitor_types::{
|
||||
entities::{
|
||||
server::Server,
|
||||
update::{Log, ResourceTarget, Update, UpdateStatus},
|
||||
Operation, PermissionLevel,
|
||||
},
|
||||
monitor_timestamp,
|
||||
requests::write::*,
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, to_bson};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{auth::RequestUser, state::State};
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<CreateServer, RequestUser> for State {
|
||||
async fn resolve(&self, req: CreateServer, user: RequestUser) -> anyhow::Result<Server> {
|
||||
if !user.is_admin && !user.create_server_permissions {
|
||||
return Err(anyhow!("user does not have create server permissions"));
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
let server = Server {
|
||||
id: Default::default(),
|
||||
name: req.name,
|
||||
created_at: start_ts,
|
||||
updated_at: start_ts,
|
||||
permissions: [(user.id.clone(), PermissionLevel::Update)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
description: Default::default(),
|
||||
config: req.config.into(),
|
||||
};
|
||||
let server_id = self
|
||||
.db
|
||||
.servers
|
||||
.create_one(&server)
|
||||
.await
|
||||
.context("failed to add server to db")?;
|
||||
let server = self.get_server(&server_id).await?;
|
||||
let update = Update {
|
||||
target: ResourceTarget::Server(server_id),
|
||||
operation: Operation::CreateServer,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
logs: vec![
|
||||
Log::simple(
|
||||
"create server",
|
||||
format!("created server\nid: {}\nname: {}", server.id, server.name),
|
||||
),
|
||||
Log::simple("config", format!("{:#?}", server.config)),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
self.update_cache_for_server(&server).await;
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<DeleteServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteServer { id }: DeleteServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Server> {
|
||||
if self.action_states.server.busy(&id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
|
||||
let server = self
|
||||
.get_server_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
|
||||
let start_ts = monitor_timestamp();
|
||||
|
||||
self.db
|
||||
.builds
|
||||
.update_many(
|
||||
doc! { "config.builder.params.server_id": &id },
|
||||
doc! { "$set": { "config.builder.params.server_id": "" } },
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from builds")?;
|
||||
|
||||
self.db
|
||||
.deployments
|
||||
.update_many(
|
||||
doc! { "config.server_id": &id },
|
||||
doc! { "$set": { "config.server_id": "" } },
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from deployments")?;
|
||||
|
||||
self.db
|
||||
.repos
|
||||
.update_many(
|
||||
doc! { "config.server_id": &id },
|
||||
doc! { "$set": { "config.server_id": "" } },
|
||||
)
|
||||
.await
|
||||
.context("failed to detach server from repos")?;
|
||||
|
||||
self.db
|
||||
.servers
|
||||
.delete_one(&id)
|
||||
.await
|
||||
.context("failed to delete server from mongo")?;
|
||||
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Server(id.clone()),
|
||||
operation: Operation::DeleteServer,
|
||||
start_ts,
|
||||
operator: user.id.clone(),
|
||||
logs: vec![Log::simple(
|
||||
"delete server",
|
||||
format!("deleted server {}", server.name),
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update.finalize();
|
||||
self.add_update(update).await?;
|
||||
|
||||
self.server_status_cache.remove(&id).await;
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<UpdateServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateServer { id, config }: UpdateServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Server> {
|
||||
if self.action_states.server.busy(&id).await {
|
||||
return Err(anyhow!("server busy"));
|
||||
}
|
||||
let start_ts = monitor_timestamp();
|
||||
self.get_server_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
self.db
|
||||
.servers
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "config": to_bson(&config)? }),
|
||||
)
|
||||
.await
|
||||
.context("failed to update server on mongo")?;
|
||||
let update = Update {
|
||||
operation: Operation::UpdateServer,
|
||||
target: ResourceTarget::Server(id.clone()),
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
status: UpdateStatus::Complete,
|
||||
logs: vec![Log::simple(
|
||||
"server update",
|
||||
serde_json::to_string_pretty(&config).unwrap(),
|
||||
)],
|
||||
operator: user.id.clone(),
|
||||
success: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let new_server = self.get_server(&id).await?;
|
||||
|
||||
self.update_cache_for_server(&new_server).await;
|
||||
|
||||
self.add_update(update).await?;
|
||||
|
||||
Ok(new_server)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<RenameServer, RequestUser> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RenameServer { id, name }: RenameServer,
|
||||
user: RequestUser,
|
||||
) -> anyhow::Result<Update> {
|
||||
let start_ts = monitor_timestamp();
|
||||
let server = self
|
||||
.get_server_check_permissions(&id, &user, PermissionLevel::Update)
|
||||
.await?;
|
||||
self.db
|
||||
.updates
|
||||
.update_one(
|
||||
&id,
|
||||
mungos::Update::Set(doc! { "name": &name, "updated_at": monitor_timestamp() }),
|
||||
)
|
||||
.await?;
|
||||
let mut update = Update {
|
||||
target: ResourceTarget::Deployment(id.clone()),
|
||||
operation: Operation::RenameServer,
|
||||
start_ts,
|
||||
end_ts: Some(monitor_timestamp()),
|
||||
logs: vec![Log::simple(
|
||||
"rename server",
|
||||
format!("renamed server {id} from {} to {name}", server.name),
|
||||
)],
|
||||
status: UpdateStatus::Complete,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = self.add_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
//
|
||||
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::{
|
||||
deployment::{
|
||||
Deployment, DeploymentActionState, DockerContainerStats, PartialDeploymentConfig,
|
||||
TerminationSignal,
|
||||
},
|
||||
update::{Log, Update},
|
||||
},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct GetDeployment {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Deployment>)]
|
||||
pub struct ListDeployments {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Log)]
|
||||
pub struct GetLog {
|
||||
pub deployment_id: String,
|
||||
#[serde(default = "default_tail")]
|
||||
pub tail: u64,
|
||||
}
|
||||
|
||||
fn default_tail() -> u64 {
|
||||
50
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(GetDeployedVersionResponse)]
|
||||
pub struct GetDeployedVersion {
|
||||
pub deployment_id: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetDeployedVersionResponse {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(DockerContainerStats)]
|
||||
pub struct GetDeploymentStats {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(DeploymentActionState)]
|
||||
pub struct GetDeploymentActionState {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct CreateDeployment {
|
||||
pub name: String,
|
||||
pub config: PartialDeploymentConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct CopyDeployment {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct DeleteDeployment {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct UpdateDeployment {
|
||||
pub id: String,
|
||||
pub config: PartialDeploymentConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RenameDeployment {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct Deploy {
|
||||
pub deployment_id: String,
|
||||
pub stop_signal: Option<TerminationSignal>,
|
||||
pub stop_time: Option<i32>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct StartContainer {
|
||||
pub deployment_id: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct StopContainer {
|
||||
pub deployment_id: String,
|
||||
pub signal: Option<TerminationSignal>,
|
||||
pub time: Option<i32>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RemoveContainer {
|
||||
pub deployment_id: String,
|
||||
pub signal: Option<TerminationSignal>,
|
||||
pub time: Option<i32>,
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::{
|
||||
repo::{PartialRepoConfig, Repo, RepoActionState},
|
||||
update::Update,
|
||||
},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct GetRepo {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Repo>)]
|
||||
pub struct ListRepos {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(RepoActionState)]
|
||||
pub struct GetRepoActionState {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct CreateRepo {
|
||||
pub name: String,
|
||||
pub config: PartialRepoConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct CopyRepo {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct DeleteRepo {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct UpdateRepo {
|
||||
pub id: String,
|
||||
pub config: PartialRepoConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct CloneRepo {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PullRepo {
|
||||
pub id: String,
|
||||
}
|
||||
14
lib/types/src/requests/execute/build.rs
Normal file
14
lib/types/src/requests/execute/build.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::update::Update;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RunBuild {
|
||||
pub build_id: String,
|
||||
}
|
||||
47
lib/types/src/requests/execute/deployment.rs
Normal file
47
lib/types/src/requests/execute/deployment.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{deployment::TerminationSignal, update::Update};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct Deploy {
|
||||
pub deployment_id: String,
|
||||
pub stop_signal: Option<TerminationSignal>,
|
||||
pub stop_time: Option<i32>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct StartContainer {
|
||||
pub deployment_id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct StopContainer {
|
||||
pub deployment_id: String,
|
||||
pub signal: Option<TerminationSignal>,
|
||||
pub time: Option<i32>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RemoveContainer {
|
||||
pub deployment_id: String,
|
||||
pub signal: Option<TerminationSignal>,
|
||||
pub time: Option<i32>,
|
||||
}
|
||||
8
lib/types/src/requests/execute/mod.rs
Normal file
8
lib/types/src/requests/execute/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod build;
|
||||
pub use build::*;
|
||||
mod deployment;
|
||||
pub use deployment::*;
|
||||
mod repo;
|
||||
pub use repo::*;
|
||||
mod server;
|
||||
pub use server::*;
|
||||
23
lib/types/src/requests/execute/repo.rs
Normal file
23
lib/types/src/requests/execute/repo.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::update::Update;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct CloneRepo {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PullRepo {
|
||||
pub id: String,
|
||||
}
|
||||
32
lib/types/src/requests/execute/server.rs
Normal file
32
lib/types/src/requests/execute/server.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::update::Update;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PruneDockerNetworks {
|
||||
pub server_id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PruneDockerImages {
|
||||
pub server_id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PruneDockerContainers {
|
||||
pub server_id: String,
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod api;
|
||||
pub mod auth;
|
||||
pub mod read;
|
||||
pub mod write;
|
||||
pub mod execute;
|
||||
|
||||
35
lib/types/src/requests/read/build.rs
Normal file
35
lib/types/src/requests/read/build.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::build::{Build, BuildActionState},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Build)]
|
||||
pub struct GetBuild {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Build>)]
|
||||
pub struct ListBuilds {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(BuildActionState)]
|
||||
pub struct GetBuildActionState {
|
||||
pub id: String,
|
||||
}
|
||||
23
lib/types/src/requests/read/builder.rs
Normal file
23
lib/types/src/requests/read/builder.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{entities::builder::Builder, MongoDocument};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Builder)]
|
||||
pub struct GetBuilder {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Builder>)]
|
||||
pub struct ListBuilders {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
77
lib/types/src/requests/read/deployment.rs
Normal file
77
lib/types/src/requests/read/deployment.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::{
|
||||
deployment::{Deployment, DeploymentActionState, DockerContainerStats},
|
||||
update::Log,
|
||||
},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct GetDeployment {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Deployment>)]
|
||||
pub struct ListDeployments {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Log)]
|
||||
pub struct GetLog {
|
||||
pub deployment_id: String,
|
||||
#[serde(default = "default_tail")]
|
||||
pub tail: u64,
|
||||
}
|
||||
|
||||
fn default_tail() -> u64 {
|
||||
50
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(GetDeployedVersionResponse)]
|
||||
pub struct GetDeployedVersion {
|
||||
pub deployment_id: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetDeployedVersionResponse {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(DockerContainerStats)]
|
||||
pub struct GetDeploymentStats {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(DeploymentActionState)]
|
||||
pub struct GetDeploymentActionState {
|
||||
pub id: String,
|
||||
}
|
||||
27
lib/types/src/requests/read/mod.rs
Normal file
27
lib/types/src/requests/read/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
mod build;
|
||||
pub use build::*;
|
||||
mod builder;
|
||||
pub use builder::*;
|
||||
mod deployment;
|
||||
pub use deployment::*;
|
||||
mod repo;
|
||||
pub use repo::*;
|
||||
mod server;
|
||||
pub use server::*;
|
||||
use typeshare::typeshare;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(GetVersionResponse)]
|
||||
pub struct GetVersion {}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetVersionResponse {
|
||||
pub version: String,
|
||||
}
|
||||
35
lib/types/src/requests/read/repo.rs
Normal file
35
lib/types/src/requests/read/repo.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::repo::{Repo, RepoActionState},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct GetRepo {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Repo>)]
|
||||
pub struct ListRepos {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(RepoActionState)]
|
||||
pub struct GetRepoActionState {
|
||||
pub id: String,
|
||||
}
|
||||
@@ -12,9 +12,8 @@ use crate::{
|
||||
AllSystemStats, BasicSystemStats, CpuUsage, DiskUsage, NetworkUsage,
|
||||
SystemComponent, SystemInformation, SystemProcess,
|
||||
},
|
||||
PartialServerConfig, Server, ServerActionState,
|
||||
Server, ServerActionState,
|
||||
},
|
||||
update::Update,
|
||||
},
|
||||
MongoDocument,
|
||||
};
|
||||
@@ -28,6 +27,8 @@ pub struct GetServer {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Server>)]
|
||||
@@ -46,45 +47,6 @@ pub struct GetServerActionState {
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Server)]
|
||||
pub struct CreateServer {
|
||||
pub name: String,
|
||||
pub config: PartialServerConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Server)]
|
||||
pub struct DeleteServer {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Server)]
|
||||
pub struct UpdateServer {
|
||||
pub id: String,
|
||||
pub config: PartialServerConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RenameServer {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(GetPeripheryVersionResponse)]
|
||||
@@ -181,15 +143,6 @@ pub struct GetDockerNetworks {
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PruneDockerNetworks {
|
||||
pub server_id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<ImageSummary>)]
|
||||
@@ -199,27 +152,9 @@ pub struct GetDockerImages {
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PruneDockerImages {
|
||||
pub server_id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<BasicContainerInfo>)]
|
||||
pub struct GetDockerContainers {
|
||||
pub server_id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct PruneDockerContainers {
|
||||
pub server_id: String,
|
||||
}
|
||||
@@ -2,40 +2,7 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::{
|
||||
build::{Build, BuildActionState, PartialBuildConfig},
|
||||
update::Update,
|
||||
},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Build)]
|
||||
pub struct GetBuild {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Build>)]
|
||||
pub struct ListBuilds {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(BuildActionState)]
|
||||
pub struct GetBuildActionState {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
use crate::entities::build::{Build, PartialBuildConfig};
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
@@ -73,12 +40,3 @@ pub struct UpdateBuild {
|
||||
pub id: String,
|
||||
pub config: PartialBuildConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RunBuild {
|
||||
pub build_id: String,
|
||||
}
|
||||
@@ -2,28 +2,7 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
entities::builder::{Builder, PartialBuilderConfig},
|
||||
MongoDocument,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Builder)]
|
||||
pub struct GetBuilder {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Vec<Builder>)]
|
||||
pub struct ListBuilders {
|
||||
pub query: Option<MongoDocument>,
|
||||
}
|
||||
use crate::entities::builder::{Builder, PartialBuilderConfig};
|
||||
|
||||
//
|
||||
|
||||
57
lib/types/src/requests/write/deployment.rs
Normal file
57
lib/types/src/requests/write/deployment.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{
|
||||
deployment::{Deployment, PartialDeploymentConfig},
|
||||
update::Update,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct CreateDeployment {
|
||||
pub name: String,
|
||||
pub config: PartialDeploymentConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct CopyDeployment {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct DeleteDeployment {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Deployment)]
|
||||
pub struct UpdateDeployment {
|
||||
pub id: String,
|
||||
pub config: PartialDeploymentConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RenameDeployment {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
14
lib/types/src/requests/write/mod.rs
Normal file
14
lib/types/src/requests/write/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
mod build;
|
||||
pub use build::*;
|
||||
mod builder;
|
||||
pub use builder::*;
|
||||
mod deployment;
|
||||
pub use deployment::*;
|
||||
mod repo;
|
||||
pub use repo::*;
|
||||
mod server;
|
||||
pub use server::*;
|
||||
mod permissions;
|
||||
pub use permissions::*;
|
||||
mod secret;
|
||||
pub use secret::*;
|
||||
@@ -24,4 +24,4 @@ pub struct UpdateUserPermissions {
|
||||
pub enabled: Option<bool>,
|
||||
pub create_servers: Option<bool>,
|
||||
pub create_builds: Option<bool>,
|
||||
}
|
||||
}
|
||||
44
lib/types/src/requests/write/repo.rs
Normal file
44
lib/types/src/requests/write/repo.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::repo::{PartialRepoConfig, Repo};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct CreateRepo {
|
||||
pub name: String,
|
||||
pub config: PartialRepoConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct CopyRepo {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct DeleteRepo {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Repo)]
|
||||
pub struct UpdateRepo {
|
||||
pub id: String,
|
||||
pub config: PartialRepoConfig,
|
||||
}
|
||||
@@ -4,24 +4,6 @@ use typeshare::typeshare;
|
||||
|
||||
use crate::I64;
|
||||
|
||||
mod server;
|
||||
pub use server::*;
|
||||
|
||||
mod deployment;
|
||||
pub use deployment::*;
|
||||
|
||||
mod build;
|
||||
pub use build::*;
|
||||
|
||||
mod builder;
|
||||
pub use builder::*;
|
||||
|
||||
mod permissions;
|
||||
pub use permissions::*;
|
||||
|
||||
mod repo;
|
||||
pub use repo::*;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
@@ -46,14 +28,3 @@ pub struct CreateLoginSecretResponse {
|
||||
pub struct DeleteLoginSecret {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(GetVersionResponse)]
|
||||
pub struct GetVersion {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GetVersionResponse {
|
||||
pub version: String,
|
||||
}
|
||||
47
lib/types/src/requests/write/server.rs
Normal file
47
lib/types/src/requests/write/server.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{
|
||||
server::{PartialServerConfig, Server},
|
||||
update::Update,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Server)]
|
||||
pub struct CreateServer {
|
||||
pub name: String,
|
||||
pub config: PartialServerConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Server)]
|
||||
pub struct DeleteServer {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Server)]
|
||||
pub struct UpdateServer {
|
||||
pub id: String,
|
||||
pub config: PartialServerConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
|
||||
#[response(Update)]
|
||||
pub struct RenameServer {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use monitor_client::MonitorClient;
|
||||
use monitor_types::requests::api;
|
||||
use monitor_types::requests::write;
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn tests() -> anyhow::Result<()> {
|
||||
@@ -7,7 +7,7 @@ pub async fn tests() -> anyhow::Result<()> {
|
||||
let monitor = MonitorClient::new_from_env().await?;
|
||||
|
||||
let secret = monitor
|
||||
.api(api::CreateLoginSecret {
|
||||
.write(write::CreateLoginSecret {
|
||||
name: String::from("tests"),
|
||||
expires: None,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user