drop the hive's push of the legacy core/meta repo
Nothing reads core/meta: builds come from internal/meta. Remove push_meta, ensure_meta_remote, meta_read_access, ensure_repo, the per-agent forge sync that only served them, and the git commit path in meta.rs; remove_permission_files now just deletes the seed files.
This commit is contained in:
parent
dd2f0d3ece
commit
b6b568e97e
1 changed files with 81 additions and 518 deletions
|
|
@ -1,149 +1,69 @@
|
|||
//! The hive's `meta/` directory: plain state beside the hive's other files
|
||||
//! (`topology.json`, the swarm limits, the permission seed files) that the
|
||||
//! manager container sees read-only. Agents are built from the swarm's
|
||||
//! `internal/meta` ([`crate::meta_rev`]), not from anything here; a hive that
|
||||
//! predates it also has a git repo in this directory, which
|
||||
//! [`remove_permission_files`] still commits to.
|
||||
//! (`topology.json`, the swarm limits) that the manager container sees
|
||||
//! read-only. Agents are built from the swarm's `internal/meta`
|
||||
//! ([`crate::meta_rev`]), not from anything here.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
use crate::lifecycle;
|
||||
|
||||
const GIT_NAME: &str = "c0re";
|
||||
const GIT_EMAIL: &str = "c0re@hyperhive.local";
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
/// Where the manager sees this directory inside its container (RO bind).
|
||||
pub const CONTAINER_MANAGER_META_MOUNT: &str = "/meta";
|
||||
|
||||
/// Delete the meta repo's permission files and commit the removal. Their
|
||||
/// grants live in the swarm's `agent-permissions` bucket once
|
||||
/// `workers::permissions_import` has sent them.
|
||||
/// Delete the permission seed files a hive that predates the swarm's
|
||||
/// permissions left in the meta directory. Their grants live in the swarm's
|
||||
/// `agent-permissions` bucket once `workers::permissions_import` has sent
|
||||
/// them. A file already gone is fine.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Removing a file or a git stage/commit step fails.
|
||||
pub async fn remove_permission_files() -> Result<()> {
|
||||
let dir = crate::paths::meta_root();
|
||||
let mut removed = Vec::new();
|
||||
for file in crate::workers::permissions_import::META_FILES {
|
||||
/// Removing a file fails.
|
||||
pub fn remove_permission_files() -> Result<()> {
|
||||
remove_in(
|
||||
&crate::paths::meta_root(),
|
||||
crate::workers::permissions_import::META_FILES,
|
||||
)
|
||||
}
|
||||
|
||||
fn remove_in(dir: &std::path::Path, files: &[&str]) -> Result<()> {
|
||||
for file in files {
|
||||
match std::fs::remove_file(dir.join(file)) {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(e) => return Err(e).with_context(|| format!("removing {file}")),
|
||||
}
|
||||
git(&dir, &["rm", "--cached", "--ignore-unmatch", "-q", file]).await?;
|
||||
// Only a tracked file has a removal to commit; naming an untracked
|
||||
// one makes `git commit -- <path>` fail.
|
||||
if paths_dirty(&dir, &[file]).await? {
|
||||
removed.push(*file);
|
||||
}
|
||||
}
|
||||
if !removed.is_empty() {
|
||||
git_commit_paths(
|
||||
&dir,
|
||||
"permissions moved to the swarm's agent-permissions",
|
||||
&removed,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `git <args>` in `dir` within [`lifecycle::GIT_LOCAL_DEADLINE`]; the caller
|
||||
/// reads the exit status.
|
||||
async fn git_output(dir: &Path, args: &[&str]) -> Result<std::process::Output> {
|
||||
lifecycle::run_with_deadline(
|
||||
lifecycle::git_command().current_dir(dir).args(args),
|
||||
lifecycle::GIT_LOCAL_DEADLINE,
|
||||
&format!("git {} in {}", args.join(" "), dir.display()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn git(dir: &Path, args: &[&str]) -> Result<()> {
|
||||
let out = git_output(dir, args).await?;
|
||||
if !out.status.success() {
|
||||
bail!(
|
||||
"git {} failed ({}): {}",
|
||||
args.join(" "),
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Path-limited commit: commits ONLY the given paths, so unrelated staged
|
||||
/// content can never be swept into the commit.
|
||||
async fn git_commit_paths(dir: &Path, message: &str, paths: &[&str]) -> Result<()> {
|
||||
let name = format!("user.name={GIT_NAME}");
|
||||
let email = format!("user.email={GIT_EMAIL}");
|
||||
let mut args = vec!["-c", &name, "-c", &email, "commit", "-m", message, "--"];
|
||||
args.extend_from_slice(paths);
|
||||
git(dir, &args).await?;
|
||||
if let Err(e) = crate::forge::push_meta(dir).await {
|
||||
tracing::warn!(error = ?e, "forge: meta push after commit failed (non-fatal)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// True when any of `paths` differs between HEAD and the index or working
|
||||
/// tree.
|
||||
async fn paths_dirty(dir: &Path, paths: &[&str]) -> Result<bool> {
|
||||
let mut args = vec!["diff", "--quiet", "HEAD", "--"];
|
||||
args.extend_from_slice(paths);
|
||||
let out = git_output(dir, &args).await?;
|
||||
// Exit 0 = no differences; 1 = differences; anything else (e.g.
|
||||
// no HEAD yet on a fresh repo) → treat as dirty so the commit
|
||||
// path runs and surfaces real errors loudly.
|
||||
Ok(!out.status.success())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::remove_in;
|
||||
|
||||
/// A path-limited commit must leave an unrelated staged file untouched.
|
||||
#[tokio::test]
|
||||
async fn path_limited_commit_leaves_unrelated_staged_file_alone() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let dir = tmp.path();
|
||||
git(dir, &["init", "--initial-branch=main"])
|
||||
.await
|
||||
.expect("git init");
|
||||
std::fs::write(dir.join("resource-limits.json"), "{}").expect("write");
|
||||
std::fs::write(dir.join("other.json"), "v1").expect("write");
|
||||
git(dir, &["add", "-A"]).await.expect("add");
|
||||
git_commit_paths(dir, "seed", &["resource-limits.json", "other.json"])
|
||||
.await
|
||||
.expect("seed commit");
|
||||
// Something stages a change (uncommitted)…
|
||||
std::fs::write(dir.join("other.json"), "v2-staged").expect("write");
|
||||
git(dir, &["add", "other.json"]).await.expect("stage");
|
||||
// …and a limits change commits, path-limited.
|
||||
std::fs::write(dir.join("resource-limits.json"), r#"{"alice":[]}"#).expect("write");
|
||||
git(dir, &["add", "resource-limits.json"])
|
||||
.await
|
||||
.expect("add");
|
||||
git_commit_paths(
|
||||
dir,
|
||||
"set resource limits for alice",
|
||||
&["resource-limits.json"],
|
||||
)
|
||||
.await
|
||||
.expect("path-limited commit");
|
||||
// The limits file is committed; the staged one is not.
|
||||
#[test]
|
||||
fn the_listed_files_go_and_nothing_else() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
for name in ["capabilities.json", "tool-groups.json", "topology.json"] {
|
||||
std::fs::write(dir.path().join(name), "{}").unwrap();
|
||||
}
|
||||
remove_in(dir.path(), &["capabilities.json", "tool-groups.json"]).unwrap();
|
||||
assert!(!dir.path().join("capabilities.json").exists());
|
||||
assert!(!dir.path().join("tool-groups.json").exists());
|
||||
assert!(
|
||||
!paths_dirty(dir, &["resource-limits.json"])
|
||||
.await
|
||||
.expect("check"),
|
||||
"limits file must be committed"
|
||||
dir.path().join("topology.json").exists(),
|
||||
"other state stays"
|
||||
);
|
||||
// A file already gone is fine.
|
||||
remove_in(dir.path(), &["capabilities.json"]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_cannot_be_removed_is_an_error_naming_it() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
// A directory where the file belongs fails with a non-NotFound error.
|
||||
std::fs::create_dir(dir.path().join("capabilities.json")).unwrap();
|
||||
let err = remove_in(dir.path(), &["capabilities.json"]).unwrap_err();
|
||||
assert!(
|
||||
paths_dirty(dir, &["other.json"]).await.expect("check"),
|
||||
"the staged file must survive the limits commit"
|
||||
format!("{err:#}").contains("removing capabilities.json"),
|
||||
"{err:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue