forked from github-starred/komodo
add ignore_services to stack
This commit is contained in:
@@ -5,13 +5,13 @@ use monitor_client::entities::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::{Deployment, DeploymentState},
|
||||
deployment::{ContainerSummary, Deployment, DeploymentState},
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::{Server, ServerState},
|
||||
server_template::ServerTemplate,
|
||||
stack::{ComposeProject, Stack, StackState},
|
||||
stack::{Stack, StackServiceNames, StackState},
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
update::{ResourceTarget, ResourceTargetVariant, Update},
|
||||
@@ -33,6 +33,11 @@ use crate::{
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::stack::{
|
||||
compose_container_match_regex,
|
||||
services::extract_services_from_stack,
|
||||
};
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
// user: Id or username
|
||||
pub async fn get_user(user: &str) -> anyhow::Result<User> {
|
||||
@@ -93,51 +98,76 @@ pub async fn get_deployment_state(
|
||||
}
|
||||
|
||||
/// Can pass all the containers from the same server
|
||||
pub fn get_stack_state_from_projects(
|
||||
stack: &Stack,
|
||||
projects: &[ComposeProject],
|
||||
pub fn get_stack_state_from_containers(
|
||||
ignore_services: &[String],
|
||||
services: &[StackServiceNames],
|
||||
containers: &[ContainerSummary],
|
||||
) -> StackState {
|
||||
let project_name = stack.project_name(false);
|
||||
let Some(status) = projects
|
||||
// first filter the containers to only ones which match the service
|
||||
let services = services
|
||||
.iter()
|
||||
.find(|project| project.name == project_name)
|
||||
.and_then(|project| project.status.as_deref())
|
||||
else {
|
||||
return StackState::Down;
|
||||
};
|
||||
let Ok(states) = status
|
||||
.split(", ")
|
||||
.filter_map(|state| state.split('(').next())
|
||||
.map(|state| {
|
||||
state.parse::<DeploymentState>().with_context(|| {
|
||||
format!("failed to parse stack state entry: {state}")
|
||||
})
|
||||
.filter(|service| {
|
||||
!ignore_services.contains(&service.service_name)
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
else {
|
||||
return StackState::Unknown;
|
||||
};
|
||||
if states.is_empty() {
|
||||
.collect::<Vec<_>>();
|
||||
let containers = containers.iter().filter(|container| {
|
||||
services.iter().any(|StackServiceNames { service_name, container_name }| {
|
||||
match compose_container_match_regex(container_name)
|
||||
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
|
||||
{
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
return false
|
||||
}
|
||||
}.is_match(&container.name)
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
if containers.is_empty() {
|
||||
return StackState::Down;
|
||||
}
|
||||
if states.len() > 1 {
|
||||
if services.len() != containers.len() {
|
||||
return StackState::Unhealthy;
|
||||
}
|
||||
match states[0] {
|
||||
DeploymentState::Unknown => StackState::Unknown,
|
||||
DeploymentState::NotDeployed => StackState::Down,
|
||||
DeploymentState::Created => StackState::Created,
|
||||
DeploymentState::Restarting => StackState::Restarting,
|
||||
DeploymentState::Running => StackState::Running,
|
||||
DeploymentState::Removing => StackState::Removing,
|
||||
DeploymentState::Paused => StackState::Paused,
|
||||
DeploymentState::Exited => StackState::Stopped,
|
||||
DeploymentState::Dead => StackState::Dead,
|
||||
let running = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Running);
|
||||
if running {
|
||||
return StackState::Running;
|
||||
}
|
||||
let paused = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Paused);
|
||||
if paused {
|
||||
return StackState::Paused;
|
||||
}
|
||||
let stopped = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Exited);
|
||||
if stopped {
|
||||
return StackState::Stopped;
|
||||
}
|
||||
let restarting = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Restarting);
|
||||
if restarting {
|
||||
return StackState::Restarting;
|
||||
}
|
||||
let dead = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Dead);
|
||||
if dead {
|
||||
return StackState::Dead;
|
||||
}
|
||||
let removing = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Removing);
|
||||
if removing {
|
||||
return StackState::Removing;
|
||||
}
|
||||
StackState::Unhealthy
|
||||
}
|
||||
|
||||
/// Gets stack state fresh from periphery
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_stack_state(
|
||||
stack: &Stack,
|
||||
@@ -150,11 +180,17 @@ pub async fn get_stack_state(
|
||||
if status != ServerState::Ok {
|
||||
return Ok(StackState::Unknown);
|
||||
}
|
||||
let projects = super::periphery_client(&server)?
|
||||
.request(periphery_client::api::compose::ListComposeProjects {})
|
||||
let containers = super::periphery_client(&server)?
|
||||
.request(periphery_client::api::container::GetContainerList {})
|
||||
.await?;
|
||||
|
||||
Ok(get_stack_state_from_projects(stack, &projects))
|
||||
let services = extract_services_from_stack(stack, false).await?;
|
||||
|
||||
Ok(get_stack_state_from_containers(
|
||||
&stack.config.ignore_services,
|
||||
&services,
|
||||
&containers,
|
||||
))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
|
||||
@@ -214,7 +214,7 @@ pub async fn update_cache_for_server(server: &Server) {
|
||||
Ok((containers, networks, images, projects)) => {
|
||||
tokio::join!(
|
||||
resources::update_deployment_cache(deployments, &containers),
|
||||
resources::update_stack_cache(stacks, &containers, &projects),
|
||||
resources::update_stack_cache(stacks, &containers),
|
||||
);
|
||||
insert_server_status(
|
||||
server,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use anyhow::Context;
|
||||
use monitor_client::entities::{
|
||||
deployment::{ContainerSummary, Deployment, DeploymentState},
|
||||
stack::{ComposeProject, Stack, StackService, StackServiceNames},
|
||||
stack::{Stack, StackService, StackServiceNames},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_stack_state_from_projects,
|
||||
query::get_stack_state_from_containers,
|
||||
stack::{
|
||||
compose_container_match_regex,
|
||||
services::extract_services_from_stack,
|
||||
@@ -55,7 +55,6 @@ pub async fn update_deployment_cache(
|
||||
pub async fn update_stack_cache(
|
||||
stacks: Vec<Stack>,
|
||||
containers: &[ContainerSummary],
|
||||
projects: &[ComposeProject],
|
||||
) {
|
||||
let stack_status_cache = stack_status_cache();
|
||||
for stack in stacks {
|
||||
@@ -93,7 +92,11 @@ pub async fn update_stack_cache(
|
||||
.map(|s| s.curr.state);
|
||||
let status = CachedStackStatus {
|
||||
id: stack.id.clone(),
|
||||
state: get_stack_state_from_projects(&stack, projects),
|
||||
state: get_stack_state_from_containers(
|
||||
&stack.config.ignore_services,
|
||||
&services,
|
||||
containers,
|
||||
),
|
||||
services: services_with_containers,
|
||||
};
|
||||
stack_status_cache
|
||||
|
||||
@@ -306,6 +306,13 @@ pub struct StackConfig {
|
||||
#[builder(default)]
|
||||
pub file_contents: String,
|
||||
|
||||
/// Ignore certain services declared in the compose file when checking
|
||||
/// the stack status. For example, an init service might be exited, but the
|
||||
/// stack should be healthy. This init service should be in `ignore_services`
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub ignore_services: Vec<String>,
|
||||
|
||||
/// The git provider domain. Default: github.com
|
||||
#[serde(default = "default_git_provider")]
|
||||
#[builder(default = "default_git_provider()")]
|
||||
@@ -402,6 +409,7 @@ impl Default for StackConfig {
|
||||
registry_provider: Default::default(),
|
||||
registry_account: Default::default(),
|
||||
file_contents: Default::default(),
|
||||
ignore_services: Default::default(),
|
||||
extra_args: Default::default(),
|
||||
environment: Default::default(),
|
||||
env_file_path: default_env_file_path(),
|
||||
|
||||
@@ -1492,6 +1492,12 @@ export interface StackConfig {
|
||||
* repo based compose file.
|
||||
*/
|
||||
file_contents?: string;
|
||||
/**
|
||||
* Ignore certain services declared in the compose file when checking
|
||||
* the stack status. For example, an init service might be exited, but the
|
||||
* stack should be healthy. This init service should be in `ignore_services`
|
||||
*/
|
||||
ignore_services?: string[];
|
||||
/** The git provider domain. Default: github.com */
|
||||
git_provider: string;
|
||||
/**
|
||||
|
||||
@@ -212,6 +212,42 @@ export const StackConfig = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Ignore Services",
|
||||
description:
|
||||
"If your compose file has init containers that exit early, ignore them here so your stack will report the correct health.",
|
||||
actions: !disabled && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
set((update) => ({
|
||||
...update,
|
||||
ignore_services: [
|
||||
...(update.ignore_services ??
|
||||
config.ignore_services ??
|
||||
[]),
|
||||
"",
|
||||
],
|
||||
}))
|
||||
}
|
||||
className="flex items-center gap-2 w-[200px]"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
Add Service
|
||||
</Button>
|
||||
),
|
||||
components: {
|
||||
ignore_services: (values, set) => (
|
||||
<InputList
|
||||
field="ignore_services"
|
||||
values={values ?? []}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
placeholder="Input service name"
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
"Git Repo": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user