run arbitrary command on periphery

This commit is contained in:
mbecker20
2023-01-09 01:34:15 +00:00
parent 8453f9ac5f
commit 80438e7a74
9 changed files with 131 additions and 6 deletions

View File

@@ -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<Collection<User>> {
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<Collection<Action>> {
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,

View File

@@ -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<Deployment>,
pub builds: Collection<Build>,
pub procedures: Collection<Procedure>,
pub actions: Collection<Action>,
pub groups: Collection<Group>,
pub updates: Collection<Update>,
pub stats: Collection<SystemStatsRecord>,
@@ -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"),

View File

@@ -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<Log> {
self.post_json(server, &format!("/command"), command)
.await
.context("failed to run command on periphery")
}
}

View File

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

63
lib/types/src/action.rs Normal file
View File

@@ -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<String>,
// run action on all servers in the group
#[serde(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub group_ids: Vec<String>,
#[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<Action> for Command {
fn from(value: Action) -> Command {
Command {
path: value.path,
command: value.command,
}
}
}

View File

@@ -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<String, PermissionLevel>;
#[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,
}

View File

@@ -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<ProcedureStage>,
#[serde(default)]
#[diff(attr(#[serde(skip_serializing_if = "vec_diff_no_change")]))]
pub webhook_branches: Vec<String>,
#[serde(default)]

View File

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

View File

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