use std::collections::BTreeMap;
use isoloom_manifest::{SnakeName, Step, Template};
use super::{Compose, NO_PINS, config, docker, pins, ps};
use crate::Result;
use crate::error::Error;
use crate::executor::{Cx, env_of};
use crate::store::write_private;
const PROJECT_LABEL: &str = "com.docker.compose.project";
pub(super) async fn pin_ports(c: &Compose, cx: &Cx<'_>, step: &Step) -> Result<()> {
let Some(file) = c.pins(cx)? else {
return Ok(());
};
let env = env_of(cx, "lifecycle.up", step)?;
let listed = c.compose(cx, step, &["ps", "--all", "--format", "json"], &env).await?;
if ps::parse_ps(&listed).iter().any(|e| !e.one_off()) {
return Ok(());
}
write_private(&file, NO_PINS.as_bytes()).map_err(Error::io(&file, "written"))?;
let config = c.compose(cx, step, &["config", "--format", "json"], &env).await?;
if let Some(yaml) = pins::pick(cx, &config) {
write_private(&file, yaml.as_bytes()).map_err(Error::io(&file, "written"))?;
}
Ok(())
}
pub(super) async fn check_ports_free(c: &Compose, cx: &Cx<'_>, step: &Step) -> Result<()> {
let env = env_of(cx, "lifecycle.up", step)?;
let listed = c.compose(cx, step, &["ps", "--format", "json"], &env).await?;
if ps::parse_ps(&listed).iter().any(|e| !e.one_off() && e.state == "running") {
return Ok(());
}
let config = c.compose(cx, step, &["config", "--format", "json"], &env).await?;
let runner = cx.run.runner;
for (port, protocol) in config::host_ports(&config) {
if runner.reserved_elsewhere(cx.run.name(), port, protocol) || runner.config.ports.in_use(port, protocol) {
let protocol = if protocol == isoloom_manifest::Protocol::Udp { "udp" } else { "tcp" };
return Err(Error::PortInUse { port, protocol });
}
}
Ok(())
}
pub(super) async fn remove_stale_networks(c: &Compose, cx: &Cx<'_>, step: &Step) -> Result<()> {
let env = env_of(cx, "lifecycle.up", step)?;
let config = c.compose(cx, step, &["config", "--format", "json"], &env).await?;
let wanted = config::networks(&config, &c.project(cx)?);
for net in project_networks(c, cx, step).await? {
if !wanted.contains(&net) {
remove_network(cx, step, &net).await?;
}
}
Ok(())
}
pub(super) async fn remove_networks(c: &Compose, cx: &Cx<'_>, step: &Step) -> Result<()> {
for net in project_networks(c, cx, step).await? {
remove_network(cx, step, &net).await?;
}
Ok(())
}
pub(super) async fn remove_containers(c: &Compose, cx: &Cx<'_>, step: &Step, with: &BTreeMap<SnakeName, Template>) -> Result<()> {
let arg = |key: &str| with.iter().find(|(k, _)| k.as_str() == key).map(|(_, v)| cx.vars.render(v, "with")).transpose();
let mut args: Vec<String> = ["ps", "-aq", "--filter"].map(String::from).to_vec();
args.push(format!("label={PROJECT_LABEL}={}", c.project(cx)?));
if let Some(service) = arg("service")? {
args.extend(["--filter".into(), format!("label=com.docker.compose.service={service}")]);
}
if arg("one_off")?.as_deref() == Some("true") {
args.extend(["--filter".into(), "label=com.docker.compose.oneoff=True".into()]);
}
let ids = docker(cx, step, args, &[]).await?;
let ids: Vec<String> = ids.split_whitespace().map(str::to_owned).collect();
if ids.is_empty() {
return Ok(());
}
let rm = ["rm", "-f"].map(String::from).into_iter().chain(ids).collect();
docker(cx, step, rm, &[]).await.map(drop)
}
async fn project_networks(c: &Compose, cx: &Cx<'_>, step: &Step) -> Result<Vec<String>> {
let filter = format!("label={PROJECT_LABEL}={}", c.project(cx)?);
let args = ["network", "ls", "--filter", &filter, "--format", "{{.Name}}"].map(String::from).to_vec();
let listed = docker(cx, step, args, &[]).await?;
Ok(listed.lines().map(str::trim).filter(|n| !n.is_empty()).map(str::to_owned).collect())
}
async fn remove_network(cx: &Cx<'_>, step: &Step, net: &str) -> Result<()> {
let inspect = ["network", "inspect", "-f", "{{range .Containers}}{{.Name}} {{end}}", net]
.map(String::from)
.to_vec();
let attached = docker(cx, step, inspect, &[]).await?;
for container in attached.split_whitespace() {
let disconnect = ["network", "disconnect", "-f", net, container].map(String::from).to_vec();
docker(cx, step, disconnect, &[]).await?;
}
docker(cx, step, ["network", "rm", net].map(String::from).to_vec(), &[]).await.map(drop)
}