From 9c09906bdd2bd73e6598736b24486a307d3925b0 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Thu, 22 Dec 2022 07:05:18 +0000 Subject: [PATCH] implement get updates list --- core/src/api/server.rs | 29 ++++- core/src/api/update.rs | 158 ++++++++++++++++++++++++++- frontend/src/state/hooks.ts | 2 +- frontend/src/util/client.ts | 21 +++- lib/monitor_client/src/build.rs | 2 +- lib/monitor_client/src/deployment.rs | 2 +- lib/monitor_client/src/lib.rs | 1 + lib/monitor_client/src/procedure.rs | 2 +- lib/monitor_client/src/server.rs | 7 +- lib/monitor_client/src/update.rs | 30 +++++ lib/types/src/lib.rs | 52 ++++++++- tests/src/main.rs | 8 +- tests/src/tests.rs | 23 ++-- 13 files changed, 304 insertions(+), 33 deletions(-) create mode 100644 lib/monitor_client/src/update.rs diff --git a/core/src/api/server.rs b/core/src/api/server.rs index 834740ac2..c96d211b2 100644 --- a/core/src/api/server.rs +++ b/core/src/api/server.rs @@ -4,11 +4,12 @@ use axum::{ routing::{delete, get, patch, post}, Extension, Json, Router, }; +use futures_util::future::join_all; use helpers::handle_anyhow_error; use mungos::{Deserialize, Document, Serialize}; use types::{ traits::Permissioned, BasicContainerInfo, ImageSummary, Network, PermissionLevel, Server, - ServerActionState, SystemStats, + ServerActionState, ServerStatus, ServerWithStatus, SystemStats, }; use typeshare::typeshare; @@ -235,8 +236,8 @@ impl State { &self, user: &RequestUser, query: impl Into>, - ) -> anyhow::Result> { - let mut servers: Vec = self + ) -> anyhow::Result> { + let futures = self .db .servers .get_some(query, None) @@ -251,8 +252,26 @@ impl State { permissions != PermissionLevel::None } }) - .collect(); - servers.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); + .map(|server| async { + let status = if server.enabled { + let res = self.periphery.health_check(&server).await; + match res { + Ok(_) => ServerStatus::Ok, + Err(_) => ServerStatus::NotOk, + } + } else { + ServerStatus::Disabled + }; + + ServerWithStatus { server, status } + }); + let mut servers: Vec = join_all(futures).await; + servers.sort_by(|a, b| { + a.server + .name + .to_lowercase() + .cmp(&b.server.name.to_lowercase()) + }); Ok(servers) } diff --git a/core/src/api/update.rs b/core/src/api/update.rs index 4e38923e2..1ee1fdf34 100644 --- a/core/src/api/update.rs +++ b/core/src/api/update.rs @@ -1,5 +1,159 @@ -use axum::Router; +use std::str::FromStr; + +use anyhow::{anyhow, Context}; +use axum::{extract::Query, routing::get, Extension, Json, Router}; +use helpers::handle_anyhow_error; +use mungos::{doc, to_bson, ObjectId}; +use serde_json::Value; +use types::{PermissionLevel, Update, UpdateTarget}; + +use crate::{ + auth::{RequestUser, RequestUserExtension}, + response, + state::{State, StateExtension}, +}; + +const NUM_UPDATES_PER_PAGE: usize = 20; pub fn router() -> Router { - Router::new() + Router::new().route( + "/list", + get( + |Extension(state): StateExtension, + Extension(user): RequestUserExtension, + Query(value): Query| async move { + let offset = value + .get("offset") + .map(|v| v.as_u64().unwrap_or(0)) + .unwrap_or(0); + let target = serde_json::from_str::(&value.to_string()).ok(); + let updates = state + .list_updates(target, offset, &user) + .await + .map_err(handle_anyhow_error)?; + response!(Json(updates)) + }, + ), + ) +} + +impl State { + async fn permission_on_update_target( + &self, + update_target: &UpdateTarget, + user: &RequestUser, + ) -> anyhow::Result<()> { + if user.is_admin { + Ok(()) + } else { + match update_target { + UpdateTarget::System => { + if user.is_admin { + Ok(()) + } else { + Err(anyhow!("user must be admin to see system updates")) + } + } + UpdateTarget::Build(id) => self + .get_build_check_permissions(id, user, PermissionLevel::Read) + .await + .map(|_| ()), + UpdateTarget::Deployment(id) => self + .get_deployment_check_permissions(id, user, PermissionLevel::Read) + .await + .map(|_| ()), + UpdateTarget::Server(id) => self + .get_server_check_permissions(id, user, PermissionLevel::Read) + .await + .map(|_| ()), + UpdateTarget::Procedure(id) => self + .get_procedure_check_permissions(id, user, PermissionLevel::Read) + .await + .map(|_| ()), + } + } + } + + pub async fn list_updates( + &self, + target: Option, + offset: u64, + user: &RequestUser, + ) -> anyhow::Result> { + let filter = match target { + Some(target) => { + self.permission_on_update_target(&target, user).await?; + Some(doc! { + "target": to_bson(&target).unwrap() + }) + } + None => { + if user.is_admin { + None + } else { + let permissions_field = format!("permissions.{}", user.id); + let target_filter = doc! { + "$or": [ + { &permissions_field: "update" }, + { &permissions_field: "execute" }, + { &permissions_field: "read" }, + ] + }; + let build_ids = self + .db + .builds + .get_some(target_filter.clone(), None) + .await + .context("failed at query to get users builds")? + .into_iter() + .map(|e| ObjectId::from_str(&e.id).unwrap()) + .collect::>(); + let deployment_ids = self + .db + .deployments + .get_some(target_filter.clone(), None) + .await + .context("failed at query to get users deployments")? + .into_iter() + .map(|e| ObjectId::from_str(&e.id).unwrap()) + .collect::>(); + let server_ids = self + .db + .servers + .get_some(target_filter.clone(), None) + .await + .context("failed at query to get users servers")? + .into_iter() + .map(|e| ObjectId::from_str(&e.id).unwrap()) + .collect::>(); + let procedure_ids = self + .db + .procedures + .get_some(target_filter, None) + .await + .context("failed at query to get users procedures")? + .into_iter() + .map(|e| ObjectId::from_str(&e.id).unwrap()) + .collect::>(); + let filter = doc! { + "$or": [ + { "target.type": "Build", "target.id": { "$in": &build_ids } }, + { "target.type": "Deployment", "target.id": { "$in": &deployment_ids } }, + { "target.type": "Server", "target.id": { "$in": &server_ids } }, + { "target.type": "Procedure", "target.id": { "$in": &procedure_ids } } + ] + }; + Some(filter) + } + } + }; + let mut updates = self + .db + .updates + .get_most_recent(NUM_UPDATES_PER_PAGE as i64, offset, filter, None) + .await + .context("mongo get most recent updates query failed")?; + updates.reverse(); + Ok(updates) + } } diff --git a/frontend/src/state/hooks.ts b/frontend/src/state/hooks.ts index e27321c97..7c958b07c 100644 --- a/frontend/src/state/hooks.ts +++ b/frontend/src/state/hooks.ts @@ -85,7 +85,7 @@ export function useServerStats() { if ( stat === undefined && !loading[serverID] && - (serverStatus ? serverStatus === "ok" : true) + (serverStatus ? serverStatus === ServerStatus.Ok : true) ) { loading[serverID] = true; load(serverID); diff --git a/frontend/src/util/client.ts b/frontend/src/util/client.ts index be3df15dc..b4cc5134b 100644 --- a/frontend/src/util/client.ts +++ b/frontend/src/util/client.ts @@ -9,8 +9,10 @@ import { Log, Server, ServerActionState, + ServerWithStatus, SystemStats, Update, + UpdateTarget, User, UserCredentials, } from "../types"; @@ -124,7 +126,7 @@ export class Client { // server - list_servers(query?: QueryObject): Promise { + list_servers(query?: QueryObject): Promise { return this.get("/api/server/list" + generateQuery(query)); } @@ -202,10 +204,7 @@ export class Client { return this.post("/api/build/create_full", build); } - copy_build( - target_id: string, - body: CopyBuildBody - ): Promise { + copy_build(target_id: string, body: CopyBuildBody): Promise { return this.post(`/api/build/${target_id}/copy`, body); } @@ -225,6 +224,18 @@ export class Client { return this.post(`/api/build/${id}/reclone`); } + // updates + + list_updates(offset: number, target?: UpdateTarget): Promise { + return this.get( + `/api/update/list${generateQuery({ + offset, + type: target && target.type, + id: target && target.id, + })}` + ); + } + // api secrets create_api_secret(body: CreateSecretBody): Promise { diff --git a/lib/monitor_client/src/build.rs b/lib/monitor_client/src/build.rs index eeae48d05..455cf5a72 100644 --- a/lib/monitor_client/src/build.rs +++ b/lib/monitor_client/src/build.rs @@ -36,7 +36,7 @@ impl MonitorClient { } pub async fn create_full_build(&self, build: &Build) -> anyhow::Result { - self.post("/api/build/create_full", build) + self.post::<&Build, _>("/api/build/create_full", build) .await .context(format!("failed at creating full build")) } diff --git a/lib/monitor_client/src/deployment.rs b/lib/monitor_client/src/deployment.rs index f42a2c107..a729332e9 100644 --- a/lib/monitor_client/src/deployment.rs +++ b/lib/monitor_client/src/deployment.rs @@ -56,7 +56,7 @@ impl MonitorClient { &self, deployment: &Deployment, ) -> anyhow::Result { - self.post("/api/deployment/create_full", deployment) + self.post::<&Deployment, _>("/api/deployment/create_full", deployment) .await .context(format!("failed at creating full deployment")) } diff --git a/lib/monitor_client/src/lib.rs b/lib/monitor_client/src/lib.rs index f651e1867..441022a89 100644 --- a/lib/monitor_client/src/lib.rs +++ b/lib/monitor_client/src/lib.rs @@ -13,6 +13,7 @@ mod permissions; mod procedure; mod secret; mod server; +mod update; #[derive(Clone)] pub struct MonitorClient { diff --git a/lib/monitor_client/src/procedure.rs b/lib/monitor_client/src/procedure.rs index f0d52c2cb..310faffdd 100644 --- a/lib/monitor_client/src/procedure.rs +++ b/lib/monitor_client/src/procedure.rs @@ -29,7 +29,7 @@ impl MonitorClient { } pub async fn create_full_procedure(&self, procedure: &Procedure) -> anyhow::Result { - self.post("/api/procedure/create_full", procedure) + self.post::<&Procedure, _>("/api/procedure/create_full", procedure) .await .context(format!("failed at creating full procedure")) } diff --git a/lib/monitor_client/src/server.rs b/lib/monitor_client/src/server.rs index 6bb882ab6..f9238b8b0 100644 --- a/lib/monitor_client/src/server.rs +++ b/lib/monitor_client/src/server.rs @@ -1,6 +1,7 @@ use anyhow::Context; use monitor_types::{ - BasicContainerInfo, ImageSummary, Log, Network, Server, ServerActionState, SystemStats, + BasicContainerInfo, ImageSummary, Log, Network, Server, ServerActionState, ServerWithStatus, + SystemStats, }; use serde_json::{json, Value}; @@ -10,7 +11,7 @@ impl MonitorClient { pub async fn list_servers( &self, query: impl Into>, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.get("/api/server/list", query.into()) .await .context("failed at list servers") @@ -44,7 +45,7 @@ impl MonitorClient { } pub async fn create_full_server(&self, server: &Server) -> anyhow::Result { - self.post("/api/server/create_full", server) + self.post::<&Server, _>("/api/server/create_full", server) .await .context(format!("failed at creating full server")) } diff --git a/lib/monitor_client/src/update.rs b/lib/monitor_client/src/update.rs new file mode 100644 index 000000000..6053b48ac --- /dev/null +++ b/lib/monitor_client/src/update.rs @@ -0,0 +1,30 @@ +use monitor_types::{Update, UpdateTarget}; +use serde_json::{json, Value}; + +use crate::MonitorClient; + +impl MonitorClient { + pub async fn list_updates( + &self, + target: impl Into>, + offset: u64, + ) -> anyhow::Result> { + let mut query = json!({ "offset": offset }); + if let Some(target) = target.into() { + let mut value = + serde_json::from_str::(&serde_json::to_string(&target).unwrap()).unwrap(); + let value = value.as_object_mut().unwrap(); + query + .as_object_mut() + .unwrap() + .insert("type".to_string(), value.remove("type").unwrap()); + if let Some(target_id) = value.remove("id") { + query + .as_object_mut() + .unwrap() + .insert("id".to_string(), target_id); + } + } + self.get("/api/update/list", query).await + } +} diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index ead5a531e..2ad3f4a99 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -194,8 +194,6 @@ fn default_disk_alert() -> f64 { 75.0 } - - #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct ServerActionState { @@ -709,6 +707,54 @@ impl Default for UpdateTarget { } } +impl From<&Build> for UpdateTarget { + fn from(build: &Build) -> Self { + Self::Build(build.id.clone()) + } +} + +impl From<&Build> for Option { + fn from(build: &Build) -> Self { + Some(UpdateTarget::Build(build.id.clone())) + } +} + +impl From<&Deployment> for UpdateTarget { + fn from(deployment: &Deployment) -> Self { + Self::Deployment(deployment.id.clone()) + } +} + +impl From<&Deployment> for Option { + fn from(deployment: &Deployment) -> Self { + Some(UpdateTarget::Deployment(deployment.id.clone())) + } +} + +impl From<&Server> for UpdateTarget { + fn from(server: &Server) -> Self { + Self::Server(server.id.clone()) + } +} + +impl From<&Server> for Option { + fn from(server: &Server) -> Self { + Some(UpdateTarget::Server(server.id.clone())) + } +} + +impl From<&Procedure> for UpdateTarget { + fn from(procedure: &Procedure) -> Self { + Self::Procedure(procedure.id.clone()) + } +} + +impl From<&Procedure> for Option { + fn from(procedure: &Procedure) -> Self { + Some(UpdateTarget::Procedure(procedure.id.clone())) + } +} + #[typeshare] #[derive(Serialize, Deserialize, Debug, Display, EnumString, PartialEq, Hash, Eq, Clone, Copy)] #[serde(rename_all = "snake_case")] @@ -867,7 +913,7 @@ pub enum PermissionsTarget { pub enum ServerStatus { Ok, NotOk, - Disabled + Disabled, } #[typeshare] diff --git a/tests/src/main.rs b/tests/src/main.rs index 890e62c93..2fd0fd50f 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -17,10 +17,10 @@ async fn main() -> anyhow::Result<()> { let start_ts = unix_timestamp_ms(); - let (server, deployment, build) = create_test_setup(&monitor, "test").await?; + // let (server, deployment, build) = create_test_setup(&monitor, "test").await?; - let server_stats = get_server_stats(&monitor).await?; - println!("server stats:\n{server_stats:#?}\n"); + // let server_stats = get_server_stats(&monitor).await?; + // println!("server stats:\n{server_stats:#?}\n"); // let (update, container) = deploy_mongo(&monitor).await?; // println!( @@ -31,6 +31,8 @@ async fn main() -> anyhow::Result<()> { // let update = test_build(&monitor).await?; // println!("build update:\n{update:#?}"); + test_updates(&monitor).await.unwrap(); + let end_ts = unix_timestamp_ms(); let finished_in = (end_ts - start_ts) as f64 / 1000.0; println!("\nfinished in {finished_in} s"); diff --git a/tests/src/tests.rs b/tests/src/tests.rs index a316c5ef4..9bd77ae3d 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -14,14 +14,11 @@ pub async fn create_test_setup( let mut servers = monitor.list_servers(None).await?; let server = if servers.is_empty() { monitor - .create_server( - &format!("{group_name}_server"), - "http://localhost:8000", - ) + .create_server(&format!("{group_name}_server"), "http://localhost:8000") .await .context("failed at create server")? } else { - servers.pop().unwrap() + servers.pop().unwrap().server }; let mut deployments = monitor.list_deployments(None).await?; let deployment = if deployments.is_empty() { @@ -49,7 +46,7 @@ pub async fn get_server_stats(monitor: &MonitorClient) -> anyhow::Result anyhow::Result { .list_servers(None) .await .context("failed at list servers")?; - let server = servers.get(0).ok_or(anyhow!("no servers"))?; + let server = &servers.get(0).ok_or(anyhow!("no servers"))?.server; let mut build = monitor.create_build("old_periphery", &server.id).await?; println!("created build. updating..."); build.repo = Some("mbecker20/monitor".to_string()); @@ -107,3 +104,13 @@ pub async fn test_build(monitor: &MonitorClient) -> anyhow::Result { let update = monitor.build(&build.id).await?; Ok(update) } + +pub async fn test_updates(monitor: &MonitorClient) -> anyhow::Result<()> { + let updates = monitor.list_updates(None, 0).await?; + println!("ALL UPDATES: {updates:#?}"); + let builds = monitor.list_builds(None).await?; + let build = builds.get(0).unwrap(); + let build_updates = monitor.list_updates(build, 0).await?; + println!("{build_updates:#?}"); + Ok(()) +}