Skip to main content

isothrum_runner/lab/
existing.rs

1//! The operations on a run that exists: `down`, `park`, `resume` and `check`. Each replays the
2//! params the run was started with.
3
4use serde_json::json;
5
6use super::Lab;
7use super::operation::Done;
8use super::up::ready_run;
9use crate::Result;
10use crate::checks::{self, CheckReport};
11use crate::error::Error;
12use crate::events::{Emitter, Events};
13use crate::executor::{Cx, run_steps};
14use crate::hooks::Edge;
15use crate::options::RunOptions;
16use crate::params::params_for_existing;
17use crate::secrets::sensitive_values;
18use crate::state::{Op, Park, RunState};
19use crate::status::{MachineState, Run, any_target_running};
20use crate::store::RunRecord;
21use crate::{ready, tools};
22
23impl Lab<'_> {
24    /// Removes everything the run created (containers, networks, volumes).
25    ///
26    /// # Errors
27    ///
28    /// Fails, after its `error` event, when the run is recorded elsewhere, a `down` step fails, or
29    /// the run's state can't be locked or written.
30    pub async fn down(&self, opts: &RunOptions, events: &dyn Events) -> Result<()> {
31        let opts = &self.connected(opts);
32        let mask = sensitive_values(self.manifest(), &opts.secrets);
33        self.operate(Op::Down, events, mask, async |e: &Emitter<'_>| {
34            let (mut record, mut cx) = self.existing(opts, e).await?;
35            self.hooks(&cx, Edge::BeforeDown, &[]).await;
36            self.begin(&mut record, Op::Down)?;
37            run_steps(&mut cx, "lifecycle.down", self.manifest().lifecycle.down.as_slice()).await?;
38            self.driver.after_down(&cx)?;
39            crate::executor::state_files::forget(&cx)?;
40            self.forget_seal()?;
41            crate::archive::forget(&cx)?;
42            Ok(Done {
43                value: (),
44                ok: true,
45                data: json!({ "state": RunState::Absent }),
46                state: Some(RunState::Absent),
47            })
48        })
49        .await
50    }
51
52    /// Stops the run, keeping what it created, with the manifest's `park` steps for `mode`.
53    ///
54    /// # Errors
55    ///
56    /// Fails with `not_parkable` when the manifest has no steps for `mode`, with `not_running` when
57    /// nothing of the run is there, and as [`Lab::down`] does for a step or the state.
58    pub async fn park(&self, mode: Park, events: &dyn Events) -> Result<()> {
59        let opts = self.connected(&RunOptions::default());
60        let mask = sensitive_values(self.manifest(), &opts.secrets);
61        self.operate(Op::Park, events, mask, async |e: &Emitter<'_>| {
62            let park = self.manifest().lifecycle.park.as_ref();
63            let steps = park.and_then(|p| if mode == Park::Pause { p.pause.as_ref() } else { p.shutdown.as_ref() });
64            let steps = steps.ok_or_else(|| Error::NotParkable {
65                run: self.name().to_string(),
66                mode: mode.as_str(),
67            })?;
68            let (mut record, mut cx) = self.existing(&opts, e).await?;
69            if !self.exists(&mut cx).await? {
70                return Err(Error::NotRunning { run: self.name().to_string() });
71            }
72            self.hooks(&cx, Edge::BeforePark, &[]).await;
73            self.begin(&mut record, Op::Park)?;
74            run_steps(&mut cx, "lifecycle.park", steps).await?;
75            Ok(Done {
76                value: (),
77                ok: true,
78                data: json!({ "state": RunState::Parked }),
79                state: Some(RunState::Parked),
80            })
81        })
82        .await
83    }
84
85    /// Starts a parked run again, as it was: nothing is re-created, so inputs aren't needed again.
86    ///
87    /// # Errors
88    ///
89    /// Fails with `nothing_to_resume` when nothing is left (the parked mark is dropped), and as
90    /// [`Lab::up`] does for a tool, a step, a ready condition or the state.
91    pub async fn resume(&self, opts: &RunOptions, events: &dyn Events) -> Result<Run> {
92        let opts = &self.connected(opts);
93        let mask = sensitive_values(self.manifest(), &opts.secrets);
94        self.operate(Op::Resume, events, mask, async |e: &Emitter<'_>| self.resume_work(opts, e, true).await)
95            .await
96    }
97
98    /// Resumes the run; `probe`: its tools are checked first (an `up` that resumes did it).
99    pub(crate) async fn resume_work(&self, opts: &RunOptions, events: &Emitter<'_>, probe: bool) -> Result<Done<Run>> {
100        let (mut record, mut cx) = self.existing(opts, events).await?;
101        if probe {
102            tools::probe(&cx).await?;
103        }
104        if !self.exists(&mut cx).await? {
105            // Nothing left: the parked mark goes, and a fresh start is the way back.
106            record.state = RunState::Absent;
107            self.runner.store.save(&record)?;
108            return Err(Error::NothingToResume { run: self.name().to_string() });
109        }
110        self.begin(&mut record, Op::Resume)?;
111        run_steps(&mut cx, "lifecycle.resume", &self.manifest().lifecycle.resume).await?;
112        ready::check(&mut cx, None).await?;
113        let (outputs, message) = self.finish(&mut cx, Edge::AfterResume).await?;
114        record.state = RunState::Ready;
115        Ok(ready_run(record, outputs, message))
116    }
117
118    /// Runs the lab's checks against the running run.
119    ///
120    /// # Errors
121    ///
122    /// Fails with `not_running` when no target machine runs, and when a setup or teardown step
123    /// fails. A failing check isn't an error: the report says it (`ok: false`).
124    pub async fn check(&self, opts: &RunOptions, events: &dyn Events) -> Result<CheckReport> {
125        let opts = &self.connected(opts);
126        let mask = sensitive_values(self.manifest(), &opts.secrets);
127        self.operate(Op::Check, events, mask, async |e: &Emitter<'_>| {
128            let (_, mut cx) = self.existing(opts, e).await?;
129            let observed = self.observe(&mut cx).await?;
130            if !any_target_running(&observed.machines) {
131                return Err(Error::NotRunning { run: self.name().to_string() });
132            }
133            // A runner's step may name an output (`${outputs.ip}`, the router it goes through).
134            self.bind_observed(&mut cx, observed);
135            let report = checks::run(&mut cx).await?;
136            let data = json!({ "passed": report.results.iter().filter(|r| r.ok).count(), "failed": report.results.iter().filter(|r| !r.ok).count(), "unfinished": report.unfinished });
137            Ok(Done { ok: report.ok, value: report, data, state: None })
138        })
139        .await
140    }
141
142    /// Runs the lab's set-up again on its running machines, every one or `machine` (a lab machine
143    /// of the manifest), without rebuilding them: the manifest's `provision` steps. The values
144    /// given at launch aren't kept by the runner: pass them again in `opts`.
145    ///
146    /// # Errors
147    ///
148    /// Fails with `machine_unknown` for a machine the lab doesn't have, `not_running` when no
149    /// target machine runs, `unsupported` when the manifest has no provision steps, and as
150    /// [`Lab::up`] does for a step or the state.
151    pub async fn provision(&self, machine: Option<&str>, opts: &RunOptions, events: &dyn Events) -> Result<()> {
152        let opts = &self.connected(opts);
153        let mask = sensitive_values(self.manifest(), &opts.secrets);
154        self.operate(Op::Provision, events, mask, async |e: &Emitter<'_>| {
155            let provision = self.manifest().lifecycle.provision.as_ref().ok_or_else(|| Error::Unsupported {
156                what: "provisioning this lab (its manifest has no provision steps)".into(),
157            })?;
158            let (_, mut cx) = self.existing(opts, e).await?;
159            if let Some(m) = machine {
160                if !self
161                    .manifest()
162                    .machines
163                    .iter()
164                    .any(|x| x.name.as_str() == m && x.role == isoloom_manifest::Role::Target)
165                {
166                    return Err(Error::MachineUnknown {
167                        run: self.name().to_string(),
168                        machine: m.to_owned(),
169                    });
170                }
171                cx.vars.set_machine(m);
172            }
173            if !any_target_running(&self.observe(&mut cx).await?.machines) {
174                return Err(Error::NotRunning { run: self.name().to_string() });
175            }
176            let (list, steps) = match machine {
177                Some(_) => ("lifecycle.provision.machine", &provision.machine),
178                None => ("lifecycle.provision.all", &provision.all),
179            };
180            run_steps(&mut cx, list, steps).await?;
181            Ok(Done {
182                value: (),
183                ok: true,
184                data: json!({ "machine": machine }),
185                state: None,
186            })
187        })
188        .await
189    }
190
191    /// The record and the context of an operation on a run that exists: refused when the run is
192    /// recorded elsewhere, its recorded params replayed.
193    async fn existing<'a>(&'a self, opts: &'a RunOptions, events: &'a Emitter<'a>) -> Result<(RunRecord, Cx<'a>)> {
194        let record = self.record_or_fresh()?;
195        self.refuse_elsewhere(&record, opts.host_label.as_deref())?;
196        let recorded = (record.started_at.is_some()).then_some(&record.params);
197        let provider = record.provider.clone().or_else(|| opts.provider.clone());
198        let params = params_for_existing(self.manifest(), recorded, &opts.params, &self.scope(provider.clone()))?;
199        // A run taken over from an embedder's old runtime, or acted on from another home, may
200        // name a key this runner hasn't made yet.
201        if crate::ssh::needed(self.manifest()) {
202            crate::ssh::ensure_key(self.runner.config.processes.as_ref(), &self.runner.config.home).await?;
203        }
204        let mut cx = self.acting(self.cx(params, &opts.secrets, events, provider)?)?;
205        Self::bind_record(&mut cx, &record);
206        self.driver.prepare(&cx)?;
207        crate::executor::state_files::write(&cx, false)?;
208        self.relay(&mut cx).await?;
209        Ok((record, cx))
210    }
211
212    /// Whether anything of the run is there.
213    async fn exists(&self, cx: &mut Cx<'_>) -> Result<bool> {
214        Ok(self.observe(cx).await?.machines.iter().any(|m| m.state != MachineState::Absent))
215    }
216}