Skip to content
This repository was archived by the owner on Mar 31, 2025. It is now read-only.

Commit e18143c

Browse files
KobzolMark-Simulacrum
authored andcommitted
Install apps for new repositories
1 parent 2019374 commit e18143c

File tree

2 files changed

+131
-7
lines changed

2 files changed

+131
-7
lines changed

src/github/api/write.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ impl GitHubWrite {
231231
if self.dry_run {
232232
Ok(Repo {
233233
id: String::from("ID"),
234+
repo_id: 0,
234235
name: name.to_string(),
235236
org: org.to_string(),
236237
description: settings.description.clone(),
@@ -273,6 +274,23 @@ impl GitHubWrite {
273274
Ok(())
274275
}
275276

277+
pub(crate) fn add_repo_to_app_installation(
278+
&self,
279+
installation_id: u64,
280+
repository_id: u64,
281+
) -> anyhow::Result<()> {
282+
debug!("Adding installation {installation_id} to repository {repository_id}");
283+
if !self.dry_run {
284+
self.client
285+
.req(
286+
Method::PUT,
287+
&format!("user/installations/{installation_id}/repositories/{repository_id}"),
288+
)?
289+
.send()?;
290+
}
291+
Ok(())
292+
}
293+
276294
/// Update a team's permissions to a repo
277295
pub(crate) fn update_team_repo_permissions(
278296
&self,

src/github/mod.rs

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ mod api;
33
mod tests;
44

55
use self::api::{BranchProtectionOp, TeamPrivacy, TeamRole};
6-
use crate::github::api::{GithubRead, Login, PushAllowanceActor, RepoPermission, RepoSettings};
6+
use crate::github::api::{
7+
GithubRead, Login, PushAllowanceActor, Repo, RepoPermission, RepoSettings,
8+
};
79
use log::debug;
810
use rust_team_data::v1::Bot;
911
use std::collections::{HashMap, HashSet};
10-
use std::fmt::Write;
12+
use std::fmt::{Display, Formatter, Write};
1113

1214
pub(crate) use self::api::{GitHubApiRead, GitHubWrite, HttpClient};
1315

@@ -26,7 +28,7 @@ pub(crate) fn create_diff(
2628
type OrgName = String;
2729
type RepoName = String;
2830

29-
#[derive(Clone, Debug)]
31+
#[derive(Clone, Debug, PartialEq)]
3032
enum GithubApp {
3133
RenovateBot,
3234
}
@@ -40,20 +42,34 @@ impl GithubApp {
4042
}
4143
}
4244

45+
impl Display for GithubApp {
46+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47+
match self {
48+
GithubApp::RenovateBot => f.write_str("RenovateBot"),
49+
}
50+
}
51+
}
52+
4353
#[derive(Clone, Debug)]
44-
struct GithubAppInstallation {
54+
struct OrgAppInstallation {
4555
app: GithubApp,
4656
installation_id: u64,
4757
repositories: HashSet<RepoName>,
4858
}
4959

60+
#[derive(Clone, Debug, PartialEq)]
61+
struct AppInstallation {
62+
app: GithubApp,
63+
installation_id: u64,
64+
}
65+
5066
struct SyncGitHub {
5167
github: Box<dyn GithubRead>,
5268
teams: Vec<rust_team_data::v1::Team>,
5369
repos: Vec<rust_team_data::v1::Repo>,
5470
usernames_cache: HashMap<u64, String>,
5571
org_owners: HashMap<OrgName, HashSet<u64>>,
56-
org_apps: HashMap<OrgName, Vec<GithubAppInstallation>>,
72+
org_apps: HashMap<OrgName, Vec<OrgAppInstallation>>,
5773
}
5874

5975
impl SyncGitHub {
@@ -88,7 +104,7 @@ impl SyncGitHub {
88104
for org in &orgs {
89105
org_owners.insert((*org).to_string(), github.org_owners(org)?);
90106

91-
let mut installations: Vec<GithubAppInstallation> = vec![];
107+
let mut installations: Vec<OrgAppInstallation> = vec![];
92108

93109
for installation in github.org_app_installations(org)? {
94110
if let Some(app) = GithubApp::from_id(installation.app_id) {
@@ -98,7 +114,7 @@ impl SyncGitHub {
98114
{
99115
repositories.insert(repo_installation.name);
100116
}
101-
installations.push(GithubAppInstallation {
117+
installations.push(OrgAppInstallation {
102118
app,
103119
installation_id: installation.installation_id,
104120
repositories,
@@ -299,6 +315,7 @@ impl SyncGitHub {
299315
},
300316
permissions,
301317
branch_protections,
318+
app_installations: self.diff_app_installations(expected_repo, &[])?,
302319
}));
303320
}
304321
};
@@ -392,6 +409,52 @@ impl SyncGitHub {
392409
Ok(branch_protection_diffs)
393410
}
394411

412+
fn diff_app_installations(
413+
&self,
414+
expected_repo: &rust_team_data::v1::Repo,
415+
existing_installations: &[AppInstallation],
416+
) -> anyhow::Result<Vec<AppInstallationDiff>> {
417+
let mut diff = vec![];
418+
let mut found_apps = Vec::new();
419+
420+
// Find apps that should be enabled on the repository
421+
for app in expected_repo.bots.iter().filter_map(|bot| match bot {
422+
Bot::Renovate => Some(GithubApp::RenovateBot),
423+
_ => None,
424+
}) {
425+
// Find installation ID of this app on GitHub
426+
let gh_installation = self
427+
.org_apps
428+
.get(&expected_repo.org)
429+
.and_then(|installations| {
430+
installations
431+
.iter()
432+
.find(|installation| installation.app == app)
433+
.map(|i| i.installation_id)
434+
});
435+
let Some(gh_installation) = gh_installation else {
436+
log::warn!("Application {app} should be enabled for repository {}/{}, but it is not installed on GitHub", expected_repo.org, expected_repo.name);
437+
continue;
438+
};
439+
let installation = AppInstallation {
440+
app,
441+
installation_id: gh_installation,
442+
};
443+
found_apps.push(installation.clone());
444+
445+
if !existing_installations.contains(&installation) {
446+
diff.push(AppInstallationDiff::Add(installation));
447+
}
448+
}
449+
for existing in existing_installations {
450+
if !found_apps.contains(existing) {
451+
diff.push(AppInstallationDiff::Remove(existing.clone()));
452+
}
453+
}
454+
455+
Ok(diff)
456+
}
457+
395458
fn expected_role(&self, org: &str, user: u64) -> TeamRole {
396459
if let Some(true) = self
397460
.org_owners
@@ -499,6 +562,7 @@ fn bot_name(bot: &Bot) -> Option<&str> {
499562
Bot::RustTimer => Some("rust-timer"),
500563
Bot::Rustbot => Some("rustbot"),
501564
Bot::Rfcbot => Some("rfcbot"),
565+
Bot::Renovate => None,
502566
}
503567
}
504568

@@ -619,6 +683,7 @@ struct CreateRepoDiff {
619683
settings: RepoSettings,
620684
permissions: Vec<RepoPermissionAssignmentDiff>,
621685
branch_protections: Vec<(String, api::BranchProtection)>,
686+
app_installations: Vec<AppInstallationDiff>,
622687
}
623688

624689
impl CreateRepoDiff {
@@ -636,6 +701,11 @@ impl CreateRepoDiff {
636701
}
637702
.apply(sync, &self.org, &self.name, &repo.id)?;
638703
}
704+
705+
for installation in &self.app_installations {
706+
installation.apply(sync, &repo)?;
707+
}
708+
639709
Ok(())
640710
}
641711
}
@@ -664,6 +734,10 @@ impl std::fmt::Display for CreateRepoDiff {
664734
writeln!(&mut f, " {branch_name}")?;
665735
log_branch_protection(branch_protection, None, &mut f)?;
666736
}
737+
writeln!(f, " App Installations:")?;
738+
for diff in &self.app_installations {
739+
write!(f, "{diff}")?;
740+
}
667741
Ok(())
668742
}
669743
}
@@ -930,6 +1004,38 @@ enum BranchProtectionDiffOperation {
9301004
Delete(String),
9311005
}
9321006

1007+
enum AppInstallationDiff {
1008+
Add(AppInstallation),
1009+
Remove(AppInstallation),
1010+
}
1011+
1012+
impl AppInstallationDiff {
1013+
fn apply(&self, sync: &GitHubWrite, repo: &Repo) -> anyhow::Result<()> {
1014+
match self {
1015+
AppInstallationDiff::Add(app) => {
1016+
sync.add_repo_to_app_installation(app.installation_id, repo.repo_id)?;
1017+
}
1018+
AppInstallationDiff::Remove(_) => {
1019+
todo!()
1020+
}
1021+
}
1022+
Ok(())
1023+
}
1024+
}
1025+
1026+
impl std::fmt::Display for AppInstallationDiff {
1027+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1028+
match self {
1029+
AppInstallationDiff::Add(app) => {
1030+
writeln!(f, " Install app {}", app.app)
1031+
}
1032+
AppInstallationDiff::Remove(app) => {
1033+
writeln!(f, " Remove app {}", app.app)
1034+
}
1035+
}
1036+
}
1037+
}
1038+
9331039
#[derive(Debug)]
9341040
enum TeamDiff {
9351041
Create(CreateTeamDiff),

0 commit comments

Comments
 (0)