Skip to content

Commit cf3693b

Browse files
committed
Implement synchronization of the trustpub_only crate property
1 parent fad9c70 commit cf3693b

File tree

2 files changed

+129
-9
lines changed

2 files changed

+129
-9
lines changed

sync-team/src/crates_io/api.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::crates_io::CratesIoPublishingConfig;
1+
use crate::crates_io::CrateConfig;
22
use crate::utils::ResponseExt;
33
use anyhow::{Context, anyhow};
44
use log::debug;
@@ -63,7 +63,7 @@ impl CratesIoApi {
6363
/// Create a new trusted publishing configuration for a given crate.
6464
pub(crate) fn create_trusted_publishing_github_config(
6565
&self,
66-
config: &CratesIoPublishingConfig,
66+
config: &CrateConfig,
6767
) -> anyhow::Result<()> {
6868
debug!(
6969
"Creating trusted publishing config for '{}' in repo '{}/{}', workflow file '{}' and environment '{}'",
@@ -134,6 +134,57 @@ impl CratesIoApi {
134134
Ok(())
135135
}
136136

137+
/// Get information about a crate.
138+
pub(crate) fn get_crate(&self, krate: &str) -> anyhow::Result<CratesIoCrate> {
139+
#[derive(serde::Deserialize)]
140+
struct CrateResponse {
141+
#[serde(rename = "crate")]
142+
krate: CratesIoCrate,
143+
}
144+
145+
let response: CrateResponse = self
146+
.req::<()>(reqwest::Method::GET, &format!("/crates/{krate}"), None)?
147+
.error_for_status()?
148+
.json_annotated()?;
149+
150+
Ok(response.krate)
151+
}
152+
153+
/// Enable or disable the `trustpub_only` crate option, which specifies whether a crate
154+
/// has to be published **only** through trusted publishing.
155+
pub(crate) fn set_trusted_publishing_only(
156+
&self,
157+
krate: &str,
158+
value: bool,
159+
) -> anyhow::Result<()> {
160+
#[derive(serde::Serialize)]
161+
struct PatchCrateRequest {
162+
#[serde(rename = "crate")]
163+
krate: Crate,
164+
}
165+
166+
#[derive(serde::Serialize)]
167+
struct Crate {
168+
trustpub_only: bool,
169+
}
170+
171+
if !self.dry_run {
172+
self.req(
173+
reqwest::Method::PATCH,
174+
&format!("/crates/{krate}"),
175+
Some(&PatchCrateRequest {
176+
krate: Crate {
177+
trustpub_only: value,
178+
},
179+
}),
180+
)?
181+
.error_for_status()
182+
.with_context(|| anyhow::anyhow!("Cannot patch crate {krate}"))?;
183+
}
184+
185+
Ok(())
186+
}
187+
137188
/// Perform a request against the crates.io API
138189
fn req<T: Serialize>(
139190
&self,
@@ -170,3 +221,9 @@ pub(crate) struct TrustedPublishingGitHubConfig {
170221
pub(crate) workflow_filename: String,
171222
pub(crate) environment: Option<String>,
172223
}
224+
225+
#[derive(serde::Deserialize, Debug)]
226+
pub(crate) struct CratesIoCrate {
227+
#[serde(rename = "trustpub_only")]
228+
pub(crate) trusted_publishing_only: bool,
229+
}

sync-team/src/crates_io/mod.rs

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,30 @@ use crate::crates_io::api::{CratesIoApi, TrustedPublishingGitHubConfig};
77
use anyhow::Context;
88
use secrecy::SecretString;
99
use std::collections::HashMap;
10+
use std::fmt::{Display, Formatter};
1011

1112
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
1213
struct CrateName(String);
1314

15+
impl Display for CrateName {
16+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17+
self.0.fmt(f)
18+
}
19+
}
20+
1421
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
15-
struct CratesIoPublishingConfig {
22+
struct CrateConfig {
1623
krate: CrateName,
1724
repo_org: String,
1825
repo_name: String,
1926
workflow_file: String,
2027
environment: String,
28+
trusted_publishing_only: bool,
2129
}
2230

2331
pub(crate) struct SyncCratesIo {
2432
crates_io_api: CratesIoApi,
25-
crates: HashMap<CrateName, CratesIoPublishingConfig>,
33+
crates: HashMap<CrateName, CrateConfig>,
2634
}
2735

2836
impl SyncCratesIo {
@@ -32,7 +40,7 @@ impl SyncCratesIo {
3240
dry_run: bool,
3341
) -> anyhow::Result<Self> {
3442
let crates_io_api = CratesIoApi::new(token, dry_run);
35-
let crates: HashMap<CrateName, CratesIoPublishingConfig> = team_api
43+
let crates: HashMap<CrateName, CrateConfig> = team_api
3644
.get_repos()?
3745
.into_iter()
3846
.flat_map(|repo| {
@@ -44,12 +52,13 @@ impl SyncCratesIo {
4452
};
4553
Some((
4654
CrateName(krate.name.clone()),
47-
CratesIoPublishingConfig {
55+
CrateConfig {
4856
krate: CrateName(krate.name.clone()),
4957
repo_org: repo.org.clone(),
5058
repo_name: repo.name.clone(),
5159
workflow_file: publishing.workflow_file.clone(),
5260
environment: publishing.environment.clone(),
61+
trusted_publishing_only: krate.trusted_publishing_only,
5362
},
5463
))
5564
})
@@ -65,6 +74,7 @@ impl SyncCratesIo {
6574

6675
pub(crate) fn diff_all(&self) -> anyhow::Result<Diff> {
6776
let mut config_diffs: Vec<ConfigDiff> = vec![];
77+
let mut crate_diffs: Vec<CrateDiff> = vec![];
6878

6979
// Note: we currently only support one trusted publishing configuration per crate
7080
for (krate, desired) in &self.crates {
@@ -102,6 +112,18 @@ impl SyncCratesIo {
102112

103113
// Non-matching configs should be deleted
104114
config_diffs.extend(configs.into_iter().map(ConfigDiff::Delete));
115+
116+
let trusted_publish_only_expected = desired.trusted_publishing_only;
117+
let crates_io_crate = self
118+
.crates_io_api
119+
.get_crate(&krate.0)
120+
.with_context(|| anyhow::anyhow!("Cannot load crate {krate}"))?;
121+
if crates_io_crate.trusted_publishing_only != trusted_publish_only_expected {
122+
crate_diffs.push(CrateDiff::SetTrustedPublishingOnly {
123+
krate: krate.to_string(),
124+
value: trusted_publish_only_expected,
125+
});
126+
}
105127
}
106128

107129
// We want to apply deletions first, and only then create new configs, to ensure that we
@@ -114,17 +136,29 @@ impl SyncCratesIo {
114136
(ConfigDiff::Create(a), ConfigDiff::Create(b)) => a.cmp(b),
115137
});
116138

117-
Ok(Diff { config_diffs })
139+
Ok(Diff {
140+
config_diffs,
141+
crate_diffs,
142+
})
118143
}
119144
}
120145

121146
pub(crate) struct Diff {
122147
config_diffs: Vec<ConfigDiff>,
148+
crate_diffs: Vec<CrateDiff>,
123149
}
124150

125151
impl Diff {
126152
pub(crate) fn apply(&self, sync: &SyncCratesIo) -> anyhow::Result<()> {
127-
for diff in &self.config_diffs {
153+
let Diff {
154+
config_diffs,
155+
crate_diffs,
156+
} = self;
157+
158+
for diff in config_diffs {
159+
diff.apply(sync)?;
160+
}
161+
for diff in crate_diffs {
128162
diff.apply(sync)?;
129163
}
130164
Ok(())
@@ -149,9 +183,10 @@ impl std::fmt::Display for Diff {
149183
}
150184

151185
enum ConfigDiff {
152-
Create(CratesIoPublishingConfig),
186+
Create(CrateConfig),
153187
Delete(TrustedPublishingGitHubConfig),
154188
}
189+
155190
impl ConfigDiff {
156191
fn apply(&self, sync: &SyncCratesIo) -> anyhow::Result<()> {
157192
match self {
@@ -200,3 +235,31 @@ impl std::fmt::Display for ConfigDiff {
200235
Ok(())
201236
}
202237
}
238+
239+
enum CrateDiff {
240+
SetTrustedPublishingOnly { krate: String, value: bool },
241+
}
242+
243+
impl CrateDiff {
244+
fn apply(&self, sync: &SyncCratesIo) -> anyhow::Result<()> {
245+
match self {
246+
Self::SetTrustedPublishingOnly { krate, value } => sync
247+
.crates_io_api
248+
.set_trusted_publishing_only(krate, *value),
249+
}
250+
}
251+
}
252+
253+
impl std::fmt::Display for CrateDiff {
254+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255+
match self {
256+
Self::SetTrustedPublishingOnly { krate, value } => {
257+
writeln!(
258+
f,
259+
" Setting trusted publishing only option for krate `{krate}` to `{value}`",
260+
)?;
261+
}
262+
}
263+
Ok(())
264+
}
265+
}

0 commit comments

Comments
 (0)