mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-11 08:01:13 +00:00
* add periphery.skip label, skip in StopAllContainers * add core config sync directory * deploy stack if changed * fix stack env_file_path when git repo and using run_directory * deploy stack if changed * write sync contents * commit to git based sync, managed git based sync * can sync non UI defined resource syncs * sync UI control * clippy * init new stack compose file in repo * better error message when attached Server / Builder invalid * specify multiple resource file paths (mixed files + folders) * use react charts * tweak stats charts * add Containers page * 1.15.6 * stack deploy check if deployes vs remote has changed * improve ux with loading indicators * sync diff accounts for deploy / after * fix new chart time axes
450 lines
10 KiB
Rust
450 lines
10 KiB
Rust
use anyhow::{anyhow, Context};
|
|
use command::run_komodo_command;
|
|
use futures::future::join_all;
|
|
use komodo_client::entities::{
|
|
docker::container::{Container, ContainerListItem, ContainerStats},
|
|
to_komodo_name,
|
|
update::Log,
|
|
};
|
|
use periphery_client::api::container::*;
|
|
use resolver_api::Resolve;
|
|
|
|
use crate::{
|
|
docker::{container_stats, docker_client, stop_container_command},
|
|
helpers::log_grep,
|
|
State,
|
|
};
|
|
|
|
// ======
|
|
// READ
|
|
// ======
|
|
|
|
//
|
|
|
|
impl Resolve<InspectContainer> for State {
|
|
#[instrument(
|
|
name = "InspectContainer",
|
|
level = "debug",
|
|
skip(self)
|
|
)]
|
|
async fn resolve(
|
|
&self,
|
|
InspectContainer { name }: InspectContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Container> {
|
|
docker_client().inspect_container(&name).await
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<GetContainerLog> for State {
|
|
#[instrument(name = "GetContainerLog", level = "debug", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
GetContainerLog {
|
|
name,
|
|
tail,
|
|
timestamps,
|
|
}: GetContainerLog,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
let timestamps =
|
|
timestamps.then_some(" --timestamps").unwrap_or_default();
|
|
let command =
|
|
format!("docker logs {name} --tail {tail}{timestamps}");
|
|
Ok(run_komodo_command("get container log", None, command).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<GetContainerLogSearch> for State {
|
|
#[instrument(
|
|
name = "GetContainerLogSearch",
|
|
level = "debug",
|
|
skip(self)
|
|
)]
|
|
async fn resolve(
|
|
&self,
|
|
GetContainerLogSearch {
|
|
name,
|
|
terms,
|
|
combinator,
|
|
invert,
|
|
timestamps,
|
|
}: GetContainerLogSearch,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
let grep = log_grep(&terms, combinator, invert);
|
|
let timestamps =
|
|
timestamps.then_some(" --timestamps").unwrap_or_default();
|
|
let command = format!(
|
|
"docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}"
|
|
);
|
|
Ok(
|
|
run_komodo_command("get container log grep", None, command)
|
|
.await,
|
|
)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<GetContainerStats> for State {
|
|
#[instrument(
|
|
name = "GetContainerStats",
|
|
level = "debug",
|
|
skip(self)
|
|
)]
|
|
async fn resolve(
|
|
&self,
|
|
req: GetContainerStats,
|
|
_: (),
|
|
) -> anyhow::Result<ContainerStats> {
|
|
let error = anyhow!("no stats matching {}", req.name);
|
|
let mut stats = container_stats(Some(req.name)).await?;
|
|
let stats = stats.pop().ok_or(error)?;
|
|
Ok(stats)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<GetContainerStatsList> for State {
|
|
#[instrument(
|
|
name = "GetContainerStatsList",
|
|
level = "debug",
|
|
skip(self)
|
|
)]
|
|
async fn resolve(
|
|
&self,
|
|
_: GetContainerStatsList,
|
|
_: (),
|
|
) -> anyhow::Result<Vec<ContainerStats>> {
|
|
container_stats(None).await
|
|
}
|
|
}
|
|
|
|
// =========
|
|
// ACTIONS
|
|
// =========
|
|
|
|
impl Resolve<StartContainer> for State {
|
|
#[instrument(name = "StartContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
StartContainer { name }: StartContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
Ok(
|
|
run_komodo_command(
|
|
"docker start",
|
|
None,
|
|
format!("docker start {name}"),
|
|
)
|
|
.await,
|
|
)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<RestartContainer> for State {
|
|
#[instrument(name = "RestartContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
RestartContainer { name }: RestartContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
Ok(
|
|
run_komodo_command(
|
|
"docker restart",
|
|
None,
|
|
format!("docker restart {name}"),
|
|
)
|
|
.await,
|
|
)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<PauseContainer> for State {
|
|
#[instrument(name = "PauseContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
PauseContainer { name }: PauseContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
Ok(
|
|
run_komodo_command(
|
|
"docker pause",
|
|
None,
|
|
format!("docker pause {name}"),
|
|
)
|
|
.await,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Resolve<UnpauseContainer> for State {
|
|
#[instrument(name = "UnpauseContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
UnpauseContainer { name }: UnpauseContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
Ok(
|
|
run_komodo_command(
|
|
"docker unpause",
|
|
None,
|
|
format!("docker unpause {name}"),
|
|
)
|
|
.await,
|
|
)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<StopContainer> for State {
|
|
#[instrument(name = "StopContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
StopContainer { name, signal, time }: StopContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
let command = stop_container_command(&name, signal, time);
|
|
let log = run_komodo_command("docker stop", None, command).await;
|
|
if log.stderr.contains("unknown flag: --signal") {
|
|
let command = stop_container_command(&name, None, time);
|
|
let mut log =
|
|
run_komodo_command("docker stop", None, command).await;
|
|
log.stderr = format!(
|
|
"old docker version: unable to use --signal flag{}",
|
|
if !log.stderr.is_empty() {
|
|
format!("\n\n{}", log.stderr)
|
|
} else {
|
|
String::new()
|
|
}
|
|
);
|
|
Ok(log)
|
|
} else {
|
|
Ok(log)
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<RemoveContainer> for State {
|
|
#[instrument(name = "RemoveContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
RemoveContainer { name, signal, time }: RemoveContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
let stop_command = stop_container_command(&name, signal, time);
|
|
let command =
|
|
format!("{stop_command} && docker container rm {name}");
|
|
let log =
|
|
run_komodo_command("docker stop and remove", None, command)
|
|
.await;
|
|
if log.stderr.contains("unknown flag: --signal") {
|
|
let stop_command = stop_container_command(&name, None, time);
|
|
let command =
|
|
format!("{stop_command} && docker container rm {name}");
|
|
let mut log =
|
|
run_komodo_command("docker stop", None, command).await;
|
|
log.stderr = format!(
|
|
"old docker version: unable to use --signal flag{}",
|
|
if !log.stderr.is_empty() {
|
|
format!("\n\n{}", log.stderr)
|
|
} else {
|
|
String::new()
|
|
}
|
|
);
|
|
Ok(log)
|
|
} else {
|
|
Ok(log)
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<RenameContainer> for State {
|
|
#[instrument(name = "RenameContainer", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
RenameContainer {
|
|
curr_name,
|
|
new_name,
|
|
}: RenameContainer,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
let new = to_komodo_name(&new_name);
|
|
let command = format!("docker rename {curr_name} {new}");
|
|
Ok(run_komodo_command("docker rename", None, command).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<PruneContainers> for State {
|
|
#[instrument(name = "PruneContainers", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
_: PruneContainers,
|
|
_: (),
|
|
) -> anyhow::Result<Log> {
|
|
let command = String::from("docker container prune -f");
|
|
Ok(run_komodo_command("prune containers", None, command).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<StartAllContainers> for State {
|
|
#[instrument(name = "StartAllContainers", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
StartAllContainers {}: StartAllContainers,
|
|
_: (),
|
|
) -> anyhow::Result<Vec<Log>> {
|
|
let containers = docker_client()
|
|
.list_containers()
|
|
.await
|
|
.context("failed to list all containers on host")?;
|
|
let futures = containers.iter().filter_map(
|
|
|ContainerListItem { name, labels, .. }| {
|
|
if labels.contains_key("komodo.skip") {
|
|
return None;
|
|
}
|
|
let command = format!("docker start {name}");
|
|
Some(async move {
|
|
run_komodo_command(&command.clone(), None, command).await
|
|
})
|
|
},
|
|
);
|
|
Ok(join_all(futures).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<RestartAllContainers> for State {
|
|
#[instrument(name = "RestartAllContainers", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
RestartAllContainers {}: RestartAllContainers,
|
|
_: (),
|
|
) -> anyhow::Result<Vec<Log>> {
|
|
let containers = docker_client()
|
|
.list_containers()
|
|
.await
|
|
.context("failed to list all containers on host")?;
|
|
let futures = containers.iter().filter_map(
|
|
|ContainerListItem { name, labels, .. }| {
|
|
if labels.contains_key("komodo.skip") {
|
|
return None;
|
|
}
|
|
let command = format!("docker restart {name}");
|
|
Some(async move {
|
|
run_komodo_command(&command.clone(), None, command).await
|
|
})
|
|
},
|
|
);
|
|
Ok(join_all(futures).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<PauseAllContainers> for State {
|
|
#[instrument(name = "PauseAllContainers", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
PauseAllContainers {}: PauseAllContainers,
|
|
_: (),
|
|
) -> anyhow::Result<Vec<Log>> {
|
|
let containers = docker_client()
|
|
.list_containers()
|
|
.await
|
|
.context("failed to list all containers on host")?;
|
|
let futures = containers.iter().filter_map(
|
|
|ContainerListItem { name, labels, .. }| {
|
|
if labels.contains_key("komodo.skip") {
|
|
return None;
|
|
}
|
|
let command = format!("docker pause {name}");
|
|
Some(async move {
|
|
run_komodo_command(&command.clone(), None, command).await
|
|
})
|
|
},
|
|
);
|
|
Ok(join_all(futures).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<UnpauseAllContainers> for State {
|
|
#[instrument(name = "UnpauseAllContainers", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
UnpauseAllContainers {}: UnpauseAllContainers,
|
|
_: (),
|
|
) -> anyhow::Result<Vec<Log>> {
|
|
let containers = docker_client()
|
|
.list_containers()
|
|
.await
|
|
.context("failed to list all containers on host")?;
|
|
let futures = containers.iter().filter_map(
|
|
|ContainerListItem { name, labels, .. }| {
|
|
if labels.contains_key("komodo.skip") {
|
|
return None;
|
|
}
|
|
let command = format!("docker unpause {name}");
|
|
Some(async move {
|
|
run_komodo_command(&command.clone(), None, command).await
|
|
})
|
|
},
|
|
);
|
|
Ok(join_all(futures).await)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
impl Resolve<StopAllContainers> for State {
|
|
#[instrument(name = "StopAllContainers", skip(self))]
|
|
async fn resolve(
|
|
&self,
|
|
StopAllContainers {}: StopAllContainers,
|
|
_: (),
|
|
) -> anyhow::Result<Vec<Log>> {
|
|
let containers = docker_client()
|
|
.list_containers()
|
|
.await
|
|
.context("failed to list all containers on host")?;
|
|
let futures = containers.iter().filter_map(
|
|
|ContainerListItem { name, labels, .. }| {
|
|
if labels.contains_key("komodo.skip") {
|
|
return None;
|
|
}
|
|
Some(async move {
|
|
run_komodo_command(
|
|
&format!("docker stop {name}"),
|
|
None,
|
|
stop_container_command(name, None, None),
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
Ok(join_all(futures).await)
|
|
}
|
|
}
|