diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 23:00:30 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 23:00:30 +0300 |
| commit | 71ffb442e5f8072c6e0a974df9ae085bcf0e5d2a (patch) | |
| tree | d336b1d64747aeebb1a80c2e7c4e9b5d24253751 /ofborg/tickborg/src/tasks | |
| parent | f96ea38d595162813a460f80f84e20f8d7f241bc (diff) | |
| download | Project-Tick-71ffb442e5f8072c6e0a974df9ae085bcf0e5d2a.tar.gz Project-Tick-71ffb442e5f8072c6e0a974df9ae085bcf0e5d2a.zip | |
NOISSUE update bootstrap script paths in documentation for Linux and Windows
remove unnecessary badges from README
add example environment configuration for Ofborg
create production configuration for Ofborg
correct RabbitMQ host in example configuration
add push event handling in GitHub webhook receiver
implement push filter task for handling push events
extend build job structure to include push event information
enhance build result structure to accommodate push event data
add push event data handling in various message processing tasks
update log message collector to prevent 404 errors on log links
add push filter task to task module
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'ofborg/tickborg/src/tasks')
| -rw-r--r-- | ofborg/tickborg/src/tasks/build.rs | 73 | ||||
| -rw-r--r-- | ofborg/tickborg/src/tasks/githubcommentposter.rs | 95 | ||||
| -rw-r--r-- | ofborg/tickborg/src/tasks/log_message_collector.rs | 3 | ||||
| -rw-r--r-- | ofborg/tickborg/src/tasks/mod.rs | 1 | ||||
| -rw-r--r-- | ofborg/tickborg/src/tasks/pushfilter.rs | 165 |
5 files changed, 302 insertions, 35 deletions
diff --git a/ofborg/tickborg/src/tasks/build.rs b/ofborg/tickborg/src/tasks/build.rs index 56583b28b4..2bac6d749b 100644 --- a/ofborg/tickborg/src/tasks/build.rs +++ b/ofborg/tickborg/src/tasks/build.rs @@ -121,6 +121,7 @@ impl JobActions { attempted_attrs: None, skipped_attrs: None, status: BuildStatus::Failure, + push: self.job.push.clone(), }; let result_exchange = self.result_exchange.clone(); @@ -209,6 +210,7 @@ impl JobActions { skipped_attrs: Some(not_attempted_attrs), attempted_attrs: None, status: BuildStatus::Skipped, + push: self.job.push.clone(), }; let result_exchange = self.result_exchange.clone(); @@ -249,6 +251,7 @@ impl JobActions { status, attempted_attrs: Some(attempted_attrs), skipped_attrs: Some(not_attempted_attrs), + push: self.job.push.clone(), }; let result_exchange = self.result_exchange.clone(); @@ -301,7 +304,12 @@ impl notifyworker::SimpleNotifyWorker for BuildWorker { dyn notifyworker::NotificationReceiver + std::marker::Send + std::marker::Sync, >, ) { - let span = debug_span!("job", pr = ?job.pr.number); + let is_push = job.is_push(); + let span = if is_push { + debug_span!("job", push_branch = ?job.push.as_ref().map(|p| &p.branch), sha = %job.pr.head_sha) + } else { + debug_span!("job", pr = ?job.pr.number) + }; let _enter = span.enter(); let actions = self.actions(job, notifier); @@ -312,10 +320,19 @@ impl notifyworker::SimpleNotifyWorker for BuildWorker { return; } - info!( - "Working on https://github.com/{}/pull/{}", - actions.job.repo.full_name, actions.job.pr.number - ); + if is_push { + let push = actions.job.push.as_ref().unwrap(); + info!( + "Working on push to {}:{} ({})", + actions.job.repo.full_name, push.branch, push.head_sha + ); + } else { + info!( + "Working on https://github.com/{}/pull/{}", + actions.job.repo.full_name, actions.job.pr.number + ); + } + let project = self.cloner.project( &actions.job.repo.full_name, actions.job.repo.clone_url.clone(), @@ -331,22 +348,38 @@ impl notifyworker::SimpleNotifyWorker for BuildWorker { let refpath = co.checkout_origin_ref(target_branch.as_ref()).unwrap(); - if co.fetch_pr(actions.job.pr.number).is_err() { - info!("Failed to fetch {}", actions.job.pr.number); - actions.pr_head_missing().await; - return; - } + if is_push { + // For push builds: the commit is already on the branch, just verify it exists + if !co.commit_exists(actions.job.pr.head_sha.as_ref()) { + info!("Push commit {} doesn't exist after fetch", actions.job.pr.head_sha); + actions.commit_missing().await; + return; + } + // Checkout the exact pushed commit + if co.checkout_ref(actions.job.pr.head_sha.as_ref()).is_err() { + info!("Failed to checkout push commit {}", actions.job.pr.head_sha); + actions.merge_failed().await; + return; + } + } else { + // For PR builds: fetch PR ref, verify commit, merge + if co.fetch_pr(actions.job.pr.number).is_err() { + info!("Failed to fetch {}", actions.job.pr.number); + actions.pr_head_missing().await; + return; + } - if !co.commit_exists(actions.job.pr.head_sha.as_ref()) { - info!("Commit {} doesn't exist", actions.job.pr.head_sha); - actions.commit_missing().await; - return; - } + if !co.commit_exists(actions.job.pr.head_sha.as_ref()) { + info!("Commit {} doesn't exist", actions.job.pr.head_sha); + actions.commit_missing().await; + return; + } - if co.merge_commit(actions.job.pr.head_sha.as_ref()).is_err() { - info!("Failed to merge {}", actions.job.pr.head_sha); - actions.merge_failed().await; - return; + if co.merge_commit(actions.job.pr.head_sha.as_ref()).is_err() { + info!("Failed to merge {}", actions.job.pr.head_sha); + actions.merge_failed().await; + return; + } } // Determine which projects to build from the requested attrs @@ -528,6 +561,7 @@ mod tests { logs: Some((Some(String::from("logs")), Some(String::from("build.log")))), statusreport: Some((Some(String::from("build-results")), None)), request_id: "bogus-request-id".to_owned(), + push: None, }; let dummyreceiver = Arc::new(notifyworker::DummyNotificationReceiver::new()); @@ -574,6 +608,7 @@ mod tests { logs: Some((Some(String::from("logs")), Some(String::from("build.log")))), statusreport: Some((Some(String::from("build-results")), None)), request_id: "bogus-request-id".to_owned(), + push: None, }; let dummyreceiver = Arc::new(notifyworker::DummyNotificationReceiver::new()); diff --git a/ofborg/tickborg/src/tasks/githubcommentposter.rs b/ofborg/tickborg/src/tasks/githubcommentposter.rs index 70c4a118e4..2f49d7401b 100644 --- a/ofborg/tickborg/src/tasks/githubcommentposter.rs +++ b/ofborg/tickborg/src/tasks/githubcommentposter.rs @@ -71,7 +71,11 @@ impl worker::SimpleWorker for GitHubCommentPoster { } }; - let span = debug_span!("job", pr = ?pr.number); + let span = if pr.number == 0 { + debug_span!("job", push_sha = %pr.head_sha) + } else { + debug_span!("job", pr = ?pr.number) + }; let _enter = span.enter(); for check in checks { @@ -111,17 +115,40 @@ fn job_to_check(job: &BuildJob, architecture: &str, timestamp: DateTime<Utc>) -> all_attrs = vec![String::from("(unknown attributes)")]; } + let details_key = if job.is_push() { + format!( + "push.{}", + job.push + .as_ref() + .map(|p| p.branch.replace('/', "-")) + .unwrap_or_default() + ) + } else { + format!("{}", job.pr.number) + }; + + let name = if job.is_push() { + let branch = job + .push + .as_ref() + .map(|p| p.branch.as_str()) + .unwrap_or("unknown"); + format!("{} on {architecture} (push to {branch})", all_attrs.join(", ")) + } else { + format!("{} on {architecture}", all_attrs.join(", ")) + }; + CheckRunOptions { - name: format!("{} on {architecture}", all_attrs.join(", ")), + name, actions: None, completed_at: None, started_at: Some(timestamp.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)), conclusion: None, details_url: Some(format!( - "https://logs.tickborg.project-tick.net/?key={}/{}.{}", + "https://logs.tickborg.projecttick.net/?key={}/{}.{}", &job.repo.owner.to_lowercase(), &job.repo.name.to_lowercase(), - job.pr.number, + details_key, )), external_id: None, head_sha: job.pr.head_sha.clone(), @@ -180,17 +207,47 @@ fn result_to_check(result: &LegacyBuildResult, timestamp: DateTime<Utc>) -> Chec String::from("No partial log is available.") }; + let is_push = result.push.is_some(); + + let details_key = if is_push { + format!( + "push.{}", + result + .push + .as_ref() + .map(|p| p.branch.replace('/', "-")) + .unwrap_or_default() + ) + } else { + format!("{}", result.pr.number) + }; + + let name = if is_push { + let branch = result + .push + .as_ref() + .map(|p| p.branch.as_str()) + .unwrap_or("unknown"); + format!( + "{} on {} (push to {branch})", + all_attrs.join(", "), + result.system + ) + } else { + format!("{} on {}", all_attrs.join(", "), result.system) + }; + CheckRunOptions { - name: format!("{} on {}", all_attrs.join(", "), result.system), + name, actions: None, completed_at: Some(timestamp.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)), started_at: None, conclusion: Some(conclusion), details_url: Some(format!( - "https://logs.tickborg.project-tick.net/?key={}/{}.{}&attempt_id={}", + "https://logs.tickborg.projecttick.net/?key={}/{}.{}&attempt_id={}", &result.repo.owner.to_lowercase(), &result.repo.name.to_lowercase(), - result.pr.number, + details_key, result.attempt_id, )), external_id: Some(result.attempt_id.clone()), @@ -244,6 +301,7 @@ mod tests { request_id: "bogus-request-id".to_owned(), attrs: vec!["foo".to_owned(), "bar".to_owned()], + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -256,7 +314,7 @@ mod tests { completed_at: None, status: Some(CheckRunState::Queued), conclusion: None, - details_url: Some("https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345".to_string()), + details_url: Some("https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345".to_string()), external_id: None, head_sha: "abc123".to_string(), output: None, @@ -296,6 +354,7 @@ mod tests { attempted_attrs: Some(vec!["foo".to_owned()]), skipped_attrs: Some(vec!["bar".to_owned()]), status: BuildStatus::Success, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -310,7 +369,7 @@ mod tests { status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Success), details_url: Some( - "https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" + "https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" .to_string() ), external_id: Some("neatattemptid".to_string()), @@ -378,6 +437,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 attempted_attrs: Some(vec!["foo".to_owned()]), skipped_attrs: None, status: BuildStatus::Failure, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -392,7 +452,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Neutral), details_url: Some( - "https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" + "https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" .to_string() ), external_id: Some("neatattemptid".to_string()), @@ -457,6 +517,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 attempted_attrs: Some(vec!["foo".to_owned()]), skipped_attrs: None, status: BuildStatus::TimedOut, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -471,7 +532,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Neutral), details_url: Some( - "https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" + "https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" .to_string() ), external_id: Some("neatattemptid".to_string()), @@ -537,6 +598,7 @@ error: build of '/nix/store/l1limh50lx2cx45yb2gqpv7k8xl1mik2-gdb-8.1.drv' failed attempted_attrs: None, skipped_attrs: None, status: BuildStatus::Success, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -551,7 +613,7 @@ error: build of '/nix/store/l1limh50lx2cx45yb2gqpv7k8xl1mik2-gdb-8.1.drv' failed status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Success), details_url: Some( - "https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" + "https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" .to_string() ), external_id: Some("neatattemptid".to_string()), @@ -615,6 +677,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 attempted_attrs: None, skipped_attrs: None, status: BuildStatus::Failure, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -629,7 +692,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Neutral), details_url: Some( - "https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" + "https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid" .to_string() ), external_id: Some("neatattemptid".to_string()), @@ -682,6 +745,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 attempted_attrs: None, skipped_attrs: Some(vec!["not-attempted".to_owned()]), status: BuildStatus::Skipped, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -695,7 +759,7 @@ patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29 completed_at: Some("2023-04-20T13:37:42Z".to_string()), status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Skipped), - details_url: Some("https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid".to_string()), + details_url: Some("https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid".to_string()), external_id: Some("neatattemptid".to_string()), head_sha: "abc123".to_string(), output: Some(Output { @@ -735,6 +799,7 @@ foo attempted_attrs: None, skipped_attrs: Some(vec!["not-attempted".to_owned()]), status: BuildStatus::Skipped, + push: None, }; let timestamp = Utc.with_ymd_and_hms(2023, 4, 20, 13, 37, 42).unwrap(); @@ -748,7 +813,7 @@ foo completed_at: Some("2023-04-20T13:37:42Z".to_string()), status: Some(CheckRunState::Completed), conclusion: Some(Conclusion::Skipped), - details_url: Some("https://logs.tickborg.project-tick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid".to_string()), + details_url: Some("https://logs.tickborg.projecttick.net/?key=project-tick/Project-Tick.2345&attempt_id=neatattemptid".to_string()), external_id: Some("neatattemptid".to_string()), head_sha: "abc123".to_string(), output: Some(Output { diff --git a/ofborg/tickborg/src/tasks/log_message_collector.rs b/ofborg/tickborg/src/tasks/log_message_collector.rs index 2d80f72f03..302445e2ff 100644 --- a/ofborg/tickborg/src/tasks/log_message_collector.rs +++ b/ofborg/tickborg/src/tasks/log_message_collector.rs @@ -215,7 +215,7 @@ impl worker::SimpleWorker for LogMessageCollector { // Make sure the log content exists by opening its handle. // This (hopefully) prevents builds that produce no output (for any reason) from - // having their logs.tickborg.project-tick.net link complaining about a 404. + // having their logs.tickborg.projecttick.net link complaining about a 404. let _ = self.handle_for(&job.from).unwrap(); } MsgType::Msg(ref message) => { @@ -448,6 +448,7 @@ mod tests { status: BuildStatus::Success, attempted_attrs: Some(vec!["foo".to_owned()]), skipped_attrs: Some(vec!["bar".to_owned()]), + push: None, })) }) .await diff --git a/ofborg/tickborg/src/tasks/mod.rs b/ofborg/tickborg/src/tasks/mod.rs index 5aab0fa631..3bf701870d 100644 --- a/ofborg/tickborg/src/tasks/mod.rs +++ b/ofborg/tickborg/src/tasks/mod.rs @@ -5,4 +5,5 @@ pub mod evaluationfilter; pub mod githubcommentfilter; pub mod githubcommentposter; pub mod log_message_collector; +pub mod pushfilter; pub mod statscollector; diff --git a/ofborg/tickborg/src/tasks/pushfilter.rs b/ofborg/tickborg/src/tasks/pushfilter.rs new file mode 100644 index 0000000000..8cf8d7a0ef --- /dev/null +++ b/ofborg/tickborg/src/tasks/pushfilter.rs @@ -0,0 +1,165 @@ +use crate::acl; +use crate::ghevent; +use crate::message::buildjob; +use crate::message::{PushTrigger, Repo}; +use crate::systems; +use crate::worker; + +use tracing::{debug_span, info}; +use uuid::Uuid; + +pub struct PushFilterWorker { + acl: acl::Acl, + /// Default projects/attrs to build when push doesn't match any known project. + default_attrs: Vec<String>, +} + +impl PushFilterWorker { + pub fn new(acl: acl::Acl, default_attrs: Vec<String>) -> PushFilterWorker { + PushFilterWorker { + acl, + default_attrs, + } + } +} + +impl worker::SimpleWorker for PushFilterWorker { + type J = ghevent::PushEvent; + + async fn msg_to_job( + &mut self, + _: &str, + _: &Option<String>, + body: &[u8], + ) -> Result<Self::J, String> { + match serde_json::from_slice(body) { + Ok(event) => Ok(event), + Err(err) => Err(format!( + "Failed to deserialize push event {err:?}: {:?}", + std::str::from_utf8(body).unwrap_or("<job not utf8>") + )), + } + } + + async fn consumer(&mut self, job: &ghevent::PushEvent) -> worker::Actions { + let branch = job.branch().unwrap_or_default(); + let span = debug_span!("push", branch = %branch, after = %job.after); + let _enter = span.enter(); + + if !self.acl.is_repo_eligible(&job.repository.full_name) { + info!("Repo not authorized ({})", job.repository.full_name); + return vec![worker::Action::Ack]; + } + + // Skip tag events + if job.is_tag() { + info!("Skipping tag push: {}", job.git_ref); + return vec![worker::Action::Ack]; + } + + // Skip branch deletion events + if job.is_delete() { + info!("Skipping branch delete: {}", job.git_ref); + return vec![worker::Action::Ack]; + } + + // Skip zero SHA (shouldn't happen for non-delete, but just in case) + if job.is_zero_sha() { + info!("Skipping zero SHA push"); + return vec![worker::Action::Ack]; + } + + let branch_name = branch.to_string(); + info!( + "Processing push to {}:{} ({})", + job.repository.full_name, branch_name, job.after + ); + + // Detect which projects changed from the push event's commit info + let changed_files: Vec<String> = if let Some(ref head) = job.head_commit { + let mut files = Vec::new(); + if let Some(ref added) = head.added { + files.extend(added.iter().cloned()); + } + if let Some(ref removed) = head.removed { + files.extend(removed.iter().cloned()); + } + if let Some(ref modified) = head.modified { + files.extend(modified.iter().cloned()); + } + files + } else { + Vec::new() + }; + + let attrs = if !changed_files.is_empty() { + let detected = crate::buildtool::detect_changed_projects(&changed_files); + if detected.is_empty() { + info!("No known projects changed in push, using defaults"); + self.default_attrs.clone() + } else { + info!("Detected changed projects: {:?}", detected); + detected + } + } else { + info!("No file change info in push event, using defaults"); + self.default_attrs.clone() + }; + + if attrs.is_empty() { + info!("No projects to build, skipping push"); + return vec![worker::Action::Ack]; + } + + let repo_msg = Repo { + clone_url: job.repository.clone_url.clone(), + full_name: job.repository.full_name.clone(), + owner: job.repository.owner.login.clone(), + name: job.repository.name.clone(), + }; + + let push_trigger = PushTrigger { + head_sha: job.after.clone(), + branch: branch_name, + before_sha: Some(job.before.clone()), + }; + + let request_id = Uuid::new_v4().to_string(); + + let build_job = buildjob::BuildJob::new_push( + repo_msg.clone(), + push_trigger, + attrs.clone(), + request_id, + ); + + // Schedule the build on all known architectures + let build_archs = systems::System::primary_systems(); + let mut response = vec![]; + + info!( + "Scheduling push build for {:?} on {:?}", + attrs, build_archs + ); + + for arch in &build_archs { + let (exchange, routingkey) = arch.as_build_destination(); + response.push(worker::publish_serde_action( + exchange, routingkey, &build_job, + )); + } + + // Also publish to build-results for the comment poster to pick up + response.push(worker::publish_serde_action( + Some("build-results".to_string()), + None, + &buildjob::QueuedBuildJobs { + job: build_job, + architectures: build_archs.iter().map(|a| a.to_string()).collect(), + }, + )); + + response.push(worker::Action::Ack); + response + } +} |
