//! The binary cache agreement: the four facts a hive needs to pull from the //! swarm's cache, where the cache's operator keeps them, and where the //! controller copies them for each hive. //! //! The cache's own unit (or, for an external cache, the operator) writes them //! under `swarm/services/attic/`. No hive may read that prefix, so //! `swarm-controller` copies each leaf to `swarm/hives//attic/`, //! which the hive's own `swarm/hives//*` stanza reads. The nix half of //! the source paths is `swarm-bao.nix`'s `atticFactLeaves`. use serde::{Deserialize, Serialize}; use crate::{ Error, path::{Kind, principal_prefix}, }; /// One of the facts a hive pulls with. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Leaf { /// The read-only token, as a basic-auth password. PullToken, /// The cache server's base URL, scheme included and no trailing slash. Url, /// The cache's name on that server. Cache, /// The cache's public signing key, `:`. PublicKey, } impl Leaf { /// Every leaf, in the order the controller copies them. pub const ALL: [Leaf; 4] = [Leaf::PullToken, Leaf::Url, Leaf::Cache, Leaf::PublicKey]; /// The last path segment, under both prefixes. #[must_use] pub fn name(self) -> &'static str { match self { Leaf::PullToken => "pull-token", Leaf::Url => "url", Leaf::Cache => "cache", Leaf::PublicKey => "public-key", } } } /// Where the cache's operator keeps `leaf`. /// /// # Errors /// Never in practice: the name segments are constants. The `Result` is /// [`principal_prefix`]'s. pub fn service_path(leaf: Leaf) -> Result { let prefix = principal_prefix(Kind::Service, "attic")?; Ok(format!("{prefix}/{}", leaf.name())) } /// Where `hive` reads its copy of `leaf`. /// /// # Errors /// [`Error::PathSegment`] when `hive` contains anything but `[A-Za-z0-9_-]`, /// which keeps one hive's name from addressing another hive's secret. pub fn hive_path(hive: &str, leaf: Leaf) -> Result { let prefix = principal_prefix(Kind::Hive, hive)?; Ok(format!("{prefix}/attic/{}", leaf.name())) } /// What every leaf holds: one string under the field `bao kv get /// -field=value` reads. /// /// No `Debug`: for [`Leaf::PullToken`] `value` reads from the cache. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Fact { /// The fact itself. pub value: String, } #[cfg(test)] mod tests { use super::*; #[test] fn the_paths_are_the_ones_the_store_grants() { // Spelled out: `swarm-bao.nix` grants each literal. let sources = Leaf::ALL.map(|l| service_path(l).expect("constant segments")); assert_eq!( sources, [ "swarm/services/attic/pull-token", "swarm/services/attic/url", "swarm/services/attic/cache", "swarm/services/attic/public-key", ] ); assert_eq!( hive_path("alpha", Leaf::PullToken).expect("a plain name"), "swarm/hives/alpha/attic/pull-token" ); } #[test] fn a_hive_name_cannot_address_another_hives_copy() { assert!(hive_path("alpha/../beta", Leaf::Url).is_err()); } }