//! The swarm builder's requests: the subjects swarm-controller sends a build //! and an archetype evaluation 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. use std::time::Duration; use serde::{Deserialize, Serialize}; /// The subject every build goes to. The controller is its one publisher and /// the builder its one subscriber; the builder answers on the request's reply /// subject. pub const SUBJECT: &str = "$SWARM.builder.build"; /// The queue group the builder subscribes in, so a second instance shares the /// requests instead of running each one twice. pub const QUEUE_GROUP: &str = "builder"; /// The longest one build may take before the builder stops it and answers /// with a timeout. pub const BUILD_DEADLINE: Duration = Duration::from_hours(1); /// How long the controller waits for the reply, and the queue lets the /// builder answer: the build at its deadline, plus a minute the builder spends /// on a request's wait for the build slot, the forge token read before the /// build and the reply's trip back. pub const REPLY_DEADLINE: Duration = Duration::from_mins(61); /// One build: an agent's config repo at one commit, on the builder's own /// agent base. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BuildAgent { /// The agent, which is also its config repo's name. pub agent: String, /// The commit to build. pub rev: String, /// The PR the commit is the head of; its `refs/pull//head` is the ref /// the builder fetches the commit through. pub pr: u64, } /// The builder's answer to one [`BuildAgent`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "state", rename_all = "kebab-case")] pub enum Reply { /// The config built; `out_path` is the system closure's store path. Built { out_path: String }, /// The build did not finish: the last lines `nix` printed, or why the /// builder could not start it. Failed { error_tail: String }, } /// The subject an archetype evaluation goes to: the controller asks, the /// builder evaluates the `meta` of every `hyperhiveArchetypes` entry of its own /// hyperhive source and answers on the request's reply subject. pub const ARCHETYPES_SUBJECT: &str = "$SWARM.builder.archetypes"; /// The longest one archetype evaluation may take, and how long the controller /// waits for its reply. pub const ARCHETYPES_DEADLINE: Duration = Duration::from_mins(2); /// One archetype evaluation. No field yet: the builder evaluates the hyperhive /// source it was deployed with. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct EvalArchetypes {} /// The builder's answer to one [`EvalArchetypes`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "state", rename_all = "kebab-case")] pub enum ArchetypesReply { /// `source` is the flake evaluated; `archetypes` maps each archetype's /// name to its `meta`, as `nix eval --json` printed it and unchecked. Evaluated { source: String, archetypes: serde_json::Map, }, /// The evaluation 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 super::*; #[test] fn the_wire_tags_the_reply_by_state() { let json = serde_json::to_value(Reply::Built { out_path: "/nix/store/x".to_owned(), }) .expect("encodes"); assert_eq!(json["state"], "built"); assert_eq!(json["out_path"], "/nix/store/x"); let json = serde_json::to_value(Reply::Failed { error_tail: "e".to_owned(), }) .expect("encodes"); assert_eq!(json["state"], "failed"); } #[test] fn the_reply_deadline_outlasts_the_build() { assert!(REPLY_DEADLINE > BUILD_DEADLINE); } #[test] fn the_archetypes_reply_is_tagged_by_state() { let json = serde_json::to_value(ArchetypesReply::Evaluated { source: "/nix/store/h".to_owned(), archetypes: serde_json::Map::new(), }) .expect("encodes"); assert_eq!(json["state"], "evaluated"); let json = serde_json::to_value(ArchetypesReply::Failed { error_tail: "e".to_owned(), }) .expect("encodes"); assert_eq!(json["state"], "failed"); } }