Skip to content

Commit 42978fa

Browse files
committed
Check that Zulip user groups are properly synced
1 parent 93a7f4f commit 42978fa

File tree

3 files changed

+97
-3
lines changed

3 files changed

+97
-3
lines changed

src/check_synced.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,81 @@
22
33
use crate::data::Data;
44
use crate::github::{self, GitHubApi};
5-
use crate::schema;
5+
use crate::schema::{self, ZulipGroupMember};
6+
use crate::zulip::ZulipApi;
67
use log::{error, warn};
78
use rayon::prelude::*;
8-
use std::collections::HashMap;
9+
use std::collections::{HashMap, HashSet};
910

1011
pub(crate) fn check(data: &Data) -> Result<(), failure::Error> {
12+
check_github(data)?;
13+
check_zulip(data)?;
14+
Ok(())
15+
}
16+
17+
fn check_zulip(data: &Data) -> Result<(), failure::Error> {
18+
let zulip = ZulipApi::new();
19+
zulip.require_auth()?;
20+
let mut remote_groups = zulip
21+
.get_user_groups()?
22+
.into_iter()
23+
.filter(|g| !g.is_system_group)
24+
.map(|g| (g.name.clone(), g))
25+
.collect::<HashMap<_, _>>();
26+
let users = zulip
27+
.get_users()?
28+
.into_iter()
29+
.map(|u| (u.email, u.user_id))
30+
.collect::<HashMap<_, _>>();
31+
let mut checked = HashSet::new();
32+
for (_, local_group) in &data.zulip_groups()? {
33+
match remote_groups.remove(local_group.name()) {
34+
Some(rg) => {
35+
for local_member in local_group.members() {
36+
if checked.contains(local_member) {
37+
continue;
38+
}
39+
40+
let i = match local_member {
41+
ZulipGroupMember::Id(i) => *i,
42+
ZulipGroupMember::Email(e) => match users.get(e) {
43+
Some(i) => *i,
44+
None => {
45+
error!("User email '{e}' is not on Zulip");
46+
continue;
47+
}
48+
},
49+
ZulipGroupMember::Missing => {
50+
error!("Member of Zulip user group '{}' does not have an email or Zulip id", local_group.name());
51+
continue;
52+
}
53+
};
54+
checked.insert(local_member);
55+
if !rg.members.contains(&i) {
56+
error!(
57+
"Zulip user '{:?}' is not on the remote Zulip user group",
58+
local_member
59+
)
60+
}
61+
}
62+
}
63+
None => error!(
64+
"User group '{}' is in the team repo but not on Zulip",
65+
local_group.name()
66+
),
67+
}
68+
}
69+
70+
for (_, remote_group) in remote_groups {
71+
error!(
72+
"Zulip group '{}' is on Zulip but not in team repo",
73+
remote_group.name
74+
)
75+
}
76+
Ok(())
77+
}
78+
79+
pub(crate) fn check_github(data: &Data) -> Result<(), failure::Error> {
1180
const BOT_TEAMS: &[&str] = &["bors", "bots", "rfcbot", "highfive"];
1281
let github = GitHubApi::new();
1382
let pending_invites = github.pending_org_invites()?;

src/schema.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ impl ZulipGroup {
619619
}
620620
}
621621

622-
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
622+
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
623623
pub(crate) enum ZulipGroupMember {
624624
Id(usize),
625625
Email(String),

src/zulip.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ impl ZulipApi {
5656
Ok(response)
5757
}
5858

59+
/// Get all user groups of the Rust Zulip instance
60+
pub(crate) fn get_user_groups(&self) -> Result<Vec<ZulipUserGroup>, Error> {
61+
let response = self
62+
.req(Method::GET, "/user_groups", None)?
63+
.error_for_status()?
64+
.json::<ZulipUserGroups>()?
65+
.user_groups;
66+
67+
Ok(response)
68+
}
69+
5970
/// Perform a request against the Zulip API
6071
fn req(
6172
&self,
@@ -91,3 +102,17 @@ pub(crate) struct ZulipUser {
91102
pub(crate) email: String,
92103
pub(crate) user_id: usize,
93104
}
105+
106+
/// A collection of Zulip user groups
107+
#[derive(Deserialize)]
108+
struct ZulipUserGroups {
109+
user_groups: Vec<ZulipUserGroup>,
110+
}
111+
112+
/// A single Zulip user group
113+
#[derive(Deserialize)]
114+
pub(crate) struct ZulipUserGroup {
115+
pub(crate) name: String,
116+
pub(crate) members: Vec<usize>,
117+
pub(crate) is_system_group: bool,
118+
}

0 commit comments

Comments
 (0)