olai_stack_runtime/reconcile.rs
1//! Matching what Docker runs against what the caller manages.
2
3use std::collections::BTreeSet;
4
5use crate::engine::ComposeEngine;
6use crate::error::Result;
7use crate::status::ProjectStatus;
8
9/// Which projects a caller manages.
10///
11/// Prefer [`Exact`](Self::Exact) when the caller knows its project names: a
12/// [`Prefix`](Self::Prefix) is only unambiguous when the prefix ends in a separator that the
13/// rest of a name can't contain. Workspace projects are `<workspace>-<env>` and both parts may
14/// contain dashes, so `demo-` also matches the projects of a workspace called `demo-x`.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum ProjectMatch {
17 /// These names.
18 Exact(BTreeSet<String>),
19 /// Every name starting with this prefix.
20 Prefix(String),
21}
22
23impl ProjectMatch {
24 /// Whether `name` is one of the caller's.
25 pub fn matches(&self, name: &str) -> bool {
26 match self {
27 ProjectMatch::Exact(names) => names.contains(name),
28 ProjectMatch::Prefix(prefix) => name.starts_with(prefix.as_str()),
29 }
30 }
31}
32
33/// The projects Docker knows of (running or stopped) that `which` picks.
34pub async fn managed_projects(
35 engine: &dyn ComposeEngine,
36 which: &ProjectMatch,
37) -> Result<Vec<ProjectStatus>> {
38 Ok(engine
39 .ls()
40 .await?
41 .into_iter()
42 .filter(|p| which.matches(&p.name))
43 .collect())
44}
45
46/// Remove the shared `network` once none of `members` is known to Docker any more; returns
47/// whether it was removed.
48///
49/// Compose labels a network with the project that created it, so no member's `down` removes
50/// a network others joined: whoever stops the last member calls this.
51pub async fn remove_mesh_if_idle(
52 engine: &dyn ComposeEngine,
53 network: &str,
54 members: &BTreeSet<String>,
55) -> Result<bool> {
56 let running = managed_projects(engine, &ProjectMatch::Exact(members.clone())).await?;
57 if !running.is_empty() {
58 return Ok(false);
59 }
60 engine.network_rm(network).await
61}