Address review: route fix, RepoConfig placement, push_to_mirror cleanup
- api.rs: revert {*name} wildcard back to {name} (no longer needed)
- quire/mod.rs: move RepoConfig / RepoGithubConfig to after impl Repo
so they sit next to the type they belong to
- mirror.rs: push_to_mirror takes token: &str (always required) and
returns Result<()>; caller logs the error; token absence is a per-ref
warning rather than a fatal error
https://claude.ai/code/session_01MtUMXi7Z3GCDWQFY8puWpu
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index 6ead68f..e1b52f7 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -48,12 +48,22 @@ pub fn trigger(quire: &Quire, event: &PushEvent) -> crate::Result<()> {
continue;
};
- push_to_mirror(
- &repo,
- &push_ref.ref_name,
- &mirror_url,
- mirror_token.as_deref(),
- );
+ let Some(token) = mirror_token.as_deref() else {
+ tracing::warn!(
+ ref_name = %push_ref.ref_name,
+ "mirror: mirror-token not configured, skipping ref",
+ );
+ continue;
+ };
+
+ if let Err(e) = push_to_mirror(&repo, &push_ref.ref_name, &mirror_url, token) {
+ tracing::error!(
+ ref_name = %push_ref.ref_name,
+ mirror_url,
+ error = &e as &(dyn std::error::Error + 'static),
+ "mirror: push failed",
+ );
+ }
}
Ok(())
@@ -63,45 +73,31 @@ fn push_to_mirror(
repo: &crate::quire::Repo,
ref_name: &str,
mirror_url: &str,
- token: Option<&str>,
-) {
+ token: &str,
+) -> crate::Result<()> {
// Force-push the ref to the mirror. The `+` prefix allows rewrites.
let refspec = format!("+{ref_name}:{ref_name}");
- let mut cmd = repo.git(&["push", "--porcelain", mirror_url, &refspec]);
// Pass the auth token via git config env vars so it never appears in argv.
- if let Some(token) = token {
- cmd.env("GIT_CONFIG_COUNT", "1")
- .env("GIT_CONFIG_KEY_0", "http.extraHeader")
- .env(
- "GIT_CONFIG_VALUE_0",
- format!("Authorization: Bearer {token}"),
- );
- }
-
- match cmd
+ let out = repo
+ .git(&["push", "--porcelain", mirror_url, &refspec])
+ .env("GIT_CONFIG_COUNT", "1")
+ .env("GIT_CONFIG_KEY_0", "http.extraHeader")
+ .env(
+ "GIT_CONFIG_VALUE_0",
+ format!("Authorization: Bearer {token}"),
+ )
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
- .output()
- {
- Ok(out) if out.status.success() => {
- tracing::info!(ref_name, mirror_url, "mirror: push succeeded");
- }
- Ok(out) => {
- tracing::error!(
- ref_name,
- mirror_url,
- stderr = %String::from_utf8_lossy(&out.stderr),
- "mirror: push failed",
- );
- }
- Err(e) => {
- tracing::error!(
- ref_name,
- mirror_url,
- error = %e,
- "mirror: failed to run git push",
- );
- }
+ .output()?;
+
+ if !out.status.success() {
+ let stderr = String::from_utf8_lossy(&out.stderr);
+ return Err(crate::Error::Io(std::io::Error::other(format!(
+ "git push to {mirror_url} failed: {stderr}"
+ ))));
}
+
+ tracing::info!(ref_name, mirror_url, "mirror: push succeeded");
+ Ok(())
}
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index a9a11ef..0907f56 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -51,22 +51,6 @@ 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
@@ -211,6 +195,22 @@ impl Repo {
}
}
+/// 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>,
+}
+
/// Application runtime context.
///
/// Carries configuration and provides resolved paths to repositories.
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index 5ef2670..ce24d4b 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -28,7 +28,7 @@ use crate::Quire;
pub fn router(quire: Quire) -> axum::Router {
let run_routes = axum::Router::new()
.route("/bootstrap", axum::routing::get(get_bootstrap))
- .route("/secrets/{*name}", axum::routing::get(get_secret))
+ .route("/secrets/{name}", axum::routing::get(get_secret))
.layer(axum::middleware::from_fn_with_state(
quire.clone(),
verify_run_token,
@@ -204,7 +204,6 @@ async fn get_bootstrap(
/// `GET /api/run/secrets/:name`
///
/// Returns the plain-text value of a named secret from the global config.
-/// Supports slash-separated names via the `{*name}` wildcard route.
/// Auth is handled by [`verify_run_token`] middleware.
/// Returns 404 if the secret is not declared in config.
#[derive(serde::Deserialize)]