mod actions;
mod config;
mod pins;
mod ps;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::Duration;
use isoloom_manifest::{Action, ComposeIdentity, SnakeName, Step, Template};
use super::{Driver, Observed};
use crate::Result;
use crate::error::Error;
use crate::executor::{Cx, Echo, call_ok};
use crate::process::ProcessSpec;
use crate::store::write_private;
pub(crate) fn read_ps(manifest: &isoloom_manifest::Manifest, out: &str) -> (Vec<crate::status::MachineStatus>, BTreeMap<crate::vars::PortKey, u16>) {
ps::machines(manifest, &ps::parse_ps(out))
}
const NO_PINS: &str = "services: {}\n";
const CALL_TIMEOUT: Duration = Duration::from_secs(120);
#[derive(Debug, Clone)]
pub(crate) struct Compose {
identity: ComposeIdentity,
}
impl Compose {
pub(crate) fn new(identity: ComposeIdentity) -> Compose {
Compose { identity }
}
fn project(&self, cx: &Cx<'_>) -> Result<String> {
cx.vars.render(&self.identity.project, "identity.compose.project")
}
fn pins(&self, cx: &Cx<'_>) -> Result<Option<PathBuf>> {
let Some(file) = &self.identity.pinned_ports_file else {
return Ok(None);
};
let path = cx.vars.render(file.template(), "identity.compose.pinned_ports_file")?;
Ok(Some(cx.lab.dir().join(path)))
}
fn compose_args(&self, cx: &Cx<'_>, rest: &[&str]) -> Result<Vec<String>> {
let mut args = vec!["compose".to_owned(), "--project-name".into(), self.project(cx)?];
for file in &self.identity.files {
args.push("--file".into());
args.push(cx.vars.render(file.template(), "identity.compose.files")?);
}
args.extend(rest.iter().map(|s| (*s).to_owned()));
Ok(args)
}
async fn compose(&self, cx: &Cx<'_>, step: &Step, rest: &[&str], env: &[(String, String)]) -> Result<String> {
let args = self.compose_args(cx, rest)?;
docker(cx, step, args, env).await
}
}
pub(super) async fn docker(cx: &Cx<'_>, step: &Step, args: Vec<String>, env: &[(String, String)]) -> Result<String> {
let spec = ProcessSpec {
program: "docker".into(),
args,
cwd: Some(cx.lab.dir().to_path_buf()),
env: env.to_vec(),
stdin: None,
};
call_ok(cx, step.id.as_str(), &spec, CALL_TIMEOUT, Echo::Quiet).await
}
impl Driver for Compose {
fn preflight(&self, _cx: &Cx<'_>) -> impl Future<Output = Result<()>> {
std::future::ready(Ok(()))
}
fn env(&self, _cx: &Cx<'_>) -> Result<Vec<(String, String)>> {
Ok(Vec::new())
}
fn prepare(&self, cx: &Cx<'_>) -> Result<()> {
if let Some(pins) = self.pins(cx)?
&& !pins.exists()
{
write_private(&pins, NO_PINS.as_bytes()).map_err(Error::io(&pins, "written"))?;
}
Ok(())
}
async fn action(&self, cx: &Cx<'_>, step: &Step, action: Action, with: &BTreeMap<SnakeName, Template>) -> Result<()> {
match action {
Action::ComposePinPorts => actions::pin_ports(self, cx, step).await,
Action::ComposeCheckPortsFree => actions::check_ports_free(self, cx, step).await,
Action::ComposeRemoveStaleNetworks => actions::remove_stale_networks(self, cx, step).await,
Action::ComposeRemoveContainers => actions::remove_containers(self, cx, step, with).await,
Action::ComposeRemoveNetworks => actions::remove_networks(self, cx, step).await,
other => Err(super::foreign_action("Compose", step, other)),
}
}
async fn observe(&self, cx: &Cx<'_>) -> Result<Observed> {
let out = super::status_report(cx).await?;
let (machines, published) = ps::machines(cx.lab.manifest(), &ps::parse_ps(&out));
let hosts = published.keys().map(|(machine, _)| (machine.clone(), "127.0.0.1".to_owned())).collect();
Ok(Observed {
machines,
published,
hosts,
..Observed::default()
})
}
fn after_down(&self, cx: &Cx<'_>) -> Result<()> {
if let Some(pins) = self.pins(cx)? {
write_private(&pins, NO_PINS.as_bytes()).map_err(Error::io(&pins, "written"))?;
}
Ok(())
}
}