Skip to content

Commit d407029

Browse files
authored
Merge pull request #7858 from Turbo87/sync-admins
Implement admin account syncing
2 parents f2107ec + 7e4b9f8 commit d407029

File tree

16 files changed

+513
-1
lines changed

16 files changed

+513
-1
lines changed

Cargo.lock

Lines changed: 74 additions & 0 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
@@ -90,6 +90,7 @@ ipnetwork = "=0.20.0"
9090
tikv-jemallocator = { version = "=0.5.4", features = ['unprefixed_malloc_on_supported_platforms', 'profiling'] }
9191
lettre = { version = "=0.11.3", default-features = false, features = ["file-transport", "smtp-transport", "native-tls", "hostname", "builder"] }
9292
minijinja = "=1.0.11"
93+
mockall = "=0.12.1"
9394
moka = { version = "=0.12.2", features = ["future"] }
9495
oauth2 = { version = "=4.4.2", default-features = false, features = ["reqwest"] }
9596
object_store = { version = "=0.9.0", features = ["aws"] }
@@ -128,4 +129,5 @@ crates_io_test_db = { path = "crates_io_test_db" }
128129
claims = "=0.7.1"
129130
googletest = "=0.10.0"
130131
insta = { version = "=1.34.0", features = ["json", "redactions"] }
132+
regex = "=1.10.2"
131133
tokio = "=1.35.1"

src/admin/enqueue_job.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::schema::{background_jobs, crates};
33
use crate::worker::jobs;
44
use anyhow::Result;
55
use crates_io_worker::BackgroundJob;
6+
use diesel::dsl::exists;
67
use diesel::prelude::*;
78
use secrecy::{ExposeSecret, SecretString};
89

@@ -30,6 +31,11 @@ pub enum Command {
3031
#[arg()]
3132
name: String,
3233
},
34+
SyncAdmins {
35+
/// Force a sync even if one is already in progress
36+
#[arg(long)]
37+
force: bool,
38+
},
3339
}
3440

3541
pub fn run(command: Command) -> Result<()> {
@@ -58,6 +64,28 @@ pub fn run(command: Command) -> Result<()> {
5864
} => {
5965
jobs::DumpDb::new(database_url.expose_secret(), target_name).enqueue(conn)?;
6066
}
67+
Command::SyncAdmins { force } => {
68+
if !force {
69+
// By default, we don't want to enqueue a sync if one is already
70+
// in progress. If a sync fails due to e.g. an expired pinned
71+
// certificate we don't want to keep adding new jobs to the
72+
// queue, since the existing job will be retried until it
73+
// succeeds.
74+
75+
let query = background_jobs::table
76+
.filter(background_jobs::job_type.eq(jobs::SyncAdmins::JOB_NAME));
77+
78+
if diesel::select(exists(query)).get_result(conn)? {
79+
info!(
80+
"Did not enqueue {}, existing job already in progress",
81+
jobs::SyncAdmins::JOB_NAME
82+
);
83+
return Ok(());
84+
}
85+
}
86+
87+
jobs::SyncAdmins.enqueue(conn)?;
88+
}
6189
Command::DailyDbMaintenance => {
6290
jobs::DailyDbMaintenance.enqueue(conn)?;
6391
}

src/bin/background-worker.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crates_io::cloudfront::CloudFront;
1818
use crates_io::db::DieselPool;
1919
use crates_io::fastly::Fastly;
2020
use crates_io::storage::Storage;
21+
use crates_io::team_repo::TeamRepoImpl;
2122
use crates_io::worker::{Environment, RunnerExt};
2223
use crates_io::{config, Emails};
2324
use crates_io::{db, ssh};
@@ -76,7 +77,8 @@ fn main() -> anyhow::Result<()> {
7677
.expect("Couldn't build client");
7778

7879
let emails = Emails::from_environment(&config);
79-
let fastly = Fastly::from_environment(client);
80+
let fastly = Fastly::from_environment(client.clone());
81+
let team_repo = TeamRepoImpl::default();
8082

8183
let connection_pool = r2d2::Pool::builder()
8284
.max_size(10)
@@ -90,6 +92,7 @@ fn main() -> anyhow::Result<()> {
9092
.storage(storage)
9193
.connection_pool(DieselPool::new_background_worker(connection_pool.clone()))
9294
.emails(emails)
95+
.team_repo(Box::new(team_repo))
9396
.build()?;
9497

9598
let environment = Arc::new(environment);

src/certs/lets-encrypt.pem

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
3+
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4+
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
5+
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
6+
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
7+
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
8+
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
9+
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
10+
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
11+
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
12+
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
13+
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
14+
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
15+
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
16+
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
17+
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
18+
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
19+
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
20+
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
21+
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
22+
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
23+
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
24+
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
25+
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
26+
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
27+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
28+
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
29+
nLRbwHOoq7hHwg==
30+
-----END CERTIFICATE-----

src/certs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub const LETS_ENCRYPT: &[u8] = include_bytes!("./lets-encrypt.pem");

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod admin;
3232
mod app;
3333
pub mod auth;
3434
pub mod boot;
35+
pub mod certs;
3536
pub mod ci;
3637
pub mod cloudfront;
3738
pub mod config;
@@ -55,6 +56,7 @@ pub mod sql;
5556
pub mod ssh;
5657
pub mod storage;
5758
pub mod tasks;
59+
pub mod team_repo;
5860
mod test_util;
5961
pub mod typosquat;
6062
pub mod util;

src/team_repo.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//! The code in this module interacts with the
2+
//! <https://github.com/rust-lang/team/> repository.
3+
//!
4+
//! The [TeamRepo] trait is used to abstract away the HTTP client for testing
5+
//! purposes. The [TeamRepoImpl] struct is the actual implementation of
6+
//! the trait.
7+
8+
use crate::certs;
9+
use async_trait::async_trait;
10+
use mockall::automock;
11+
use reqwest::{Certificate, Client};
12+
13+
#[automock]
14+
#[async_trait]
15+
pub trait TeamRepo {
16+
async fn get_team(&self, name: &str) -> anyhow::Result<Team>;
17+
}
18+
19+
#[derive(Debug, Clone, Deserialize)]
20+
pub struct Team {
21+
pub name: String,
22+
pub kind: String,
23+
pub members: Vec<Member>,
24+
}
25+
26+
#[derive(Debug, Clone, Deserialize)]
27+
pub struct Member {
28+
pub name: String,
29+
pub github: String,
30+
pub github_id: i32,
31+
pub is_lead: bool,
32+
}
33+
34+
pub struct TeamRepoImpl {
35+
client: Client,
36+
}
37+
38+
impl TeamRepoImpl {
39+
fn new(client: Client) -> Self {
40+
TeamRepoImpl { client }
41+
}
42+
}
43+
44+
impl Default for TeamRepoImpl {
45+
fn default() -> Self {
46+
let client = build_client();
47+
TeamRepoImpl::new(client)
48+
}
49+
}
50+
51+
fn build_client() -> Client {
52+
let lets_encrypt_cert = Certificate::from_pem(certs::LETS_ENCRYPT).unwrap();
53+
54+
Client::builder()
55+
.tls_built_in_root_certs(false)
56+
.add_root_certificate(lets_encrypt_cert)
57+
.build()
58+
.unwrap()
59+
}
60+
61+
#[async_trait]
62+
impl TeamRepo for TeamRepoImpl {
63+
async fn get_team(&self, name: &str) -> anyhow::Result<Team> {
64+
let url = format!("https://team-api.infra.rust-lang.org/v1/teams/{name}.json");
65+
let response = self.client.get(url).send().await?.error_for_status()?;
66+
Ok(response.json().await?)
67+
}
68+
}
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use crate::team_repo::build_client;
73+
74+
/// This test is here to make sure that the client is built
75+
/// correctly without panicking.
76+
#[test]
77+
fn test_build_client() {
78+
let _client = build_client();
79+
}
80+
}

src/tests/util/test_app.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crates_io::middleware::cargo_compat::StatusCodeConfig;
77
use crates_io::models::token::{CrateScope, EndpointScope};
88
use crates_io::rate_limiter::{LimitedAction, RateLimiterConfig};
99
use crates_io::storage::StorageConfig;
10+
use crates_io::team_repo::MockTeamRepo;
1011
use crates_io::worker::{Environment, RunnerExt};
1112
use crates_io::{App, Emails, Env};
1213
use crates_io_index::testing::UpstreamIndex;
@@ -80,6 +81,7 @@ impl TestApp {
8081
index: None,
8182
build_job_runner: false,
8283
use_chaos_proxy: false,
84+
team_repo: MockTeamRepo::new(),
8385
}
8486
}
8587

@@ -204,6 +206,7 @@ pub struct TestAppBuilder {
204206
index: Option<UpstreamIndex>,
205207
build_job_runner: bool,
206208
use_chaos_proxy: bool,
209+
team_repo: MockTeamRepo,
207210
}
208211

209212
impl TestAppBuilder {
@@ -259,11 +262,13 @@ impl TestAppBuilder {
259262
index_location: index.url(),
260263
credentials: Credentials::Missing,
261264
};
265+
262266
let environment = Environment::builder()
263267
.repository_config(repository_config)
264268
.storage(app.storage.clone())
265269
.connection_pool(app.primary_database.clone())
266270
.emails(app.emails.clone())
271+
.team_repo(Box::new(self.team_repo))
267272
.build()
268273
.unwrap();
269274

@@ -351,6 +356,11 @@ impl TestAppBuilder {
351356
self
352357
}
353358

359+
pub fn with_team_repo(mut self, team_repo: MockTeamRepo) -> Self {
360+
self.team_repo = team_repo;
361+
self
362+
}
363+
354364
pub fn with_replica(mut self) -> Self {
355365
let primary = &self.config.db.primary;
356366

src/tests/worker/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
mod git;
2+
mod sync_admins;

0 commit comments

Comments
 (0)