use isoloom_manifest::{Machine, Native};
use serde::Serialize;
use serde_json::json;
use super::Running;
use super::operation::Done;
use crate::Result;
use crate::error::Error;
use crate::events::{Emitter, Events};
use crate::options::RunOptions;
use crate::secrets::sensitive_values;
use crate::state::Op;
use crate::status::{ShellCommand, any_target_running};
#[derive(Debug, Clone, Serialize)]
pub struct AttachReport {
pub machine: String,
pub native: String,
pub network: Option<String>,
pub address: Option<String>,
pub image: Option<String>,
}
impl Running<'_> {
pub async fn attach(&self, machine: Option<&str>, network: Option<&str>, image: Option<&str>, events: &dyn Events) -> Result<AttachReport> {
let opts = self.connected(&RunOptions::default());
let mask = sensitive_values(self.manifest(), &opts.secrets);
self.operate(Op::Attach, events, mask, async |e: &Emitter<'_>| {
let access = self.access_machine(machine)?.clone();
let native = native_name(&access).to_owned();
let (_, mut cx) = self.existing(&opts, e).await?;
if !any_target_running(&self.observe(&mut cx).await?.machines) {
return Err(Error::NotRunning { run: self.name().to_string() });
}
self.driver.attach(&cx, &native, image, network).await?;
let (network, address) = address_on(&access, network);
let report = AttachReport {
machine: access.name.as_str().to_owned(),
native,
network,
address,
image: image.map(str::to_owned),
};
let data = json!({ "machine": report.machine, "native": report.native, "network": report.network, "address": report.address });
Ok(Done {
value: report,
ok: true,
data,
state: None,
})
})
.await
}
pub async fn detach(&self, machine: Option<&str>, events: &dyn Events) -> Result<()> {
let opts = self.connected(&RunOptions::default());
let mask = sensitive_values(self.manifest(), &opts.secrets);
self.operate(Op::Detach, events, mask, async |e: &Emitter<'_>| {
let access = self.access_machine(machine)?.clone();
let native = native_name(&access).to_owned();
let (_, cx) = self.existing(&opts, e).await?;
self.driver.detach(&cx, &native).await?;
Ok(Done {
value: (),
ok: true,
data: json!({ "machine": access.name.as_str(), "native": native }),
state: None,
})
})
.await
}
pub fn shell_command(&self, machine: Option<&str>) -> Result<ShellCommand> {
let access = self.access_machine(machine)?;
let native = native_name(access).to_owned();
let (_, provider, params) = self.query_prelude()?;
let silent = |_: &crate::events::Event| {};
let emit = Emitter::new(self.name().as_str(), Op::Check, self.runner.config.clock.as_ref(), &silent);
let cx = self.cx(params, &self.connection, &emit, provider)?;
self.driver.shell_command(&cx, &native)
}
fn access_machine(&self, machine: Option<&str>) -> Result<&Machine> {
let mut on_demand = self.manifest().machines.iter().filter(|m| m.on_demand);
if let Some(name) = machine {
return on_demand.find(|m| m.name.as_str() == name).ok_or_else(|| Error::MachineUnknown {
run: self.name().to_string(),
machine: name.to_owned(),
});
}
match (on_demand.next(), on_demand.next()) {
(Some(one), None) => Ok(one),
(None, _) => Err(Error::Unsupported {
what: "hot-adding an access machine: this lab has no on-demand access machine".into(),
}),
(Some(_), Some(_)) => {
let names = self
.manifest()
.machines
.iter()
.filter(|m| m.on_demand)
.map(|m| m.name.as_str())
.collect::<Vec<_>>()
.join(", ");
Err(Error::Unsupported {
what: format!("hot-adding an access machine: this lab has several ({names}); name which"),
})
}
}
}
}
fn native_name(m: &Machine) -> &str {
match &m.native {
Some(Native::Compose { service }) => service.as_str(),
Some(Native::Vagrant { vagrant, .. }) => vagrant.as_str(),
_ => m.name.as_str(),
}
}
fn address_on(m: &Machine, network: Option<&str>) -> (Option<String>, Option<String>) {
match network {
Some(n) => (
Some(n.to_owned()),
m.addresses.iter().find(|(k, _)| k.as_str() == n).map(|(_, a)| a.to_string()),
),
None => m
.addresses
.iter()
.next()
.map_or((None, None), |(k, a)| (Some(k.as_str().to_owned()), Some(a.to_string()))),
}
}