split api into read, write, execute

This commit is contained in:
mbecker20
2023-07-04 05:34:13 +00:00
parent f16176f298
commit d0b5e3e241
48 changed files with 3197 additions and 2860 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

@@ -1,2 +1,4 @@
pub mod api;
pub mod auth;
pub mod execute;
pub mod read;
pub mod write;

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

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

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

View File

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

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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

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

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

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

View File

@@ -1,2 +1,4 @@
pub mod api;
pub mod auth;
pub mod read;
pub mod write;
pub mod execute;

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

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

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

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

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

View File

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

View File

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

View File

@@ -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};
//

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

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

View File

@@ -24,4 +24,4 @@ pub struct UpdateUserPermissions {
pub enabled: Option<bool>,
pub create_servers: Option<bool>,
pub create_builds: Option<bool>,
}
}

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

View File

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

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

View File

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