From b1e9f5ded56aa81538650c9464c7f6ebc2c787b4 Mon Sep 17 00:00:00 2001 From: Nurseit Date: Thu, 27 Nov 2025 01:07:21 +0600 Subject: [PATCH 1/2] Set up permissions for local development --- README.md | 2 + data/permissions/bors.review.json.example | 3 + data/permissions/bors.try.json.example | 3 + docs/development.md | 6 ++ src/bin/bors.rs | 11 +++- src/permissions.rs | 73 ++++++++++++++--------- 6 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 data/permissions/bors.review.json.example create mode 100644 data/permissions/bors.try.json.example diff --git a/README.md b/README.md index b2b8e51d..31096ef3 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ required. | `--client-secret` | `OAUTH_CLIENT_SECRET`| | GitHub OAuth client secret for rollup UI (optional). | | `--db` | `DATABASE_URL` | | Database connection string. Only PostgreSQL is supported. | | `--cmd-prefix` | `CMD_PREFIX` | @bors | Prefix used to invoke bors commands in PR comments. | +| `--web_url` | `WEB_URL` | http://localhost:8080| Web URL where the bot's website is deployed (optional).| +| `--permissions` | `PERMISSIONS` | Rust Team API url| List of users with permissions to perform try/review (optional).| ### Special branches The bot uses the following branch names for its operations. diff --git a/data/permissions/bors.review.json.example b/data/permissions/bors.review.json.example new file mode 100644 index 00000000..80cfee76 --- /dev/null +++ b/data/permissions/bors.review.json.example @@ -0,0 +1,3 @@ +{ + "github_ids": [] +} diff --git a/data/permissions/bors.try.json.example b/data/permissions/bors.try.json.example new file mode 100644 index 00000000..80cfee76 --- /dev/null +++ b/data/permissions/bors.try.json.example @@ -0,0 +1,3 @@ +{ + "github_ids": [] +} diff --git a/docs/development.md b/docs/development.md index d4f3e1fe..5df2a06b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -57,6 +57,11 @@ One-time setup: - Subscribe it to webhook events `Issue comment`, `Pull request`, `Pull request review`, `Pull request review comment` and `Workflow run`. - Install your GitHub app on some test repository where you want to test bors. - Don't forget to configure `rust-bors.toml` in the root of the repository, and also add some example CI workflows. +- Create try/review permissions for Github users + - Copy a review json file `cp data/permissions/bors.review.json.example data/permissions/bors.review.json` + - Copy a try json file `cp data/permissions/bors.try.json.example data/permissions/bors.try.json` + - Get your Github user `ID` `https://api.github.com/users/` + - Edit both `bors.review.json` and `bors.try.json` files to include your GitHub `ID`: `{ "github_ids": [12345678] }` Everytime you want to run bors: - Run bors locally. @@ -65,6 +70,7 @@ Everytime you want to run bors: - Set `PRIVATE_KEY` to the private key of the app. - (optional) Set `WEB_URL` to the public URL of the website of the app. - (optional) Set `CMD_PREFIX` to the command prefix used to control the bot (e.g. `@bors`). + - (optional) Set `PERMISSIONS` `"data/permissions"` directory path to list users with permissions to perform try/review. - Set up some globally reachable URL/IP address for your computer, e.g. using [ngrok](https://ngrok.com/). - Configure the webhook URL for your app to point to `
/github`. You can use [gh webhook](https://docs.github.com/en/webhooks/testing-and-troubleshooting-webhooks/using-the-github-cli-to-forward-webhooks-for-testing) for that. - Try `@bors ping` on some PR on the test repository :) diff --git a/src/bin/bors.rs b/src/bin/bors.rs index f7d5c4a9..55f74b52 100644 --- a/src/bin/bors.rs +++ b/src/bin/bors.rs @@ -72,6 +72,14 @@ struct Opts { /// Web URL where the bot's website is deployed. #[arg(long, env = "WEB_URL", default_value = "http://localhost:8080")] web_url: String, + + /// Source of list of users with permissions to perform try/review. + #[arg( + long, + env = "PERMISSIONS", + default_value = "https://team-api.infra.rust-lang.org" + )] + permissions: Option, } /// Starts a server that receives GitHub webhooks and generates events into a queue @@ -117,7 +125,8 @@ fn try_main(opts: Opts) -> anyhow::Result<()> { let db = runtime .block_on(initialize_db(&opts.db)) .context("Cannot initialize database")?; - let team_api = TeamApiClient::default(); + // Unwrap will not panic due to default_value for the 'permissions' argument + let team_api = TeamApiClient::new(opts.permissions.as_deref().unwrap()); let (client, loaded_repos) = runtime.block_on(async { let client = create_github_client( opts.app_id.into(), diff --git a/src/permissions.rs b/src/permissions.rs index 8a0c72e6..cde4bcde 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -48,15 +48,24 @@ pub(crate) struct UserPermissionsResponse { github_ids: HashSet, } +enum TeamSource { + Url(String), + Directory(String), +} + pub struct TeamApiClient { - base_url: String, + team_source: TeamSource, } impl TeamApiClient { - pub(crate) fn new(base_url: impl Into) -> Self { - Self { - base_url: base_url.into(), - } + pub fn new(source: impl Into) -> Self { + let source_str = source.into(); + let team_source = if source_str.starts_with("http") { + TeamSource::Url(source_str) + } else { + TeamSource::Directory(source_str) + }; + Self { team_source } } pub(crate) async fn load_permissions( @@ -81,7 +90,8 @@ impl TeamApiClient { }) } - /// Loads users that are allowed to perform try/review from the Rust Team API. + /// Loads users that are allowed to perform try/review from the Rust Team API + /// or from directory for local environment. async fn load_users( &self, repository_name: &str, @@ -92,26 +102,35 @@ impl TeamApiClient { PermissionType::Try => "try", }; - let normalized_name = repository_name.replace('-', "_"); - let url = format!( - "{}/v1/permissions/bors.{normalized_name}.{permission}.json", - self.base_url - ); - let users = reqwest::get(url) - .await - .and_then(|res| res.error_for_status()) - .map_err(|error| anyhow::anyhow!("Cannot load users from team API: {error:?}"))? - .json::() - .await - .map_err(|error| { - anyhow::anyhow!("Cannot deserialize users from team API: {error:?}") - })?; - Ok(users.github_ids) - } -} - -impl Default for TeamApiClient { - fn default() -> Self { - Self::new("https://team-api.infra.rust-lang.org") + match &self.team_source { + TeamSource::Url(base_url) => { + let normalized_name = repository_name.replace('-', "_"); + let url = format!( + "{}/v1/permissions/bors.{normalized_name}.{permission}.json", + base_url + ); + let users = reqwest::get(url) + .await + .and_then(|res| res.error_for_status()) + .map_err(|error| anyhow::anyhow!("Cannot load users from team API: {error:?}"))? + .json::() + .await + .map_err(|error| { + anyhow::anyhow!("Cannot deserialize users from team API: {error:?}") + })?; + Ok(users.github_ids) + } + TeamSource::Directory(base_path) => { + let path = format!("{base_path}/bors.{permission}.json"); + let data = std::fs::read_to_string(&path).map_err(|error| { + anyhow::anyhow!("Could not read users from a file '{path}': {error:?}") + })?; + let users: UserPermissionsResponse = + serde_json::from_str(&data).map_err(|error| { + anyhow::anyhow!("Cannot deserialize users from a file '{path}': {error:?}") + })?; + Ok(users.github_ids) + } + } } } From cd67030c58cceed0e18c3a1bea9c7048e13c9f08 Mon Sep 17 00:00:00 2001 From: Nurseit Date: Thu, 27 Nov 2025 01:31:55 +0600 Subject: [PATCH 2/2] Fix clippy and add files to gitignore --- .gitignore | 2 ++ src/permissions.rs | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d82a2cd5..434cc2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ target/ .env .DS_Store __pycache__/ +*.review.json +*.try.json diff --git a/src/permissions.rs b/src/permissions.rs index cde4bcde..55891c00 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -105,10 +105,8 @@ impl TeamApiClient { match &self.team_source { TeamSource::Url(base_url) => { let normalized_name = repository_name.replace('-', "_"); - let url = format!( - "{}/v1/permissions/bors.{normalized_name}.{permission}.json", - base_url - ); + let url = + format!("{base_url}/v1/permissions/bors.{normalized_name}.{permission}.json",); let users = reqwest::get(url) .await .and_then(|res| res.error_for_status())