//! This hive's reads of `internal/meta` and of an agent's config head, as //! [`crate::meta_rev::MetaSource`], under the hive's forge read token. use forgejo_api::structs::RepoGetRawFileQuery; use forgejo_api::{ApiErrorKind, Forgejo, ForgejoError}; use reqwest::StatusCode; use super::reader::ReaderToken; use super::{CONFIG_ORG, bounded}; use crate::meta_rev::MetaSource; /// The org and repo the swarm commits its meta flake to. const META_OWNER: &str = "internal"; const META_REPO: &str = "meta"; /// The branch an agent's config is deployed from, which `fetch_forge_main` /// and the swarm controller's pins both read. const CONFIG_BRANCH: &str = "main"; /// [`MetaSource`] over the forge API. pub(crate) struct ForgeSource { api: Forgejo, } /// A source reading as this hive, or `None` when it holds no forge token. pub(crate) async fn internal_meta_source() -> Option { let token: ReaderToken = super::reader_token().await?; match token.api() { Ok(api) => Some(ForgeSource { api }), Err(e) => { tracing::warn!(error = ?e, "internal/meta: cannot build a forge client"); None } } } fn is_not_found(e: &ForgejoError) -> bool { match e { ForgejoError::ApiError(api) => match api.error_kind() { ApiErrorKind::NotFound { .. } => true, ApiErrorKind::Other(s) => *s == StatusCode::NOT_FOUND, _ => false, }, ForgejoError::UnexpectedStatusCode(s) => *s == StatusCode::NOT_FOUND, _ => false, } } impl ForgeSource { /// The head commit of `branch` in `owner/repo`; `None` when the repo or /// the branch is not there. async fn branch_head( &self, owner: &str, repo: &str, branch: &str, ) -> anyhow::Result> { let op = format!("read branch {branch} of {owner}/{repo}"); match bounded(&op, self.api.repo_get_branch(owner, repo, branch)).await? { Ok(branch) => Ok(branch.commit.and_then(|c| c.id)), Err(e) if is_not_found(&e) => Ok(None), Err(e) => Err(anyhow::Error::new(e).context(op)), } } } impl MetaSource for ForgeSource { fn nix_can_fetch(&self) -> bool { super::core_token().is_some() } async fn meta_head(&self) -> anyhow::Result> { use anyhow::Context as _; let op = format!("read repo {META_OWNER}/{META_REPO}"); let repo = match bounded(&op, self.api.repo_get(META_OWNER, META_REPO)).await? { Ok(repo) => repo, Err(e) if is_not_found(&e) => return Ok(None), Err(e) => return Err(anyhow::Error::new(e).context(op)), }; if repo.empty.unwrap_or(false) { return Ok(None); } let branch = repo .default_branch .with_context(|| format!("{META_OWNER}/{META_REPO} names no default branch"))?; self.branch_head(META_OWNER, META_REPO, &branch).await } async fn flake_at(&self, rev: &str) -> anyhow::Result { let op = format!("read flake.nix of {META_OWNER}/{META_REPO} at {rev}"); let query = RepoGetRawFileQuery { r#ref: Some(rev.to_owned()), }; let raw = bounded( &op, self.api .repo_get_raw_file(META_OWNER, META_REPO, "flake.nix", query), ) .await? .map_err(|e| anyhow::Error::new(e).context(op))?; Ok(String::from_utf8_lossy(&raw).into_owned()) } async fn config_head(&self, agent: &str) -> anyhow::Result> { self.branch_head(CONFIG_ORG, agent, CONFIG_BRANCH).await } }