mirror of
https://github.com/moghtech/komodo.git
synced 2026-08-24 00:00:16 +00:00
implement the swarm resource associations for stack / deployment targeting swarm
This commit is contained in:
committed by
Maxwell Becker
parent
775f5bf703
commit
a1c00a8d32
@@ -61,14 +61,6 @@ pub async fn get_user(user: &str) -> anyhow::Result<User> {
|
||||
.with_context(|| format!("No user found matching '{user}'"))
|
||||
}
|
||||
|
||||
pub async fn get_swarm_with_reachability(
|
||||
swarm_id_or_name: &str,
|
||||
) -> anyhow::Result<(Swarm, bool)> {
|
||||
let swarm = resource::get::<Swarm>(swarm_id_or_name).await?;
|
||||
let reachable = get_swarm_reachability(&swarm).await.is_ok();
|
||||
Ok((swarm, reachable))
|
||||
}
|
||||
|
||||
pub async fn get_swarm_reachability(
|
||||
swarm: &Swarm,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -202,7 +194,9 @@ pub fn get_stack_state_from_containers(
|
||||
pub async fn get_stack_state(
|
||||
stack: &Stack,
|
||||
) -> anyhow::Result<StackState> {
|
||||
if stack.config.server_id.is_empty() {
|
||||
if stack.config.swarm_id.is_empty()
|
||||
&& stack.config.server_id.is_empty()
|
||||
{
|
||||
return Ok(StackState::Down);
|
||||
}
|
||||
let state = stack_status_cache()
|
||||
|
||||
@@ -60,6 +60,7 @@ pub async fn insert_stacks_status_unknown(stacks: Vec<Stack>) {
|
||||
id: stack.id,
|
||||
state: StackState::Unknown,
|
||||
services: Vec::new(),
|
||||
swarm_stack: None,
|
||||
},
|
||||
prev,
|
||||
}
|
||||
@@ -84,6 +85,7 @@ pub async fn insert_deployments_status_unknown(
|
||||
id: deployment.id,
|
||||
state: DeploymentState::Unknown,
|
||||
container: None,
|
||||
service: None,
|
||||
update_available: false,
|
||||
},
|
||||
prev,
|
||||
|
||||
@@ -197,19 +197,19 @@ pub async fn update_cache_for_server(server: &Server, force: bool) {
|
||||
.unwrap_or(&[]);
|
||||
|
||||
tokio::join!(
|
||||
resources::update_deployment_cache(
|
||||
resources::update_server_stack_cache(
|
||||
server.name.clone(),
|
||||
resources.stacks,
|
||||
containers,
|
||||
images
|
||||
),
|
||||
resources::update_server_deployment_cache(
|
||||
server.name.clone(),
|
||||
resources.deployments,
|
||||
containers,
|
||||
images,
|
||||
&resources.builds,
|
||||
),
|
||||
resources::update_stack_cache(
|
||||
server.name.clone(),
|
||||
resources.stacks,
|
||||
containers,
|
||||
images
|
||||
),
|
||||
);
|
||||
|
||||
insert_server_status(
|
||||
|
||||
@@ -15,9 +15,13 @@ use komodo_client::{
|
||||
docker::{
|
||||
container::{ContainerListItem, ContainerStateStatusEnum},
|
||||
image::ImageListItem,
|
||||
service::SwarmServiceListItem,
|
||||
stack::SwarmStackListItem,
|
||||
task::SwarmTaskListItem,
|
||||
},
|
||||
komodo_timestamp,
|
||||
stack::{Stack, StackService, StackServiceNames, StackState},
|
||||
swarm::SwarmState,
|
||||
user::auto_redeploy_user,
|
||||
},
|
||||
};
|
||||
@@ -44,7 +48,87 @@ fn stack_alert_sent_cache() -> &'static AlertCache<(String, String)> {
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn update_stack_cache(
|
||||
pub async fn update_swarm_stack_cache(
|
||||
stacks: Vec<Stack>,
|
||||
swarm_stacks: &[SwarmStackListItem],
|
||||
swarm_services: &[SwarmServiceListItem],
|
||||
) {
|
||||
let stack_status_cache = stack_status_cache();
|
||||
for stack in stacks {
|
||||
let project_name = stack.project_name(false);
|
||||
let swarm_stack = swarm_stacks
|
||||
.iter()
|
||||
.find(|stack| {
|
||||
stack
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| name == &project_name)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.cloned();
|
||||
let services = extract_services_from_stack(&stack);
|
||||
let mut services_with_swarm_services = services
|
||||
.iter()
|
||||
.map(
|
||||
|StackServiceNames {
|
||||
service_name,
|
||||
image,
|
||||
..
|
||||
}| {
|
||||
let swarm_service = swarm_services
|
||||
.iter()
|
||||
.find(|service| {
|
||||
service
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| name == service_name)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.cloned();
|
||||
StackService {
|
||||
service: service_name.clone(),
|
||||
image: image.clone(),
|
||||
container: None,
|
||||
swarm_service,
|
||||
update_available: false,
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
services_with_swarm_services
|
||||
.sort_by(|a, b| a.service.cmp(&b.service));
|
||||
let current_state = swarm_stack
|
||||
.as_ref()
|
||||
.map(|stack| match stack.state {
|
||||
Some(SwarmState::Healthy) => StackState::Running,
|
||||
Some(SwarmState::Unhealthy) => StackState::Unhealthy,
|
||||
Some(SwarmState::Unknown) | None => StackState::Unknown,
|
||||
})
|
||||
.unwrap_or(StackState::Down);
|
||||
let prev_state = stack_status_cache
|
||||
.get(&stack.id)
|
||||
.await
|
||||
.map(|s| s.curr.state);
|
||||
let status = CachedStackStatus {
|
||||
id: stack.id.clone(),
|
||||
state: current_state,
|
||||
services: services_with_swarm_services,
|
||||
swarm_stack,
|
||||
};
|
||||
stack_status_cache
|
||||
.insert(
|
||||
stack.id,
|
||||
History {
|
||||
curr: status,
|
||||
prev: prev_state,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_server_stack_cache(
|
||||
server_name: String,
|
||||
stacks: Vec<Stack>,
|
||||
containers: &[ContainerListItem],
|
||||
@@ -132,6 +216,7 @@ pub async fn update_stack_cache(
|
||||
service: service_name.clone(),
|
||||
image: image.clone(),
|
||||
container,
|
||||
swarm_service: None,
|
||||
update_available,
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
@@ -155,14 +240,14 @@ pub async fn update_stack_cache(
|
||||
}
|
||||
}
|
||||
|
||||
let state = get_stack_state_from_containers(
|
||||
let current_state = get_stack_state_from_containers(
|
||||
&stack.config.ignore_services,
|
||||
&services,
|
||||
containers,
|
||||
);
|
||||
if !services_to_update.is_empty()
|
||||
&& stack.config.auto_update
|
||||
&& state == StackState::Running
|
||||
&& current_state == StackState::Running
|
||||
&& !action_states()
|
||||
.stack
|
||||
.get_or_insert_default(&stack.id)
|
||||
@@ -221,17 +306,25 @@ pub async fn update_stack_cache(
|
||||
}
|
||||
services_with_containers
|
||||
.sort_by(|a, b| a.service.cmp(&b.service));
|
||||
let prev = stack_status_cache
|
||||
let prev_state = stack_status_cache
|
||||
.get(&stack.id)
|
||||
.await
|
||||
.map(|s| s.curr.state);
|
||||
let status = CachedStackStatus {
|
||||
id: stack.id.clone(),
|
||||
state,
|
||||
state: current_state,
|
||||
services: services_with_containers,
|
||||
swarm_stack: None,
|
||||
};
|
||||
stack_status_cache
|
||||
.insert(stack.id, History { curr: status, prev }.into())
|
||||
.insert(
|
||||
stack.id,
|
||||
History {
|
||||
curr: status,
|
||||
prev: prev_state,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -241,7 +334,75 @@ fn deployment_alert_sent_cache() -> &'static AlertCache<String> {
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub async fn update_deployment_cache(
|
||||
pub async fn update_swarm_deployment_cache(
|
||||
deployments: Vec<Deployment>,
|
||||
swarm_services: &[SwarmServiceListItem],
|
||||
swarm_tasks: &[SwarmTaskListItem],
|
||||
) {
|
||||
let deployment_status_cache = deployment_status_cache();
|
||||
for deployment in deployments {
|
||||
let service = swarm_services
|
||||
.iter()
|
||||
.find(|service| {
|
||||
service
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| name == &deployment.name)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.cloned();
|
||||
let prev_state = deployment_status_cache
|
||||
.get(&deployment.id)
|
||||
.await
|
||||
.map(|s| s.curr.state);
|
||||
let current_state = service
|
||||
.as_ref()
|
||||
.map(|service| {
|
||||
let Some(service_id) = &service.id else {
|
||||
return DeploymentState::Unknown;
|
||||
};
|
||||
let tasks = swarm_tasks
|
||||
.iter()
|
||||
.filter(|task| {
|
||||
task
|
||||
.service_id
|
||||
.as_ref()
|
||||
.map(|id| id == service_id)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// If service exists but no tasks, it is unhealthy
|
||||
if tasks.is_empty() {
|
||||
return DeploymentState::Unhealthy;
|
||||
}
|
||||
for task in tasks {
|
||||
if task.desired_state != task.state {
|
||||
return DeploymentState::Unhealthy;
|
||||
}
|
||||
}
|
||||
DeploymentState::Running
|
||||
})
|
||||
.unwrap_or(DeploymentState::NotDeployed);
|
||||
deployment_status_cache
|
||||
.insert(
|
||||
deployment.id.clone(),
|
||||
History {
|
||||
curr: CachedDeploymentStatus {
|
||||
id: deployment.id,
|
||||
state: current_state,
|
||||
service,
|
||||
container: None,
|
||||
update_available: false,
|
||||
},
|
||||
prev: prev_state,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_server_deployment_cache(
|
||||
server_name: String,
|
||||
deployments: Vec<Deployment>,
|
||||
containers: &[ContainerListItem],
|
||||
@@ -256,11 +417,11 @@ pub async fn update_deployment_cache(
|
||||
.iter()
|
||||
.find(|container| container.name == deployment.name)
|
||||
.cloned();
|
||||
let prev = deployment_status_cache
|
||||
let prev_state = deployment_status_cache
|
||||
.get(&deployment.id)
|
||||
.await
|
||||
.map(|s| s.curr.state);
|
||||
let state = container
|
||||
let current_state = container
|
||||
.as_ref()
|
||||
.map(|c| c.state.into())
|
||||
.unwrap_or(DeploymentState::NotDeployed);
|
||||
@@ -307,7 +468,7 @@ pub async fn update_deployment_cache(
|
||||
|
||||
if update_available {
|
||||
if deployment.config.auto_update {
|
||||
if state == DeploymentState::Running
|
||||
if current_state == DeploymentState::Running
|
||||
&& !action_states()
|
||||
.deployment
|
||||
.get_or_insert_default(&deployment.id)
|
||||
@@ -362,7 +523,7 @@ pub async fn update_deployment_cache(
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if state == DeploymentState::Running
|
||||
} else if current_state == DeploymentState::Running
|
||||
&& deployment.config.send_alerts
|
||||
&& deployment_alert_sent_cache.contains(&deployment.id)
|
||||
{
|
||||
@@ -404,11 +565,12 @@ pub async fn update_deployment_cache(
|
||||
History {
|
||||
curr: CachedDeploymentStatus {
|
||||
id: deployment.id,
|
||||
state,
|
||||
state: current_state,
|
||||
container,
|
||||
service: None,
|
||||
update_available,
|
||||
},
|
||||
prev,
|
||||
prev: prev_state,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
||||
@@ -22,7 +22,12 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
config::monitoring_interval,
|
||||
helpers::swarm::swarm_request_custom_timeout,
|
||||
monitor::UpdateCacheResources,
|
||||
monitor::{
|
||||
UpdateCacheResources,
|
||||
resources::{
|
||||
update_swarm_deployment_cache, update_swarm_stack_cache,
|
||||
},
|
||||
},
|
||||
state::{CachedSwarmStatus, db_client, swarm_status_cache},
|
||||
};
|
||||
|
||||
@@ -30,17 +35,16 @@ const ADDITIONAL_MS: u128 = 1000;
|
||||
|
||||
pub fn spawn_swarm_monitoring_loop() {
|
||||
tokio::spawn(async move {
|
||||
refresh_swarm_cache(komodo_timestamp()).await;
|
||||
refresh_swarm_cache().await;
|
||||
let interval = monitoring_interval();
|
||||
loop {
|
||||
let ts = (wait_until_timelength(interval, ADDITIONAL_MS).await
|
||||
- ADDITIONAL_MS) as i64;
|
||||
refresh_swarm_cache(ts).await;
|
||||
wait_until_timelength(interval, ADDITIONAL_MS).await;
|
||||
refresh_swarm_cache().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn refresh_swarm_cache(_ts: i64) {
|
||||
async fn refresh_swarm_cache() {
|
||||
let swarms =
|
||||
match find_collect(&db_client().swarms, None, None).await {
|
||||
Ok(swarms) => swarms,
|
||||
@@ -148,7 +152,18 @@ pub async fn update_cache_for_swarm(swarm: &Swarm, force: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: UPDATE STACKS / DEPLOYMENT CACHES
|
||||
tokio::join!(
|
||||
update_swarm_stack_cache(
|
||||
resources.stacks,
|
||||
&lists.stacks,
|
||||
&lists.services,
|
||||
),
|
||||
update_swarm_deployment_cache(
|
||||
resources.deployments,
|
||||
&lists.services,
|
||||
&lists.tasks,
|
||||
)
|
||||
);
|
||||
|
||||
swarm_status_cache()
|
||||
.insert(
|
||||
|
||||
@@ -3,7 +3,7 @@ use database::mungos::mongodb::Collection;
|
||||
use formatting::format_serror;
|
||||
use indexmap::IndexSet;
|
||||
use komodo_client::entities::{
|
||||
Operation, ResourceTarget, ResourceTargetVariant,
|
||||
Operation, ResourceTarget, ResourceTargetVariant, SwarmOrServer,
|
||||
build::Build,
|
||||
deployment::{
|
||||
Deployment, DeploymentConfig, DeploymentConfigDiff,
|
||||
@@ -19,13 +19,16 @@ use komodo_client::entities::{
|
||||
update::Update,
|
||||
user::User,
|
||||
};
|
||||
use periphery_client::api::container::RemoveContainer;
|
||||
use periphery_client::api::{
|
||||
container::RemoveContainer, swarm::RemoveSwarmServices,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::{
|
||||
empty_or_only_spaces, periphery_client,
|
||||
query::get_deployment_state,
|
||||
query::{get_deployment_state, get_swarm_or_server},
|
||||
swarm::swarm_request,
|
||||
},
|
||||
monitor::update_cache_for_server,
|
||||
state::{action_states, db_client, deployment_status_cache},
|
||||
@@ -115,14 +118,28 @@ impl super::KomodoResource for Deployment {
|
||||
let (image, update_available) = status
|
||||
.as_ref()
|
||||
.and_then(|s| {
|
||||
s.curr.container.as_ref().map(|c| {
|
||||
(
|
||||
c.image
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("Unknown")),
|
||||
s.curr.update_available,
|
||||
)
|
||||
})
|
||||
s.curr
|
||||
.service
|
||||
.as_ref()
|
||||
.map(|service| {
|
||||
(
|
||||
service
|
||||
.image
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("Unknown")),
|
||||
s.curr.update_available,
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
s.curr.container.as_ref().map(|c| {
|
||||
(
|
||||
c.image
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("Unknown")),
|
||||
s.curr.update_available,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or((build_image, false));
|
||||
DeploymentListItem {
|
||||
@@ -229,6 +246,11 @@ impl super::KomodoResource for Deployment {
|
||||
deployment: &Resource<Self::Config, Self::Info>,
|
||||
update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
if deployment.config.swarm_id.is_empty()
|
||||
&& deployment.config.server_id.is_empty()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let state = get_deployment_state(&deployment.id)
|
||||
.await
|
||||
.context("Failed to get deployment state")?;
|
||||
@@ -238,65 +260,86 @@ impl super::KomodoResource for Deployment {
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
// container needs to be destroyed
|
||||
let server = match super::get::<Server>(
|
||||
// container / service needs to be destroyed
|
||||
let swarm_or_server = match get_swarm_or_server(
|
||||
&deployment.config.swarm_id,
|
||||
&deployment.config.server_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(server) => server,
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"Remove Container",
|
||||
"Remove Container / Service",
|
||||
format_serror(
|
||||
&e.context(format!(
|
||||
"failed to retrieve server at {} from db.",
|
||||
deployment.config.server_id
|
||||
))
|
||||
&e.context(
|
||||
"Failed to retrieve Swarm / Server from database",
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if !server.config.enabled {
|
||||
// Don't need to
|
||||
update.push_simple_log(
|
||||
"Remove Container",
|
||||
"Skipping container removal, server is disabled.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let periphery = match periphery_client(&server).await {
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
// This case won't ever happen, as periphery_client only fallible if the server is disabled.
|
||||
// Leaving it for completeness sake
|
||||
update.push_error_log(
|
||||
"Remove Container",
|
||||
format_serror(
|
||||
&e.context("Failed to get periphery client").into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match periphery
|
||||
.request(RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: deployment.config.termination_signal.into(),
|
||||
time: deployment.config.termination_timeout.into(),
|
||||
})
|
||||
match swarm_or_server {
|
||||
SwarmOrServer::Swarm(swarm) => match swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
RemoveSwarmServices {
|
||||
services: vec![deployment.name.clone()],
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"Remove Container",
|
||||
format_serror(
|
||||
&e.context("Failed to remove container").into(),
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"Remove Service",
|
||||
format_serror(
|
||||
&e.context("Failed to remove service").into(),
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
SwarmOrServer::Server(server) => {
|
||||
if !server.config.enabled {
|
||||
// Don't need to
|
||||
update.push_simple_log(
|
||||
"Remove Container",
|
||||
"Skipping container removal, server is disabled.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let periphery = match periphery_client(&server).await {
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
// This case won't ever happen, as periphery_client only fallible if the server is disabled.
|
||||
// Leaving it for completeness sake
|
||||
update.push_error_log(
|
||||
"Remove Container",
|
||||
format_serror(
|
||||
&e.context("Failed to get periphery client").into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match periphery
|
||||
.request(RemoveContainer {
|
||||
name: deployment.name.clone(),
|
||||
signal: deployment.config.termination_signal.into(),
|
||||
time: deployment.config.termination_timeout.into(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_error_log(
|
||||
"Remove Container",
|
||||
format_serror(
|
||||
&e.context("Failed to remove container").into(),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+120
-72
@@ -5,7 +5,7 @@ use indexmap::IndexSet;
|
||||
use komodo_client::{
|
||||
api::write::RefreshStackCache,
|
||||
entities::{
|
||||
Operation, ResourceTarget, ResourceTargetVariant,
|
||||
Operation, ResourceTarget, ResourceTargetVariant, SwarmOrServer,
|
||||
permission::{PermissionLevel, SpecificPermission},
|
||||
repo::Repo,
|
||||
resource::Resource,
|
||||
@@ -20,14 +20,21 @@ use komodo_client::{
|
||||
user::{User, stack_user},
|
||||
},
|
||||
};
|
||||
use periphery_client::api::compose::ComposeExecution;
|
||||
use periphery_client::api::{
|
||||
compose::ComposeExecution, swarm::RemoveSwarmStacks,
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::write::WriteArgs,
|
||||
config::core_config,
|
||||
helpers::{periphery_client, query::get_stack_state, repo_link},
|
||||
monitor::update_cache_for_server,
|
||||
helpers::{
|
||||
periphery_client,
|
||||
query::{get_stack_state, get_swarm_or_server},
|
||||
repo_link,
|
||||
swarm::swarm_request,
|
||||
},
|
||||
monitor::{update_cache_for_server, update_cache_for_swarm},
|
||||
state::{
|
||||
action_states, all_resources_cache, db_client,
|
||||
server_status_cache, stack_status_cache,
|
||||
@@ -138,14 +145,14 @@ impl super::KomodoResource for Stack {
|
||||
|
||||
// This is only true if it is KNOWN to be true. so other cases are false.
|
||||
let (project_missing, status) =
|
||||
if stack.config.server_id.is_empty()
|
||||
|| matches!(state, StackState::Down | StackState::Unknown)
|
||||
{
|
||||
if matches!(state, StackState::Down | StackState::Unknown) {
|
||||
(false, None)
|
||||
} else if let Some(status) = server_status_cache()
|
||||
.get(&stack.config.server_id)
|
||||
.await
|
||||
.as_ref()
|
||||
} else if stack.config.swarm_id.is_empty()
|
||||
&& !stack.config.server_id.is_empty()
|
||||
&& let Some(status) = server_status_cache()
|
||||
.get(&stack.config.server_id)
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
if let Some(docker) = &status.docker {
|
||||
if let Some(project) = docker
|
||||
@@ -177,6 +184,7 @@ impl super::KomodoResource for Stack {
|
||||
services,
|
||||
project_missing,
|
||||
file_contents: !stack.config.file_contents.is_empty(),
|
||||
swarm_id: stack.config.swarm_id,
|
||||
server_id: stack.config.server_id,
|
||||
linked_repo: stack.config.linked_repo,
|
||||
missing_files: stack.info.missing_files,
|
||||
@@ -239,21 +247,32 @@ impl super::KomodoResource for Stack {
|
||||
format_serror(&e.error.context("The stack cache has failed to refresh. This may be due to a misconfiguration of the Stack").into())
|
||||
);
|
||||
};
|
||||
if created.config.server_id.is_empty() {
|
||||
if created.config.swarm_id.is_empty()
|
||||
&& created.config.server_id.is_empty()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let Ok(server) = super::get::<Server>(&created.config.server_id)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
"Failed to get Server for Stack {} | {e:#}",
|
||||
created.name
|
||||
)
|
||||
})
|
||||
else {
|
||||
let Ok(swarm_or_server) = get_swarm_or_server(
|
||||
&created.config.swarm_id,
|
||||
&created.config.server_id,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!(
|
||||
"Failed to get Swarm or Server for Stack {} | {e:#}",
|
||||
created.name
|
||||
)
|
||||
}) else {
|
||||
return Ok(());
|
||||
};
|
||||
update_cache_for_server(&server, true).await;
|
||||
match swarm_or_server {
|
||||
SwarmOrServer::Swarm(swarm) => {
|
||||
update_cache_for_swarm(&swarm, true).await;
|
||||
}
|
||||
SwarmOrServer::Server(server) => {
|
||||
update_cache_for_server(&server, true).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -302,65 +321,94 @@ impl super::KomodoResource for Stack {
|
||||
return Ok(());
|
||||
}
|
||||
// stack needs to be destroyed
|
||||
let server =
|
||||
match super::get::<Server>(&stack.config.server_id).await {
|
||||
Ok(server) => server,
|
||||
Err(e) => {
|
||||
update.push_error_log(
|
||||
"destroy stack",
|
||||
format_serror(
|
||||
&e.context(format!(
|
||||
"failed to retrieve server at {} from db.",
|
||||
stack.config.server_id
|
||||
))
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if !server.config.enabled {
|
||||
update.push_simple_log(
|
||||
"destroy stack",
|
||||
"skipping stack destroy, server is disabled.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let periphery = match periphery_client(&server).await {
|
||||
Ok(periphery) => periphery,
|
||||
let swarm_or_server = match get_swarm_or_server(
|
||||
&stack.config.swarm_id,
|
||||
&stack.config.server_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// This case won't ever happen, as periphery_client only fallible if the server is disabled.
|
||||
// Leaving it for completeness sake
|
||||
update.push_error_log(
|
||||
"destroy stack",
|
||||
"Destroy Stack",
|
||||
format_serror(
|
||||
&e.context("failed to get periphery client").into(),
|
||||
&e.context(
|
||||
"Failed to retrieve Swarm or Server from database.",
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: String::from("down --remove-orphans"),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_simple_log(
|
||||
"Failed to destroy stack",
|
||||
format_serror(
|
||||
&e.context(
|
||||
"failed to destroy stack on periphery server before delete",
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
};
|
||||
match swarm_or_server {
|
||||
SwarmOrServer::Swarm(swarm) => {
|
||||
match swarm_request(
|
||||
&swarm.config.server_ids,
|
||||
RemoveSwarmStacks {
|
||||
stacks: vec![stack.project_name(false)],
|
||||
detach: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_simple_log(
|
||||
"Failed to destroy stack",
|
||||
format_serror(
|
||||
&e.context(
|
||||
"Failed to destroy Stack on Swarm before delete",
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
SwarmOrServer::Server(server) => {
|
||||
if !server.config.enabled {
|
||||
update.push_simple_log(
|
||||
"Destroy Stack",
|
||||
"Skipping stack destroy, Server is disabled.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let periphery = match periphery_client(&server).await {
|
||||
Ok(periphery) => periphery,
|
||||
Err(e) => {
|
||||
// This case won't ever happen, as periphery_client only fallible if the server is disabled.
|
||||
// Leaving it for completeness sake
|
||||
update.push_error_log(
|
||||
"Destroy Stack",
|
||||
format_serror(
|
||||
&e.context("Failed to get periphery client").into(),
|
||||
),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match periphery
|
||||
.request(ComposeExecution {
|
||||
project: stack.project_name(false),
|
||||
command: String::from("down --remove-orphans"),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(log) => update.logs.push(log),
|
||||
Err(e) => update.push_simple_log(
|
||||
"Failed to destroy stack",
|
||||
format_serror(
|
||||
&e.context(
|
||||
"failed to destroy stack on periphery server before delete",
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use komodo_client::entities::{
|
||||
deployment::DeploymentState,
|
||||
docker::{
|
||||
DockerLists, SwarmLists, container::ContainerListItem,
|
||||
service::SwarmServiceListItem, stack::SwarmStackListItem,
|
||||
swarm::SwarmInspectInfo,
|
||||
},
|
||||
procedure::ProcedureState,
|
||||
@@ -141,6 +142,8 @@ pub struct CachedStackStatus {
|
||||
pub state: StackState,
|
||||
/// The services connected to the stack
|
||||
pub services: Vec<StackService>,
|
||||
/// Swarm mode only. Associated swarm stack.
|
||||
pub swarm_stack: Option<SwarmStackListItem>,
|
||||
}
|
||||
|
||||
pub type StackStatusCache =
|
||||
@@ -158,6 +161,7 @@ pub struct CachedDeploymentStatus {
|
||||
pub id: String,
|
||||
pub state: DeploymentState,
|
||||
pub container: Option<ContainerListItem>,
|
||||
pub service: Option<SwarmServiceListItem>,
|
||||
pub update_available: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ pub struct DeploymentConfig {
|
||||
/// Extra args which are interpolated into the
|
||||
/// `docker run` / `docker service create` command,
|
||||
/// and affect the container configuration.
|
||||
///
|
||||
///
|
||||
/// - Container ref: https://docs.docker.com/reference/cli/docker/container/run/#options
|
||||
/// - Swarm Service ref: https://docs.docker.com/reference/cli/docker/service/create/#options
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
@@ -379,23 +379,25 @@ pub fn conversions_from_str(
|
||||
pub enum DeploymentState {
|
||||
/// The deployment is currently re/deploying
|
||||
Deploying,
|
||||
/// Container is running
|
||||
/// Container / Service is running
|
||||
Running,
|
||||
/// Container is created but not running
|
||||
/// Server mode only. Container is created but not running.
|
||||
Created,
|
||||
/// Container is in restart loop
|
||||
/// Server mode only. Container is in restart loop
|
||||
Restarting,
|
||||
/// Container is being removed
|
||||
/// Server mode only. Container is being removed
|
||||
Removing,
|
||||
/// Container is paused
|
||||
/// Server mode only. Container is paused
|
||||
Paused,
|
||||
/// Container is exited
|
||||
/// Server mode only. Container is exited
|
||||
Exited,
|
||||
/// Container is dead
|
||||
/// Server mode only. Container is dead
|
||||
Dead,
|
||||
/// The deployment is not deployed (no matching container)
|
||||
/// Swarm mode only. Some tasks don't match their desired state.
|
||||
Unhealthy,
|
||||
/// The deployment is not deployed (no matching Container / Service)
|
||||
NotDeployed,
|
||||
/// Server not reachable for status
|
||||
/// Server / Swarm not reachable for status
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ pub mod volume;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SwarmLists {
|
||||
pub nodes: Vec<SwarmNodeListItem>,
|
||||
pub stacks: Vec<SwarmStackListItem>,
|
||||
pub services: Vec<SwarmServiceListItem>,
|
||||
pub tasks: Vec<SwarmTaskListItem>,
|
||||
pub secrets: Vec<SwarmSecretListItem>,
|
||||
pub configs: Vec<SwarmConfigListItem>,
|
||||
pub stacks: Vec<SwarmStackListItem>,
|
||||
pub secrets: Vec<SwarmSecretListItem>,
|
||||
}
|
||||
|
||||
/// Standard docker lists available from a Server.
|
||||
|
||||
@@ -20,7 +20,10 @@ use crate::{
|
||||
option_maybe_string_i64_deserializer,
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
},
|
||||
entities::{EnvironmentVar, environment_vars_from_str},
|
||||
entities::{
|
||||
EnvironmentVar, docker::service::SwarmServiceListItem,
|
||||
environment_vars_from_str,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -133,7 +136,9 @@ pub type StackListItem = ResourceListItem<StackListItemInfo>;
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StackListItemInfo {
|
||||
/// The server that stack is deployed on.
|
||||
/// The swarm that stack is deployed on, when in Swarm mode.
|
||||
pub swarm_id: String,
|
||||
/// The server that stack is deployed on, when in Server mode.
|
||||
pub server_id: String,
|
||||
/// Whether stack is using files on host mode
|
||||
pub files_on_host: bool,
|
||||
@@ -731,8 +736,10 @@ pub struct StackService {
|
||||
pub service: String,
|
||||
/// The service image
|
||||
pub image: String,
|
||||
/// The container
|
||||
/// The container (Server mode)
|
||||
pub container: Option<ContainerListItem>,
|
||||
/// The service (Swarm mode)
|
||||
pub swarm_service: Option<SwarmServiceListItem>,
|
||||
/// Whether there is an update available for this services image.
|
||||
pub update_available: bool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user