Skip to content
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

Registries report which commands they support. #5915

Closed
wants to merge 3 commits into from
Closed
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
54 changes: 37 additions & 17 deletions src/bin/cargo/commands/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,49 @@ pub fn cli() -> App {
}

pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
// 1. Get the registry src for this command
let registry = args.registry(config)?;
let src_id = match &registry {
Some(registry) => SourceId::alt_registry(config, registry),
None => SourceId::crates_io(config),
}?;
let mut src = RegistrySource::remote(&src_id, config);

// 2. Update the src so that we have the latest information
src.update()?;

// 3. Check if this registry supports cargo login v1
let reg_cfg = src.config()?.unwrap();
if !reg_cfg.commands.get("login").unwrap_or(&vec![]).iter().any(|v| v == "v1") {
let registry = match &registry {
Some(registry) => registry,
None => "crates-io",
};
Err(format_err!("`{}` does not support the `cargo login` command with \
Copy link
Member

Choose a reason for hiding this comment

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

We should only check this if the token wasn't provided up front right?

version `v1`", registry))?;
}

// 4. Either we already have the token, or we get it from the command line
let token = match args.value_of("token") {
Some(token) => token.to_string(),
None => {
let host = match registry {
Some(ref _registry) => {
return Err(format_err!(
"token must be provided when \
--registry is provided."
).into());
}
None => {
let src = SourceId::crates_io(config)?;
let mut src = RegistrySource::remote(&src, config);
src.update()?;
let config = src.config()?.unwrap();
args.value_of("host")
.map(|s| s.to_string())
.unwrap_or_else(|| config.api.unwrap())
}
// Print instructions to stdout. The exact wording of the message is determined by
// whether or not the user passed `--registry`.
let host = args.value_of("host")
.map(|s| s.to_string())
.unwrap_or_else(|| reg_cfg.api.unwrap());
let separator = match host.ends_with("/") {
true => "",
false => "/",
};
println!("please visit {}me and paste the API Token below", host);
if registry.is_some() {
println!("please paste the API Token below (you may be able to obtain your token
by visiting {}{}me", host, separator);
} else {
println!("please visit {}{}me and paste the API Token below", host, separator);
}

// Read the token from stdin.
let mut line = String::new();
let input = io::stdin();
input
Expand Down
14 changes: 8 additions & 6 deletions src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,14 +315,15 @@ pub fn registry(
} = registry_configuration(config, registry.clone())?;
let token = token.or(token_config);
let sid = get_source_id(config, index_config.or(index), registry)?;
let api_host = {
let (api_host, commands) = {
let mut src = RegistrySource::remote(&sid, config);
src.update()
.chain_err(|| format!("failed to update {}", sid))?;
(src.config()?).unwrap().api.unwrap()
let cfg = src.config()?.unwrap();
(cfg.api.unwrap(), cfg.commands)
};
let handle = http_handle(config)?;
Ok((Registry::new_handle(api_host, token, handle), sid))
Ok((Registry::new_handle(api_host, commands, token, handle), sid))
}

/// Create a new HTTP handle with appropriate global configuration for cargo.
Expand Down Expand Up @@ -599,11 +600,12 @@ pub fn search(
.chain_err(|| format!("failed to update {}", &sid))?;
regsrc.config()?
}
};
}.unwrap();

let api_host = cfg.unwrap().api.unwrap();
let api_host = cfg.api.unwrap();
let commands = cfg.commands;
let handle = http_handle(config)?;
let mut registry = Registry::new_handle(api_host, None, handle);
let mut registry = Registry::new_handle(api_host, commands, None, handle);
let (crates, total_crates) = registry
.search(query, limit)
.chain_err(|| "failed to retrieve search results from the registry")?;
Expand Down
3 changes: 3 additions & 0 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ pub struct RegistryConfig {
/// API endpoint for the registry. This is what's actually hit to perform
/// operations like yanks, owner modifications, publish new crates, etc.
pub api: Option<String>,

#[serde(default)]
pub commands: BTreeMap<String, Vec<String>>,
}

#[derive(Deserialize)]
Expand Down
27 changes: 24 additions & 3 deletions src/crates-io/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ use curl::easy::{Easy, List};
use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};

pub type Result<T> = std::result::Result<T, failure::Error>;
pub type Commands = BTreeMap<String, Vec<String>>;

pub struct Registry {
host: String,
commands: Commands,
token: Option<String>,
handle: Easy,
}
Expand Down Expand Up @@ -120,38 +122,45 @@ struct Crates {
meta: TotalCrates,
}
impl Registry {
pub fn new(host: String, token: Option<String>) -> Registry {
Registry::new_handle(host, token, Easy::new())
pub fn new(host: String, commands: Commands, token: Option<String>) -> Registry {
Registry::new_handle(host, commands, token, Easy::new())
}

pub fn new_handle(host: String, token: Option<String>, handle: Easy) -> Registry {
pub fn new_handle(host: String, commands: Commands, token: Option<String>, handle: Easy)
-> Registry
{
Registry {
host,
commands,
token,
handle,
}
}

pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result<String> {
self.supports_command("owner", "v1")?;
let body = serde_json::to_string(&OwnersReq { users: owners })?;
let body = self.put(&format!("/crates/{}/owners", krate), body.as_bytes())?;
assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
Ok(serde_json::from_str::<OwnerResponse>(&body)?.msg)
}

pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
self.supports_command("owner", "v1")?;
let body = serde_json::to_string(&OwnersReq { users: owners })?;
let body = self.delete(&format!("/crates/{}/owners", krate), Some(body.as_bytes()))?;
assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
Ok(())
}

pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
self.supports_command("owner", "v1")?;
let body = self.get(&format!("/crates/{}/owners", krate))?;
Ok(serde_json::from_str::<Users>(&body)?.users)
}

pub fn publish(&mut self, krate: &NewCrate, tarball: &File) -> Result<Warnings> {
self.supports_command("publish", "v1")?;
let json = serde_json::to_string(krate)?;
// Prepare the body. The format of the upload request is:
//
Expand Down Expand Up @@ -227,6 +236,7 @@ impl Registry {
}

pub fn search(&mut self, query: &str, limit: u32) -> Result<(Vec<Crate>, u32)> {
self.supports_command("search", "v1")?;
let formatted_query = percent_encode(query.as_bytes(), QUERY_ENCODE_SET);
let body = self.req(
&format!("/crates?q={}&per_page={}", formatted_query, limit),
Expand All @@ -239,17 +249,28 @@ impl Registry {
}

pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
self.supports_command("yank", "v1")?;
let body = self.delete(&format!("/crates/{}/{}/yank", krate, version), None)?;
assert!(serde_json::from_str::<R>(&body)?.ok);
Ok(())
}

pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> {
self.supports_command("yank", "v1")?;
let body = self.put(&format!("/crates/{}/{}/unyank", krate, version), &[])?;
assert!(serde_json::from_str::<R>(&body)?.ok);
Ok(())
}

fn supports_command(&self, cmd: &str, version: &str) -> Result<()> {
if let Some(versions) = self.commands.get(cmd) {
if versions.iter().any(|v| v == version) {
return Ok(())
}
}
bail!("`{}` does not support `cargo {}` with version `{}`", self.host, cmd, version)
}

fn put(&mut self, path: &str, b: &[u8]) -> Result<String> {
self.handle.put(true)?;
self.req(path, Some(b), Auth::Authorized)
Expand Down
5 changes: 3 additions & 2 deletions tests/testsuite/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::path::Path;
use support::{cargo_process, execs};
use support::git::repo;
use support::paths;
use support::registry::{api_path, registry as registry_url, registry_path};
use support::registry::{api_path, registry as registry_url, registry_path, COMMANDS};
use support::hamcrest::assert_that;
use url::Url;

Expand Down Expand Up @@ -65,7 +65,8 @@ fn setup() {

// Init a new registry
let _ = repo(&registry_path())
.file("config.json", &format!(r#"{{"dl":"{0}","api":"{0}"}}"#, api()))
.file("config.json", &format!(r#"{{"dl":"{0}","api":"{0}","commands":{1}}}"#, api(),
COMMANDS))
.build();

let base = api_path().join("api/v1/crates");
Expand Down
11 changes: 7 additions & 4 deletions tests/testsuite/support/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::fs::{self, File};

use support::paths;
use support::git::{repo, Repository};
use support::registry::COMMANDS;

use url::Url;

Expand Down Expand Up @@ -39,10 +40,12 @@ pub fn setup() -> Repository {
"config.json",
&format!(
r#"{{
"dl": "{0}",
"api": "{0}"
}}"#,
upload()
"dl": "{0}",
"api": "{0}",
"commands": {1}
}}"#,
upload(),
COMMANDS,
),
)
.build()
Expand Down
20 changes: 14 additions & 6 deletions tests/testsuite/support/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ pub fn alt_api_url() -> Url {
Url::from_file_path(&*alt_api_path()).ok().unwrap()
}

pub const COMMANDS: &str = r#"{
"publish": ["v1"],
"yank": ["v1"],
"search": ["v1"],
"owner": ["v1"],
"login": ["v1"]
}"#;

pub struct Package {
name: String,
vers: String,
Expand Down Expand Up @@ -102,10 +110,9 @@ pub fn init() {
.file(
"config.json",
&format!(
r#"
{{"dl":"{0}","api":"{0}"}}
"#,
dl_url()
r#"{{"dl":"{0}","api":"{0}","commands":{1}}}"#,
dl_url(),
COMMANDS,
),
)
.build();
Expand All @@ -117,10 +124,11 @@ pub fn init() {
"config.json",
&format!(
r#"
{{"dl":"{}","api":"{}"}}
{{"dl":"{}","api":"{}","commands":{}}}
"#,
alt_dl_url(),
alt_api_url()
alt_api_url(),
COMMANDS,
),
)
.build();
Expand Down