Watch
0
0
Fork
You've already forked hyperhive
0

copy the cache's pull facts to each hive and create them in the in-swarm cache

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.
This commit is contained in:
atlas 2026-10-27 15:51:13 +01:00 • committed by mara
commit 1e5070796d
2 changed files with 640 additions and 67 deletions

View file

@ -0,0 +1,104 @@
//! 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());
}
}

View file

@ -6,7 +6,7 @@
//! deployment's environment into a logged-in client ([`client`]), and, per kind
//! of secret, the path it lives at together with the fields it holds
//! ([`matrix`], [`queue`], [`mtls`], [`forge`], [`github`], [`grafana`],
//! [`acp`], [`ci`], [`review_classifier`], [`builder`], [`ci_metrics`],
//! [`acp`], [`attic`], [`ci`], [`review_classifier`], [`builder`], [`ci_metrics`],
//! [`claude_login`]).
//! Each of those is a thing the controller and a hive must say identically, so
//! it is said once here.
@ -30,6 +30,7 @@
//! needs only the provider and the store links nothing else.
pub mod acp;
pub mod attic;
pub mod builder;
pub mod cert;
#[cfg(feature = "otel")]