use isoloom_manifest::{Machine, Native};
use serde::Serialize;
use serde_json::{Value, json};
use super::Running;
use super::operation::Done;
use crate::Result;
use crate::driver::{Attached, Attacker};
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>,
}
enum Target<'a> {
Machine(&'a Machine),
AdHoc,
}
impl Target<'_> {
fn as_attacker(&self) -> Attacker<'_> {
match self {
Target::Machine(m) => Attacker::Machine(native_name(m)),
Target::AdHoc => Attacker::AdHoc,
}
}
fn report(&self, network: Option<&str>, image: Option<&str>, attached: Attached) -> AttachReport {
let image = image.map(str::to_owned);
match self {
Target::Machine(m) => {
let (network, address) = address_on(m, network);
AttachReport {
machine: m.name.as_str().to_owned(),
native: native_name(m).to_owned(),
network,
address,
image,
}
}
Target::AdHoc => {
let name = attached.name.unwrap_or_default();
AttachReport {
machine: name.clone(),
native: name,
network: attached.network,
address: attached.address,
image,
}
}
}
}
fn detach_data(&self) -> Value {
match self {
Target::Machine(m) => json!({ "machine": m.name.as_str(), "native": native_name(m) }),
Target::AdHoc => json!({ "machine": Value::Null, "native": Value::Null }),
}
}
}
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 target = self.attacker(machine)?;
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() });
}
let attached = self.driver.attach(&cx, target.as_attacker(), image, network).await?;
let report = target.report(network, image, attached);
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 target = self.attacker(machine)?;
let (_, cx) = self.existing(&opts, e).await?;
self.driver.detach(&cx, target.as_attacker()).await?;
Ok(Done {
value: (),
ok: true,
data: target.detach_data(),
state: None,
})
})
.await
}
pub fn shell_command(&self, machine: Option<&str>) -> Result<ShellCommand> {
let target = self.attacker(machine)?;
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, target.as_attacker())
}
fn attacker(&self, machine: Option<&str>) -> Result<Target<'_>> {
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)
.map(Target::Machine)
.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(Target::Machine(one)),
(None, _) => Ok(Target::AdHoc),
(Some(_), Some(_)) => Err(Error::Unsupported {
what: format!("hot-adding an access machine: this lab has several ({}); name which", self.on_demand_names()),
}),
}
}
fn on_demand_names(&self) -> String {
self.manifest()
.machines
.iter()
.filter(|m| m.on_demand)
.map(|m| m.name.as_str())
.collect::<Vec<_>>()
.join(", ")
}
}
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()))),
}
}