Skip to content

Commit 7b4a90b

Browse files
committed
Remove sync-team CLI and call it from team as a library
1 parent 6c38b77 commit 7b4a90b

File tree

7 files changed

+152
-185
lines changed

7 files changed

+152
-185
lines changed

Cargo.lock

Lines changed: 2 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ serde_json = "1"
2222
serde-untagged = "0.1"
2323
toml = "0.8"
2424

25+
sync-team = { path = "sync-team" }
26+
2527
[dev-dependencies]
2628
ansi_term = "0.12.1"
2729
atty = "0.2.14"

src/main.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ mod schema;
99
mod static_api;
1010
mod validate;
1111

12+
const AVAILABLE_SERVICES: &[&str] = &["github", "mailgun", "zulip"];
13+
1214
const USER_AGENT: &str = "https://github.com/rust-lang/team (infra@rust-lang.org)";
1315

1416
use api::zulip::ZulipApi;
@@ -23,6 +25,8 @@ use clap::Parser;
2325
use log::{error, info, warn};
2426
use std::collections::{BTreeMap, HashMap};
2527
use std::path::PathBuf;
28+
use sync_team::run_sync_team;
29+
use sync_team::team_api::TeamApi;
2630

2731
#[derive(clap::ValueEnum, Clone, Debug)]
2832
enum DumpIndividualAccessGroupBy {
@@ -83,6 +87,15 @@ enum Cli {
8387
/// CI scripts
8488
#[clap(subcommand)]
8589
Ci(CiOpts),
90+
/// Perform synchronization of the local data to live services.
91+
/// Environment variables:
92+
/// - GITHUB_TOKEN Authentication token with GitHub
93+
/// - MAILGUN_API_TOKEN Authentication token with Mailgun
94+
/// - EMAIL_ENCRYPTION_KEY Key used to decrypt encrypted emails in the team repo
95+
/// - ZULIP_USERNAME Username of the Zulip bot
96+
/// - ZULIP_API_TOKEN Authentication token of the Zulip bot
97+
#[clap(verbatim_doc_comment)]
98+
Sync(SyncOpts),
8699
}
87100

88101
#[derive(clap::Parser, Debug)]
@@ -93,11 +106,42 @@ enum CiOpts {
93106
CheckCodeowners,
94107
}
95108

109+
#[derive(clap::Parser, Debug)]
110+
struct SyncOpts {
111+
/// Comma-separated list of available services
112+
#[clap(long, global(true), value_parser = clap::builder::PossibleValuesParser::new(
113+
AVAILABLE_SERVICES
114+
), value_delimiter = ',')]
115+
services: Vec<String>,
116+
117+
/// Path to a checkout of `rust-lang/team`.
118+
#[clap(long, global(true), conflicts_with = "team_json")]
119+
team_repo: Option<PathBuf>,
120+
121+
/// Path to a directory with prebuilt JSON data from the `team` repository.
122+
#[clap(long, global(true))]
123+
team_json: Option<PathBuf>,
124+
125+
#[clap(subcommand)]
126+
command: Option<SyncCommand>,
127+
}
128+
129+
#[derive(clap::Parser, Debug)]
130+
enum SyncCommand {
131+
/// Try to apply changes, but do not send any outgoing API requests.
132+
DryRun,
133+
/// Only print a diff of what would be changed.
134+
PrintPlan,
135+
/// Apply the changes to the specified services.
136+
Apply,
137+
}
138+
96139
fn main() {
97140
let mut env = env_logger::Builder::new();
98141
env.format_timestamp(None);
99142
env.format_module_path(false);
100143
env.filter_module("rust_team", log::LevelFilter::Info);
144+
env.filter_module("sync_team", log::LevelFilter::Info);
101145
if std::env::var("RUST_TEAM_FORCE_COLORS").is_ok() {
102146
env.write_style(env_logger::WriteStyle::Always);
103147
}
@@ -406,6 +450,16 @@ fn run() -> Result<(), Error> {
406450
CiOpts::GenerateCodeowners => generate_codeowners_file(data)?,
407451
CiOpts::CheckCodeowners => check_codeowners(data)?,
408452
},
453+
Cli::Sync(opts) => {
454+
if let Err(err) = perform_sync(opts) {
455+
// Display shows just the first element of the chain.
456+
error!("failed: {}", err);
457+
for cause in err.chain().skip(1) {
458+
error!("caused by: {}", cause);
459+
}
460+
std::process::exit(1);
461+
}
462+
}
409463
}
410464

411465
Ok(())
@@ -437,3 +491,27 @@ fn dump_team_members(
437491
}
438492
Ok(())
439493
}
494+
495+
fn perform_sync(opts: SyncOpts) -> anyhow::Result<()> {
496+
let team_api = if let Some(path) = opts.team_repo {
497+
TeamApi::Checkout(path)
498+
} else if let Some(path) = opts.team_json {
499+
TeamApi::Prebuilt(path)
500+
} else {
501+
TeamApi::Production
502+
};
503+
504+
let mut services = opts.services;
505+
if services.is_empty() {
506+
info!("no service to synchronize specified, defaulting to all services");
507+
services = AVAILABLE_SERVICES
508+
.iter()
509+
.map(|s| (*s).to_string())
510+
.collect();
511+
}
512+
513+
let subcmd = opts.command.unwrap_or(SyncCommand::DryRun);
514+
let only_print_plan = matches!(subcmd, SyncCommand::PrintPlan);
515+
let dry_run = only_print_plan || matches!(subcmd, SyncCommand::DryRun);
516+
run_sync_team(team_api, &services, dry_run, only_print_plan)
517+
}

sync-team/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ authors = ["Pietro Albini <pietro@pietroalbini.org>"]
55
edition = "2024"
66

77
[dependencies]
8-
clap = { version = "4.5", features = ["derive", "env"] }
98
reqwest = { version = "0.12.8", features = ["blocking", "json", "rustls-tls", "charset", "http2", "macos-system-configuration"], default-features = false }
109
log = "0.4"
11-
env_logger = "0.11"
1210
rust_team_data = { path = "../rust_team_data", features = ["email-encryption"] }
1311
serde = { version = "1.0", features = ["derive"] }
1412
anyhow = "1.0"

sync-team/src/lib.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
mod github;
2+
mod mailgun;
3+
pub mod team_api;
4+
mod utils;
5+
mod zulip;
6+
7+
use crate::github::{GitHubApiRead, GitHubWrite, HttpClient, create_diff};
8+
use crate::team_api::TeamApi;
9+
use crate::zulip::SyncZulip;
10+
use anyhow::Context;
11+
use log::{info, warn};
12+
use secrecy::SecretString;
13+
14+
const USER_AGENT: &str = "rust-lang teams sync (https://github.com/rust-lang/sync-team)";
15+
16+
pub fn run_sync_team(
17+
team_api: TeamApi,
18+
services: &[String],
19+
dry_run: bool,
20+
only_print_plan: bool,
21+
) -> anyhow::Result<()> {
22+
if dry_run {
23+
warn!("sync-team is running in dry mode, no changes will be applied.");
24+
}
25+
26+
for service in services {
27+
info!("synchronizing {}", service);
28+
match service.as_str() {
29+
"github" => {
30+
let client = HttpClient::new()?;
31+
let gh_read = Box::new(GitHubApiRead::from_client(client.clone())?);
32+
let teams = team_api.get_teams()?;
33+
let repos = team_api.get_repos()?;
34+
let diff = create_diff(gh_read, teams, repos)?;
35+
if !diff.is_empty() {
36+
info!("{}", diff);
37+
}
38+
if !only_print_plan {
39+
let gh_write = GitHubWrite::new(client, dry_run)?;
40+
diff.apply(&gh_write)?;
41+
}
42+
}
43+
"mailgun" => {
44+
let token = SecretString::from(get_env("MAILGUN_API_TOKEN")?);
45+
let encryption_key = get_env("EMAIL_ENCRYPTION_KEY")?;
46+
mailgun::run(token, &encryption_key, &team_api, dry_run)?;
47+
}
48+
"zulip" => {
49+
let username = get_env("ZULIP_USERNAME")?;
50+
let token = SecretString::from(get_env("ZULIP_API_TOKEN")?);
51+
let sync = SyncZulip::new(username, token, &team_api, dry_run)?;
52+
let diff = sync.diff_all()?;
53+
if !diff.is_empty() {
54+
info!("{}", diff);
55+
}
56+
if !only_print_plan {
57+
diff.apply(&sync)?;
58+
}
59+
}
60+
_ => panic!("unknown service: {service}"),
61+
}
62+
}
63+
64+
Ok(())
65+
}
66+
67+
fn get_env(key: &str) -> anyhow::Result<String> {
68+
std::env::var(key).with_context(|| format!("failed to get the {key} environment variable"))
69+
}

0 commit comments

Comments
 (0)