Skip to content

Commit 14d88bf

Browse files
author
Vo Hoang Long
committed
feat: allow a ci dedicated repo to run workflow
1 parent ea91f11 commit 14d88bf

File tree

19 files changed

+327
-164
lines changed

19 files changed

+327
-164
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ chrono = "0.4"
5454

5555
itertools = "0.13.0"
5656

57+
derive_builder = "0.20.0"
58+
5759
[dev-dependencies]
5860
insta = "1.26"
59-
derive_builder = "0.20.0"
6061
wiremock = "0.6.0"
6162
base64 = "0.22.1"
6263
tracing-test = "0.2.4"

src/bin/bors.rs

+50-11
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use std::time::Duration;
66

77
use anyhow::Context;
88
use bors::{
9-
create_app, create_bors_process, create_github_client, load_repositories, BorsContext,
10-
BorsGlobalEvent, CommandParser, PgDbClient, ServerState, TeamApiClient, WebhookSecret,
9+
create_app, create_bors_process, create_github_client, create_github_client_from_access_token,
10+
load_repositories, BorsContextBuilder, BorsGlobalEvent, CommandParser, GithubRepoName,
11+
PgDbClient, ServerState, TeamApiClient, WebhookSecret,
1112
};
1213
use clap::Parser;
1314
use sqlx::postgres::PgConnectOptions;
@@ -18,6 +19,8 @@ use tracing_subscriber::filter::EnvFilter;
1819
/// How often should the bot check DB state, e.g. for handling timeouts.
1920
const PERIODIC_REFRESH: Duration = Duration::from_secs(120);
2021

22+
const GITHUB_API_URL: &str = "https://api.github.com";
23+
2124
#[derive(clap::Parser)]
2225
struct Opts {
2326
/// Github App ID.
@@ -39,6 +42,10 @@ struct Opts {
3942
/// Prefix used for bot commands in PR comments.
4043
#[arg(long, env = "CMD_PREFIX", default_value = "@bors")]
4144
cmd_prefix: String,
45+
46+
/// Prefix used for bot commands in PR comments.
47+
#[arg(long, env = "CI_ACCESS_TOKEN")]
48+
ci_access_token: Option<String>,
4249
}
4350

4451
/// Starts a server that receives GitHub webhooks and generates events into a queue
@@ -81,18 +88,34 @@ fn try_main(opts: Opts) -> anyhow::Result<()> {
8188
let db = runtime
8289
.block_on(initialize_db(&opts.db))
8390
.context("Cannot initialize database")?;
84-
let team_api = TeamApiClient::default();
85-
let (client, loaded_repos) = runtime.block_on(async {
86-
let client = create_github_client(
91+
let team_api_client = TeamApiClient::default();
92+
let client = runtime.block_on(async {
93+
create_github_client(
8794
opts.app_id.into(),
88-
"https://api.github.com".to_string(),
95+
GITHUB_API_URL.to_string(),
8996
opts.private_key.into(),
90-
)?;
91-
let repos = load_repositories(&client, &team_api).await?;
92-
Ok::<_, anyhow::Error>((client, repos))
97+
)
98+
})?;
99+
let ci_client = match opts.ci_access_token {
100+
Some(access_token) => {
101+
let client = runtime.block_on(async {
102+
tracing::warn!("creating client ci");
103+
create_github_client_from_access_token(
104+
GITHUB_API_URL.to_string(),
105+
access_token.into(),
106+
)
107+
})?;
108+
Some(client)
109+
}
110+
None => None,
111+
};
112+
let loaded_repos = runtime.block_on(async {
113+
let repos = load_repositories(&client, ci_client.clone(), &team_api_client).await?;
114+
Ok::<_, anyhow::Error>(repos)
93115
})?;
94116

95117
let mut repos = HashMap::default();
118+
let mut ci_repo_map: HashMap<GithubRepoName, GithubRepoName> = HashMap::default();
96119
for (name, repo) in loaded_repos {
97120
let repo = match repo {
98121
Ok(repo) => {
@@ -105,11 +128,27 @@ fn try_main(opts: Opts) -> anyhow::Result<()> {
105128
));
106129
}
107130
};
131+
if repo.ci_client.repository() != repo.client.repository() {
132+
ci_repo_map.insert(
133+
repo.ci_client.repository().clone(),
134+
repo.client.repository().clone(),
135+
);
136+
}
108137
repos.insert(name, Arc::new(repo));
109138
}
110139

111-
let ctx = BorsContext::new(CommandParser::new(opts.cmd_prefix), Arc::new(db), repos);
112-
let (repository_tx, global_tx, bors_process) = create_bors_process(ctx, client, team_api);
140+
let ctx = BorsContextBuilder::default()
141+
.parser(CommandParser::new(opts.cmd_prefix))
142+
.db(Arc::new(db))
143+
.repositories(repos)
144+
.gh_client(client)
145+
.ci_client(ci_client)
146+
.ci_repo_map(ci_repo_map)
147+
.team_api_client(team_api_client)
148+
.build()
149+
.unwrap();
150+
151+
let (repository_tx, global_tx, bors_process) = create_bors_process(ctx);
113152

114153
let refresh_tx = global_tx.clone();
115154
let refresh_process = async move {

src/bors/command/parser.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enum CommandPart<'a> {
2222
KeyValue { key: &'a str, value: &'a str },
2323
}
2424

25+
#[derive(Clone)]
2526
pub struct CommandParser {
2627
prefix: String,
2728
}

src/bors/comment.rs

+20
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,23 @@ pub fn try_build_in_progress_comment() -> Comment {
5555
pub fn cant_find_last_parent_comment() -> Comment {
5656
Comment::new(":exclamation: There was no previous build. Please set an explicit parent or remove the `parent=last` argument to use the default parent.".to_string())
5757
}
58+
59+
pub fn no_try_build_in_progress_comment() -> Comment {
60+
Comment::new(":exclamation: There is currently no try build in progress.".to_string())
61+
}
62+
63+
pub fn unclean_try_build_cancelled_comment() -> Comment {
64+
Comment::new(
65+
"Try build was cancelled. It was not possible to cancel some workflows.".to_string(),
66+
)
67+
}
68+
69+
pub fn try_build_cancelled_comment(workflow_urls: impl Iterator<Item = String>) -> Comment {
70+
let mut try_build_cancelled_comment = r#"Try build cancelled.
71+
Cancelled workflows:"#
72+
.to_string();
73+
for url in workflow_urls {
74+
try_build_cancelled_comment += format!("\n- {}", url).as_str();
75+
}
76+
Comment::new(try_build_cancelled_comment)
77+
}

src/bors/context.rs

+14-16
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,25 @@ use std::{
33
sync::{Arc, RwLock},
44
};
55

6-
use crate::{bors::command::CommandParser, github::GithubRepoName, PgDbClient};
6+
use derive_builder::Builder;
7+
use octocrab::Octocrab;
8+
9+
use crate::{bors::command::CommandParser, github::GithubRepoName, PgDbClient, TeamApiClient};
710

811
use super::RepositoryState;
912

13+
#[derive(Builder)]
1014
pub struct BorsContext {
1115
pub parser: CommandParser,
1216
pub db: Arc<PgDbClient>,
17+
#[builder(field(
18+
ty = "HashMap<GithubRepoName, Arc<RepositoryState>>",
19+
build = "RwLock::new(self.repositories.clone())"
20+
))]
1321
pub repositories: RwLock<HashMap<GithubRepoName, Arc<RepositoryState>>>,
14-
}
15-
16-
impl BorsContext {
17-
pub fn new(
18-
parser: CommandParser,
19-
db: Arc<PgDbClient>,
20-
repositories: HashMap<GithubRepoName, Arc<RepositoryState>>,
21-
) -> Self {
22-
let repositories = RwLock::new(repositories);
23-
Self {
24-
parser,
25-
db,
26-
repositories,
27-
}
28-
}
22+
pub gh_client: Octocrab,
23+
#[builder(default)]
24+
pub ci_client: Option<Octocrab>,
25+
pub ci_repo_map: HashMap<GithubRepoName, GithubRepoName>,
26+
pub team_api_client: TeamApiClient,
2927
}

src/bors/handlers/mod.rs

+12-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::sync::Arc;
22

33
use anyhow::Context;
4-
use octocrab::Octocrab;
54
use tracing::Instrument;
65

76
use crate::bors::command::{BorsCommand, CommandParseError};
@@ -17,7 +16,7 @@ use crate::bors::handlers::workflow::{
1716
handle_check_suite_completed, handle_workflow_completed, handle_workflow_started,
1817
};
1918
use crate::bors::{BorsContext, Comment, RepositoryState};
20-
use crate::{load_repositories, PgDbClient, TeamApiClient};
19+
use crate::{load_repositories, PgDbClient};
2120

2221
#[cfg(test)]
2322
use crate::tests::util::TestSyncMarker;
@@ -39,13 +38,12 @@ pub async fn handle_bors_repository_event(
3938
ctx: Arc<BorsContext>,
4039
) -> anyhow::Result<()> {
4140
let db = Arc::clone(&ctx.db);
42-
let Some(repo) = ctx
43-
.repositories
44-
.read()
45-
.unwrap()
46-
.get(event.repository())
47-
.cloned()
48-
else {
41+
let repo_name = if let Some(repo_name) = ctx.ci_repo_map.get(event.repository()) {
42+
repo_name
43+
} else {
44+
event.repository()
45+
};
46+
let Some(repo) = ctx.repositories.read().unwrap().get(repo_name).cloned() else {
4947
return Err(anyhow::anyhow!(
5048
"Repository {} not found in the bot state",
5149
event.repository()
@@ -142,16 +140,12 @@ pub static WAIT_FOR_REFRESH: TestSyncMarker = TestSyncMarker::new();
142140
pub async fn handle_bors_global_event(
143141
event: BorsGlobalEvent,
144142
ctx: Arc<BorsContext>,
145-
gh_client: &Octocrab,
146-
team_api_client: &TeamApiClient,
147143
) -> anyhow::Result<()> {
148144
let db = Arc::clone(&ctx.db);
149145
match event {
150146
BorsGlobalEvent::InstallationsChanged => {
151147
let span = tracing::info_span!("Installations changed");
152-
reload_repos(ctx, gh_client, team_api_client)
153-
.instrument(span)
154-
.await?;
148+
reload_repos(ctx).instrument(span).await?;
155149
}
156150
BorsGlobalEvent::Refresh => {
157151
let span = tracing::info_span!("Refresh");
@@ -161,7 +155,7 @@ pub async fn handle_bors_global_event(
161155
let repo = Arc::clone(&repo);
162156
async {
163157
let subspan = tracing::info_span!("Repo", repo = repo.repository().to_string());
164-
refresh_repository(repo, Arc::clone(&db), team_api_client)
158+
refresh_repository(repo, Arc::clone(&db), &ctx.team_api_client)
165159
.instrument(subspan)
166160
.await
167161
}
@@ -274,12 +268,9 @@ async fn handle_comment(
274268
Ok(())
275269
}
276270

277-
async fn reload_repos(
278-
ctx: Arc<BorsContext>,
279-
gh_client: &Octocrab,
280-
team_api_client: &TeamApiClient,
281-
) -> anyhow::Result<()> {
282-
let reloaded_repos = load_repositories(gh_client, team_api_client).await?;
271+
async fn reload_repos(ctx: Arc<BorsContext>) -> anyhow::Result<()> {
272+
let reloaded_repos =
273+
load_repositories(&ctx.gh_client, ctx.ci_client.clone(), &ctx.team_api_client).await?;
283274
let mut repositories = ctx.repositories.write().unwrap();
284275
for repo in repositories.values() {
285276
if !reloaded_repos.contains_key(repo.repository()) {

src/bors/handlers/refresh.rs

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub async fn refresh_repository(
2323
) {
2424
Ok(())
2525
} else {
26+
// FIXME: better error handling
27+
// If a repo failed to be reload, there is no way to know
2628
tracing::error!("Failed to refresh repository");
2729
anyhow::bail!("Failed to refresh repository")
2830
}

0 commit comments

Comments
 (0)