1use isoloom_manifest::{Machine, Native};
11use serde::Serialize;
12use serde_json::{Value, json};
13
14use super::Running;
15use super::operation::Done;
16use crate::Result;
17use crate::driver::{Attached, Attacker};
18use crate::error::Error;
19use crate::events::{Emitter, Events};
20use crate::options::RunOptions;
21use crate::secrets::sensitive_values;
22use crate::state::Op;
23use crate::status::{ShellCommand, any_target_running};
24
25#[derive(Debug, Clone, Serialize)]
27pub struct AttachReport {
28 pub machine: String,
30 pub native: String,
32 pub network: Option<String>,
34 pub address: Option<String>,
36 pub image: Option<String>,
38}
39
40enum Target<'a> {
43 Machine(&'a Machine),
44 AdHoc,
45}
46
47impl Target<'_> {
48 fn as_attacker(&self) -> Attacker<'_> {
50 match self {
51 Target::Machine(m) => Attacker::Machine(native_name(m)),
52 Target::AdHoc => Attacker::AdHoc,
53 }
54 }
55
56 fn report(&self, network: Option<&str>, image: Option<&str>, attached: Attached) -> AttachReport {
59 let image = image.map(str::to_owned);
60 match self {
61 Target::Machine(m) => {
62 let (network, address) = address_on(m, network);
63 AttachReport {
64 machine: m.name.as_str().to_owned(),
65 native: native_name(m).to_owned(),
66 network,
67 address,
68 image,
69 }
70 }
71 Target::AdHoc => {
72 let name = attached.name.unwrap_or_default();
73 AttachReport {
74 machine: name.clone(),
75 native: name,
76 network: attached.network,
77 address: attached.address,
78 image,
79 }
80 }
81 }
82 }
83
84 fn detach_data(&self) -> Value {
86 match self {
87 Target::Machine(m) => json!({ "machine": m.name.as_str(), "native": native_name(m) }),
88 Target::AdHoc => json!({ "machine": Value::Null, "native": Value::Null }),
89 }
90 }
91}
92
93impl Running<'_> {
94 pub async fn attach(&self, machine: Option<&str>, network: Option<&str>, image: Option<&str>, events: &dyn Events) -> Result<AttachReport> {
107 let opts = self.connected(&RunOptions::default());
108 let mask = sensitive_values(self.manifest(), &opts.secrets);
109 self.operate(Op::Attach, events, mask, async |e: &Emitter<'_>| {
110 let target = self.attacker(machine)?;
111 let (_, mut cx) = self.existing(&opts, e).await?;
112 if !any_target_running(&self.observe(&mut cx).await?.machines) {
113 return Err(Error::NotRunning { run: self.name().to_string() });
114 }
115 let attached = self.driver.attach(&cx, target.as_attacker(), image, network).await?;
116 let report = target.report(network, image, attached);
117 let data = json!({ "machine": report.machine, "native": report.native, "network": report.network, "address": report.address });
118 Ok(Done {
119 value: report,
120 ok: true,
121 data,
122 state: None,
123 })
124 })
125 .await
126 }
127
128 pub async fn detach(&self, machine: Option<&str>, events: &dyn Events) -> Result<()> {
135 let opts = self.connected(&RunOptions::default());
136 let mask = sensitive_values(self.manifest(), &opts.secrets);
137 self.operate(Op::Detach, events, mask, async |e: &Emitter<'_>| {
138 let target = self.attacker(machine)?;
139 let (_, cx) = self.existing(&opts, e).await?;
140 self.driver.detach(&cx, target.as_attacker()).await?;
141 Ok(Done {
142 value: (),
143 ok: true,
144 data: target.detach_data(),
145 state: None,
146 })
147 })
148 .await
149 }
150
151 pub fn shell_command(&self, machine: Option<&str>) -> Result<ShellCommand> {
162 let target = self.attacker(machine)?;
163 let (_, provider, params) = self.query_prelude()?;
164 let silent = |_: &crate::events::Event| {};
165 let emit = Emitter::new(self.name().as_str(), Op::Check, self.runner.config.clock.as_ref(), &silent);
166 let cx = self.cx(params, &self.connection, &emit, provider)?;
167 self.driver.shell_command(&cx, target.as_attacker())
168 }
169
170 fn attacker(&self, machine: Option<&str>) -> Result<Target<'_>> {
173 let mut on_demand = self.manifest().machines.iter().filter(|m| m.on_demand);
174 if let Some(name) = machine {
175 return on_demand
176 .find(|m| m.name.as_str() == name)
177 .map(Target::Machine)
178 .ok_or_else(|| Error::MachineUnknown {
179 run: self.name().to_string(),
180 machine: name.to_owned(),
181 });
182 }
183 match (on_demand.next(), on_demand.next()) {
184 (Some(one), None) => Ok(Target::Machine(one)),
185 (None, _) => Ok(Target::AdHoc),
186 (Some(_), Some(_)) => Err(Error::Unsupported {
187 what: format!("hot-adding an access machine: this lab has several ({}); name which", self.on_demand_names()),
188 }),
189 }
190 }
191
192 fn on_demand_names(&self) -> String {
194 self.manifest()
195 .machines
196 .iter()
197 .filter(|m| m.on_demand)
198 .map(|m| m.name.as_str())
199 .collect::<Vec<_>>()
200 .join(", ")
201 }
202}
203
204fn native_name(m: &Machine) -> &str {
206 match &m.native {
207 Some(Native::Compose { service }) => service.as_str(),
208 Some(Native::Vagrant { vagrant, .. }) => vagrant.as_str(),
209 _ => m.name.as_str(),
210 }
211}
212
213fn address_on(m: &Machine, network: Option<&str>) -> (Option<String>, Option<String>) {
215 match network {
216 Some(n) => (
217 Some(n.to_owned()),
218 m.addresses.iter().find(|(k, _)| k.as_str() == n).map(|(_, a)| a.to_string()),
219 ),
220 None => m
221 .addresses
222 .iter()
223 .next()
224 .map_or((None, None), |(k, a)| (Some(k.as_str().to_owned()), Some(a.to_string()))),
225 }
226}