Skip to content

Sync organization members #1868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ allowed-github-orgs = [
"rust-analyzer",
]

# GitHub organizations where member management is independent
# and should not be automatically synchronized
independent-github-orgs = [
"rust-embedded",
]

permissions-bors-repos = [
"bors-kindergarten",
"rust",
Expand All @@ -27,3 +33,25 @@ permissions-bools = [
"dev-desktop",
"sync-team-confirmation",
]

# GitHub accounts that are allowed to stay in the GitHub organizations,
# even if they may not be members of any team.
# Note: Infra admins are automatically included from the infra-admins team.
special-org-members = [
# Bots.
"bors",
"craterbot",
"rust-embedded-bot",
"rust-highfive",
"rust-lang-core-team-agenda-bot",
"rust-lang-glacier-bot",
"rust-lang-owner",
"rust-log-analyzer",
"rust-timer",
"rustbot",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to add rust-log-analyzer here too, it's currently getting removed looking at the dry-run results.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, thanks!


# Ferrous systems employees who manage the github sponsors
# for rust-analyzer.
"MissHolmes",
"skade",
]
20 changes: 20 additions & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,26 @@ impl Data {
.flatten()
.any(|github_team| github_team.name == team_name && github_team.org == org)
}

pub(crate) fn get_sync_team_config(&self) -> anyhow::Result<sync_team::Config> {
let mut special_org_members = self.config.special_org_members().clone();

// Add infra admins from the infra-admins team
let infra_admins_team = self
.team("infra-admins")
.context("cannot find infra-admins team")?;
let infra_admins_usernames = infra_admins_team
.raw_people()
.members
.iter()
.map(|m| m.github.clone());
special_org_members.extend(infra_admins_usernames);

Ok(sync_team::Config {
special_org_members,
independent_github_orgs: self.config.independent_github_orgs().clone(),
})
}
}

fn load_file<T: DeserializeOwned>(path: &Path) -> Result<T, Error> {
Expand Down
8 changes: 7 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,5 +590,11 @@ fn perform_sync(opts: SyncOpts, data: Data) -> anyhow::Result<()> {
let subcmd = opts.command.unwrap_or(SyncCommand::DryRun);
let only_print_plan = matches!(subcmd, SyncCommand::PrintPlan);
let dry_run = only_print_plan || matches!(subcmd, SyncCommand::DryRun);
run_sync_team(team_api, &services, dry_run, only_print_plan)
run_sync_team(
team_api,
&services,
dry_run,
only_print_plan,
data.get_sync_team_config()?,
)
}
13 changes: 12 additions & 1 deletion src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ pub(crate) use crate::permissions::Permissions;
use anyhow::{bail, format_err, Error};
use serde::de::{Deserialize, Deserializer};
use serde_untagged::UntaggedEnumVisitor;
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeSet, HashMap, HashSet};

#[derive(serde_derive::Deserialize, Debug)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub(crate) struct Config {
allowed_mailing_lists_domains: HashSet<String>,
allowed_github_orgs: HashSet<String>,
independent_github_orgs: BTreeSet<String>,
permissions_bors_repos: HashSet<String>,
permissions_bools: HashSet<String>,
// Use a BTreeSet for consistent ordering in tests
special_org_members: BTreeSet<String>,
}

impl Config {
Expand All @@ -30,6 +33,14 @@ impl Config {
pub(crate) fn permissions_bools(&self) -> &HashSet<String> {
&self.permissions_bools
}

pub(crate) fn independent_github_orgs(&self) -> &BTreeSet<String> {
&self.independent_github_orgs
}

pub(crate) fn special_org_members(&self) -> &BTreeSet<String> {
&self.special_org_members
}
}

// This is an enum to allow two kinds of values for the email field:
Expand Down
23 changes: 23 additions & 0 deletions sync-team/src/github/api/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub(crate) trait GithubRead {
/// Get the owners of an org
fn org_owners(&self, org: &str) -> anyhow::Result<HashSet<u64>>;

/// Get the members of an org
fn org_members(&self, org: &str) -> anyhow::Result<HashMap<u64, String>>;

/// Get all teams associated with a org
///
/// Returns a list of tuples of team name and slug
Expand Down Expand Up @@ -119,6 +122,26 @@ impl GithubRead for GitHubApiRead {
Ok(owners)
}

fn org_members(&self, org: &str) -> anyhow::Result<HashMap<u64, String>> {
#[derive(serde::Deserialize, Eq, PartialEq, Hash)]
struct User {
id: u64,
login: String,
}
let mut members = HashMap::new();
self.client.rest_paginated(
&Method::GET,
&GitHubUrl::orgs(org, "members")?,
|resp: Vec<User>| {
for user in resp {
members.insert(user.id, user.login);
}
Ok(())
},
)?;
Ok(members)
}

fn org_teams(&self, org: &str) -> anyhow::Result<Vec<(String, String)>> {
let mut teams = Vec::new();

Expand Down
12 changes: 12 additions & 0 deletions sync-team/src/github/api/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,18 @@ impl GitHubWrite {
Ok(())
}

/// Remove a member from an org
pub(crate) fn remove_gh_member_from_org(&self, org: &str, user: &str) -> anyhow::Result<()> {
debug!("Removing user {user} from org {org}");
if !self.dry_run {
let method = Method::DELETE;
let url = GitHubUrl::orgs(org, &format!("members/{user}"))?;
let resp = self.client.req(method.clone(), &url)?.send()?;
allow_not_found(resp, method, url.url())?;
}
Ok(())
}

/// Remove a collaborator from a repo
pub(crate) fn remove_collaborator_from_repo(
&self,
Expand Down
Loading