From 80438e7a74813bab3e49a73937761688819c874a Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Mon, 9 Jan 2023 01:34:15 +0000 Subject: [PATCH] run arbitrary command on periphery --- lib/db_client/src/collections.rs | 13 +++++- lib/db_client/src/lib.rs | 13 ++++-- lib/periphery_client/src/command.rs | 12 ++++++ lib/periphery_client/src/lib.rs | 1 + lib/types/src/action.rs | 63 +++++++++++++++++++++++++++++ lib/types/src/lib.rs | 4 ++ lib/types/src/procedure.rs | 10 ++++- periphery/src/api/command.rs | 19 +++++++++ periphery/src/api/mod.rs | 2 + 9 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 lib/periphery_client/src/command.rs create mode 100644 lib/types/src/action.rs create mode 100644 periphery/src/api/command.rs diff --git a/lib/db_client/src/collections.rs b/lib/db_client/src/collections.rs index ef7a0f01e..27896bb3f 100644 --- a/lib/db_client/src/collections.rs +++ b/lib/db_client/src/collections.rs @@ -1,6 +1,6 @@ use anyhow::Context; use mungos::{Collection, Mungos}; -use types::{Build, Deployment, Group, Procedure, Server, SystemStatsRecord, Update, User}; +use types::{Action, Build, Deployment, Group, Procedure, Server, SystemStatsRecord, Update, User}; pub async fn users_collection(mungos: &Mungos, db_name: &str) -> anyhow::Result> { let coll = mungos.collection(db_name, "users"); @@ -92,6 +92,17 @@ pub async fn procedures_collection( Ok(coll) } +pub async fn actions_collection( + mungos: &Mungos, + db_name: &str, +) -> anyhow::Result> { + let coll = mungos.collection(db_name, "actions"); + coll.create_unique_index("name") + .await + .context("failed at creating entity_id index")?; + Ok(coll) +} + pub async fn groups_collection( mungos: &Mungos, db_name: &str, diff --git a/lib/db_client/src/lib.rs b/lib/db_client/src/lib.rs index dc5656fac..c96e9c5b5 100644 --- a/lib/db_client/src/lib.rs +++ b/lib/db_client/src/lib.rs @@ -2,13 +2,14 @@ use std::time::Duration; use anyhow::{anyhow, Context}; use collections::{ - builds_collection, deployments_collection, groups_collection, procedures_collection, - server_stats_collection, servers_collection, updates_collection, users_collection, + actions_collection, builds_collection, deployments_collection, groups_collection, + procedures_collection, server_stats_collection, servers_collection, updates_collection, + users_collection, }; use mungos::{Collection, Mungos}; use types::{ - Build, Deployment, Group, MongoConfig, PermissionLevel, Procedure, Server, SystemStatsRecord, - Update, User, + Action, Build, Deployment, Group, MongoConfig, PermissionLevel, Procedure, Server, + SystemStatsRecord, Update, User, }; mod collections; @@ -19,6 +20,7 @@ pub struct DbClient { pub deployments: Collection, pub builds: Collection, pub procedures: Collection, + pub actions: Collection, pub groups: Collection, pub updates: Collection, pub stats: Collection, @@ -49,6 +51,9 @@ impl DbClient { procedures: procedures_collection(&mungos, db_name) .await .expect("failed to make procedures collection"), + actions: actions_collection(&mungos, db_name) + .await + .expect("failed to make actions collection"), groups: groups_collection(&mungos, db_name) .await .expect("failed to make groups collection"), diff --git a/lib/periphery_client/src/command.rs b/lib/periphery_client/src/command.rs new file mode 100644 index 000000000..074b4127b --- /dev/null +++ b/lib/periphery_client/src/command.rs @@ -0,0 +1,12 @@ +use anyhow::Context; +use types::{Command, Log, Server}; + +use crate::PeripheryClient; + +impl PeripheryClient { + pub async fn run_command(&self, server: &Server, command: &Command) -> anyhow::Result { + self.post_json(server, &format!("/command"), command) + .await + .context("failed to run command on periphery") + } +} diff --git a/lib/periphery_client/src/lib.rs b/lib/periphery_client/src/lib.rs index 79405163c..54bfd7d4c 100644 --- a/lib/periphery_client/src/lib.rs +++ b/lib/periphery_client/src/lib.rs @@ -6,6 +6,7 @@ use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use types::{Server, SystemStats, SystemStatsQuery}; mod build; +mod command; mod container; mod git; mod image; diff --git a/lib/types/src/action.rs b/lib/types/src/action.rs new file mode 100644 index 000000000..bb5b04748 --- /dev/null +++ b/lib/types/src/action.rs @@ -0,0 +1,63 @@ +use bson::serde_helpers::hex_string_as_object_id; +use derive_builder::Builder; +use diff::Diff; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::{diff::*, Command}; + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Default, Diff, Builder)] +#[diff(attr(#[derive(Debug, Serialize)]))] +pub struct Action { + #[serde( + default, + rename = "_id", + skip_serializing_if = "String::is_empty", + with = "hex_string_as_object_id" + )] + #[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))] + #[builder(setter(skip))] + pub id: String, + + #[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))] + pub name: String, + + #[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))] + pub path: String, + + #[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))] + pub command: String, + + // run action on all servers in this array + #[serde(default)] + #[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))] + pub server_ids: Vec, + + // run action on all servers in the group + #[serde(default)] + #[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))] + pub group_ids: Vec, + + #[serde(default)] + #[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))] + pub run_on_all: bool, + + #[serde(default, skip_serializing_if = "String::is_empty")] + #[diff(attr(#[serde(skip)]))] + #[builder(setter(skip))] + pub created_at: String, + #[serde(default)] + #[diff(attr(#[serde(skip)]))] + #[builder(setter(skip))] + pub updated_at: String, +} + +impl From for Command { + fn from(value: Action) -> Command { + Command { + path: value.path, + command: value.command, + } + } +} diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index e34857088..92edad2ee 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -11,6 +11,7 @@ pub use bollard::service::{ImageSummary, Network}; pub mod traits; +mod action; mod build; mod config; mod deployment; @@ -21,6 +22,7 @@ mod server; mod update; mod user; +pub use action::*; pub use build::*; pub use config::*; pub use deployment::*; @@ -40,7 +42,9 @@ pub type PermissionsMap = HashMap; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Diff)] #[diff(attr(#[derive(Debug, PartialEq, Serialize)]))] pub struct Command { + #[serde(default)] pub path: String, + #[serde(default)] pub command: String, } diff --git a/lib/types/src/procedure.rs b/lib/types/src/procedure.rs index dce3505e6..1eb1efe1a 100644 --- a/lib/types/src/procedure.rs +++ b/lib/types/src/procedure.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString}; use typeshare::typeshare; -use crate::PermissionsMap; +use crate::{diff::*, PermissionsMap}; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Default, Diff, Builder)] @@ -19,8 +19,16 @@ pub struct Procedure { )] #[builder(setter(skip))] pub id: String, + + #[diff(attr(#[serde(skip_serializing_if = "Option::is_none")]))] pub name: String, + + #[serde(default)] + #[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))] pub stages: Vec, + + #[serde(default)] + #[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))] pub webhook_branches: Vec, #[serde(default)] diff --git a/periphery/src/api/command.rs b/periphery/src/api/command.rs new file mode 100644 index 000000000..8615c151f --- /dev/null +++ b/periphery/src/api/command.rs @@ -0,0 +1,19 @@ +use axum::{routing::post, Json, Router}; +use helpers::run_monitor_command; +use types::Command; + +pub fn router() -> Router { + Router::new().route( + "/", + post(|Json(Command { path, command })| async move { + let command = if path.is_empty() { + command + } else { + let path = path.replace("~", &std::env::var("HOME").unwrap()); + format!("cd {path} && {command}") + }; + let log = run_monitor_command("run command", command).await; + Json(log) + }), + ) +} diff --git a/periphery/src/api/mod.rs b/periphery/src/api/mod.rs index 37591bf0a..a8856792a 100644 --- a/periphery/src/api/mod.rs +++ b/periphery/src/api/mod.rs @@ -17,6 +17,7 @@ use crate::PeripheryConfigExtension; mod accounts; mod build; +mod command; mod container; mod git; mod image; @@ -27,6 +28,7 @@ pub fn router(config: PeripheryConfigExtension) -> Router { Router::new() .route("/health", get(|| async {})) .route("/accounts/:account_type", get(accounts::get_accounts)) + .nest("/command", command::router()) .nest("/container", container::router()) .nest("/network", network::router()) .nest(