implement get updates list

This commit is contained in:
mbecker20
2022-12-22 07:05:18 +00:00
parent 90173a30e7
commit 9c09906bdd
13 changed files with 304 additions and 33 deletions

View File

@@ -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<Option<Document>>,
) -> anyhow::Result<Vec<Server>> {
let mut servers: Vec<Server> = self
) -> anyhow::Result<Vec<ServerWithStatus>> {
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<ServerWithStatus> = join_all(futures).await;
servers.sort_by(|a, b| {
a.server
.name
.to_lowercase()
.cmp(&b.server.name.to_lowercase())
});
Ok(servers)
}

View File

@@ -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<Value>| async move {
let offset = value
.get("offset")
.map(|v| v.as_u64().unwrap_or(0))
.unwrap_or(0);
let target = serde_json::from_str::<UpdateTarget>(&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<UpdateTarget>,
offset: u64,
user: &RequestUser,
) -> anyhow::Result<Vec<Update>> {
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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)
}
}

View File

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

View File

@@ -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<Server[]> {
list_servers(query?: QueryObject): Promise<ServerWithStatus[]> {
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<Build> {
copy_build(target_id: string, body: CopyBuildBody): Promise<Build> {
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<Update[]> {
return this.get(
`/api/update/list${generateQuery({
offset,
type: target && target.type,
id: target && target.id,
})}`
);
}
// api secrets
create_api_secret(body: CreateSecretBody): Promise<string> {

View File

@@ -36,7 +36,7 @@ impl MonitorClient {
}
pub async fn create_full_build(&self, build: &Build) -> anyhow::Result<Build> {
self.post("/api/build/create_full", build)
self.post::<&Build, _>("/api/build/create_full", build)
.await
.context(format!("failed at creating full build"))
}

View File

@@ -56,7 +56,7 @@ impl MonitorClient {
&self,
deployment: &Deployment,
) -> anyhow::Result<Deployment> {
self.post("/api/deployment/create_full", deployment)
self.post::<&Deployment, _>("/api/deployment/create_full", deployment)
.await
.context(format!("failed at creating full deployment"))
}

View File

@@ -13,6 +13,7 @@ mod permissions;
mod procedure;
mod secret;
mod server;
mod update;
#[derive(Clone)]
pub struct MonitorClient {

View File

@@ -29,7 +29,7 @@ impl MonitorClient {
}
pub async fn create_full_procedure(&self, procedure: &Procedure) -> anyhow::Result<Procedure> {
self.post("/api/procedure/create_full", procedure)
self.post::<&Procedure, _>("/api/procedure/create_full", procedure)
.await
.context(format!("failed at creating full procedure"))
}

View File

@@ -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<Option<Value>>,
) -> anyhow::Result<Vec<Server>> {
) -> anyhow::Result<Vec<ServerWithStatus>> {
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<Server> {
self.post("/api/server/create_full", server)
self.post::<&Server, _>("/api/server/create_full", server)
.await
.context(format!("failed at creating full server"))
}

View File

@@ -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<Option<UpdateTarget>>,
offset: u64,
) -> anyhow::Result<Vec<Update>> {
let mut query = json!({ "offset": offset });
if let Some(target) = target.into() {
let mut value =
serde_json::from_str::<Value>(&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
}
}

View File

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

View File

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

View File

@@ -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<SystemS
.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 stats = monitor
.get_server_stats(&server.id)
.await
@@ -64,7 +61,7 @@ pub async fn deploy_mongo(
.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 deployment = monitor.create_deployment("mongo_test", &server.id).await?;
println!("created deployment");
deployment.docker_run_args.image = "mongo".to_string();
@@ -84,7 +81,7 @@ pub async fn test_build(monitor: &MonitorClient) -> anyhow::Result<Update> {
.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<Update> {
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(())
}