Skip to content

Commit 629e5ec

Browse files
refactor: remove GithubAppState
1 parent ac357d2 commit 629e5ec

File tree

10 files changed

+145
-165
lines changed

10 files changed

+145
-165
lines changed

src/bin/bors.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::time::Duration;
55

66
use anyhow::Context;
77
use bors::{
8-
create_app, create_bors_process, BorsContext, BorsGlobalEvent, CommandParser, GithubAppState,
9-
SeaORMClient, ServerState, WebhookSecret,
8+
create_app, create_bors_process, create_github_client, BorsContext, BorsGlobalEvent,
9+
CommandParser, SeaORMClient, ServerState, WebhookSecret,
1010
};
1111
use clap::Parser;
1212
use sea_orm::Database;
@@ -72,14 +72,14 @@ fn try_main(opts: Opts) -> anyhow::Result<()> {
7272
.block_on(initialize_db(&opts.db))
7373
.context("Cannot initialize database")?;
7474

75-
let state = runtime
76-
.block_on(GithubAppState::load(
77-
opts.app_id.into(),
78-
opts.private_key.into_bytes().into(),
79-
))
80-
.context("Cannot load GitHub repository state")?;
81-
let ctx = BorsContext::new(CommandParser::new(opts.cmd_prefix), Arc::new(db));
82-
let (repository_tx, global_tx, bors_process) = create_bors_process(state, ctx);
75+
let client = create_github_client(opts.app_id.into(), opts.private_key.into_bytes().into())?;
76+
77+
let ctx = runtime.block_on(BorsContext::new(
78+
CommandParser::new(opts.cmd_prefix),
79+
Arc::new(db),
80+
Arc::new(client),
81+
));
82+
let (repository_tx, global_tx, bors_process) = create_bors_process(ctx);
8383

8484
let refresh_tx = global_tx.clone();
8585
let refresh_process = async move {

src/bors/context.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
1-
use crate::{bors::command::CommandParser, database::DbClient};
2-
use std::sync::Arc;
1+
use arc_swap::ArcSwap;
32

4-
pub struct BorsContext {
3+
use crate::{bors::command::CommandParser, database::DbClient, github::GithubRepoName};
4+
use std::{collections::HashMap, sync::Arc};
5+
6+
use super::{GlobalClient, RepositoryClient, RepositoryState};
7+
8+
pub struct BorsContext<Client: RepositoryClient> {
59
pub parser: CommandParser,
610
pub db: Arc<dyn DbClient>,
11+
pub global_client: Arc<dyn GlobalClient<Client>>,
12+
pub repositories: Arc<ArcSwap<HashMap<GithubRepoName, Arc<RepositoryState<Client>>>>>,
713
}
814

9-
impl BorsContext {
10-
pub fn new(parser: CommandParser, db: Arc<dyn DbClient>) -> Self {
11-
Self { parser, db }
15+
impl<Client: RepositoryClient> BorsContext<Client> {
16+
pub async fn new(
17+
parser: CommandParser,
18+
db: Arc<dyn DbClient>,
19+
global_client: Arc<dyn GlobalClient<Client>>,
20+
) -> Self {
21+
// this unwrap is making me nervous, but if lhe repos loading
22+
// fails we might as well restart the bot
23+
let repositories = global_client.load_repositories().await.unwrap();
24+
let repositories = Arc::new(ArcSwap::new(Arc::new(repositories)));
25+
Self {
26+
parser,
27+
db,
28+
global_client,
29+
repositories,
30+
}
1231
}
1332
}

src/bors/handlers/mod.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use anyhow::Context;
2+
use arc_swap::ArcSwap;
3+
use std::collections::HashMap;
24
use std::sync::Arc;
35
use tracing::Instrument;
46

@@ -11,9 +13,8 @@ use crate::bors::handlers::trybuild::{command_try_build, command_try_cancel, TRY
1113
use crate::bors::handlers::workflow::{
1214
handle_check_suite_completed, handle_workflow_completed, handle_workflow_started,
1315
};
14-
use crate::bors::{BorsContext, BorsState, Comment, RepositoryClient, RepositoryState};
16+
use crate::bors::{BorsContext, Comment, RepositoryClient, RepositoryState};
1517
use crate::database::DbClient;
16-
use crate::github::GithubRepoName;
1718
use crate::utils::logging::LogError;
1819

1920
mod help;
@@ -26,11 +27,15 @@ mod workflow;
2627
/// This function executes a single BORS repository event
2728
pub async fn handle_bors_repository_event<Client: RepositoryClient>(
2829
event: BorsRepositoryEvent,
29-
state: Arc<dyn BorsState<Client>>,
30-
ctx: Arc<BorsContext>,
30+
ctx: Arc<BorsContext<Client>>,
3131
) -> anyhow::Result<()> {
3232
let db = Arc::clone(&ctx.db);
33-
let Some(repo) = get_repo_state(state, event.repository()) else {
33+
let Some(repo) = ctx
34+
.repositories
35+
.load()
36+
.get(event.repository())
37+
.map(Arc::clone)
38+
else {
3439
return Err(anyhow::anyhow!(
3540
"Repository {} not found in the bot state",
3641
event.repository()
@@ -113,20 +118,27 @@ pub async fn handle_bors_repository_event<Client: RepositoryClient>(
113118
/// This function executes a single BORS global event
114119
pub async fn handle_bors_global_event<Client: RepositoryClient>(
115120
event: BorsGlobalEvent,
116-
state: Arc<dyn BorsState<Client>>,
117-
ctx: Arc<BorsContext>,
121+
ctx: Arc<BorsContext<Client>>,
118122
) -> anyhow::Result<()> {
119123
let db = Arc::clone(&ctx.db);
120124
match event {
121125
BorsGlobalEvent::InstallationsChanged => {
122-
let span = tracing::info_span!("Repository reload");
123-
if let Err(error) = state.reload_repositories().instrument(span.clone()).await {
124-
span.log_error(error);
126+
let span = tracing::info_span!("Repository reload").entered();
127+
128+
match ctx.global_client.load_repositories().await {
129+
Ok(repos) => {
130+
ctx.repositories.store(Arc::new(repos));
131+
}
132+
Err(error) => {
133+
span.log_error(error);
134+
}
125135
}
136+
span.exit();
126137
}
127138
BorsGlobalEvent::Refresh => {
128139
let span = tracing::info_span!("Refresh");
129-
let repos = state.get_all_repos();
140+
let repos: Vec<Arc<RepositoryState<Client>>> =
141+
ctx.repositories.load().values().cloned().collect();
130142
futures::future::join_all(repos.into_iter().map(|repo| {
131143
let repo = Arc::clone(&repo);
132144
async {
@@ -143,23 +155,10 @@ pub async fn handle_bors_global_event<Client: RepositoryClient>(
143155
Ok(())
144156
}
145157

146-
fn get_repo_state<Client: RepositoryClient>(
147-
state: Arc<dyn BorsState<Client>>,
148-
repo: &GithubRepoName,
149-
) -> Option<Arc<RepositoryState<Client>>> {
150-
match state.get_repo_state(repo) {
151-
Some(result) => Some(result),
152-
None => {
153-
tracing::warn!("Repository {} not found", repo);
154-
None
155-
}
156-
}
157-
}
158-
159158
async fn handle_comment<Client: RepositoryClient>(
160159
repo: Arc<RepositoryState<Client>>,
161160
database: Arc<dyn DbClient>,
162-
ctx: Arc<BorsContext>,
161+
ctx: Arc<BorsContext<Client>>,
163162
comment: PullRequestComment,
164163
) -> anyhow::Result<()> {
165164
let pr_number = comment.pr_number;

src/bors/mod.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod context;
44
pub mod event;
55
mod handlers;
66

7+
use std::collections::HashMap;
78
use std::sync::Arc;
89

910
use arc_swap::ArcSwap;
@@ -70,6 +71,16 @@ pub trait RepositoryClient: Send + Sync {
7071
fn get_workflow_url(&self, run_id: RunId) -> String;
7172
}
7273

74+
/// Temporary trait to sastify the test mocking.
75+
/// TODO: Remove this trait once we move to mock REST API call.
76+
#[async_trait]
77+
pub trait GlobalClient<Client: RepositoryClient>: Send + Sync {
78+
/// Load state of repositories.
79+
async fn load_repositories(
80+
&self,
81+
) -> anyhow::Result<HashMap<GithubRepoName, Arc<RepositoryState<Client>>>>;
82+
}
83+
7384
#[derive(Clone)]
7485
pub enum CheckSuiteStatus {
7586
Pending,
@@ -84,20 +95,6 @@ pub struct CheckSuite {
8495
pub(crate) status: CheckSuiteStatus,
8596
}
8697

87-
/// Main state holder for the bot.
88-
/// It is behind a trait to allow easier mocking in tests.
89-
#[async_trait]
90-
pub trait BorsState<Client: RepositoryClient>: Send + Sync {
91-
/// Get repository and database state for the given repository name.
92-
fn get_repo_state(&self, repo: &GithubRepoName) -> Option<Arc<RepositoryState<Client>>>;
93-
94-
/// Get all repositories.
95-
fn get_all_repos(&self) -> Vec<Arc<RepositoryState<Client>>>;
96-
97-
/// Reload state of repositories due to some external change.
98-
async fn reload_repositories(&self) -> anyhow::Result<()>;
99-
}
100-
10198
/// An access point to a single repository.
10299
/// Can be used to query permissions for the repository, and also to perform various
103100
/// actions using the stored client.

src/github/api/client.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
use std::collections::HashMap;
2+
use std::sync::Arc;
3+
14
use anyhow::Context;
25
use axum::async_trait;
36
use octocrab::models::{App, Repository, RunId};
47
use octocrab::{Error, Octocrab};
58
use tracing::log;
69

710
use crate::bors::event::PullRequestComment;
8-
use crate::bors::{CheckSuite, CheckSuiteStatus, Comment, RepositoryClient};
11+
use crate::bors::{
12+
CheckSuite, CheckSuiteStatus, Comment, GlobalClient, RepositoryClient, RepositoryState,
13+
};
914
use crate::config::{RepositoryConfig, CONFIG_FILE_PATH};
1015
use crate::github::api::base_github_html_url;
1116
use crate::github::api::operations::{merge_branches, set_branch_to_commit, MergeError};
1217
use crate::github::{Branch, CommitSha, GithubRepoName, PullRequest, PullRequestNumber};
1318

19+
use super::load_repositories;
20+
1421
/// Provides access to a single app installation (repository) using the GitHub API.
1522
pub struct GithubRepositoryClient {
1623
pub app: App,
@@ -263,6 +270,15 @@ impl RepositoryClient for GithubRepositoryClient {
263270
}
264271
}
265272

273+
#[async_trait]
274+
impl GlobalClient<GithubRepositoryClient> for Octocrab {
275+
async fn load_repositories(
276+
&self,
277+
) -> anyhow::Result<HashMap<GithubRepoName, Arc<RepositoryState<GithubRepositoryClient>>>> {
278+
load_repositories(self).await
279+
}
280+
}
281+
266282
fn github_pr_to_pr(pr: octocrab::models::pulls::PullRequest) -> PullRequest {
267283
PullRequest {
268284
number: pr.number.into(),

src/github/api/mod.rs

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ use std::sync::Arc;
33

44
use anyhow::Context;
55
use arc_swap::ArcSwap;
6-
use axum::async_trait;
76
use octocrab::models::{App, AppId, InstallationRepositories, Repository};
87
use octocrab::Octocrab;
98
use secrecy::{ExposeSecret, SecretVec};
109

1110
use client::GithubRepositoryClient;
1211

13-
use crate::bors::{BorsState, RepositoryClient, RepositoryState};
12+
use crate::bors::{RepositoryClient, RepositoryState};
1413
use crate::github::GithubRepoName;
1514
use crate::permissions::load_permissions;
1615

@@ -19,39 +18,24 @@ pub(crate) mod operations;
1918

2019
type GithubRepositoryState = RepositoryState<GithubRepositoryClient>;
2120

22-
type RepositoryMap = HashMap<GithubRepoName, Arc<GithubRepositoryState>>;
21+
type GithubRepositoryMap = HashMap<GithubRepoName, Arc<GithubRepositoryState>>;
2322

2423
fn base_github_html_url() -> &'static str {
2524
"https://github.com"
2625
}
2726

28-
/// Provides access to managed GitHub repositories.
29-
pub struct GithubAppState {
30-
client: Octocrab,
31-
repositories: ArcSwap<RepositoryMap>,
32-
}
27+
pub fn create_github_client(app_id: AppId, private_key: SecretVec<u8>) -> anyhow::Result<Octocrab> {
28+
let key = jsonwebtoken::EncodingKey::from_rsa_pem(private_key.expose_secret().as_ref())
29+
.context("Could not encode private key")?;
3330

34-
impl GithubAppState {
35-
/// Loads repositories managed by the Bors GitHub app with the given ID.
36-
pub async fn load(app_id: AppId, private_key: SecretVec<u8>) -> anyhow::Result<GithubAppState> {
37-
let key = jsonwebtoken::EncodingKey::from_rsa_pem(private_key.expose_secret().as_ref())
38-
.context("Could not encode private key")?;
39-
40-
let client = Octocrab::builder()
41-
.app(app_id, key)
42-
.build()
43-
.context("Could not create octocrab builder")?;
44-
45-
let repositories = load_repositories(&client).await?;
46-
Ok(GithubAppState {
47-
client,
48-
repositories: ArcSwap::new(Arc::new(repositories)),
49-
})
50-
}
31+
Octocrab::builder()
32+
.app(app_id, key)
33+
.build()
34+
.context("Could not create octocrab builder")
5135
}
5236

5337
/// Loads repositories that are connected to the given GitHub App client.
54-
pub async fn load_repositories(client: &Octocrab) -> anyhow::Result<RepositoryMap> {
38+
pub async fn load_repositories(client: &Octocrab) -> anyhow::Result<GithubRepositoryMap> {
5539
let installations = client
5640
.apps()
5741
.installations()
@@ -179,27 +163,3 @@ async fn create_repo_state(
179163
permissions: ArcSwap::new(Arc::new(permissions)),
180164
})
181165
}
182-
183-
#[async_trait]
184-
impl BorsState<GithubRepositoryClient> for GithubAppState {
185-
fn get_repo_state(
186-
&self,
187-
repo: &GithubRepoName,
188-
) -> Option<Arc<RepositoryState<GithubRepositoryClient>>> {
189-
self.repositories
190-
.load()
191-
.get(repo)
192-
.map(|repo| Arc::clone(&repo))
193-
}
194-
195-
fn get_all_repos(&self) -> Vec<Arc<RepositoryState<GithubRepositoryClient>>> {
196-
self.repositories.load().values().cloned().collect()
197-
}
198-
199-
/// Re-download information about repositories connected to this GitHub app.
200-
async fn reload_repositories(&self) -> anyhow::Result<()> {
201-
self.repositories
202-
.store(Arc::new(load_repositories(&self.client).await?));
203-
Ok(())
204-
}
205-
}

src/github/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ pub mod server;
1111
mod webhook;
1212

1313
pub use api::operations::MergeError;
14-
pub use api::GithubAppState;
1514
pub use labels::{LabelModification, LabelTrigger};
1615
pub use webhook::WebhookSecret;
1716

0 commit comments

Comments
 (0)