implement a not good busy guard

This commit is contained in:
mbecker20
2022-12-20 07:45:25 +00:00
parent a0beb27072
commit b2226efd59
15 changed files with 577 additions and 168 deletions

View File

@@ -3,8 +3,9 @@ use diff::Diff;
use helpers::to_monitor_name;
use mungos::{doc, to_bson};
use types::{
monitor_timestamp, traits::Permissioned, Build, Log, Operation, PermissionLevel, Update,
UpdateStatus, UpdateTarget,
monitor_timestamp,
traits::{Busy, Permissioned},
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget,
};
use crate::{
@@ -31,19 +32,26 @@ impl State {
}
}
pub fn build_busy(&self, id: &str) -> bool {
match self.build_action_states.lock().unwrap().get(id) {
Some(a) => a.busy(),
None => false,
}
}
pub async fn create_build(
&self,
name: &str,
server_id: String,
user: &RequestUser,
) -> anyhow::Result<Build> {
self.get_server_check_permissions(&server_id, user, PermissionLevel::Write)
self.get_server_check_permissions(&server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let build = Build {
name: to_monitor_name(name),
server_id,
permissions: [(user.id.clone(), PermissionLevel::Write)]
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
created_at: start_ts.clone(),
@@ -84,8 +92,11 @@ impl State {
}
pub async fn delete_build(&self, build_id: &str, user: &RequestUser) -> anyhow::Result<Build> {
if self.build_busy(build_id) {
return Err(anyhow!("build busy"))
}
let build = self
.get_build_check_permissions(build_id, user, PermissionLevel::Write)
.get_build_check_permissions(build_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let server = self.db.get_server(&build.server_id).await?;
@@ -120,8 +131,17 @@ impl State {
mut new_build: Build,
user: &RequestUser,
) -> anyhow::Result<Build> {
if self.build_busy(&new_build.id) {
return Err(anyhow!("build busy"))
}
{
let mut lock = self.build_action_states.lock().unwrap();
let entry = lock.entry(new_build.id.clone()).or_default();
entry.updating = true;
}
let current_build = self
.get_build_check_permissions(&new_build.id, user, PermissionLevel::Write)
.get_build_check_permissions(&new_build.id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
@@ -175,12 +195,28 @@ impl State {
update.status = UpdateStatus::Complete;
self.update_update(update).await?;
{
let mut lock = self.build_action_states.lock().unwrap();
let entry = lock.entry(new_build.id.clone()).or_default();
entry.updating = false;
}
Ok(new_build)
}
pub async fn build(&self, build_id: &str, user: &RequestUser) -> anyhow::Result<Update> {
if self.build_busy(build_id) {
return Err(anyhow!("build busy"))
}
{
let mut lock = self.build_action_states.lock().unwrap();
let entry = lock.entry(build_id.to_string()).or_default();
entry.building = true;
}
let mut build = self
.get_build_check_permissions(build_id, user, PermissionLevel::Write)
.get_build_check_permissions(build_id, user, PermissionLevel::Update)
.await?;
let server = self.db.get_server(&build.server_id).await?;
@@ -231,6 +267,13 @@ impl State {
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
self.update_update(update.clone()).await?;
{
let mut lock = self.build_action_states.lock().unwrap();
let entry = lock.entry(build_id.to_string()).or_default();
entry.building = false;
}
Ok(update)
}
@@ -239,8 +282,16 @@ impl State {
build_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.build_busy(build_id) {
return Err(anyhow!("build busy"))
}
{
let mut lock = self.build_action_states.lock().unwrap();
let entry = lock.entry(build_id.to_string()).or_default();
entry.recloning = true;
}
let build = self
.get_build_check_permissions(build_id, user, PermissionLevel::Write)
.get_build_check_permissions(build_id, user, PermissionLevel::Update)
.await?;
let server = self.db.get_server(&build.server_id).await?;
let mut update = Update {
@@ -272,6 +323,12 @@ impl State {
self.update_update(update.clone()).await?;
{
let mut lock = self.build_action_states.lock().unwrap();
let entry = lock.entry(build_id.to_string()).or_default();
entry.recloning = false;
}
Ok(update)
}
}

View File

@@ -2,8 +2,9 @@ use anyhow::{anyhow, Context};
use diff::Diff;
use helpers::to_monitor_name;
use types::{
monitor_timestamp, traits::Permissioned, Deployment, Log, Operation, PermissionLevel, Update,
UpdateStatus, UpdateTarget,
monitor_timestamp,
traits::{Busy, Permissioned},
Deployment, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget,
};
use crate::{
@@ -30,19 +31,26 @@ impl State {
}
}
pub fn deployment_busy(&self, id: &str) -> bool {
match self.deployment_action_states.lock().unwrap().get(id) {
Some(a) => a.busy(),
None => false,
}
}
pub async fn create_deployment(
&self,
name: &str,
server_id: String,
user: &RequestUser,
) -> anyhow::Result<Deployment> {
self.get_server_check_permissions(&server_id, user, PermissionLevel::Write)
self.get_server_check_permissions(&server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let deployment = Deployment {
name: to_monitor_name(name),
server_id,
permissions: [(user.id.clone(), PermissionLevel::Write)]
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
created_at: start_ts.clone(),
@@ -87,8 +95,11 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Deployment> {
if self.deployment_busy(deployment_id) {
return Err(anyhow!("deployment busy"))
}
let deployment = self
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Write)
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
let server = self.db.get_server(&deployment.server_id).await?;
@@ -131,8 +142,16 @@ impl State {
mut new_deployment: Deployment,
user: &RequestUser,
) -> anyhow::Result<Deployment> {
if self.deployment_busy(&new_deployment.id) {
return Err(anyhow!("deployment busy"))
}
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(new_deployment.id.clone()).or_default();
entry.updating = true;
}
let current_deployment = self
.get_deployment_check_permissions(&new_deployment.id, user, PermissionLevel::Write)
.get_deployment_check_permissions(&new_deployment.id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
@@ -190,6 +209,12 @@ impl State {
self.update_update(update).await?;
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(new_deployment.id.clone()).or_default();
entry.updating = false;
}
Ok(new_deployment)
}
@@ -198,8 +223,16 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id) {
return Err(anyhow!("deployment busy"))
}
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.recloning = true;
}
let deployment = self
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Write)
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Execute)
.await?;
let server = self.db.get_server(&deployment.server_id).await?;
let mut update = Update {
@@ -231,6 +264,12 @@ impl State {
self.update_update(update.clone()).await?;
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.recloning = false;
}
Ok(update)
}
@@ -239,8 +278,16 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id) {
return Err(anyhow!("deployment busy"))
}
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.deploying = true;
}
let mut deployment = self
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Write)
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Execute)
.await?;
if let Some(build_id) = &deployment.build_id {
let build = self.db.get_build(build_id).await?;
@@ -281,6 +328,12 @@ impl State {
self.update_update(update.clone()).await?;
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.deploying = false;
}
Ok(update)
}
@@ -289,9 +342,17 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id) {
return Err(anyhow!("deployment busy"))
}
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.starting = true;
}
let start_ts = monitor_timestamp();
let deployment = self
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Write)
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Execute)
.await?;
let server = self.db.get_server(&deployment.server_id).await?;
let mut update = Update {
@@ -329,6 +390,12 @@ impl State {
self.update_update(update.clone()).await?;
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.starting = false;
}
Ok(update)
}
@@ -337,9 +404,17 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id) {
return Err(anyhow!("deployment busy"))
}
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.stopping = true;
}
let start_ts = monitor_timestamp();
let deployment = self
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Write)
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Execute)
.await?;
let server = self.db.get_server(&deployment.server_id).await?;
let mut update = Update {
@@ -377,6 +452,12 @@ impl State {
self.update_update(update.clone()).await?;
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.stopping = false;
}
Ok(update)
}
@@ -385,9 +466,17 @@ impl State {
deployment_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.deployment_busy(deployment_id) {
return Err(anyhow!("deployment busy"))
}
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.removing = true;
}
let start_ts = monitor_timestamp();
let deployment = self
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Write)
.get_deployment_check_permissions(deployment_id, user, PermissionLevel::Execute)
.await?;
let server = self.db.get_server(&deployment.server_id).await?;
let mut update = Update {
@@ -425,6 +514,12 @@ impl State {
self.update_update(update.clone()).await?;
{
let mut lock = self.deployment_action_states.lock().unwrap();
let entry = lock.entry(deployment_id.to_string()).or_default();
entry.removing = false;
}
Ok(update)
}
}

View File

@@ -34,7 +34,7 @@ impl State {
let start_ts = monitor_timestamp();
let procedure = Procedure {
name: to_monitor_name(name),
permissions: [(user.id.clone(), PermissionLevel::Write)]
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
created_at: start_ts.clone(),
@@ -78,7 +78,7 @@ impl State {
user: &RequestUser,
) -> anyhow::Result<Procedure> {
let procedure = self
.get_procedure_check_permissions(id, user, PermissionLevel::Write)
.get_procedure_check_permissions(id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
self.db
@@ -109,7 +109,7 @@ impl State {
user: &RequestUser,
) -> anyhow::Result<Procedure> {
let current_procedure = self
.get_procedure_check_permissions(&new_procedure.id, user, PermissionLevel::Write)
.get_procedure_check_permissions(&new_procedure.id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
@@ -154,7 +154,7 @@ impl State {
pub async fn run_procedure(&self, id: &str, user: &RequestUser) -> anyhow::Result<Vec<Update>> {
let procedure = self
.get_procedure_check_permissions(id, user, PermissionLevel::Write)
.get_procedure_check_permissions(id, user, PermissionLevel::Execute)
.await?;
let mut updates = Vec::new();
for ProcedureStage {
@@ -163,7 +163,7 @@ impl State {
} in procedure.stages
{
match operation {
None => {},
None => {}
// deployment
StartContainer => {
let update = self

View File

@@ -2,8 +2,9 @@ use anyhow::{anyhow, Context};
use diff::Diff;
use helpers::to_monitor_name;
use types::{
monitor_timestamp, traits::Permissioned, Log, Operation, PermissionLevel, Server, Update,
UpdateStatus, UpdateTarget,
monitor_timestamp,
traits::{Busy, Permissioned},
Log, Operation, PermissionLevel, Server, Update, UpdateStatus, UpdateTarget,
};
use crate::{auth::RequestUser, state::State};
@@ -26,6 +27,13 @@ impl State {
}
}
pub fn server_busy(&self, id: &str) -> bool {
match self.server_action_states.lock().unwrap().get(id) {
Some(a) => a.busy(),
None => false,
}
}
pub async fn create_server(
&self,
name: &str,
@@ -41,7 +49,7 @@ impl State {
let server = Server {
name: to_monitor_name(name),
address,
permissions: [(user.id.clone(), PermissionLevel::Write)]
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
created_at: start_ts.clone(),
@@ -87,8 +95,11 @@ impl State {
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Server> {
if self.server_busy(server_id) {
return Err(anyhow!("server busy"))
}
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Write)
.get_server_check_permissions(server_id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
self.db.servers.delete_one(&server_id).await?;
@@ -114,8 +125,11 @@ impl State {
mut new_server: Server,
user: &RequestUser,
) -> anyhow::Result<Server> {
if self.server_busy(&new_server.id) {
return Err(anyhow!("server busy"))
}
let current_server = self
.get_server_check_permissions(&new_server.id, user, PermissionLevel::Write)
.get_server_check_permissions(&new_server.id, user, PermissionLevel::Update)
.await?;
let start_ts = monitor_timestamp();
@@ -149,4 +163,168 @@ impl State {
self.add_update(update).await?;
Ok(new_server)
}
pub async fn prune_networks(
&self,
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.server_busy(server_id) {
return Err(anyhow!("server busy"))
}
{
let mut lock = self.server_action_states.lock().unwrap();
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_networks = true;
}
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Execute)
.await?;
let start_ts = monitor_timestamp();
let mut update = Update {
target: UpdateTarget::Server(server_id.to_owned()),
operation: Operation::PruneNetworksServer,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let log = match self.periphery.network_prune(&server).await.context(format!(
"failed to prune networks on server {}",
server.name
)) {
Ok(log) => log,
Err(e) => Log::error("prune networks", format!("{e:#?}")),
};
update.success = log.success;
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.logs.push(log);
self.update_update(update.clone()).await?;
{
let mut lock = self.server_action_states.lock().unwrap();
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_networks = false;
}
Ok(update)
}
pub async fn prune_images(
&self,
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.server_busy(server_id) {
return Err(anyhow!("server busy"))
}
{
let mut lock = self.server_action_states.lock().unwrap();
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_images = true;
}
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Execute)
.await?;
let start_ts = monitor_timestamp();
let mut update = Update {
target: UpdateTarget::Server(server_id.to_owned()),
operation: Operation::PruneImagesServer,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let log = match self
.periphery
.image_prune(&server)
.await
.context(format!("failed to prune images on server {}", server.name))
{
Ok(log) => log,
Err(e) => Log::error("prune images", format!("{e:#?}")),
};
update.success = log.success;
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.logs.push(log);
self.update_update(update.clone()).await?;
{
let mut lock = self.server_action_states.lock().unwrap();
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_images = false;
}
Ok(update)
}
pub async fn prune_containers(
&self,
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
if self.server_busy(server_id) {
return Err(anyhow!("server busy"))
}
{
let mut lock = self.server_action_states.lock().unwrap();
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_containers = true;
}
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Execute)
.await?;
let start_ts = monitor_timestamp();
let mut update = Update {
target: UpdateTarget::Server(server_id.to_owned()),
operation: Operation::PruneContainersServer,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let log = match self
.periphery
.container_prune(&server)
.await
.context(format!(
"failed to prune containers on server {}",
server.name
)) {
Ok(log) => log,
Err(e) => Log::error("prune containers", format!("{e:#?}")),
};
update.success = log.success;
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.logs.push(log);
self.update_update(update.clone()).await?;
{
let mut lock = self.server_action_states.lock().unwrap();
let entry = lock.entry(server_id.to_string()).or_default();
entry.pruning_containers = false;
}
Ok(update)
}
}

View File

@@ -6,7 +6,7 @@ use axum::{
};
use helpers::handle_anyhow_error;
use mungos::{Deserialize, Document, Serialize};
use types::{traits::Permissioned, Build, PermissionLevel};
use types::{traits::Permissioned, Build, BuildActionState, PermissionLevel};
use typeshare::typeshare;
use crate::{
@@ -141,6 +141,20 @@ pub fn router() -> Router {
},
),
)
.route(
"/:id/action_state",
get(
|Extension(state): StateExtension,
Extension(user): RequestUserExtension,
Path(BuildId { id }): Path<BuildId>| async move {
let action_state = state
.get_build_action_states(id, &user)
.await
.map_err(handle_anyhow_error)?;
response!(Json(action_state))
},
),
)
}
impl State {
@@ -168,4 +182,21 @@ impl State {
builds.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
Ok(builds)
}
async fn get_build_action_states(
&self,
id: String,
user: &RequestUser,
) -> anyhow::Result<BuildActionState> {
self.get_server_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let action_state = self
.build_action_states
.lock()
.unwrap()
.entry(id)
.or_default()
.clone();
Ok(action_state)
}
}

View File

@@ -9,7 +9,10 @@ use axum::{
use futures_util::future::join_all;
use helpers::handle_anyhow_error;
use mungos::{Deserialize, Document, Serialize};
use types::{traits::Permissioned, Deployment, DeploymentWithContainer, PermissionLevel, Server};
use types::{
traits::Permissioned, Deployment, DeploymentActionState, DeploymentWithContainer,
PermissionLevel, Server,
};
use typeshare::typeshare;
use crate::{
@@ -186,6 +189,20 @@ pub fn router() -> Router {
},
),
)
.route(
"/:id/action_state",
get(
|Extension(state): StateExtension,
Extension(user): RequestUserExtension,
Path(DeploymentId { id }): Path<DeploymentId>| async move {
let action_state = state
.get_deployment_action_states(id, &user)
.await
.map_err(handle_anyhow_error)?;
response!(Json(action_state))
},
),
)
}
impl State {
@@ -263,4 +280,21 @@ impl State {
.collect();
Ok(res)
}
async fn get_deployment_action_states(
&self,
id: String,
user: &RequestUser,
) -> anyhow::Result<DeploymentActionState> {
self.get_server_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let action_state = self
.deployment_action_states
.lock()
.unwrap()
.entry(id)
.or_default()
.clone();
Ok(action_state)
}
}

View File

@@ -1,9 +1,6 @@
use std::sync::Arc;
use anyhow::anyhow;
use axum::{middleware, routing::get, Extension, Json, Router};
use helpers::handle_anyhow_error;
use periphery::PeripheryClient;
use types::User;
use crate::{
@@ -32,7 +29,6 @@ pub fn router() -> Router {
.nest("/update", update::router())
.nest("/permissions", permissions::router())
.nest("/secret", secret::router())
.layer(Extension(Arc::new(PeripheryClient::new())))
.layer(middleware::from_fn(auth_request))
}

View File

@@ -7,8 +7,8 @@ use axum::{
use helpers::handle_anyhow_error;
use mungos::{Deserialize, Document, Serialize};
use types::{
monitor_timestamp, traits::Permissioned, BasicContainerInfo, ImageSummary, Log, Network,
Operation, PermissionLevel, Server, SystemStats, Update, UpdateStatus, UpdateTarget,
traits::Permissioned, BasicContainerInfo, ImageSummary, Network, PermissionLevel, Server,
ServerActionState, SystemStats,
};
use typeshare::typeshare;
@@ -214,6 +214,20 @@ pub fn router() -> Router {
},
),
)
.route(
"/:id/action_state",
get(
|Extension(state): StateExtension,
Extension(user): RequestUserExtension,
Path(ServerId { id }): Path<ServerId>| async move {
let action_state = state
.get_server_action_states(id, &user)
.await
.map_err(handle_anyhow_error)?;
response!(Json(action_state))
},
),
)
}
impl State {
@@ -273,45 +287,6 @@ impl State {
Ok(stats)
}
pub async fn prune_networks(
&self,
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Write)
.await?;
let start_ts = monitor_timestamp();
let mut update = Update {
target: UpdateTarget::Server(server_id.to_owned()),
operation: Operation::PruneNetworksServer,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let log = match self.periphery.network_prune(&server).await.context(format!(
"failed to prune networks on server {}",
server.name
)) {
Ok(log) => log,
Err(e) => Log::error("prune networks", format!("{e:#?}")),
};
update.success = log.success;
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.logs.push(log);
self.update_update(update.clone()).await?;
Ok(update)
}
async fn get_images(
&self,
server_id: &str,
@@ -328,46 +303,6 @@ impl State {
Ok(images)
}
pub async fn prune_images(
&self,
server_id: &str,
user: &RequestUser,
) -> anyhow::Result<Update> {
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Write)
.await?;
let start_ts = monitor_timestamp();
let mut update = Update {
target: UpdateTarget::Server(server_id.to_owned()),
operation: Operation::PruneImagesServer,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let log = match self
.periphery
.image_prune(&server)
.await
.context(format!("failed to prune images on server {}", server.name))
{
Ok(log) => log,
Err(e) => Log::error("prune images", format!("{e:#?}")),
};
update.success = log.success;
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.logs.push(log);
self.update_update(update.clone()).await?;
Ok(update)
}
async fn get_containers(
&self,
server_id: &str,
@@ -387,46 +322,20 @@ impl State {
Ok(containers)
}
pub async fn prune_containers(
async fn get_server_action_states(
&self,
server_id: &str,
id: String,
user: &RequestUser,
) -> anyhow::Result<Update> {
let server = self
.get_server_check_permissions(server_id, user, PermissionLevel::Write)
) -> anyhow::Result<ServerActionState> {
self.get_server_check_permissions(&id, &user, PermissionLevel::Read)
.await?;
let start_ts = monitor_timestamp();
let mut update = Update {
target: UpdateTarget::Server(server_id.to_owned()),
operation: Operation::PruneContainersServer,
start_ts,
status: UpdateStatus::InProgress,
success: true,
operator: user.id.clone(),
..Default::default()
};
update.id = self.add_update(update.clone()).await?;
let log = match self
.periphery
.container_prune(&server)
.await
.context(format!(
"failed to prune containers on server {}",
server.name
)) {
Ok(log) => log,
Err(e) => Log::error("prune containers", format!("{e:#?}")),
};
update.success = log.success;
update.status = UpdateStatus::Complete;
update.end_ts = Some(monitor_timestamp());
update.logs.push(log);
self.update_update(update.clone()).await?;
Ok(update)
let action_state = self
.server_action_states
.lock()
.unwrap()
.entry(id)
.or_default()
.clone();
Ok(action_state)
}
}

View File

@@ -1,28 +1,39 @@
use std::sync::Arc;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use axum::Extension;
use db::DbClient;
use periphery::PeripheryClient;
use types::CoreConfig;
use types::{BuildActionState, CoreConfig, DeploymentActionState, ServerActionState};
use crate::ws::update::UpdateWsChannel;
pub type StateExtension = Extension<Arc<State>>;
pub type ActionStateMap<T> = Mutex<HashMap<String, T>>;
pub struct State {
pub config: CoreConfig,
pub db: DbClient,
pub update: UpdateWsChannel,
pub periphery: PeripheryClient,
pub build_action_states: ActionStateMap<BuildActionState>,
pub deployment_action_states: ActionStateMap<DeploymentActionState>,
pub server_action_states: ActionStateMap<ServerActionState>,
}
impl State {
pub async fn new(config: CoreConfig) -> State {
State {
db: DbClient::new(config.mongo.clone()).await,
periphery: PeripheryClient::new(),
update: UpdateWsChannel::new(),
config,
update: UpdateWsChannel::new(),
periphery: PeripheryClient::default(),
build_action_states: Default::default(),
deployment_action_states: Default::default(),
server_action_states: Default::default(),
}
}

View File

@@ -31,7 +31,6 @@ use crate::{
pub type UpdateWsSender = Mutex<Sender<Update>>;
pub type UpdateWsReciever = Receiver<Update>;
pub struct UpdateWsChannel {
pub sender: UpdateWsSender,
pub reciever: UpdateWsReciever,

View File

@@ -42,6 +42,12 @@ export interface Server {
updated_at?: string;
}
export interface ServerActionState {
pruning_networks: boolean;
pruning_containers: boolean;
pruning_images: boolean;
}
export interface Deployment {
_id?: string;
name: string;
@@ -63,6 +69,15 @@ export interface DeploymentWithContainer {
container?: BasicContainerInfo;
}
export interface DeploymentActionState {
deploying: boolean;
stopping: boolean;
starting: boolean;
removing: boolean;
pulling: boolean;
recloning: boolean;
}
export interface Build {
_id?: string;
name: string;
@@ -80,6 +95,11 @@ export interface Build {
updated_at?: string;
}
export interface BuildActionState {
building: boolean;
recloning: boolean;
}
export interface Update {
_id?: string;
target: UpdateTarget;
@@ -270,7 +290,8 @@ export enum ProcedureOperation {
export enum PermissionLevel {
None = "none",
Read = "read",
Write = "write",
Execute = "execute",
Update = "update",
}
export enum PermissionsTarget {

View File

@@ -2,10 +2,13 @@ import axios from "axios";
import {
BasicContainerInfo,
Build,
BuildActionState,
Deployment,
DeploymentActionState,
DeploymentWithContainer,
Log,
Server,
ServerActionState,
SystemStats,
Update,
User,
@@ -102,6 +105,10 @@ export class Client {
return this.post(`/api/deployment/${deployment_id}/remove_container`);
}
get_deployment_action_state(id: string): Promise<DeploymentActionState> {
return this.get(`/api/deployment/${id}/action_state`);
}
// server
list_servers(query?: QueryObject): Promise<Server[]> {
@@ -132,10 +139,18 @@ export class Client {
return this.get(`/api/server/${server_id}/stats`);
}
get_docker_networks(server_id: string): Promise<any[]> {
return this.get(`/api/server/${server_id}/networks`);
}
prune_docker_networks(server_id: string): Promise<Log> {
return this.post(`/api/server/${server_id}/networks/prune`);
}
get_docker_images(server_id: string): Promise<any[]> {
return this.get(`/api/server/${server_id}/images`);
}
prune_docker_images(server_id: string): Promise<Log> {
return this.post(`/api/server/${server_id}/images/prune`);
}
@@ -148,6 +163,10 @@ export class Client {
return this.post(`/api/server/${server_id}/containers/prune`);
}
get_server_action_state(id: string): Promise<ServerActionState> {
return this.get(`/api/server/${id}/action_state`);
}
// build
list_builds(query?: QueryObject): Promise<Build[]> {
@@ -182,6 +201,10 @@ export class Client {
return this.post(`/api/build/${id}/reclone`);
}
get_build_action_state(id: string): Promise<BuildActionState> {
return this.get(`/api/build/${id}/action_state`);
}
// api secrets
create_api_secret(body: CreateSecretBody): Promise<string> {

View File

@@ -9,17 +9,12 @@ mod git;
mod image;
mod network;
#[derive(Default)]
pub struct PeripheryClient {
http_client: reqwest::Client,
}
impl PeripheryClient {
pub fn new() -> PeripheryClient {
PeripheryClient {
http_client: reqwest::Client::new(),
}
}
pub async fn health_check(&self, server: &Server) -> anyhow::Result<String> {
self.get_text(server, "health")
.await

View File

@@ -183,6 +183,14 @@ fn default_disk_alert() -> f64 {
75.0
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ServerActionState {
pub pruning_networks: bool,
pub pruning_containers: bool,
pub pruning_images: bool,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default, Diff, Builder)]
#[diff(attr(#[derive(Debug, Serialize)]))]
@@ -253,6 +261,18 @@ pub struct DeploymentWithContainer {
pub container: Option<BasicContainerInfo>,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct DeploymentActionState {
pub deploying: bool,
pub stopping: bool,
pub starting: bool,
pub removing: bool,
pub pulling: bool,
pub recloning: bool,
pub updating: bool,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default, Diff, Builder)]
#[diff(attr(#[derive(Debug, Serialize)]))]
@@ -315,6 +335,14 @@ pub struct Build {
pub updated_at: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct BuildActionState {
pub building: bool,
pub recloning: bool,
pub updating: bool,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Update {
@@ -793,7 +821,8 @@ impl Default for ProcedureOperation {
pub enum PermissionLevel {
None,
Read,
Write,
Execute,
Update,
}
impl Default for PermissionLevel {

View File

@@ -1,4 +1,7 @@
use crate::{Build, Deployment, PermissionLevel, PermissionsMap, Procedure, Server};
use crate::{
Build, BuildActionState, Deployment, DeploymentActionState, PermissionLevel, PermissionsMap,
Procedure, Server, ServerActionState,
};
pub trait Permissioned {
fn permissions_map(&self) -> &PermissionsMap;
@@ -31,3 +34,31 @@ impl Permissioned for Procedure {
&self.permissions
}
}
pub trait Busy {
fn busy(&self) -> bool;
}
impl Busy for ServerActionState {
fn busy(&self) -> bool {
self.pruning_containers || self.pruning_images || self.pruning_networks
}
}
impl Busy for DeploymentActionState {
fn busy(&self) -> bool {
self.deploying
|| self.pulling
|| self.recloning
|| self.removing
|| self.starting
|| self.stopping
|| self.updating
}
}
impl Busy for BuildActionState {
fn busy(&self) -> bool {
self.building || self.recloning || self.updating
}
}