Address review: move RepoConfig to quire-server, trigger returns Result
- Remove quire-core::ci::repo_config; define RepoConfig / RepoGithubConfig
in quire-server::quire alongside the other config structs
- mirror::trigger now returns crate::Result<()>; use ? for global config
and token reveal; call site in server.rs logs a single tracing::error
https://claude.ai/code/session_01MtUMXi7Z3GCDWQFY8puWpu
diff --git a/quire-core/src/ci/mod.rs b/quire-core/src/ci/mod.rs
index eaeb2ee..6f417fc 100644
--- a/quire-core/src/ci/mod.rs
+++ b/quire-core/src/ci/mod.rs
@@ -10,6 +10,5 @@ pub mod event;
pub mod logs;
pub mod pipeline;
pub mod registration;
-pub mod repo_config;
pub mod run;
pub mod runtime;
diff --git a/quire-core/src/ci/repo_config.rs b/quire-core/src/ci/repo_config.rs
deleted file mode 100644
index bba9f78..0000000
--- a/quire-core/src/ci/repo_config.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-//! Per-repo CI configuration parsed from `.quire/config.fnl`.
-//!
-//! Loaded at run time (inside `quire-ci`) from the materialized
-//! workspace. Missing file → all defaults.
-
-/// Per-repo CI configuration.
-#[derive(serde::Deserialize, Debug, Default, Clone)]
-#[serde(default, rename_all = "kebab-case")]
-pub struct RepoConfig {
- pub github: GithubRepoConfig,
-}
-
-/// Per-repo GitHub configuration.
-#[derive(serde::Deserialize, Debug, Default, Clone)]
-#[serde(default, rename_all = "kebab-case")]
-pub struct GithubRepoConfig {
- /// Remote URL to mirror every pushed ref to.
- /// E.g. `"https://github.com/user/repo.git"`.
- pub mirror: Option<String>,
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn load(source: &str) -> RepoConfig {
- crate::fennel::Fennel::new()
- .expect("Fennel::new")
- .load_string(source, "config.fnl")
- .expect("load_string")
- }
-
- #[test]
- fn defaults_when_empty_table() {
- let cfg = load("{}");
- assert!(cfg.github.mirror.is_none());
- }
-
- #[test]
- fn parses_mirror_url() {
- let cfg = load(r#"{:github {:mirror "https://github.com/user/repo.git"}}"#);
- assert_eq!(
- cfg.github.mirror.as_deref(),
- Some("https://github.com/user/repo.git")
- );
- }
-}
diff --git a/quire-server/src/bin/quire/server.rs b/quire-server/src/bin/quire/server.rs
index d0fd40d..6db216e 100644
--- a/quire-server/src/bin/quire/server.rs
+++ b/quire-server/src/bin/quire/server.rs
@@ -157,5 +157,11 @@ async fn handle_event_connection(mut stream: tokio::net::UnixStream, quire: Quir
}
ci::trigger(&quire, &event);
- mirror::trigger(&quire, &event);
+ if let Err(e) = mirror::trigger(&quire, &event) {
+ tracing::error!(
+ repo = %event.repo,
+ error = &e as &(dyn std::error::Error + 'static),
+ "mirror failed",
+ );
+ }
}
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index b19943e..6ead68f 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -11,44 +11,24 @@ use crate::quire::Quire;
/// Reads `github.mirror-token` from global config for auth. For each updated
/// ref, reads `.quire/config.fnl` at the new SHA to obtain the `github.mirror`
/// URL. Skips repos with no mirror URL configured.
-pub fn trigger(quire: &Quire, event: &PushEvent) {
+pub fn trigger(quire: &Quire, event: &PushEvent) -> crate::Result<()> {
let repo = match quire.repo(&event.repo) {
Ok(r) if r.exists() => r,
Ok(_) => {
tracing::warn!(repo = %event.repo, "mirror: repo not found on disk");
- return;
+ return Ok(());
}
Err(e) => {
- tracing::error!(repo = %event.repo, error = %e, "mirror: invalid repo name");
- return;
+ return Err(crate::Error::Io(std::io::Error::other(e.to_string())));
}
};
- let config = match quire.global_config() {
- Ok(c) => c,
- Err(e) => {
- tracing::error!(
- repo = %event.repo,
- error = &e as &(dyn std::error::Error + 'static),
- "mirror: failed to load global config",
- );
- return;
- }
- };
-
- let mirror_token = match config.github.mirror_token {
- None => None,
- Some(ref secret) => match secret.reveal() {
- Ok(t) => Some(t.to_string()),
- Err(e) => {
- tracing::error!(
- error = &e as &(dyn std::error::Error + 'static),
- "mirror: failed to reveal mirror token",
- );
- return;
- }
- },
- };
+ let config = quire.global_config()?;
+ let mirror_token = config
+ .github
+ .mirror_token
+ .map(|s| s.reveal().map(str::to_owned))
+ .transpose()?;
for push_ref in event.updated_refs() {
let repo_config = match repo.repo_config(&push_ref.new_sha) {
@@ -75,6 +55,8 @@ pub fn trigger(quire: &Quire, event: &PushEvent) {
mirror_token.as_deref(),
);
}
+
+ Ok(())
}
fn push_to_mirror(
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index 7bc5384..a9a11ef 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -51,6 +51,22 @@ fn default_port() -> u16 {
3000
}
+/// Per-repo CI configuration parsed from `.quire/config.fnl`.
+#[derive(serde::Deserialize, Debug, Default, Clone)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct RepoConfig {
+ pub github: RepoGithubConfig,
+}
+
+/// Per-repo GitHub configuration.
+#[derive(serde::Deserialize, Debug, Default, Clone)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct RepoGithubConfig {
+ /// Remote URL to mirror every pushed ref to.
+ /// E.g. `"https://github.com/user/repo.git"`.
+ pub mirror: Option<String>,
+}
+
#[derive(serde::Deserialize, Debug, Default)]
pub struct CiConfig {
/// How the orchestrator dispatches CI runs. Defaults to shelling
@@ -159,7 +175,7 @@ impl Repo {
/// Read and parse `.quire/config.fnl` at the given commit SHA.
///
/// Returns defaults if the file does not exist at that commit.
- pub fn repo_config(&self, sha: &str) -> AppResult<quire_core::ci::repo_config::RepoConfig> {
+ pub fn repo_config(&self, sha: &str) -> AppResult<RepoConfig> {
let output = self
.git(&["show", &format!("{sha}:.quire/config.fnl")])
.stdout(std::process::Stdio::piped())
@@ -169,7 +185,7 @@ impl Repo {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("does not exist") || stderr.contains("not found") {
- return Ok(quire_core::ci::repo_config::RepoConfig::default());
+ return Ok(RepoConfig::default());
}
return Err(Error::Io(std::io::Error::other(format!(
"failed to read .quire/config.fnl at {sha}: {stderr}"