diff --git a/TODO.txt b/TODO.txt index 12cd241..c4017af 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,6 @@ -- Return status to codeberg: Extract repo from Webhook and keep in messages - Make RabbitMQ Messages Print nicely - move runner logs to debug level so they can be logged in the CI job but don't spam the deployed version - Make Orchestrator serve the runner binaries so no external server is needed - Make orchestrator detect the address it will be reachable by checking the libvirt config or on illumos use it's external IP -- Make VM reachable IP of the orchestrator configurable in case the setup on illumos gets more complicated (via config file) \ No newline at end of file +- Make VM reachable IP of the orchestrator configurable in case the setup on illumos gets more complicated (via config file) +- Make the forge-integration task use fnox secrets \ No newline at end of file diff --git a/crates/common/src/messages.rs b/crates/common/src/messages.rs index 6326c8b..863f8b9 100644 --- a/crates/common/src/messages.rs +++ b/crates/common/src/messages.rs @@ -15,6 +15,12 @@ pub struct JobRequest { pub source: SourceSystem, /// Repository clone URL (SSH or HTTPS). pub repo_url: String, + /// Repository owner (parsed from webhook or URL); optional for backward-compat. + #[serde(default)] + pub repo_owner: Option, + /// Repository name (parsed from webhook or URL); optional for backward-compat. + #[serde(default)] + pub repo_name: Option, /// Commit SHA to check out. pub commit_sha: String, /// Optional path to the workflow file within the repo (KDL). @@ -50,6 +56,8 @@ impl JobRequest { request_id: Uuid::new_v4(), source, repo_url: repo_url.into(), + repo_owner: None, + repo_name: None, commit_sha: commit_sha.into(), workflow_path: None, workflow_job_id: None, @@ -69,6 +77,12 @@ pub struct JobResult { pub request_id: Uuid, /// Repository and commit info (for convenience in consumers) pub repo_url: String, + /// Repository owner (when known). Optional for backward-compat. + #[serde(default)] + pub repo_owner: Option, + /// Repository name (when known). Optional for backward-compat. + #[serde(default)] + pub repo_name: Option, pub commit_sha: String, /// Outcome info pub success: bool, @@ -96,6 +110,8 @@ impl JobResult { schema_version: default_jobresult_schema(), request_id, repo_url, + repo_owner: None, + repo_name: None, commit_sha, success, exit_code, diff --git a/crates/forge-integration/src/main.rs b/crates/forge-integration/src/main.rs index f3507fc..4ae5a51 100644 --- a/crates/forge-integration/src/main.rs +++ b/crates/forge-integration/src/main.rs @@ -221,6 +221,20 @@ async fn post_commit_status( ) -> Result<()> { // Extract owner/repo from repo_url (supports https://.../owner/repo.git and ssh://git@host/owner/repo.git) let (owner, repo) = parse_owner_repo(repo_url).ok_or_else(|| miette::miette!("cannot parse owner/repo from repo_url: {repo_url}"))?; + post_commit_status_owner(base, token, &owner, &repo, sha, context, state, target_url, description).await +} + +async fn post_commit_status_owner( + base: &str, + token: &str, + owner: &str, + repo: &str, + sha: &str, + context: &str, + state: &str, + target_url: Option<&str>, + description: Option<&str>, +) -> Result<()> { let api = format!("{}/repos/{}/{}/statuses/{}", base.trim_end_matches('/'), owner, repo, sha); let mut body = serde_json::json!({ "state": state, @@ -377,17 +391,31 @@ async fn handle_job_result(state: &AppState, jobres: &common::messages::JobResul let state_str = if jobres.success { "success" } else { "failure" }; if let (Some(base), Some(token)) = (state.forgejo_base.as_ref(), state.forgejo_token.as_ref()) { let desc = if jobres.success { Some("Job succeeded") } else { Some("Job failed") }; - let _ = post_commit_status( - base, - token, - &jobres.repo_url, - &jobres.commit_sha, - &state.forge_context, - state_str, - target_url.as_deref(), - desc, - ) - .await; + // Prefer explicit owner/repo from JobResult when available + if let (Some(owner), Some(repo)) = (jobres.repo_owner.as_ref(), jobres.repo_name.as_ref()) { + let _ = post_commit_status_owner( + base, + token, + owner, + repo, + &jobres.commit_sha, + &state.forge_context, + state_str, + target_url.as_deref(), + desc, + ).await; + } else { + let _ = post_commit_status( + base, + token, + &jobres.repo_url, + &jobres.commit_sha, + &state.forge_context, + state_str, + target_url.as_deref(), + desc, + ).await; + } } Ok(()) @@ -679,6 +707,11 @@ async fn enqueue_job(state: &Arc, repo_url: String, commit_sha: String miette::bail!("missing repo_url in webhook payload"); } let mut jr = common::JobRequest::new(common::SourceSystem::Forgejo, repo_url, commit_sha); + // Try to populate repo_owner/repo_name from URL for accurate status routing + if let Some((owner, name)) = parse_owner_repo(&jr.repo_url) { + jr.repo_owner = Some(owner); + jr.repo_name = Some(name); + } // Infer runs_on from repo map, labels, or default jr.runs_on = infer_runs_on(state, &jr.repo_url, labels.as_ref().map(|v| v.as_slice())); common::publish_job(&state.mq_cfg, &jr).await?; diff --git a/crates/orchestrator/src/grpc.rs b/crates/orchestrator/src/grpc.rs index 2812a5d..bd3f7e2 100644 --- a/crates/orchestrator/src/grpc.rs +++ b/crates/orchestrator/src/grpc.rs @@ -13,6 +13,25 @@ use std::sync::Arc; use crate::persist::Persist; +fn parse_owner_repo(repo_url: &str) -> Option<(String, String)> { + let url = repo_url.trim_end_matches(".git"); + if let Some(rest) = url.strip_prefix("https://").or_else(|| url.strip_prefix("http://")) { + let parts: Vec<&str> = rest.split('/').collect(); + if parts.len() >= 3 { return Some((parts[1].to_string(), parts[2].to_string())); } + } else if let Some(rest) = url.strip_prefix("ssh://") { + // ssh://git@host/owner/repo + let after_host = rest.splitn(2, '/').nth(1)?; + let parts: Vec<&str> = after_host.split('/').collect(); + if parts.len() >= 2 { return Some((parts[0].to_string(), parts[1].to_string())); } + } else if let Some(idx) = url.find(':') { + // git@host:owner/repo + let after = &url[idx+1..]; + let parts: Vec<&str> = after.split('/').collect(); + if parts.len() >= 2 { return Some((parts[0].to_string(), parts[1].to_string())); } + } + None +} + pub struct RunnerSvc { mq_cfg: MqConfig, persist: Arc, @@ -95,7 +114,7 @@ impl Runner for RunnerSvc { if let (Some(id), Some(repo), Some(sha)) = (req_id.as_ref(), repo_url.as_ref(), commit_sha.as_ref()) { - let result = common::messages::JobResult::new( + let mut result = common::messages::JobResult::new( id.clone(), repo.clone(), sha.clone(), @@ -103,6 +122,11 @@ impl Runner for RunnerSvc { exit_code, None, ); + // Try to parse owner/repo for downstream integrations to avoid reparsing URLs + if let Some((owner, name)) = parse_owner_repo(&repo) { + result.repo_owner = Some(owner); + result.repo_name = Some(name); + } if let Err(e) = publish_job_result(&self.mq_cfg, &result).await { error!(error = %e, request_id = %id, "failed to publish JobResult"); }