swarm-builder: lock the swarm meta flake on request
A request on $SWARM.builder.lock-meta carries a rendered flake.nix. The builder writes it into a scratch directory, runs `nix flake lock` there with the same forge credential helper its builds use, answers with the flake.lock nix wrote, and removes the directory. It commits nothing; the controller commits the lock beside the flake. Locks are answered in their own loop, like archetype evaluations, so one does not wait behind an hour-long build. The auth callout grants the subject to the builder (subscribe) and the controller (publish) only. Ported from c574a1694 onto the swarm-wire crate. Refs #4857
This commit is contained in:
parent
d331eb394b
commit
de323a72dd
4 changed files with 320 additions and 42 deletions
|
|
@ -1,5 +1,5 @@
|
|||
//! The swarm builder's requests: the subjects swarm-controller sends a build
|
||||
//! and an archetype evaluation on, and what travels each way.
|
||||
//! The swarm builder's requests: the subjects swarm-controller sends a build,
|
||||
//! an archetype evaluation and a meta-flake lock on, and what travels each way.
|
||||
//!
|
||||
//! Three crates name these: the controller sends the requests, the builder
|
||||
//! answers them, and the auth-callout responder grants the subjects to each.
|
||||
|
|
@ -80,6 +80,38 @@ pub enum ArchetypesReply {
|
|||
Failed { error_tail: String },
|
||||
}
|
||||
|
||||
/// The subject a swarm meta-flake lock goes to, with the same one publisher and
|
||||
/// one subscriber as [`SUBJECT`].
|
||||
pub const LOCK_SUBJECT: &str = "$SWARM.builder.lock-meta";
|
||||
|
||||
/// The longest one lock may take. A lock fetches every input's source and
|
||||
/// builds nothing.
|
||||
pub const LOCK_DEADLINE: Duration = Duration::from_mins(10);
|
||||
|
||||
/// How long the controller waits for a lock's reply: the lock at its deadline,
|
||||
/// plus a minute for the rest.
|
||||
pub const LOCK_REPLY_DEADLINE: Duration = Duration::from_mins(11);
|
||||
|
||||
/// One lock: the swarm meta flake's `flake.nix`, locked on its own in a scratch
|
||||
/// directory. The builder writes nothing anywhere else and commits nothing; the
|
||||
/// controller commits the returned lock beside the flake.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LockMeta {
|
||||
/// The rendered `flake.nix`.
|
||||
pub flake_nix: String,
|
||||
}
|
||||
|
||||
/// The builder's answer to one [`LockMeta`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "state", rename_all = "kebab-case")]
|
||||
pub enum LockReply {
|
||||
/// The flake locked; `flake_lock` is the `flake.lock` `nix` wrote.
|
||||
Locked { flake_lock: String },
|
||||
/// The lock did not finish: the last lines `nix` printed, or why the
|
||||
/// builder could not start it.
|
||||
Failed { error_tail: String },
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
|
@ -168,6 +200,7 @@ mod tests {
|
|||
fn the_subjects_are_pinned() {
|
||||
assert_eq!(SUBJECT, "$SWARM.builder.build");
|
||||
assert_eq!(ARCHETYPES_SUBJECT, "$SWARM.builder.archetypes");
|
||||
assert_eq!(LOCK_SUBJECT, "$SWARM.builder.lock-meta");
|
||||
assert_eq!(QUEUE_GROUP, "builder");
|
||||
}
|
||||
|
||||
|
|
@ -175,4 +208,42 @@ mod tests {
|
|||
fn the_reply_deadline_outlasts_the_build() {
|
||||
assert!(REPLY_DEADLINE > BUILD_DEADLINE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_lock_reply_deadline_outlasts_the_lock() {
|
||||
assert!(LOCK_REPLY_DEADLINE > LOCK_DEADLINE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_lock_request_and_reply_serialize_as_pinned() {
|
||||
let req = LockMeta {
|
||||
flake_nix: "{ }".to_owned(),
|
||||
};
|
||||
let value = serde_json::to_value(&req).expect("encodes");
|
||||
assert_eq!(value, json!({"flake_nix": "{ }"}));
|
||||
assert_eq!(
|
||||
serde_json::from_value::<LockMeta>(value).expect("decodes"),
|
||||
req
|
||||
);
|
||||
for (reply, wire) in [
|
||||
(
|
||||
LockReply::Locked {
|
||||
flake_lock: "{}".to_owned(),
|
||||
},
|
||||
json!({"state": "locked", "flake_lock": "{}"}),
|
||||
),
|
||||
(
|
||||
LockReply::Failed {
|
||||
error_tail: "e".to_owned(),
|
||||
},
|
||||
json!({"state": "failed", "error_tail": "e"}),
|
||||
),
|
||||
] {
|
||||
assert_eq!(serde_json::to_value(&reply).expect("encodes"), wire);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<LockReply>(wire).expect("decodes"),
|
||||
reply
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue