diff --git a/Cargo.lock b/Cargo.lock index f7b990613..5e6d98e97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -650,9 +650,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -660,9 +660,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" @@ -677,15 +677,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -694,21 +694,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -794,6 +794,8 @@ dependencies = [ "async_timing_util", "axum", "bollard", + "futures", + "futures-util", "monitor_types", "rand", "run_command", diff --git a/lib/helpers/Cargo.toml b/lib/helpers/Cargo.toml index c601be536..2d4b602ef 100644 --- a/lib/helpers/Cargo.toml +++ b/lib/helpers/Cargo.toml @@ -10,10 +10,12 @@ types = { package = "monitor_types", path = "../types" } async_timing_util = "0.1.11" bollard = "0.13" anyhow = "1.0" -axum = "0.6" +axum = { version = "0.6", features = ["ws", "json"] } serde = "1.0" serde_derive = "1.0" serde_json = "1.0" toml = "0.5" run_command = { version = "0.0.5", features = ["async_tokio"] } -rand = "0.8" \ No newline at end of file +rand = "0.8" +futures = "0.3" +futures-util = "0.3.25" \ No newline at end of file diff --git a/lib/helpers/src/docker/client.rs b/lib/helpers/src/docker/client.rs index ae4f7be2b..de1f0aff8 100644 --- a/lib/helpers/src/docker/client.rs +++ b/lib/helpers/src/docker/client.rs @@ -2,8 +2,15 @@ use std::sync::Arc; use anyhow::anyhow; use axum::Extension; -use bollard::{container::ListContainersOptions, Docker}; -use types::BasicContainerInfo; +use bollard::{ + container::{ListContainersOptions, StatsOptions}, + Docker, +}; +use futures::{future::join_all, Stream}; +use futures_util::stream::StreamExt; +use types::{BasicContainerInfo, DockerContainerState}; + +pub use bollard::container::Stats; pub type DockerExtension = Extension>; @@ -46,4 +53,65 @@ impl DockerClient { .collect::>>()?; Ok(res) } + + pub fn container_stats_stream( + &self, + container_name: &str, + ) -> impl Stream> { + self.docker.stats( + container_name, + Some(StatsOptions { + stream: true, + ..Default::default() + }), + ) + } + + pub async fn get_container_stats(&self, container_name: &str) -> anyhow::Result { + let mut stats = self + .docker + .stats( + container_name, + Some(StatsOptions { + stream: false, + ..Default::default() + }), + ) + .take(1) + .next() + .await + .ok_or(anyhow!("got no stats for {container_name}"))??; + stats.name = stats.name.replace("/", ""); + Ok(stats) + } + + pub async fn get_container_stats_list(&self) -> anyhow::Result> { + let futures = self + .list_containers() + .await? + .into_iter() + .filter(|c| c.state == DockerContainerState::Running) + .map(|c| async move { + let mut stats = self + .docker + .stats( + &c.name, + Some(StatsOptions { + stream: false, + ..Default::default() + }), + ) + .take(1) + .next() + .await + .ok_or(anyhow!("got no stats for {}", c.name))??; + stats.name = stats.name.replace("/", ""); + Ok::<_, anyhow::Error>(stats) + }); + let stats = join_all(futures) + .await + .into_iter() + .collect::>()?; + Ok(stats) + } } diff --git a/lib/helpers/src/docker/container.rs b/lib/helpers/src/docker/container.rs index a598f892f..7bc43930c 100644 --- a/lib/helpers/src/docker/container.rs +++ b/lib/helpers/src/docker/container.rs @@ -15,9 +15,16 @@ pub async fn container_log(container_name: &str, tail: Option) -> Log { run_monitor_command("get container log", command).await } -pub async fn container_stats() -> anyhow::Result> { - let command = "docker stats --no-stream --format \"{{json .}}\""; - let output = async_run_command(command).await; +pub async fn container_stats( + container_name: Option, +) -> anyhow::Result> { + let format = "--format \"{{ json . }}\""; + let container_name = match container_name { + Some(name) => format!(" {name}"), + None => "".to_string(), + }; + let command = format!("docker stats{container_name} --no-stream {format}"); + let output = async_run_command(&command).await; if output.success() { let res = output .stdout @@ -31,7 +38,7 @@ pub async fn container_stats() -> anyhow::Result> { .collect::>>()?; Ok(res) } else { - Err(anyhow!("failed to get docker logs")) + Err(anyhow!("{}", output.stderr.replace("\n", ""))) } } diff --git a/lib/monitor_client/src/build.rs b/lib/monitor_client/src/build.rs index 8b1378917..dda684fd3 100644 --- a/lib/monitor_client/src/build.rs +++ b/lib/monitor_client/src/build.rs @@ -1 +1,23 @@ +use monitor_types::{Build, SystemStats}; +use serde_json::json; +use crate::MonitorClient; + +impl MonitorClient { + pub async fn list_builds(&self) -> anyhow::Result> { + self.get("/api/build/list").await + } + + pub async fn create_build(&self, name: &str, address: &str) -> anyhow::Result<()> { + self.post( + "/api/build/create", + json!({ "name": name, "address": address }), + ) + .await + } + + pub async fn delete_build(&self, id: &str) -> anyhow::Result<()> { + self.delete::<(), _>(&format!("/api/build/delete/{id}"), None) + .await + } +} diff --git a/lib/monitor_client/src/deployment.rs b/lib/monitor_client/src/deployment.rs index 8b1378917..1a367b676 100644 --- a/lib/monitor_client/src/deployment.rs +++ b/lib/monitor_client/src/deployment.rs @@ -1 +1,23 @@ +use monitor_types::{Deployment, SystemStats}; +use serde_json::json; +use crate::MonitorClient; + +impl MonitorClient { + pub async fn list_deployments(&self) -> anyhow::Result> { + self.get("/api/deployment/list").await + } + + pub async fn create_deployment(&self, name: &str, address: &str) -> anyhow::Result<()> { + self.post( + "/api/deployment/create", + json!({ "name": name, "address": address }), + ) + .await + } + + pub async fn delete_deployment(&self, id: &str) -> anyhow::Result<()> { + self.delete::<(), _>(&format!("/api/deployment/delete/{id}"), None) + .await + } +} diff --git a/lib/periphery_client/src/container.rs b/lib/periphery_client/src/container.rs index 2cee56a9c..75580a215 100644 --- a/lib/periphery_client/src/container.rs +++ b/lib/periphery_client/src/container.rs @@ -1,5 +1,5 @@ use serde_json::json; -use types::{BasicContainerInfo, Deployment, Log, Server}; +use types::{BasicContainerInfo, Deployment, DockerContainerStats, Log, Server}; use crate::PeripheryClient; @@ -53,6 +53,22 @@ impl PeripheryClient { } pub async fn container_prune(&self, server: &Server) -> anyhow::Result { - self.post_json(server, "container/prune", &json!({})).await + self.post_json(server, "/container/prune", &json!({})).await + } + + pub async fn container_stats( + &self, + server: &Server, + container_name: &str, + ) -> anyhow::Result> { + self.get_json(server, &format!("/container/stats/{container_name}")) + .await + } + + pub async fn container_stats_list( + &self, + server: &Server, + ) -> anyhow::Result> { + self.get_json(server, "/container/stats/list").await } } diff --git a/periphery/src/api/container.rs b/periphery/src/api/container.rs index 0ab9c4585..0f9f19de4 100644 --- a/periphery/src/api/container.rs +++ b/periphery/src/api/container.rs @@ -1,6 +1,6 @@ +use anyhow::anyhow; use axum::{ extract::Path, - http::StatusCode, routing::{get, post}, Extension, Json, Router, }; @@ -27,6 +27,51 @@ pub fn router() -> Router { response!(Json(containers)) }), ) + // .route( + // "/stats/:name", + // get( + // |Extension(dc): DockerExtension, Path(Container { name }): Path| async move { + // let stats = dc + // .get_container_stats(&name) + // .await + // .map_err(handle_anyhow_error)?; + // response!(Json(stats)) + // }, + // ), + // ) + // .route( + // "/stats/list", + // get( + // |Extension(dc): DockerExtension| async move { + // let stats_list = dc + // .get_container_stats_list() + // .await + // .map_err(handle_anyhow_error)?; + // response!(Json(stats_list)) + // }, + // ), + // ) + .route( + "/stats/:name", + get(|Path(c): Path| async move { + let stats = docker::container_stats(Some(c.name.clone())) + .await + .map_err(handle_anyhow_error)? + .pop() + .ok_or(anyhow!("no stats for container {}", c.name)) + .map_err(handle_anyhow_error)?; + response!(Json(stats)) + }), + ) + .route( + "/stats/list", + get(|| async { + let stats = docker::container_stats(None) + .await + .map_err(handle_anyhow_error)?; + response!(Json(stats)) + }), + ) .route( "/start", post(|Json(container): Json| async move { diff --git a/periphery/src/api/git.rs b/periphery/src/api/git.rs index 11f3ca12e..74f44b5e1 100644 --- a/periphery/src/api/git.rs +++ b/periphery/src/api/git.rs @@ -4,7 +4,7 @@ use helpers::{ git::{self, CloneArgs}, handle_anyhow_error, }; -use types::{Build, Deployment, GithubToken, Log, PeripheryConfig}; +use types::{GithubToken, Log, PeripheryConfig}; use crate::PeripheryConfigExtension; diff --git a/periphery/src/api/stats.rs b/periphery/src/api/stats.rs index 2cb9b035b..d787dc0dd 100644 --- a/periphery/src/api/stats.rs +++ b/periphery/src/api/stats.rs @@ -1,15 +1,11 @@ use std::{ - path::Path, sync::{Arc, RwLock}, }; use axum::{routing::get, Extension, Json, Router}; -use helpers::{docker, handle_anyhow_error}; use sysinfo::{CpuExt, DiskExt, NetworkExt, ProcessExt, ProcessRefreshKind, SystemExt}; use types::{DiskUsage, SingleDiskUsage, SystemNetwork, SystemStats}; -use crate::response; - pub fn router() -> Router { Router::new() .route( @@ -19,15 +15,6 @@ pub fn router() -> Router { Json(stats) }), ) - .route( - "/docker", - get(|| async { - let stats = docker::container_stats() - .await - .map_err(handle_anyhow_error)?; - response!(Json(stats)) - }), - ) .layer(StatsClient::extension()) } diff --git a/periphery/src/config.rs b/periphery/src/config.rs index 77718bac2..6926359a1 100644 --- a/periphery/src/config.rs +++ b/periphery/src/config.rs @@ -1,4 +1,4 @@ -use std::{fs::File, io::Read, sync::Arc}; +use std::sync::Arc; use axum::Extension; use dotenv::dotenv; diff --git a/periphery/src/helpers.rs b/periphery/src/helpers.rs index d684efec0..9fadd9b54 100644 --- a/periphery/src/helpers.rs +++ b/periphery/src/helpers.rs @@ -1,5 +1,3 @@ -use types::PeripheryConfig; - #[macro_export] macro_rules! response { ($x:expr) => { diff --git a/periphery/src/main.rs b/periphery/src/main.rs index 02912fce1..4922d4fff 100644 --- a/periphery/src/main.rs +++ b/periphery/src/main.rs @@ -1,9 +1,9 @@ -#![allow(unused)] +// #![allow(unused)] use std::sync::Arc; use ::helpers::get_socket_addr; -use axum::{extract::Path, http::StatusCode, routing::get, Extension, Json, Router}; +use axum::Extension; use types::PeripheryConfig; mod api;