the in-swarm cache stores its address, name and public key beside its tokens, create-only. swarm-controller copies the four cache facts to swarm/hives/<hive>/attic/* every five minutes, with read on the sources and write on the copies. part 1 of 3: nothing reads the copies until the hive reader and probe land.
104 lines
3.3 KiB
Rust
104 lines
3.3 KiB
Rust
//! 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/<hive>/attic/<leaf>`,
|
|
//! which the hive's own `swarm/hives/<hive>/*` 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, `<name>:<base64>`.
|
|
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<String, Error> {
|
|
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<String, Error> {
|
|
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());
|
|
}
|
|
}
|