mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-11 16:01:27 +00:00
* inc version * Komodo interp in ui compose file * fix auto update when image doesn't specify tag by defaulting to latest * Pull image buttons don't need safety dialog * WIP crosscompile * rename * entrypoint * fix copy * remove example/* from workspace * add targets * multiarch pkg config * use specific COPY * update deps * multiarch build command * pre compile deps * cross compile * enable-linger * remove spammed log when server doesn't have docker * add multiarch.Dockerfile * fix casing * fix tag * try not let COPY fail * try * ARG TARGETPLATFORM * use /app for consistency * try * delete cross-compile approach * add multiarch core build * multiarch Deno * single arch multi arch * typeshare cli note * new typeshare * remove note about aarch64 image * test configs * fix config file headers * binaries dockerfile * update cargo build * docs * simple * just simple * use -p * add configurable binaries tag * add multi-arch * allow copy to fail * fix binary paths * frontend Dockerfiel * use dedicated static frontend build * auto retry getting instance state from aws * retry 5 times * cleanup * simplify binary build * try alpine and musl * install alpine deps * back to debian, try rustls * move fully to rustls * single arch builds using single binary image * default IMAGE_TAG * cleanup * try caching deps * single arch add frontend build * rustls::crypto::ring::default_provider() * back to simple * comment dockerfile * add select options prop, render checkboxes if present * add allowSelectedIf to enable / disable rows where necessary * rename allowSelectIf to isSelectable, allow false as global disable, disable checkboxes when not allowed * rename isSelectable to disableRow (it works the oppsite way lol) * selected resources hook, start deployment batch execute component * add deployment group actions * add deployment group actions * add default (empty) group actions for other resources * fix checkbox header styles * explicitly check if disableRow is passed (this prop is cursed) * don't disable row selection for deployments table * don't need id for groupactions * add group actions to resources page * fix row checkbox (prop not cursed, i dumb) * re-implement group action list using dropdown menu * only make group actions clickable when at least one row selected * add loading indicator * gap betwen new resource and group actions * refactor group actions * remove "Batch" from action labels * add group actions for relevant resources * fix hardcode * add selectOptions to relevant tables * select by name not id * expect selected to be names * add note re selection state init for future reference * multi select working nicely for all resources * configure server health check timeout * config message * refresh processes remove dead processes * simplify the build args * default timeout seconds 3 --------- Co-authored-by: kv <karamvir.singh98@gmail.com>
301 lines
7.9 KiB
Rust
301 lines
7.9 KiB
Rust
use async_timing_util::wait_until_timelength;
|
|
use futures::future::join_all;
|
|
use helpers::insert_stacks_status_unknown;
|
|
use komodo_client::entities::{
|
|
deployment::DeploymentState,
|
|
docker::{
|
|
container::ContainerListItem, image::ImageListItem,
|
|
network::NetworkListItem, volume::VolumeListItem,
|
|
},
|
|
komodo_timestamp,
|
|
server::{Server, ServerHealth, ServerState},
|
|
stack::{ComposeProject, StackService, StackState},
|
|
stats::SystemStats,
|
|
};
|
|
use mungos::{find::find_collect, mongodb::bson::doc};
|
|
use periphery_client::api::{self, git::GetLatestCommit};
|
|
use serror::Serror;
|
|
|
|
use crate::{
|
|
config::core_config,
|
|
helpers::periphery_client,
|
|
monitor::{alert::check_alerts, record::record_server_stats},
|
|
state::{db_client, deployment_status_cache, repo_status_cache},
|
|
};
|
|
|
|
use self::helpers::{
|
|
insert_deployments_status_unknown, insert_repos_status_unknown,
|
|
insert_server_status,
|
|
};
|
|
|
|
mod alert;
|
|
mod helpers;
|
|
mod lists;
|
|
mod record;
|
|
mod resources;
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct History<Curr: Default, Prev> {
|
|
pub curr: Curr,
|
|
pub prev: Option<Prev>,
|
|
}
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub struct CachedServerStatus {
|
|
pub id: String,
|
|
pub state: ServerState,
|
|
pub version: String,
|
|
pub stats: Option<SystemStats>,
|
|
pub health: Option<ServerHealth>,
|
|
pub containers: Option<Vec<ContainerListItem>>,
|
|
pub networks: Option<Vec<NetworkListItem>>,
|
|
pub images: Option<Vec<ImageListItem>>,
|
|
pub volumes: Option<Vec<VolumeListItem>>,
|
|
pub projects: Option<Vec<ComposeProject>>,
|
|
/// Store the error in reaching periphery
|
|
pub err: Option<serror::Serror>,
|
|
}
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub struct CachedDeploymentStatus {
|
|
/// The deployment id
|
|
pub id: String,
|
|
pub state: DeploymentState,
|
|
pub container: Option<ContainerListItem>,
|
|
pub update_available: bool,
|
|
}
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub struct CachedRepoStatus {
|
|
pub latest_hash: Option<String>,
|
|
pub latest_message: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub struct CachedStackStatus {
|
|
/// The stack id
|
|
pub id: String,
|
|
/// The stack state
|
|
pub state: StackState,
|
|
/// The services connected to the stack
|
|
pub services: Vec<StackService>,
|
|
}
|
|
|
|
const ADDITIONAL_MS: u128 = 500;
|
|
|
|
pub fn spawn_monitor_loop() {
|
|
let interval: async_timing_util::Timelength = core_config()
|
|
.monitoring_interval
|
|
.try_into()
|
|
.expect("Invalid monitoring interval");
|
|
tokio::spawn(async move {
|
|
refresh_server_cache(komodo_timestamp()).await;
|
|
loop {
|
|
let ts = (wait_until_timelength(interval, ADDITIONAL_MS).await
|
|
- ADDITIONAL_MS) as i64;
|
|
refresh_server_cache(ts).await;
|
|
}
|
|
});
|
|
}
|
|
|
|
async fn refresh_server_cache(ts: i64) {
|
|
let servers =
|
|
match find_collect(&db_client().servers, None, None).await {
|
|
Ok(servers) => servers,
|
|
Err(e) => {
|
|
error!(
|
|
"failed to get server list (manage status cache) | {e:#}"
|
|
);
|
|
return;
|
|
}
|
|
};
|
|
let futures = servers.into_iter().map(|server| async move {
|
|
update_cache_for_server(&server).await;
|
|
});
|
|
join_all(futures).await;
|
|
tokio::join!(check_alerts(ts), record_server_stats(ts));
|
|
}
|
|
|
|
#[instrument(level = "debug")]
|
|
pub async fn update_cache_for_server(server: &Server) {
|
|
let (deployments, builds, repos, stacks) = tokio::join!(
|
|
find_collect(
|
|
&db_client().deployments,
|
|
doc! { "config.server_id": &server.id },
|
|
None,
|
|
),
|
|
find_collect(&db_client().builds, doc! {}, None,),
|
|
find_collect(
|
|
&db_client().repos,
|
|
doc! { "config.server_id": &server.id },
|
|
None,
|
|
),
|
|
find_collect(
|
|
&db_client().stacks,
|
|
doc! { "config.server_id": &server.id },
|
|
None,
|
|
)
|
|
);
|
|
|
|
let deployments = deployments.inspect_err(|e| error!("failed to get deployments list from db (update status cache) | server : {} | {e:#}", server.name)).unwrap_or_default();
|
|
let builds = builds.inspect_err(|e| error!("failed to get builds list from db (update status cache) | server : {} | {e:#}", server.name)).unwrap_or_default();
|
|
let repos = repos.inspect_err(|e| error!("failed to get repos list from db (update status cache) | server: {} | {e:#}", server.name)).unwrap_or_default();
|
|
let stacks = stacks.inspect_err(|e| error!("failed to get stacks list from db (update status cache) | server: {} | {e:#}", server.name)).unwrap_or_default();
|
|
|
|
// Handle server disabled
|
|
if !server.config.enabled {
|
|
insert_deployments_status_unknown(deployments).await;
|
|
insert_repos_status_unknown(repos).await;
|
|
insert_stacks_status_unknown(stacks).await;
|
|
insert_server_status(
|
|
server,
|
|
ServerState::Disabled,
|
|
String::from("unknown"),
|
|
None,
|
|
(None, None, None, None, None),
|
|
None,
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
|
|
let Ok(periphery) = periphery_client(server) else {
|
|
error!(
|
|
"somehow periphery not ok to create. should not be reached."
|
|
);
|
|
return;
|
|
};
|
|
|
|
let version = match periphery.request(api::GetVersion {}).await {
|
|
Ok(version) => version.version,
|
|
Err(e) => {
|
|
insert_deployments_status_unknown(deployments).await;
|
|
insert_repos_status_unknown(repos).await;
|
|
insert_stacks_status_unknown(stacks).await;
|
|
insert_server_status(
|
|
server,
|
|
ServerState::NotOk,
|
|
String::from("unknown"),
|
|
None,
|
|
(None, None, None, None, None),
|
|
Serror::from(&e),
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
};
|
|
|
|
let stats = if server.config.stats_monitoring {
|
|
match periphery.request(api::stats::GetSystemStats {}).await {
|
|
Ok(stats) => Some(filter_volumes(server, stats)),
|
|
Err(e) => {
|
|
insert_deployments_status_unknown(deployments).await;
|
|
insert_repos_status_unknown(repos).await;
|
|
insert_stacks_status_unknown(stacks).await;
|
|
insert_server_status(
|
|
server,
|
|
ServerState::NotOk,
|
|
String::from("unknown"),
|
|
None,
|
|
(None, None, None, None, None),
|
|
Serror::from(&e),
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
match lists::get_docker_lists(&periphery).await {
|
|
Ok((mut containers, networks, images, volumes, projects)) => {
|
|
containers.iter_mut().for_each(|container| {
|
|
container.server_id = Some(server.id.clone())
|
|
});
|
|
tokio::join!(
|
|
resources::update_deployment_cache(
|
|
server.name.clone(),
|
|
deployments,
|
|
&containers,
|
|
&images,
|
|
&builds,
|
|
),
|
|
resources::update_stack_cache(
|
|
server.name.clone(),
|
|
stacks,
|
|
&containers,
|
|
&images
|
|
),
|
|
);
|
|
insert_server_status(
|
|
server,
|
|
ServerState::Ok,
|
|
version,
|
|
stats,
|
|
(
|
|
Some(containers.clone()),
|
|
Some(networks),
|
|
Some(images),
|
|
Some(volumes),
|
|
Some(projects),
|
|
),
|
|
None,
|
|
)
|
|
.await;
|
|
}
|
|
Err(e) => {
|
|
insert_deployments_status_unknown(deployments).await;
|
|
insert_stacks_status_unknown(stacks).await;
|
|
insert_server_status(
|
|
server,
|
|
ServerState::Ok,
|
|
version,
|
|
stats,
|
|
(None, None, None, None, None),
|
|
Some(e.into()),
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
let status_cache = repo_status_cache();
|
|
for repo in repos {
|
|
let (latest_hash, latest_message) = periphery
|
|
.request(GetLatestCommit {
|
|
name: repo.name.clone(),
|
|
})
|
|
.await
|
|
.map(|r| (r.hash, r.message))
|
|
.ok()
|
|
.unzip();
|
|
status_cache
|
|
.insert(
|
|
repo.id,
|
|
CachedRepoStatus {
|
|
latest_hash,
|
|
latest_message,
|
|
}
|
|
.into(),
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
fn filter_volumes(
|
|
server: &Server,
|
|
mut stats: SystemStats,
|
|
) -> SystemStats {
|
|
stats.disks.retain(|disk| {
|
|
// Always filter out volume mounts
|
|
!disk.mount.starts_with("/var/lib/docker/volumes")
|
|
// Filter out any that were declared to ignore in server config
|
|
&& !server
|
|
.config
|
|
.ignore_mounts
|
|
.iter()
|
|
.any(|mount| disk.mount.starts_with(mount))
|
|
});
|
|
stats
|
|
}
|