Skip to content
Draft
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
102 changes: 102 additions & 0 deletions crates/crates-io/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,50 @@ struct Crates {
meta: TotalCrates,
}

#[derive(Deserialize)]
pub struct GitHubConfig {
pub id: u32,
#[serde(rename = "crate")]
pub krate: String,
pub repository_owner: String,
pub repository_owner_id: Option<u32>,
pub repository_name: String,
pub workflow_filename: String,
pub environment: Option<String>,
pub created_at: Option<String>,
}
#[derive(Deserialize)]
struct GitHubConfigs {
github_configs: Vec<GitHubConfig>,
}
#[derive(Deserialize)]
struct GitHubConfigResponse {
github_config: GitHubConfig,
}
#[derive(Serialize)]
struct NewGitHubConfig<'a> {
#[serde(rename = "crate")]
krate: &'a str,
repository_owner: &'a str,
repository_name: &'a str,
workflow_filename: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
environment: Option<&'a str>,
}
#[derive(Serialize)]
struct NewGitHubConfigReq<'a> {
github_config: NewGitHubConfig<'a>,
}
#[derive(Serialize)]
struct CrateUpdate {
trustpub_only: bool,
}
#[derive(Serialize)]
struct CrateUpdateReq {
#[serde(rename = "crate")]
krate: CrateUpdate,
}

/// Error returned when interacting with a registry.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
Expand Down Expand Up @@ -274,6 +318,56 @@ impl<T: HttpClient> Registry<T> {
Ok(serde_json::from_str::<Users>(&body)?.users)
}

pub fn list_github_trustpub_configs(
&mut self,
krate: &str,
) -> RegistryResult<Vec<GitHubConfig>, T::Error> {
let krate = percent_encode(krate.as_bytes(), NON_ALPHANUMERIC);
let body = self.get(&format!(
"/trusted_publishing/github_configs?crate={}",
krate
))?;
Ok(serde_json::from_str::<GitHubConfigs>(&body)?.github_configs)
}

pub fn add_github_trustpub_config(
&mut self,
krate: &str,
repository_owner: &str,
repository_name: &str,
workflow_filename: &str,
environment: Option<&str>,
) -> RegistryResult<GitHubConfig, T::Error> {
let body = serde_json::to_string(&NewGitHubConfigReq {
github_config: NewGitHubConfig {
krate,
repository_owner,
repository_name,
workflow_filename,
environment,
},
})?;
let body = self.post("/trusted_publishing/github_configs", Some(body.as_bytes()))?;
Ok(serde_json::from_str::<GitHubConfigResponse>(&body)?.github_config)
}

pub fn remove_github_trustpub_config(&mut self, id: u32) -> RegistryResult<(), T::Error> {
self.delete(&format!("/trusted_publishing/github_configs/{}", id), None)?;
Ok(())
}

pub fn set_trustpub_only(
&mut self,
krate: &str,
trustpub_only: bool,
) -> RegistryResult<(), T::Error> {
let body = serde_json::to_string(&CrateUpdateReq {
krate: CrateUpdate { trustpub_only },
})?;
self.patch(&format!("/crates/{}", krate), Some(body.as_bytes()))?;
Ok(())
}

pub fn publish(
&mut self,
krate: &NewCrate,
Expand Down Expand Up @@ -390,6 +484,14 @@ impl<T: HttpClient> Registry<T> {
self.req(Method::PUT, path, b, Auth::Authorized)
}

fn post(&mut self, path: &str, b: Option<&[u8]>) -> RegistryResult<String, T::Error> {
self.req(Method::POST, path, b, Auth::Authorized)
}

fn patch(&mut self, path: &str, b: Option<&[u8]>) -> RegistryResult<String, T::Error> {
self.req(Method::PATCH, path, b, Auth::Authorized)
}

fn get(&mut self, path: &str) -> RegistryResult<String, T::Error> {
self.req(Method::GET, path, None, Auth::Authorized)
}
Expand Down
3 changes: 3 additions & 0 deletions src/bin/cargo/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub fn builtin() -> Vec<Command> {
search::cli(),
test::cli(),
tree::cli(),
trustpub::cli(),
uninstall::cli(),
update::cli(),
vendor::cli(),
Expand Down Expand Up @@ -81,6 +82,7 @@ pub fn builtin_exec(cmd: &str) -> Option<Exec> {
"search" => search::exec,
"test" => test::exec,
"tree" => tree::exec,
"trustpub" => trustpub::exec,
"uninstall" => uninstall::exec,
"update" => update::exec,
"vendor" => vendor::exec,
Expand Down Expand Up @@ -125,6 +127,7 @@ pub mod rustdoc;
pub mod search;
pub mod test;
pub mod tree;
pub mod trustpub;
pub mod uninstall;
pub mod update;
pub mod vendor;
Expand Down
103 changes: 103 additions & 0 deletions src/bin/cargo/commands/trustpub.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::command_prelude::*;

use cargo::ops::{self, TrustpubCommand, TrustpubOptions};
use cargo_credential::Secret;

pub fn cli() -> Command {
subcommand("trustpub")
.about("Manage Trusted Publishing configuration for a crate on the registry")
.subcommand_required(true)
.arg_required_else_help(true)
.arg(
opt("crate", "Crate to operate on")
.value_name("CRATE")
.global(true),
)
.arg(
opt("token", "API token to use when authenticating")
.value_name("TOKEN")
.global(true),
)
.arg_silent_suggestion()
.subcommand(subcommand("list").about("List the Trusted Publishing configs for a crate"))
.subcommand(
subcommand("add")
.about("Add a GitHub Actions Trusted Publishing config to a crate")
.arg(
opt("owner", "GitHub repository owner (user or organization)")
.value_name("OWNER")
.required(true),
)
.arg(
opt("repo", "GitHub repository name")
.value_name("REPO")
.required(true),
)
.arg(
opt(
"pipeline",
"GitHub Actions workflow filename (e.g. `ci.yml`)",
)
.value_name("PIPELINE")
.required(true),
)
.arg(
opt("env", "GitHub Actions environment the workflow must run in")
.value_name("ENV"),
),
)
.subcommand(
subcommand("remove")
.about("Remove a Trusted Publishing config from a crate")
.arg(
opt(
"id",
"Id of the config to remove (see `cargo trustpub list`)",
)
.value_name("ID")
.value_parser(value_parser!(u32))
.required(true),
),
)
.subcommand(
subcommand("set")
.about("Control whether new versions must be published via Trusted Publishing")
.arg(
opt(
"trustpub-only",
"Require Trusted Publishing for new versions of the crate",
)
.value_name("BOOL")
.value_parser(value_parser!(bool))
.required(true),
),
)
}

pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
let command = match args.subcommand() {
Some(("list", _)) => TrustpubCommand::List,
Some(("add", sub)) => TrustpubCommand::Add {
repository_owner: sub.get_one::<String>("owner").cloned().unwrap(),
repository_name: sub.get_one::<String>("repo").cloned().unwrap(),
workflow_filename: sub.get_one::<String>("pipeline").cloned().unwrap(),
environment: sub.get_one::<String>("env").cloned(),
},
Some(("remove", sub)) => TrustpubCommand::Remove {
id: *sub.get_one::<u32>("id").unwrap(),
},
Some(("set", sub)) => TrustpubCommand::Set {
trustpub_only: *sub.get_one::<bool>("trustpub-only").unwrap(),
},
Some((cmd, _)) => unreachable!("unexpected command {}", cmd),
None => unreachable!("unexpected command"),
};

let opts = TrustpubOptions {
krate: args.get_one::<String>("crate").cloned(),
token: args.get_one::<String>("token").cloned().map(Secret::from),
command,
};
ops::trusted_publish(gctx, &opts)?;
Ok(())
}
3 changes: 3 additions & 0 deletions src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ pub use self::registry::OwnersOptions;
pub use self::registry::PublishOpts;
pub use self::registry::RegistryCredentialConfig;
pub use self::registry::RegistryOrIndex;
pub use self::registry::TrustpubCommand;
pub use self::registry::TrustpubOptions;
pub use self::registry::info;
pub use self::registry::modify_owners;
pub use self::registry::publish;
pub use self::registry::registry_login;
pub use self::registry::registry_logout;
pub use self::registry::search;
pub use self::registry::trusted_publish;
pub use self::registry::yank;
pub use self::resolve::{
WorkspaceResolve, add_overrides, get_resolved_packages, resolve_with_previous, resolve_ws,
Expand Down
4 changes: 4 additions & 0 deletions src/cargo/ops/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod logout;
mod owner;
mod publish;
mod search;
mod trustpub;
mod yank;

use std::collections::HashSet;
Expand Down Expand Up @@ -35,6 +36,9 @@ pub use self::owner::modify_owners;
pub use self::publish::PublishOpts;
pub use self::publish::publish;
pub use self::search::search;
pub use self::trustpub::TrustpubCommand;
pub use self::trustpub::TrustpubOptions;
pub use self::trustpub::trusted_publish;
pub use self::yank::yank;

pub(crate) use self::publish::prepare_transmit;
Expand Down
Loading