1mod attach;
6mod existing;
7mod operation;
8mod seal;
9#[cfg(test)]
10mod tests;
11mod up;
12
13pub use attach::AttachReport;
14
15use std::collections::BTreeMap;
16use std::path::Path;
17
18use isoloom_manifest::{Manifest, RunVar};
19
20use crate::Result;
21use crate::driver::AnyDriver;
22use crate::error::Error;
23use crate::events::Emitter;
24use crate::events::WarningCode;
25use crate::executor::{Cx, Stale};
26use crate::hooks::{Edge, RunView};
27use crate::manifest::LoadedManifest;
28use crate::name::RunName;
29use crate::options::RunOptions;
30use crate::outputs;
31use crate::params::params_for_existing;
32use crate::runner::Runner;
33use crate::scope::Scope;
34use crate::secrets::Secrets;
35use crate::state::Op;
36use crate::status::{self, OutputValue, Status};
37
38type QueryPrelude = (Option<crate::store::RunRecord>, Option<String>, BTreeMap<String, String>);
40
41pub struct Running<'r> {
43 pub(crate) runner: &'r Runner,
44 loaded: LoadedManifest,
45 name: RunName,
46 pub(crate) driver: AnyDriver,
47 connection: Secrets,
49}
50
51impl std::fmt::Debug for Running<'_> {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("Running")
54 .field("name", &self.name)
55 .field("manifest", &self.loaded.path)
56 .finish_non_exhaustive()
57 }
58}
59
60impl<'r> Running<'r> {
61 pub(crate) fn new(runner: &'r Runner, loaded: LoadedManifest, name: RunName) -> Result<Running<'r>> {
62 if !loaded.manifest.hosts.contains(&runner.config.host) {
63 let target = serde_json::to_value(loaded.manifest.target).unwrap_or_default();
64 return Err(Error::Unsupported {
65 what: format!("target {target} on this host"),
66 });
67 }
68 let driver = AnyDriver::for_manifest(&loaded.manifest)?;
69 Ok(Running {
70 runner,
71 loaded,
72 name,
73 driver,
74 connection: Secrets::new(),
75 })
76 }
77
78 #[must_use]
82 pub fn with_connection(mut self, secrets: Secrets) -> Running<'r> {
83 self.connection = secrets;
84 self
85 }
86
87 pub(crate) fn connected(&self, opts: &RunOptions) -> RunOptions {
89 let mut out = opts.clone();
90 for (name, value) in self.connection.iter() {
91 if out.secrets.get(name).is_none() {
92 out.secrets.insert(name, value.clone());
93 }
94 }
95 out
96 }
97
98 #[must_use]
100 pub fn manifest(&self) -> &Manifest {
101 &self.loaded.manifest
102 }
103
104 #[must_use]
106 pub fn name(&self) -> &RunName {
107 &self.name
108 }
109
110 #[must_use]
112 pub fn path(&self) -> &Path {
113 &self.loaded.path
114 }
115
116 pub(crate) fn dir(&self) -> &Path {
118 &self.loaded.dir
119 }
120
121 pub(crate) fn loaded_sha256(&self) -> &str {
123 &self.loaded.sha256
124 }
125
126 pub(crate) fn query_prelude(&self) -> Result<QueryPrelude> {
129 let record = self.runner.store.record(&self.name)?;
130 let provider = record.as_ref().and_then(|r| r.provider.clone());
131 let params = params_for_existing(
132 self.manifest(),
133 record.as_ref().map(|r| &r.params),
134 &BTreeMap::new(),
135 &self.scope(provider.clone()),
136 )?;
137 Ok((record, provider, params))
138 }
139
140 pub async fn status(&self) -> Result<Status> {
147 let (record, provider, params) = self.query_prelude()?;
148 let silent = |_: &crate::events::Event| {};
149 let events = Emitter::new(self.name.as_str(), Op::Check, self.runner.config.clock.as_ref(), &silent);
150 let secrets = self.connection.clone();
151 let mut cx = self.cx(params, &secrets, &events, provider.clone())?;
152 if let Some(r) = &record {
153 Self::bind_record(&mut cx, r);
154 }
155 self.driver.prepare(&cx)?;
156 self.relay(&mut cx).await?;
157 let observed = self.observe(&mut cx).await?;
158 cx.vars.set_published(observed.published.clone());
159 cx.vars.set_hosts(observed.hosts.clone());
160 let state = match self.runner.op_of(&self.name).and_then(Op::during) {
161 Some(during) => during,
162 None => status::derive_state(&observed.machines, record.as_ref()),
163 };
164 let outputs = outputs::read(self.manifest(), &cx.vars, &observed.outputs, &observed.sensitive);
165 outputs::bind(&mut cx.vars, &outputs);
166 let message = (state == crate::RunState::Ready)
167 .then(|| outputs::message(self.manifest(), &cx.vars, &outputs))
168 .flatten();
169 Ok(Status {
170 state,
171 provider,
172 machines: observed.machines,
173 networks: self.networks(&cx),
174 outputs,
175 message,
176 })
177 }
178
179 pub async fn outputs(&self) -> Result<Vec<OutputValue>> {
185 Ok(self.status().await?.outputs)
186 }
187
188 pub(crate) fn cx<'a>(
190 &'a self,
191 params: BTreeMap<String, String>,
192 secrets: &'a Secrets,
193 events: &'a Emitter<'a>,
194 provider: Option<String>,
195 ) -> Result<Cx<'a>> {
196 let state_dir = self.runner.store.run_dir(&self.name)?;
197 let mut vars = crate::vars::Vars::default();
198 vars.set_run(RunVar::Name, self.name.as_str());
199 vars.set_run(RunVar::Dir, self.dir().display().to_string());
200 vars.set_run(RunVar::StateDir, state_dir.display().to_string());
201 vars.set_run(RunVar::KnownHosts, state_dir.join("known_hosts").display().to_string());
202 vars.set_run(RunVar::SshKey, crate::ssh::key_path(&self.runner.config.home).display().to_string());
203 if let Some(key) = crate::ssh::public_key(&self.runner.config.home) {
204 vars.set_run(RunVar::SshPublicKey, key);
205 }
206 if let Some(p) = &provider {
207 vars.set_run(RunVar::Provider, p.clone());
208 }
209 vars.set_params(params);
210 let stale = self.manifest().recovery.as_ref().map(|r| Stale::new(&r.stale_markers)).unwrap_or_default();
211 Ok(Cx {
212 run: self,
213 vars,
214 secrets,
215 events,
216 scope: self.scope(provider),
217 stale,
218 processes: std::sync::Arc::clone(&self.runner.config.processes),
219 ssh_jump: None,
220 expires_at: None,
221 relayed: None,
222 })
223 }
224
225 pub(crate) fn acting<'a>(&'a self, mut cx: Cx<'a>) -> Result<Cx<'a>> {
229 if cx.scope.host == isoloom_manifest::HostOs::Windows && self.driver.elevates(&cx.scope) {
230 let dir = self.runner.store.run_dir(&self.name)?.join("elevated");
231 let own = std::env::vars_os().filter_map(|(k, v)| Some((k.into_string().ok()?, v.into_string().ok()?)));
232 let inner = std::sync::Arc::clone(&self.runner.config.processes);
233 cx.processes = std::sync::Arc::new(crate::process::Elevated::new(inner, dir, own));
234 }
235 Ok(cx)
236 }
237
238 pub(crate) async fn relay(&self, cx: &mut Cx<'_>) -> Result<()> {
240 let config = &self.runner.config;
241 let vagrant = self.manifest().driver == isoloom_manifest::Driver::Vagrant;
242 let params = cx.vars.params();
243 let Some(host) = crate::relay::esxi_host(config.host == isoloom_manifest::HostOs::Macos, vagrant, cx.scope.provider.as_deref(), params) else {
244 return Ok(());
245 };
246 let port = params.get("ESXI_HOSTPORT").and_then(|p| p.parse().ok()).unwrap_or(22);
247 let relayed = crate::relay::prepare(host, port, &config.home, config.path.as_deref()).await;
248 cx.relayed = Some(relayed.map_err(Error::io(config.home.join("relay"), "set up"))?);
249 Ok(())
250 }
251
252 pub(crate) fn bind_record(cx: &mut Cx<'_>, record: &crate::store::RunRecord) {
255 if let Some(slot) = record.slot {
256 cx.vars.set_run(RunVar::Slot, slot.to_string());
257 }
258 cx.ssh_jump.clone_from(&record.ssh_jump);
259 cx.expires_at = record.expires_at;
260 }
261
262 pub(crate) fn scope(&self, provider: Option<String>) -> Scope {
264 Scope {
265 host: self.runner.config.host,
266 provider,
267 }
268 }
269
270 pub(crate) async fn read_outputs(&self, cx: &mut Cx<'_>) -> Result<Vec<status::OutputValue>> {
273 let observed = self.observe(cx).await?;
274 Ok(self.bind_observed(cx, observed))
275 }
276
277 pub(crate) async fn observe(&self, cx: &mut Cx<'_>) -> Result<crate::driver::Observed> {
280 let mut observed = self.driver.observe(cx).await?;
281 if let Some(guest) = &self.manifest().guest {
282 crate::in_vm::observe(cx, guest, &mut observed).await;
283 }
284 Ok(observed)
285 }
286
287 pub(crate) fn bind_observed(&self, cx: &mut Cx<'_>, observed: crate::driver::Observed) -> Vec<status::OutputValue> {
289 cx.vars.set_published(observed.published);
290 cx.vars.set_hosts(observed.hosts);
291 let outputs = outputs::read(self.manifest(), &cx.vars, &observed.outputs, &observed.sensitive);
292 cx.events.hide(outputs.iter().filter(|o| o.sensitive).filter_map(|o| o.value.clone()));
294 outputs::bind(&mut cx.vars, &outputs);
295 outputs
296 }
297
298 fn networks(&self, cx: &Cx<'_>) -> Vec<status::NetworkView> {
300 status::networks(self.manifest(), |t| cx.vars.render(t, "networks.native").ok())
301 }
302
303 pub(crate) async fn hooks(&self, cx: &Cx<'_>, edge: Edge, outputs: &[OutputValue]) {
305 if self.runner.hooks.is_empty() {
306 return;
307 }
308 let networks = self.networks(cx);
309 let view = RunView {
310 name: &self.name,
311 manifest: self.manifest(),
312 networks: &networks,
313 outputs,
314 };
315 for failure in edge.run(&self.runner.hooks, &view).await {
316 cx.events.warning(WarningCode::HookFailed, &failure);
317 }
318 }
319}