queue the build of a config head that moved without a webhook build
This commit is contained in:
parent
a0c8892b99
commit
b7d8c0dfc0
1 changed files with 689 additions and 28 deletions
|
|
@ -978,6 +978,71 @@ impl Client {
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
/// Every config repo in [`CONFIG_ORG`] with a commit on `branch`, as
|
||||
/// `(agent, head commit)`: the heads [`crate::config_drift`] compares with
|
||||
/// what was built.
|
||||
///
|
||||
/// Repos the listing marks `empty` are skipped without a branch call, as is
|
||||
/// a repo with no `branch`. A branch read that fails is logged and skips
|
||||
/// that repo only.
|
||||
///
|
||||
/// # Errors
|
||||
/// The repo listing failed.
|
||||
pub async fn list_config_heads(&self, branch: &str) -> Result<Vec<(String, String)>> {
|
||||
let repos = self
|
||||
.api
|
||||
.call(|api| Box::pin(api.org_list_repos(CONFIG_ORG).all()))
|
||||
.await
|
||||
.with_context(|| format!("list repos in {CONFIG_ORG}"))?;
|
||||
let mut heads = Vec::new();
|
||||
for repo in repos {
|
||||
let Some(agent) = repo.name else {
|
||||
continue;
|
||||
};
|
||||
if repo.empty.unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
match self
|
||||
.api
|
||||
.call(|api| {
|
||||
api.repo_get_branch(CONFIG_ORG, &agent, branch)
|
||||
.into_future()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(head) => {
|
||||
if let Some(commit) = head.commit.and_then(|c| c.id) {
|
||||
heads.push((agent, commit));
|
||||
}
|
||||
}
|
||||
Err(e) if is_repo_not_found(&e) => {}
|
||||
Err(e) => {
|
||||
tracing::warn!(%agent, error = %e, "swarm forge: reading a config head failed, skipping repo");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(heads)
|
||||
}
|
||||
|
||||
/// The bytes of `path` in `owner/repo` at `commit`; `None` when no blob is
|
||||
/// there.
|
||||
///
|
||||
/// # Errors
|
||||
/// A forge failure; a [`ForgejoError`] 404 underneath means the repo or the
|
||||
/// commit is not on the forge ([`is_repo_not_found`]).
|
||||
pub async fn file_at_commit(
|
||||
&self,
|
||||
owner: &str,
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
path: &str,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let file = self
|
||||
.file_at(owner, repo, commit, path, &mut HashMap::new())
|
||||
.await?;
|
||||
Ok(file.map(|(_, bytes)| bytes))
|
||||
}
|
||||
|
||||
/// Pages a search-shaped result (`data: Option<Vec<T>>`, no total-count
|
||||
/// header) to exhaustion via `fetch`, which must issue one request per
|
||||
/// `page` (1-based) at `page_size` and return its `data`. `repo_search`'s
|
||||
|
|
@ -2844,6 +2909,57 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn the_config_heads_skip_empty_repos_and_repos_without_the_branch() {
|
||||
use axum::http::{StatusCode, Uri};
|
||||
|
||||
let seen = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let recorded = Arc::clone(&seen);
|
||||
let app = axum::Router::new().fallback(move |uri: Uri| {
|
||||
recorded.lock().unwrap().push(uri.path().to_owned());
|
||||
let (status, count, body) = match uri.path() {
|
||||
"/api/v1/orgs/agent-configs/repos" => (
|
||||
200,
|
||||
"3",
|
||||
r#"[{"name":"bare","empty":true},{"name":"nomain","empty":false},{"name":"full","empty":false}]"#,
|
||||
),
|
||||
"/api/v1/repos/agent-configs/full/branches/main" => (
|
||||
200,
|
||||
"0",
|
||||
r#"{"name":"main","commit":{"id":"abc123"}}"#,
|
||||
),
|
||||
_ => (404, "0", r#"{"message":"not found"}"#),
|
||||
};
|
||||
async move {
|
||||
(
|
||||
StatusCode::from_u16(status).unwrap(),
|
||||
[("x-total-count", count)],
|
||||
body,
|
||||
)
|
||||
}
|
||||
});
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let url = url::Url::parse(&format!("http://{}/", listener.local_addr().unwrap())).unwrap();
|
||||
tokio::spawn(async move { axum::serve(listener, app).await });
|
||||
let api = crate::forge::api::tests::fixed(url.clone(), "stub-token");
|
||||
let client = Client { api, url };
|
||||
|
||||
let heads = client.list_config_heads("main").await.unwrap();
|
||||
|
||||
assert_eq!(heads, [("full".to_owned(), "abc123".to_owned())]);
|
||||
let paths = seen.lock().unwrap();
|
||||
assert!(
|
||||
paths
|
||||
.iter()
|
||||
.any(|p| p.ends_with("/repos/agent-configs/nomain/branches/main"))
|
||||
);
|
||||
assert!(
|
||||
!paths
|
||||
.iter()
|
||||
.any(|p| p.contains("/repos/agent-configs/bare/"))
|
||||
);
|
||||
}
|
||||
|
||||
/// One PR commit whose parent is `mb`.
|
||||
const ON_MB: &str = r#"[{"sha":"c1","parents":[{"sha":"mb"}]}]"#;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue