summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.envrc2
-rw-r--r--.github/pull_request_template.md4
-rw-r--r--.gitignore2
-rw-r--r--CODE_OF_CONDUCT.md (renamed from meshmc/CODE_OF_CONDUCT.md)0
-rw-r--r--REUSE.toml5
-rw-r--r--bootstrap.cmd (renamed from meshmc/bootstrap.cmd)0
-rwxr-xr-xbootstrap.sh (renamed from meshmc/bootstrap.sh)0
-rw-r--r--flake.lock24
-rw-r--r--flake.nix78
-rw-r--r--lefthook.yml (renamed from meshmc/lefthook.yml)0
-rw-r--r--meshmc/BUILD.md4
-rw-r--r--meshmc/README.md2
-rw-r--r--ofborg/.env.example4
-rw-r--r--ofborg/config.production.json101
-rw-r--r--ofborg/example.config.json2
-rw-r--r--ofborg/tickborg/src/bin/build-faker.rs1
-rw-r--r--ofborg/tickborg/src/bin/github-webhook-receiver.rs18
-rw-r--r--ofborg/tickborg/src/bin/push-filter.rs105
-rw-r--r--ofborg/tickborg/src/config.rs16
-rw-r--r--ofborg/tickborg/src/ghevent/mod.rs2
-rw-r--r--ofborg/tickborg/src/ghevent/pushevent.rs53
-rw-r--r--ofborg/tickborg/src/message/buildjob.rs44
-rw-r--r--ofborg/tickborg/src/message/buildresult.rs22
-rw-r--r--ofborg/tickborg/src/message/common.rs11
-rw-r--r--ofborg/tickborg/src/message/mod.rs2
-rw-r--r--ofborg/tickborg/src/tasks/build.rs73
-rw-r--r--ofborg/tickborg/src/tasks/githubcommentposter.rs95
-rw-r--r--ofborg/tickborg/src/tasks/log_message_collector.rs3
-rw-r--r--ofborg/tickborg/src/tasks/mod.rs1
-rw-r--r--ofborg/tickborg/src/tasks/pushfilter.rs165
30 files changed, 793 insertions, 46 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000000..1d11c53545
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,2 @@
+use nix
+watch_file nix/*.nix
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index bd2abdad87..5b75623fd4 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,8 +1,8 @@
<!--
-Hey there! Thanks for your contribution for MeshMC.
+Hey there! Thanks for your contribution for Project Tick.
Please make sure that your commits are signed off and please sign CLA first.
-If you don't know how that works, check out our contribution guidelines: https://github.com/Project-Tick/MeshMC/blob/master/CONTRIBUTING.md#signing-your-work
+If you don't know how that works, check out our contribution guidelines: https://github.com/Project-Tick/Project-Tick/blob/master/CONTRIBUTING.md#signing-your-work
If you already created your commits, you can run `git rebase --signoff develop` to retroactively sign-off all your commits and `git push --force` to override what you have pushed already.
Note that signing and signing-off are two different things!
diff --git a/.gitignore b/.gitignore
index 6ac77d6f4d..ba0dc780cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,5 @@ node_modules/
#Ignore vscode AI rules
.github/instructions/codacy.instructions.md
+
+tree.txt
diff --git a/meshmc/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index f3f877ff21..f3f877ff21 100644
--- a/meshmc/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
diff --git a/REUSE.toml b/REUSE.toml
index 5ec4afbba4..d4db8d268c 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -154,3 +154,8 @@ SPDX-FileCopyrightText = "MultiMC Contributors & PolyMC Contributors & PrismLaun
path = ["images4docker/**"]
SPDX-License-Identifier = "GPL-3.0-or-later"
SPDX-FileCopyrightText = "Project Tick"
+
+[[annotations]]
+path = ["ofborg/**"]
+SPDX-License-Identifier = "MIT"
+SPDX-FileCopyrightText = "NixOS Contributors & Project Tick"
diff --git a/meshmc/bootstrap.cmd b/bootstrap.cmd
index 9da8b46195..9da8b46195 100644
--- a/meshmc/bootstrap.cmd
+++ b/bootstrap.cmd
diff --git a/meshmc/bootstrap.sh b/bootstrap.sh
index 3762deaa2e..3762deaa2e 100755
--- a/meshmc/bootstrap.sh
+++ b/bootstrap.sh
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000000..55a204d6b5
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,24 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1775036866,
+ "narHash": "sha256-ByAX1LkhCwZ94+KnFAmnJSMAvui7kgCxjHgUHsWAbfI=",
+ "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
+ "type": "tarball",
+ "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre972949.6201e203d095/nixexprs.tar.xz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000000..d510656840
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,78 @@
+{
+ description = " Project Tick is a project dedicated to providing developers with ease of use and users with long-lasting software.";
+
+ inputs = {
+ nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz";
+ };
+
+ outputs =
+ {
+ self,
+ nixpkgs,
+ }:
+
+ let
+ inherit (nixpkgs) lib;
+
+ # While we only officially support aarch and x86_64 on Linux and MacOS,
+ # we expose a reasonable amount of other systems for users who want to
+ # build for most exotic platforms
+ systems = lib.systems.flakeExposed;
+
+ forAllSystems = lib.genAttrs systems;
+ nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
+ in
+
+ {
+ devShells = forAllSystems (
+ system:
+
+ let
+ pkgs = nixpkgsFor.${system};
+ llvm = pkgs.llvmPackages_22;
+ python = pkgs.python3;
+ mkShell = pkgs.mkShell.override { inherit (llvm) stdenv; };
+
+ packages' = self.packages.${system};
+
+ welcomeMessage = ''
+ Welcome to Project Tick!
+ '';
+ in
+
+ {
+ default = mkShell {
+ name = "project-tick";
+
+ packages = [
+
+ (pkgs.stdenvNoCC.mkDerivation {
+ pname = "clang-tidy-diff";
+ inherit (llvm.clang) version;
+
+ nativeBuildInputs = [
+ pkgs.installShellFiles
+ python.pkgs.wrapPython
+ ];
+
+ dontUnpack = true;
+ dontConfigure = true;
+ dontBuild = true;
+
+ postInstall = "installBin ${llvm.libclang.python}/share/clang/clang-tidy-diff.py";
+ postFixup = "wrapPythonPrograms";
+ })
+ ];
+
+ shellHook = ''
+ git submodule update --init --force
+
+ echo ${lib.escapeShellArg welcomeMessage}
+ '';
+ };
+ }
+ );
+
+ formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
+ };
+}
diff --git a/meshmc/lefthook.yml b/lefthook.yml
index f92640489b..f92640489b 100644
--- a/meshmc/lefthook.yml
+++ b/lefthook.yml
diff --git a/meshmc/BUILD.md b/meshmc/BUILD.md
index d9058f7eb9..118324d2f8 100644
--- a/meshmc/BUILD.md
+++ b/meshmc/BUILD.md
@@ -36,7 +36,7 @@ and sets up lefthook git hooks.
### Linux / macOS
```bash
-./bootstrap.sh
+../bootstrap.sh
```
Supported distributions: Debian, Ubuntu, Fedora, RHEL/CentOS, openSUSE, Arch Linux, macOS (via Homebrew).
@@ -44,7 +44,7 @@ Supported distributions: Debian, Ubuntu, Fedora, RHEL/CentOS, openSUSE, Arch Lin
### Windows
```cmd
-bootstrap.cmd
+..\bootstrap.cmd
```
Uses [Scoop](https://scoop.sh) for CLI tools and [vcpkg](https://github.com/microsoft/vcpkg) for C/C++ libraries.
diff --git a/meshmc/README.md b/meshmc/README.md
index 87d86cc82c..b8ab5172e2 100644
--- a/meshmc/README.md
+++ b/meshmc/README.md
@@ -1,6 +1,6 @@
MeshMC
======
-[![REUSE status](https://api.reuse.software/badge/github.com/Project-Tick/MeshMC)](https://api.reuse.software/info/github.com/Project-Tick/MeshMC) [![CLA assistant](https://cla-assistant.io/readme/badge/Project-Tick/MeshMC)](https://cla-assistant.io/Project-Tick/MeshMC) [![LICENSE](https://img.shields.io/badge/license-GPL--3.0--or--later-blue?logo=GNU)](https://www.gnu.org/licenses/gpl-3.0.html) [![Crowdin](https://badges.crowdin.net/projtlauncher/localized.svg)](https://crowdin.com/project/projtlauncher)
+[![LICENSE](https://img.shields.io/badge/license-GPL--3.0--or--later-blue?logo=GNU)](https://www.gnu.org/licenses/gpl-3.0.html) [![Crowdin](https://badges.crowdin.net/projtlauncher/localized.svg)](https://crowdin.com/project/projtlauncher)
MeshMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
diff --git a/ofborg/.env.example b/ofborg/.env.example
new file mode 100644
index 0000000000..81b01b4b68
--- /dev/null
+++ b/ofborg/.env.example
@@ -0,0 +1,4 @@
+# Aşağıdakileri doldur
+GITHUB_APP_ID=
+GITHUB_WEBHOOK_SECRET=
+RABBITMQ_PASSWORD=changeme
diff --git a/ofborg/config.production.json b/ofborg/config.production.json
new file mode 100644
index 0000000000..3dc45cd943
--- /dev/null
+++ b/ofborg/config.production.json
@@ -0,0 +1,101 @@
+{
+ "runner": {
+ "identity": "mail-tickborg-1",
+ "repos": ["project-tick/Project-Tick"],
+ "trusted_users": ["AhmetSamet06"]
+ },
+ "checkout": {
+ "root": "/var/lib/tickborg/checkout"
+ },
+ "build": {
+ "system": "x86_64-linux",
+ "build_timeout_seconds": 1800
+ },
+ "github_app": {
+ "app_id": 0,
+ "private_key": "/etc/tickborg/github-private-key.pem",
+ "oauth_client_id": "GITHUB_APP_CLIENT_ID",
+ "oauth_client_secret_file": "/etc/tickborg/github-oauth-secret"
+ },
+ "github_webhook_receiver": {
+ "listen": "0.0.0.0:9899",
+ "webhook_secret_file": "/etc/tickborg/webhook-secret",
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ }
+ },
+ "evaluation_filter": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ }
+ },
+ "github_comment_filter": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ }
+ },
+ "github_comment_poster": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ }
+ },
+ "builder": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ }
+ },
+ "push_filter": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ },
+ "default_attrs": []
+ },
+ "log_message_collector": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ },
+ "logs_path": "/var/log/tickborg"
+ },
+ "log_api_config": {
+ "listen": "127.0.0.1:9898",
+ "logs_path": "/var/log/tickborg",
+ "serve_root": "https://logs.tickborg.projecttick.net/logfile"
+ },
+ "stats": {
+ "rabbitmq": {
+ "ssl": false,
+ "host": "localhost",
+ "virtualhost": "/",
+ "username": "tickborg",
+ "password_file": "/etc/tickborg/rabbitmq-password"
+ }
+ }
+}
diff --git a/ofborg/example.config.json b/ofborg/example.config.json
index 0344d169ea..420ae5efdd 100644
--- a/ofborg/example.config.json
+++ b/ofborg/example.config.json
@@ -13,7 +13,7 @@
},
"rabbitmq": {
"ssl": true,
- "host": "events.tickborg.project-tick.net",
+ "host": "events.tickborg.projecttick.net",
"virtualhost": "tickborg",
"username": "...",
"password": "..."
diff --git a/ofborg/tickborg/src/bin/build-faker.rs b/ofborg/tickborg/src/bin/build-faker.rs
index df8fcbfa50..086e96493d 100644
--- a/ofborg/tickborg/src/bin/build-faker.rs
+++ b/ofborg/tickborg/src/bin/build-faker.rs
@@ -42,6 +42,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
logs: Some((Some("logs".to_owned()), Some(logbackrk.to_lowercase()))),
statusreport: Some((None, Some("scratch".to_owned()))),
request_id: "bogus-request-id".to_owned(),
+ push: None,
};
{
diff --git a/ofborg/tickborg/src/bin/github-webhook-receiver.rs b/ofborg/tickborg/src/bin/github-webhook-receiver.rs
index 910cd4b350..60698a5019 100644
--- a/ofborg/tickborg/src/bin/github-webhook-receiver.rs
+++ b/ofborg/tickborg/src/bin/github-webhook-receiver.rs
@@ -87,6 +87,24 @@ async fn setup_amqp(chan: &mut Channel) -> Result<(), Box<dyn Error + Send + Syn
no_wait: false,
})
.await?;
+
+ let queue_name = String::from("push-build-inputs");
+ chan.declare_queue(easyamqp::QueueConfig {
+ queue: queue_name.clone(),
+ passive: false,
+ durable: true,
+ exclusive: false,
+ auto_delete: false,
+ no_wait: false,
+ })
+ .await?;
+ chan.bind_queue(easyamqp::BindQueueConfig {
+ queue: queue_name.clone(),
+ exchange: "github-events".to_owned(),
+ routing_key: Some(String::from("push.*")),
+ no_wait: false,
+ })
+ .await?;
Ok(())
}
diff --git a/ofborg/tickborg/src/bin/push-filter.rs b/ofborg/tickborg/src/bin/push-filter.rs
new file mode 100644
index 0000000000..81d1597e0f
--- /dev/null
+++ b/ofborg/tickborg/src/bin/push-filter.rs
@@ -0,0 +1,105 @@
+use std::env;
+use std::error::Error;
+
+use tracing::{error, info};
+
+use tickborg::config;
+use tickborg::easyamqp::{self, ChannelExt, ConsumerExt};
+use tickborg::easylapin;
+use tickborg::tasks;
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error>> {
+ tickborg::setup_log();
+
+ let arg = env::args()
+ .nth(1)
+ .unwrap_or_else(|| panic!("usage: {} <config>", std::env::args().next().unwrap()));
+ let cfg = config::load(arg.as_ref());
+
+ let Some(filter_cfg) = config::load(arg.as_ref()).push_filter else {
+ error!("No push filter configuration found!");
+ panic!();
+ };
+
+ let conn = easylapin::from_config(&filter_cfg.rabbitmq).await?;
+ let mut chan = conn.create_channel().await?;
+
+ chan.declare_exchange(easyamqp::ExchangeConfig {
+ exchange: "github-events".to_owned(),
+ exchange_type: easyamqp::ExchangeType::Topic,
+ passive: false,
+ durable: true,
+ auto_delete: false,
+ no_wait: false,
+ internal: false,
+ })
+ .await?;
+
+ // Declare the build-jobs exchange (fanout) that the builder consumes from
+ chan.declare_exchange(easyamqp::ExchangeConfig {
+ exchange: "build-jobs".to_owned(),
+ exchange_type: easyamqp::ExchangeType::Fanout,
+ passive: false,
+ durable: true,
+ auto_delete: false,
+ no_wait: false,
+ internal: false,
+ })
+ .await?;
+
+ // Declare the build-results exchange for the comment poster
+ chan.declare_exchange(easyamqp::ExchangeConfig {
+ exchange: "build-results".to_owned(),
+ exchange_type: easyamqp::ExchangeType::Fanout,
+ passive: false,
+ durable: true,
+ auto_delete: false,
+ no_wait: false,
+ internal: false,
+ })
+ .await?;
+
+ let queue_name = String::from("push-build-inputs");
+ chan.declare_queue(easyamqp::QueueConfig {
+ queue: queue_name.clone(),
+ passive: false,
+ durable: true,
+ exclusive: false,
+ auto_delete: false,
+ no_wait: false,
+ })
+ .await?;
+
+ chan.bind_queue(easyamqp::BindQueueConfig {
+ queue: queue_name.clone(),
+ exchange: "github-events".to_owned(),
+ routing_key: Some("push.*".to_owned()),
+ no_wait: false,
+ })
+ .await?;
+
+ let handle = easylapin::WorkerChannel(chan)
+ .consume(
+ tasks::pushfilter::PushFilterWorker::new(
+ cfg.acl(),
+ filter_cfg.default_attrs,
+ ),
+ easyamqp::ConsumeConfig {
+ queue: queue_name.clone(),
+ consumer_tag: format!("{}-push-filter", cfg.whoami()),
+ no_local: false,
+ no_ack: false,
+ no_wait: false,
+ exclusive: false,
+ },
+ )
+ .await?;
+
+ info!("Fetching push events from {}", &queue_name);
+ handle.await;
+
+ drop(conn); // Close connection.
+ info!("Closed the session... EOF");
+ Ok(())
+}
diff --git a/ofborg/tickborg/src/config.rs b/ofborg/tickborg/src/config.rs
index 7d7475e3b6..623b9f5e9a 100644
--- a/ofborg/tickborg/src/config.rs
+++ b/ofborg/tickborg/src/config.rs
@@ -30,6 +30,8 @@ pub struct Config {
pub mass_rebuilder: Option<MassRebuilder>,
/// Configuration for the builder
pub builder: Option<Builder>,
+ /// Configuration for the push filter
+ pub push_filter: Option<PushFilter>,
/// Configuration for the log message collector
pub log_message_collector: Option<LogMessageCollector>,
/// Configuration for the stats server
@@ -57,7 +59,7 @@ fn default_logs_path() -> String {
}
fn default_serve_root() -> String {
- "https://logs.tickborg.project-tick.net/logfile".into()
+ "https://logs.tickborg.projecttick.net/logfile".into()
}
/// Configuration for logapi
@@ -112,6 +114,18 @@ pub struct Builder {
pub rabbitmq: RabbitMqConfig,
}
+/// Configuration for the push filter
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+#[serde(deny_unknown_fields)]
+pub struct PushFilter {
+ /// RabbitMQ broker to connect to
+ pub rabbitmq: RabbitMqConfig,
+ /// Default projects/attrs to build when no changed projects are detected.
+ /// If empty and no projects detected, push builds are skipped.
+ #[serde(default)]
+ pub default_attrs: Vec<String>,
+}
+
/// Configuration for the log message collector
#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(deny_unknown_fields)]
diff --git a/ofborg/tickborg/src/ghevent/mod.rs b/ofborg/tickborg/src/ghevent/mod.rs
index 243758800a..06d67acef6 100644
--- a/ofborg/tickborg/src/ghevent/mod.rs
+++ b/ofborg/tickborg/src/ghevent/mod.rs
@@ -1,9 +1,11 @@
mod common;
mod issuecomment;
mod pullrequestevent;
+mod pushevent;
pub use self::common::{Comment, GenericWebhook, Issue, Repository, User};
pub use self::issuecomment::{IssueComment, IssueCommentAction};
pub use self::pullrequestevent::{
PullRequest, PullRequestAction, PullRequestEvent, PullRequestState,
};
+pub use self::pushevent::{HeadCommit, PushEvent, Pusher};
diff --git a/ofborg/tickborg/src/ghevent/pushevent.rs b/ofborg/tickborg/src/ghevent/pushevent.rs
new file mode 100644
index 0000000000..9ae45ad9ae
--- /dev/null
+++ b/ofborg/tickborg/src/ghevent/pushevent.rs
@@ -0,0 +1,53 @@
+use crate::ghevent::Repository;
+
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+pub struct PushEvent {
+ #[serde(rename = "ref")]
+ pub git_ref: String,
+ pub before: String,
+ pub after: String,
+ pub created: bool,
+ pub deleted: bool,
+ pub forced: bool,
+ pub repository: Repository,
+ pub pusher: Pusher,
+ pub head_commit: Option<HeadCommit>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+pub struct Pusher {
+ pub name: String,
+ pub email: Option<String>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+pub struct HeadCommit {
+ pub id: String,
+ pub message: String,
+ pub timestamp: String,
+ pub added: Option<Vec<String>>,
+ pub removed: Option<Vec<String>>,
+ pub modified: Option<Vec<String>>,
+}
+
+impl PushEvent {
+ /// Branch adını döndürür (refs/heads/main -> main)
+ pub fn branch(&self) -> Option<&str> {
+ self.git_ref.strip_prefix("refs/heads/")
+ }
+
+ /// Tag push mi?
+ pub fn is_tag(&self) -> bool {
+ self.git_ref.starts_with("refs/tags/")
+ }
+
+ /// Branch silme event'i mi?
+ pub fn is_delete(&self) -> bool {
+ self.deleted
+ }
+
+ /// Boş commit (000...) mi?
+ pub fn is_zero_sha(&self) -> bool {
+ self.after.chars().all(|c| c == '0')
+ }
+}
diff --git a/ofborg/tickborg/src/message/buildjob.rs b/ofborg/tickborg/src/message/buildjob.rs
index b09eae58bf..c4cc61d1fa 100644
--- a/ofborg/tickborg/src/message/buildjob.rs
+++ b/ofborg/tickborg/src/message/buildjob.rs
@@ -1,5 +1,5 @@
use crate::commentparser::Subset;
-use crate::message::{Pr, Repo};
+use crate::message::{Pr, PushTrigger, Repo};
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct BuildJob {
@@ -10,6 +10,9 @@ pub struct BuildJob {
pub request_id: String,
pub logs: Option<ExchangeQueue>, // (Exchange, Routing Key)
pub statusreport: Option<ExchangeQueue>, // (Exchange, Routing Key)
+ /// If set, this build was triggered by a push event, not a PR.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub push: Option<PushTrigger>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
@@ -42,8 +45,47 @@ impl BuildJob {
logs: Some(logs.unwrap_or((Some("logs".to_owned()), Some(logbackrk)))),
statusreport: Some(statusreport.unwrap_or((Some("build-results".to_owned()), None))),
request_id,
+ push: None,
}
}
+
+ /// Create a build job triggered by a push event.
+ pub fn new_push(
+ repo: Repo,
+ push: PushTrigger,
+ attrs: Vec<String>,
+ request_id: String,
+ ) -> BuildJob {
+ let logbackrk = format!(
+ "{}.push.{}",
+ repo.full_name.to_lowercase(),
+ push.branch.replace('/', "-")
+ );
+
+ // Fill pr with push info so downstream consumers (comment poster, etc.)
+ // can still use pr.head_sha for commit statuses / check runs.
+ let pr = Pr {
+ number: 0,
+ head_sha: push.head_sha.clone(),
+ target_branch: Some(push.branch.clone()),
+ };
+
+ BuildJob {
+ repo,
+ pr,
+ subset: None,
+ attrs,
+ logs: Some((Some("logs".to_owned()), Some(logbackrk))),
+ statusreport: Some((Some("build-results".to_owned()), None)),
+ request_id,
+ push: Some(push),
+ }
+ }
+
+ /// Returns true if this build was triggered by a push event.
+ pub fn is_push(&self) -> bool {
+ self.push.is_some()
+ }
}
pub fn from(data: &[u8]) -> Result<BuildJob, serde_json::error::Error> {
diff --git a/ofborg/tickborg/src/message/buildresult.rs b/ofborg/tickborg/src/message/buildresult.rs
index 122edacae3..de482f64da 100644
--- a/ofborg/tickborg/src/message/buildresult.rs
+++ b/ofborg/tickborg/src/message/buildresult.rs
@@ -1,4 +1,4 @@
-use crate::message::{Pr, Repo};
+use crate::message::{Pr, PushTrigger, Repo};
use hubcaps::checks::Conclusion;
@@ -48,6 +48,7 @@ pub struct LegacyBuildResult {
pub status: BuildStatus,
pub skipped_attrs: Option<Vec<String>>,
pub attempted_attrs: Option<Vec<String>>,
+ pub push: Option<PushTrigger>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
@@ -70,6 +71,8 @@ pub enum BuildResult {
status: BuildStatus,
skipped_attrs: Option<Vec<String>>,
attempted_attrs: Option<Vec<String>>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ push: Option<PushTrigger>,
},
Legacy {
repo: Repo,
@@ -82,6 +85,8 @@ pub enum BuildResult {
status: Option<BuildStatus>,
skipped_attrs: Option<Vec<String>>,
attempted_attrs: Option<Vec<String>>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ push: Option<PushTrigger>,
},
}
@@ -100,6 +105,7 @@ impl BuildResult {
ref request_id,
ref attempted_attrs,
ref skipped_attrs,
+ ref push,
..
} => LegacyBuildResult {
repo: repo.to_owned(),
@@ -111,6 +117,7 @@ impl BuildResult {
status: self.status(),
attempted_attrs: attempted_attrs.to_owned(),
skipped_attrs: skipped_attrs.to_owned(),
+ push: push.to_owned(),
},
BuildResult::V1 {
ref repo,
@@ -121,6 +128,7 @@ impl BuildResult {
ref request_id,
ref attempted_attrs,
ref skipped_attrs,
+ ref push,
..
} => LegacyBuildResult {
repo: repo.to_owned(),
@@ -132,6 +140,7 @@ impl BuildResult {
status: self.status(),
attempted_attrs: attempted_attrs.to_owned(),
skipped_attrs: skipped_attrs.to_owned(),
+ push: push.to_owned(),
},
}
}
@@ -143,6 +152,17 @@ impl BuildResult {
}
}
+ pub fn push(&self) -> Option<PushTrigger> {
+ match self {
+ BuildResult::Legacy { push, .. } => push.to_owned(),
+ BuildResult::V1 { push, .. } => push.to_owned(),
+ }
+ }
+
+ pub fn is_push(&self) -> bool {
+ self.push().is_some()
+ }
+
pub fn status(&self) -> BuildStatus {
match *self {
BuildResult::Legacy {
diff --git a/ofborg/tickborg/src/message/common.rs b/ofborg/tickborg/src/message/common.rs
index c8fcd16ea2..75b5dec04f 100644
--- a/ofborg/tickborg/src/message/common.rs
+++ b/ofborg/tickborg/src/message/common.rs
@@ -6,9 +6,18 @@ pub struct Repo {
pub clone_url: String,
}
-#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Pr {
pub target_branch: Option<String>,
pub number: u64,
pub head_sha: String,
}
+
+/// Information about a push event trigger (direct push to a branch).
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct PushTrigger {
+ pub head_sha: String,
+ pub branch: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub before_sha: Option<String>,
+}
diff --git a/ofborg/tickborg/src/message/mod.rs b/ofborg/tickborg/src/message/mod.rs
index 03551cd1ce..8621f45668 100644
--- a/ofborg/tickborg/src/message/mod.rs
+++ b/ofborg/tickborg/src/message/mod.rs
@@ -4,4 +4,4 @@ pub mod buildresult;
mod common;
pub mod evaluationjob;
-pub use self::common::{Pr, Repo};
+pub use self::common::{Pr, PushTrigger, Repo};
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
+ }
+}