A new `swarm-builder` service, in its own container on the host that sets
`deploy.builder.enable` (defaulted on by `allSwarmServices` where the host
has the builder's store leaf, like the review classifier).
When an `agent-configs/<agent>` PR is opened, reopened or synchronized,
swarm-controller queues a `build_agent` jobq node. The node sets a
`swarm-builder` commit status to pending on the PR head, sends
`{agent, rev, pr}` to the builder over NATS request-reply
(`$SWARM.builder.build`), and writes success (store path), failure (last
line nix printed) or error (no builder / no reply) on the head. The node
fails when the build does, so the Jobs page matches the PR.
The builder builds nix/builder/check-flake's `check` with every input
overridden: hyperhive and nixpkgs to the host's own
`c0re.hyperhiveFlake`/`c0re.nixpkgsFlake` (what a hive there renders into
its meta flake), and the agent to the PR head on the forge. Evaluation is
pure and runs in the container; builds go to the host nix daemon as an
untrusted client. The private config repo is fetched with the token of a
new read-only `builder` forge account (in the `hives` team), which the
controller mints into `swarm/services/builder/forge/token`; git gets it
from a credential helper reading the child's environment, so it is in no
argument or URL.
Credentials: a `builder` store leaf minted by glue-bao-tls on the store
host, a `swarm-builder` reader policy (forge token + its own OIDC client
secret), an authelia machine client, and a `--builder-client` grant in the
queue's auth-callout responder. The controller gains a grant on the token
path and the secret publisher a deny on it.
This is the agent-config build check atlas scoped on #5081 (slice 3):
nothing is pushed to a cache, nothing is written to KV, and a red status
gates no deploy. The check composes agent-base + the config without the
values a hive adds (CA, docs, claude-code path, ports, input follows), so
it can disagree with a hive's own build.
64 lines
2.1 KiB
Rust
64 lines
2.1 KiB
Rust
//! The swarm builder agreement: where its forge token and its queue client
|
|
//! secret live in the store, and what each object holds.
|
|
//!
|
|
//! swarm-controller mints the forge token; the builder reads it, and its queue
|
|
//! client secret, under its own certificate. Its read stanza in
|
|
//! `swarm-bao.nix` names exactly these two paths.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
Error,
|
|
path::{Kind, principal_prefix},
|
|
};
|
|
|
|
/// The builder's name segment under [`Kind::Service`].
|
|
const SERVICE: &str = "builder";
|
|
|
|
/// The path holding the token of the builder's read-only forge account, as a
|
|
/// [`crate::forge::Credential`].
|
|
///
|
|
/// # Errors
|
|
/// Never in practice: the name segment is a constant. The `Result` is
|
|
/// [`principal_prefix`]'s.
|
|
pub fn forge_token_path() -> Result<String, Error> {
|
|
let prefix = principal_prefix(Kind::Service, SERVICE)?;
|
|
Ok(format!("{prefix}/forge/token"))
|
|
}
|
|
|
|
/// The path holding the queue client secret of the builder's OIDC client
|
|
/// `client_id`, where `swarm-secret-publisher.nix` puts every swarm service's.
|
|
///
|
|
/// # Errors
|
|
/// [`Error::PathSegment`] when `client_id` is not a single path segment.
|
|
pub fn client_secret_path(client_id: &str) -> Result<String, Error> {
|
|
let prefix = principal_prefix(Kind::Service, client_id)?;
|
|
Ok(format!("{prefix}/oidc/client"))
|
|
}
|
|
|
|
/// What [`client_secret_path`] holds.
|
|
///
|
|
/// No `Debug`: `value` authenticates the builder at the queue.
|
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct ClientSecret {
|
|
/// The secret, under the field name the publisher writes.
|
|
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.
|
|
assert_eq!(
|
|
forge_token_path().expect("a constant segment is legal"),
|
|
"swarm/services/builder/forge/token"
|
|
);
|
|
assert_eq!(
|
|
client_secret_path("swarm-builder").expect("a plain id is legal"),
|
|
"swarm/services/swarm-builder/oidc/client"
|
|
);
|
|
}
|
|
}
|