diff --git a/core/src/actions/deployment.rs b/core/src/actions/deployment.rs index ea2ad0720..e811c0637 100644 --- a/core/src/actions/deployment.rs +++ b/core/src/actions/deployment.rs @@ -1,10 +1,12 @@ use anyhow::{anyhow, Context}; use diff::Diff; use helpers::{all_logs_success, to_monitor_name}; +use mungos::doc; use types::{ monitor_timestamp, traits::{Busy, Permissioned}, - Deployment, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget, + Deployment, DeploymentWithContainerState, DockerContainerState, Log, Operation, + PermissionLevel, ServerStatus, ServerWithStatus, Update, UpdateStatus, UpdateTarget, }; use crate::{ @@ -274,6 +276,157 @@ impl State { Ok(new_deployment) } + pub async fn rename_deployment( + &self, + deployment_id: &str, + new_name: &str, + user: &RequestUser, + ) -> anyhow::Result { + if self.deployment_busy(&deployment_id).await { + return Err(anyhow!("deployment busy")); + } + { + let mut lock = self.deployment_action_states.lock().await; + let entry = lock.entry(deployment_id.to_string()).or_default(); + entry.renaming = true; + } + let res = self + .rename_deployment_inner(deployment_id, new_name, user) + .await; + { + let mut lock = self.deployment_action_states.lock().await; + let entry = lock.entry(deployment_id.to_string()).or_default(); + entry.renaming = false; + } + res + } + + async fn rename_deployment_inner( + &self, + deployment_id: &str, + new_name: &str, + user: &RequestUser, + ) -> anyhow::Result { + let start_ts = monitor_timestamp(); + let deployment = self + .get_deployment_check_permissions(deployment_id, user, PermissionLevel::Update) + .await?; + let mut update = Update { + target: UpdateTarget::Deployment(deployment_id.to_string()), + operation: Operation::RenameDeployment, + start_ts, + status: UpdateStatus::InProgress, + operator: user.id.to_string(), + success: true, + ..Default::default() + }; + update.id = self.add_update(update.clone()).await?; + let server_with_status = self.get_server(&deployment.server_id, user).await; + if server_with_status.is_err() { + update.logs.push(Log::error( + "get server", + format!( + "failed to get server info: {:?}", + server_with_status.as_ref().err().unwrap() + ), + )); + update.status = UpdateStatus::Complete; + update.end_ts = monitor_timestamp().into(); + update.success = false; + self.update_update(update).await?; + return Err(server_with_status.err().unwrap()); + } + let ServerWithStatus { server, status } = server_with_status.unwrap(); + if status != ServerStatus::Ok { + update.logs.push(Log::error( + "check server status", + String::from("cannot rename deployment when periphery is disabled or unreachable"), + )); + update.status = UpdateStatus::Complete; + update.end_ts = monitor_timestamp().into(); + update.success = false; + self.update_update(update).await?; + return Err(anyhow!( + "cannot rename deployment when periphery is disabled or unreachable" + )); + } + let deployment_state = self + .get_deployment_with_container_state(user, deployment_id) + .await; + if deployment_state.is_err() { + update.logs.push(Log::error( + "check deployment status", + format!( + "could not get current state of deployment: {:?}", + deployment_state.as_ref().err().unwrap() + ), + )); + update.status = UpdateStatus::Complete; + update.end_ts = monitor_timestamp().into(); + update.success = false; + self.update_update(update).await?; + return Err(deployment_state.err().unwrap()); + } + let DeploymentWithContainerState { state, .. } = deployment_state.unwrap(); + if state != DockerContainerState::NotDeployed { + let log = self + .periphery + .container_rename(&server, &deployment.name, new_name) + .await; + if log.is_err() { + update.logs.push(Log::error( + "rename container", + format!("{:?}", log.as_ref().err().unwrap()), + )); + update.status = UpdateStatus::Complete; + update.end_ts = monitor_timestamp().into(); + update.success = false; + self.update_update(update).await?; + return Err(log.err().unwrap()); + } + let log = log.unwrap(); + if !log.success { + update.logs.push(log); + update.status = UpdateStatus::Complete; + update.end_ts = monitor_timestamp().into(); + update.success = false; + self.update_update(update).await?; + return Err(anyhow!("rename container on periphery not successful")); + } + update.logs.push(log); + } + let res = self + .db + .deployments + .update_one( + deployment_id, + mungos::Update::<()>::Set( + doc! { "name": to_monitor_name(new_name), "updated_at": monitor_timestamp() }, + ), + ) + .await + .context("failed to update deployment name on mongo"); + + if let Err(e) = res { + update + .logs + .push(Log::error("mongo update", format!("{e:?}"))); + } else { + update.logs.push(Log::simple( + "mongo update", + String::from("updated name on mongo"), + )) + } + + update.end_ts = monitor_timestamp().into(); + update.status = UpdateStatus::Complete; + update.success = all_logs_success(&update.logs); + + self.update_update(update.clone()).await?; + + Ok(update) + } + pub async fn reclone_deployment( &self, deployment_id: &str, diff --git a/core/src/api/deployment.rs b/core/src/api/deployment.rs index b34fe99a0..2fa343d3c 100644 --- a/core/src/api/deployment.rs +++ b/core/src/api/deployment.rs @@ -43,6 +43,12 @@ pub struct CopyDeploymentBody { server_id: String, } +#[typeshare] +#[derive(Serialize, Deserialize)] +pub struct RenameDeploymentBody { + new_name: String, +} + #[typeshare] #[derive(Deserialize)] pub struct GetContainerLogQuery { @@ -162,6 +168,24 @@ pub fn router() -> Router { }, ), ) + .route( + "/:id/rename", + patch( + |state: StateExtension, + user: RequestUserExtension, + deployment: Path, + body: Json| async move { + let update = spawn_request_action(async move { + state + .rename_deployment(&deployment.id, &body.new_name, &user) + .await + .map_err(handle_anyhow_error) + }) + .await??; + response!(Json(update)) + }, + ), + ) .route( "/:id/reclone", post( @@ -324,7 +348,7 @@ pub fn router() -> Router { } impl State { - async fn get_deployment_with_container_state( + pub async fn get_deployment_with_container_state( &self, user: &RequestUser, id: &str, diff --git a/core/src/api/server.rs b/core/src/api/server.rs index a7befb996..84ce9d24d 100644 --- a/core/src/api/server.rs +++ b/core/src/api/server.rs @@ -370,7 +370,11 @@ pub fn router() -> Router { } impl State { - async fn get_server(&self, id: &str, user: &RequestUser) -> anyhow::Result { + pub async fn get_server( + &self, + id: &str, + user: &RequestUser, + ) -> anyhow::Result { let server = self .get_server_check_permissions(id, user, PermissionLevel::Read) .await?; diff --git a/lib/db_client/src/lib.rs b/lib/db_client/src/lib.rs index d5fa13ad7..91fd03e47 100644 --- a/lib/db_client/src/lib.rs +++ b/lib/db_client/src/lib.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use anyhow::{anyhow, Context}; use collections::{ actions_collection, builds_collection, deployments_collection, groups_collection, diff --git a/lib/monitor_client/src/deployment.rs b/lib/monitor_client/src/deployment.rs index 7827982be..ef2ed23fc 100644 --- a/lib/monitor_client/src/deployment.rs +++ b/lib/monitor_client/src/deployment.rs @@ -124,6 +124,15 @@ impl MonitorClient { .context("failed at updating deployment") } + pub async fn rename_deployment(&self, id: &str, new_name: &str) -> anyhow::Result { + self.patch( + &format!("/api/deployment/{id}/rename"), + json!({ "new_name": new_name }), + ) + .await + .context("failed at renaming deployment") + } + pub async fn reclone_deployment(&self, id: &str) -> anyhow::Result { self.post::<(), _>(&format!("/api/deployment/{id}/reclone"), None) .await diff --git a/lib/periphery_client/src/container.rs b/lib/periphery_client/src/container.rs index 13f7a969d..620aa0062 100644 --- a/lib/periphery_client/src/container.rs +++ b/lib/periphery_client/src/container.rs @@ -70,6 +70,21 @@ impl PeripheryClient { .context("failed to remove container on periphery") } + pub async fn container_rename( + &self, + server: &Server, + curr_name: &str, + new_name: &str, + ) -> anyhow::Result { + self.post_json( + server, + "/container/rename", + &json!({ "curr_name": curr_name, "new_name": new_name }), + ) + .await + .context("failed to rename container on periphery") + } + pub async fn deploy(&self, server: &Server, deployment: &Deployment) -> anyhow::Result { self.post_json(server, "/container/deploy", deployment) .await diff --git a/lib/types/src/deployment.rs b/lib/types/src/deployment.rs index 455d51340..10a3a155a 100644 --- a/lib/types/src/deployment.rs +++ b/lib/types/src/deployment.rs @@ -107,6 +107,7 @@ pub struct DeploymentActionState { pub pulling: bool, pub recloning: bool, pub updating: bool, + pub renaming: bool, } #[typeshare] diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 0c9a7c083..0d5b44a29 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -100,6 +100,7 @@ pub enum Operation { PruneImagesServer, PruneContainersServer, PruneNetworksServer, + RenameServer, // build CreateBuild, @@ -117,6 +118,7 @@ pub enum Operation { RemoveContainer, PullDeployment, RecloneDeployment, + RenameDeployment, // procedure CreateProcedure, diff --git a/lib/types/src/traits.rs b/lib/types/src/traits.rs index 0d2051283..bc609d54e 100644 --- a/lib/types/src/traits.rs +++ b/lib/types/src/traits.rs @@ -60,6 +60,7 @@ impl Busy for DeploymentActionState { || self.starting || self.stopping || self.updating + || self.renaming } } diff --git a/periphery/src/api/container.rs b/periphery/src/api/container.rs index f94ac4158..dae311bb8 100644 --- a/periphery/src/api/container.rs +++ b/periphery/src/api/container.rs @@ -4,7 +4,7 @@ use axum::{ routing::{get, post}, Extension, Json, Router, }; -use helpers::{handle_anyhow_error, to_monitor_name}; +use helpers::handle_anyhow_error; use serde::Deserialize; use types::{Deployment, Log}; @@ -21,6 +21,12 @@ struct Container { name: String, } +#[derive(Deserialize)] +struct RenameContainerBody { + curr_name: String, + new_name: String, +} + #[derive(Deserialize)] struct GetLogQuery { tail: Option, // default is 1000 if not passed @@ -67,20 +73,26 @@ pub fn router() -> Router { ) .route( "/start", - post(|Json(container): Json| async move { - Json(docker::start_container(&to_monitor_name(&container.name)).await) + post(|container: Json| async move { + Json(docker::start_container(&container.name).await) }), ) .route( "/stop", - post(|Json(container): Json| async move { - Json(docker::stop_container(&to_monitor_name(&container.name)).await) + post(|container: Json| async move { + Json(docker::stop_container(&container.name).await) }), ) .route( "/remove", - post(|Json(container): Json| async move { - Json(docker::stop_and_remove_container(&to_monitor_name(&container.name)).await) + post(|container: Json| async move { + Json(docker::stop_and_remove_container(&container.name).await) + }), + ) + .route( + "/rename", + post(|body: Json| async move { + Json(docker::rename_container(&body.curr_name, &body.new_name).await) }), ) .route( diff --git a/periphery/src/helpers/docker/container.rs b/periphery/src/helpers/docker/container.rs index 29688ad79..6d58230ff 100644 --- a/periphery/src/helpers/docker/container.rs +++ b/periphery/src/helpers/docker/container.rs @@ -69,6 +69,13 @@ pub async fn stop_and_remove_container(container_name: &str) -> Log { run_monitor_command("docker stop and remove", command).await } +pub async fn rename_container(curr_name: &str, new_name: &str) -> Log { + let curr = to_monitor_name(curr_name); + let new = to_monitor_name(new_name); + let command = format!("docker rename {curr} {new}"); + run_monitor_command("docker rename", command).await +} + pub async fn pull_image(image: &str) -> Log { let command = format!("docker pull {image}"); run_monitor_command("docker pull", command).await