1use isoloom_manifest::{Machine, Native};
7use serde::Serialize;
8use serde_json::json;
9
10use super::Running;
11use super::operation::Done;
12use crate::Result;
13use crate::error::Error;
14use crate::events::{Emitter, Events};
15use crate::options::RunOptions;
16use crate::secrets::sensitive_values;
17use crate::state::Op;
18use crate::status::{ShellCommand, any_target_running};
19
20#[derive(Debug, Clone, Serialize)]
22pub struct AttachReport {
23 pub machine: String,
25 pub native: String,
27 pub network: Option<String>,
29 pub address: Option<String>,
31 pub image: Option<String>,
33}
34
35impl Running<'_> {
36 pub async fn attach(&self, machine: Option<&str>, network: Option<&str>, image: Option<&str>, events: &dyn Events) -> Result<AttachReport> {
47 let opts = self.connected(&RunOptions::default());
48 let mask = sensitive_values(self.manifest(), &opts.secrets);
49 self.operate(Op::Attach, events, mask, async |e: &Emitter<'_>| {
50 let access = self.access_machine(machine)?.clone();
51 let native = native_name(&access).to_owned();
52 let (_, mut cx) = self.existing(&opts, e).await?;
53 if !any_target_running(&self.observe(&mut cx).await?.machines) {
54 return Err(Error::NotRunning { run: self.name().to_string() });
55 }
56 self.driver.attach(&cx, &native, image, network).await?;
57 let (network, address) = address_on(&access, network);
58 let report = AttachReport {
59 machine: access.name.as_str().to_owned(),
60 native,
61 network,
62 address,
63 image: image.map(str::to_owned),
64 };
65 let data = json!({ "machine": report.machine, "native": report.native, "network": report.network, "address": report.address });
66 Ok(Done {
67 value: report,
68 ok: true,
69 data,
70 state: None,
71 })
72 })
73 .await
74 }
75
76 pub async fn detach(&self, machine: Option<&str>, events: &dyn Events) -> Result<()> {
83 let opts = self.connected(&RunOptions::default());
84 let mask = sensitive_values(self.manifest(), &opts.secrets);
85 self.operate(Op::Detach, events, mask, async |e: &Emitter<'_>| {
86 let access = self.access_machine(machine)?.clone();
87 let native = native_name(&access).to_owned();
88 let (_, cx) = self.existing(&opts, e).await?;
89 self.driver.detach(&cx, &native).await?;
90 Ok(Done {
91 value: (),
92 ok: true,
93 data: json!({ "machine": access.name.as_str(), "native": native }),
94 state: None,
95 })
96 })
97 .await
98 }
99
100 pub fn shell_command(&self, machine: Option<&str>) -> Result<ShellCommand> {
111 let access = self.access_machine(machine)?;
112 let native = native_name(access).to_owned();
113 let (_, provider, params) = self.query_prelude()?;
114 let silent = |_: &crate::events::Event| {};
115 let emit = Emitter::new(self.name().as_str(), Op::Check, self.runner.config.clock.as_ref(), &silent);
116 let cx = self.cx(params, &self.connection, &emit, provider)?;
117 self.driver.shell_command(&cx, &native)
118 }
119
120 fn access_machine(&self, machine: Option<&str>) -> Result<&Machine> {
122 let mut on_demand = self.manifest().machines.iter().filter(|m| m.on_demand);
123 if let Some(name) = machine {
124 return on_demand.find(|m| m.name.as_str() == name).ok_or_else(|| Error::MachineUnknown {
125 run: self.name().to_string(),
126 machine: name.to_owned(),
127 });
128 }
129 match (on_demand.next(), on_demand.next()) {
130 (Some(one), None) => Ok(one),
131 (None, _) => Err(Error::Unsupported {
132 what: "hot-adding an access machine: this lab has no on-demand access machine".into(),
133 }),
134 (Some(_), Some(_)) => {
135 let names = self
136 .manifest()
137 .machines
138 .iter()
139 .filter(|m| m.on_demand)
140 .map(|m| m.name.as_str())
141 .collect::<Vec<_>>()
142 .join(", ");
143 Err(Error::Unsupported {
144 what: format!("hot-adding an access machine: this lab has several ({names}); name which"),
145 })
146 }
147 }
148 }
149}
150
151fn native_name(m: &Machine) -> &str {
153 match &m.native {
154 Some(Native::Compose { service }) => service.as_str(),
155 Some(Native::Vagrant { vagrant, .. }) => vagrant.as_str(),
156 _ => m.name.as_str(),
157 }
158}
159
160fn address_on(m: &Machine, network: Option<&str>) -> (Option<String>, Option<String>) {
162 match network {
163 Some(n) => (
164 Some(n.to_owned()),
165 m.addresses.iter().find(|(k, _)| k.as_str() == n).map(|(_, a)| a.to_string()),
166 ),
167 None => m
168 .addresses
169 .iter()
170 .next()
171 .map_or((None, None), |(k, a)| (Some(k.as_str().to_owned()), Some(a.to_string()))),
172 }
173}