controller: turn an archetype agent into a custom one
This commit is contained in:
parent
8a7f254c74
commit
0737798d78
1 changed files with 1008 additions and 44 deletions
|
|
@ -43,12 +43,12 @@ pub fn pinned_git_url(forge_git_url: &str, rev: Option<&str>) -> Result<String>
|
|||
Ok(format!("git+{forge_git_url}?rev={rev}"))
|
||||
}
|
||||
|
||||
/// Canonical inputs meta knows how to dedup. An agent that declares one
|
||||
/// of these as a top-level input in its own `flake.nix` will get a
|
||||
/// Canonical inputs meta knows how to dedup. An agent whose `flake.lock` or
|
||||
/// `flake.nix` declares one of these as a top-level input gets a
|
||||
/// `follows = "<name>"` line emitted in meta — collapsing the
|
||||
/// otherwise-separate-but-identical `nixpkgs_N` nodes into a single
|
||||
/// meta-level reference.
|
||||
pub const CANONICAL_INPUTS: &[&str] = &["nixpkgs"];
|
||||
/// otherwise-separate-but-identical `nixpkgs_N` and `hyperhive_N` nodes into
|
||||
/// a single meta-level reference.
|
||||
pub const CANONICAL_INPUTS: &[&str] = &["nixpkgs", "hyperhive"];
|
||||
|
||||
/// Env vars the meta flake sets in every agent container, from the
|
||||
/// `(name, value)` pairs [`render_flake`] is given. A caller passes only the
|
||||
|
|
@ -143,6 +143,32 @@ pub fn push_forwarded_var_options(out: &mut String, vars: &[(&'static str, Strin
|
|||
}
|
||||
}
|
||||
|
||||
/// The subset of `CANONICAL_INPUTS` that an agent config declares, in its
|
||||
/// `flake.lock` text `lock` or its `flake.nix` text `flake_nix`. A repo that
|
||||
/// was only just seeded has no lock yet, so its `flake.nix` counts too: a line
|
||||
/// starting `<input>.url` or `inputs.<input>.url`.
|
||||
///
|
||||
/// A `hyperhive` node in a lock does not count: it can be a leftover from an
|
||||
/// input the config dropped from its `flake.nix`, and a `follows` for an input
|
||||
/// the flake does not declare makes nix warn.
|
||||
#[must_use]
|
||||
pub fn canonical_inputs_declared(lock: Option<&str>, flake_nix: Option<&str>) -> Vec<&'static str> {
|
||||
let locked = lock.map(canonical_inputs_in_lock).unwrap_or_default();
|
||||
CANONICAL_INPUTS
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|canon| {
|
||||
(*canon != "hyperhive" && locked.contains(canon))
|
||||
|| flake_nix.is_some_and(|text| {
|
||||
text.lines().map(str::trim_start).any(|l| {
|
||||
l.starts_with(&format!("{canon}.url"))
|
||||
|| l.starts_with(&format!("inputs.{canon}.url"))
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The subset of `CANONICAL_INPUTS` that the `flake.lock` text `raw` declares
|
||||
/// as direct (root-level) inputs. Empty when the lock is unparsable — safe
|
||||
/// degradation, the worst case is no dedup for that agent.
|
||||
|
|
@ -460,17 +486,26 @@ pub fn is_rev(rev: &str) -> bool {
|
|||
rev.len() == 40 && rev.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
|
||||
}
|
||||
|
||||
/// The URL of `input` in a flake [`render_flake`] rendered: the text of its
|
||||
/// `<input>.url = "<url>";` line. `None` when the flake has no such input.
|
||||
#[must_use]
|
||||
pub fn input_url<'a>(flake_nix: &'a str, input: &str) -> Option<&'a str> {
|
||||
let prefix = format!(" {input}.url = \"");
|
||||
Some(
|
||||
flake_nix
|
||||
.lines()
|
||||
.find_map(|l| l.strip_prefix(prefix.as_str()))?
|
||||
.split_once('"')?
|
||||
.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// The commit `input` is pinned to in a flake [`render_flake`] rendered:
|
||||
/// the `<rev>` of its `<input>.url = "git+<url>?rev=<rev>";` line. `None` when
|
||||
/// the flake has no such input, or its rev is not a commit id.
|
||||
#[must_use]
|
||||
pub fn pinned_rev<'a>(flake_nix: &'a str, input: &str) -> Option<&'a str> {
|
||||
let prefix = format!(" {input}.url = \"");
|
||||
let url = flake_nix
|
||||
.lines()
|
||||
.find_map(|l| l.strip_prefix(prefix.as_str()))?
|
||||
.split_once('"')?
|
||||
.0;
|
||||
let url = input_url(flake_nix, input)?;
|
||||
let (_, rev) = url.split_once("?rev=")?;
|
||||
let rev = rev.split('&').next()?;
|
||||
is_rev(rev).then_some(rev)
|
||||
|
|
@ -695,4 +730,51 @@ mod tests {
|
|||
assert!(!builds_agent_at(&flake, "gus", None, Some(ARGUS)));
|
||||
assert!(!builds_agent_at(&flake, "v", Some(&reviewer()), None));
|
||||
}
|
||||
/// An agent that declares `hyperhive` follows meta's own pin, so the
|
||||
/// lock holds one hyperhive; one that declares nothing gets no line (the
|
||||
/// control that the line is the declared input's, not every agent's).
|
||||
#[test]
|
||||
fn a_declared_hyperhive_input_follows_meta_s_own() {
|
||||
let url = |repo: &str, rev: &str| {
|
||||
pinned_git_url(&format!("https://f.t/{repo}.git"), Some(rev)).unwrap()
|
||||
};
|
||||
let agents = |canonical_inputs: Vec<&'static str>| {
|
||||
render_flake(
|
||||
&url("hyperhive/hyperhive", HYPERHIVE),
|
||||
&[spec(
|
||||
"argus",
|
||||
None,
|
||||
Some(AgentPin {
|
||||
url: url("agent-configs/argus", ARGUS),
|
||||
canonical_inputs,
|
||||
}),
|
||||
)],
|
||||
&[],
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let line = "agent-argus.inputs.hyperhive.follows = \"hyperhive\";";
|
||||
assert!(agents(vec!["hyperhive"]).contains(line));
|
||||
assert!(!agents(vec![]).contains(line));
|
||||
}
|
||||
|
||||
/// A seeded repo has only a `flake.nix`; its input counts, from either
|
||||
/// spelling, and a lock-declared input still does.
|
||||
#[test]
|
||||
fn canonical_inputs_are_read_from_the_flake_text_and_the_lock() {
|
||||
let flake = "{\n inputs = {\n hyperhive.url = \"git+x\";\n };\n}\n";
|
||||
assert_eq!(canonical_inputs_declared(None, Some(flake)), ["hyperhive"]);
|
||||
let dotted = "{\n inputs.nixpkgs.url = \"x\";\n}\n";
|
||||
assert_eq!(canonical_inputs_declared(None, Some(dotted)), ["nixpkgs"]);
|
||||
let lock = r#"{"root":"root","nodes":{"root":{"inputs":{"nixpkgs":"n"}}}}"#;
|
||||
assert_eq!(canonical_inputs_declared(Some(lock), None), ["nixpkgs"]);
|
||||
assert!(canonical_inputs_declared(None, Some("{ inputs = { }; }")).is_empty());
|
||||
// A leftover `hyperhive` node in a lock declares nothing.
|
||||
let stale = r#"{"root":"root","nodes":{"root":{"inputs":{"hyperhive":"hyperhive"}}}}"#;
|
||||
assert!(canonical_inputs_declared(Some(stale), Some("{ inputs = { }; }")).is_empty());
|
||||
// Another archetype source is not canonical: meta does not follow it.
|
||||
let other = "{\n inputs = {\n extra.url = \"git+x\";\n };\n}\n";
|
||||
assert!(canonical_inputs_declared(None, Some(other)).is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue