drop the proposed config repo: applied seeds from forge main, and the dead pending-init topology list goes with it
This commit is contained in:
parent
08b8eb3f73
commit
25295fd33f
1 changed files with 225 additions and 520 deletions
|
|
@ -1,26 +1,23 @@
|
|||
//! Repo plumbing on the local Forgejo: reseeding an agent's `applied` and
|
||||
//! `proposed` repos from `agent-configs/<name>`. The typed API-client
|
||||
//! constructor + org-name constants live in the module root (`super`).
|
||||
|
||||
use std::path::Path;
|
||||
//! Repo plumbing on the local Forgejo: seeding an agent's `applied` repo from
|
||||
//! `agent-configs/<name>`. The typed API-client constructor + org-name
|
||||
//! constants live in the module root (`super`).
|
||||
|
||||
use super::{CONFIG_ORG, forge_git_url, reader_token};
|
||||
|
||||
/// Reseed a revived agent's `applied` repo — missing its `.git` entirely,
|
||||
/// e.g. after `destroy --purge` — by fetching `agent-configs/<name>`'s
|
||||
/// `main` from the forge. Unlike `proposed` (seeded once at first spawn and
|
||||
/// never touched again — see `lifecycle::setup_proposed`'s own doc
|
||||
/// comment), forge `main` advances with every operator-merged config PR, so
|
||||
/// this reflects the last merged config rather than a stale creation-time
|
||||
/// snapshot. A config deployed through hive-c0re without a forge merge never
|
||||
/// reaches it.
|
||||
/// Seed an agent's `applied` repo, missing its `.git` entirely (first spawn,
|
||||
/// or a revival after `destroy --purge`), by fetching `agent-configs/<name>`'s
|
||||
/// `main` from the forge. Forge `main` advances with every operator-merged
|
||||
/// config PR, so this reflects the last merged config. A config deployed
|
||||
/// through hive-c0re without a forge merge never reaches it.
|
||||
///
|
||||
/// Best-effort: returns `false` (not an error) rather than bailing when there
|
||||
/// is no [`reader_token`], `applied` already has a `.git` (nothing to do), or
|
||||
/// any git step fails — callers fall back to a less-current source or the
|
||||
/// original hard error. Only `true` once `applied` is
|
||||
/// actually seeded and tagged `deployed/0`, mirroring first-spawn's own
|
||||
/// `setup_applied` seed shape exactly.
|
||||
/// is no [`reader_token`], `applied` already has a `.git` (nothing to do), the
|
||||
/// forge has no `main` for the agent yet (the normal case right after
|
||||
/// `swarm-controller` creates it, before it has pushed anything), or any git
|
||||
/// step fails. Callers fall back to the template or the original hard error. A
|
||||
/// failed seed leaves no `.git` behind, so a later "is it seeded" check reads
|
||||
/// the same as before the attempt. Only `true` once `applied` is seeded and
|
||||
/// tagged `deployed/0`.
|
||||
pub async fn fetch_config_main_into_applied(name: &str) -> bool {
|
||||
let Some(reader) = reader_token().await else {
|
||||
return false;
|
||||
|
|
@ -29,111 +26,60 @@ pub async fn fetch_config_main_into_applied(name: &str) -> bool {
|
|||
if applied.join(".git").exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = std::fs::create_dir_all(&applied) {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed mkdir failed");
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = crate::lifecycle::git(&applied, &["init", "--initial-branch=main"]).await {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed init failed");
|
||||
return false;
|
||||
}
|
||||
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
|
||||
let auth = reader.auth_header();
|
||||
let out = crate::lifecycle::git_command_authed(&auth)
|
||||
.current_dir(&applied)
|
||||
.args([
|
||||
"fetch",
|
||||
"--no-tags",
|
||||
"--update-head-ok",
|
||||
&url,
|
||||
"refs/heads/main:refs/heads/main",
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
match out {
|
||||
Ok(o) if o.status.success() => {}
|
||||
Ok(o) => {
|
||||
tracing::warn!(
|
||||
%name,
|
||||
stderr = %String::from_utf8_lossy(&o.stderr).trim(),
|
||||
"forge: applied reseed fetch failed"
|
||||
);
|
||||
let seeded = async {
|
||||
if let Err(e) = std::fs::create_dir_all(&applied) {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed mkdir failed");
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed fetch failed");
|
||||
if let Err(e) = crate::lifecycle::git(&applied, &["init", "--initial-branch=main"]).await {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed init failed");
|
||||
return false;
|
||||
}
|
||||
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
|
||||
let auth = reader.auth_header();
|
||||
let out = crate::lifecycle::git_command_authed(&auth)
|
||||
.current_dir(&applied)
|
||||
.args([
|
||||
"fetch",
|
||||
"--no-tags",
|
||||
"--update-head-ok",
|
||||
&url,
|
||||
"refs/heads/main:refs/heads/main",
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
match out {
|
||||
Ok(o) if o.status.success() => {}
|
||||
Ok(o) => {
|
||||
// `info`, not `warn`: the normal case right after
|
||||
// `swarm-controller` creates the agent, before it has pushed
|
||||
// anything into the repo it just made, and nothing is wrong.
|
||||
tracing::info!(
|
||||
%name,
|
||||
stderr = %String::from_utf8_lossy(&o.stderr).trim(),
|
||||
"forge: no config to fetch for this agent yet"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed fetch failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Err(e) = crate::lifecycle::git_read_tree_reset(&applied, "refs/heads/main").await {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed read-tree failed");
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = crate::lifecycle::git_tag(&applied, "deployed/0", "refs/heads/main").await {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed tag failed");
|
||||
return false;
|
||||
}
|
||||
tracing::info!(%name, "forge: seeded applied repo from agent-configs main");
|
||||
true
|
||||
}
|
||||
if let Err(e) = crate::lifecycle::git_read_tree_reset(&applied, "refs/heads/main").await {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed read-tree failed");
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = crate::lifecycle::git_tag(&applied, "deployed/0", "refs/heads/main").await {
|
||||
tracing::warn!(%name, error = ?e, "forge: applied reseed tag failed");
|
||||
return false;
|
||||
}
|
||||
tracing::info!(%name, "forge: reseeded applied repo from agent-configs main");
|
||||
true
|
||||
}
|
||||
|
||||
/// Seed an agent's `proposed` repo from `agent-configs/<name>` on the forge.
|
||||
///
|
||||
/// When the swarm creates an agent it writes that agent's config to the forge
|
||||
/// before any hive is told to deploy it, so the hive's job here is to **take**
|
||||
/// that config rather than author a second one beside it. The local template
|
||||
/// is the fallback, not the default.
|
||||
///
|
||||
/// Best-effort like [`fetch_config_main_into_applied`], and `false` is an
|
||||
/// ordinary answer rather than an error: there is no [`reader_token`], the
|
||||
/// forge is unreachable, or `agent-configs/<name>` has no `main` — the last of
|
||||
/// which is the *normal* case right after `swarm-controller` creates the
|
||||
/// agent, before it has pushed anything into the repo it just made.
|
||||
///
|
||||
/// `clone` rather than `init` + `fetch`: git removes a directory it created
|
||||
/// when the clone fails, and cloning into an existing empty dir leaves no
|
||||
/// `.git` behind either — so the caller's "does this repo already exist"
|
||||
/// check reads exactly the same before and after a failed attempt.
|
||||
pub async fn clone_config_into_proposed(dir: &Path, name: &str) -> bool {
|
||||
let Some(reader) = reader_token().await else {
|
||||
return false;
|
||||
};
|
||||
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
|
||||
// `--branch main` fails outright against a repo with no commits, so an
|
||||
// empty repo reads as "nothing to take" instead of cloning to an unborn
|
||||
// HEAD that would then look like a seeded checkout.
|
||||
let out = crate::lifecycle::git_command_authed(&reader.auth_header())
|
||||
.args([
|
||||
"clone",
|
||||
"--branch",
|
||||
"main",
|
||||
&url,
|
||||
&dir.display().to_string(),
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
match out {
|
||||
Ok(o) if o.status.success() => {
|
||||
tracing::info!(%name, "forge: seeded proposed repo from agent-configs main");
|
||||
true
|
||||
}
|
||||
Ok(o) => {
|
||||
// `info`, not `warn`: the normal case right after
|
||||
// `swarm-controller` creates the agent, before it has pushed
|
||||
// anything into the repo it just made, and nothing is wrong.
|
||||
// Logged at all because "where did this agent's config come
|
||||
// from" is worth answering from the journal, and proposed is
|
||||
// seeded exactly once.
|
||||
tracing::info!(
|
||||
%name,
|
||||
stderr = %String::from_utf8_lossy(&o.stderr).trim(),
|
||||
"forge: no config to clone for this agent; seeding the template"
|
||||
);
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(%name, error = ?e, "forge: proposed clone failed to run");
|
||||
false
|
||||
}
|
||||
.await;
|
||||
if !seeded {
|
||||
let _ = std::fs::remove_dir_all(applied.join(".git"));
|
||||
}
|
||||
seeded
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue