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

Track which commands a registry supports in its config.json #4747

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
49 changes: 38 additions & 11 deletions src/bin/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,46 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult {

let token = match options.arg_token {
Some(token) => token,
None => {
let host = match options.flag_registry {
Some(ref _registry) => {
return Err(CargoError::from("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();
options.flag_host.clone().unwrap_or(config.api.unwrap())
None => {
let host = match options.flag_host.clone() {
Some(host) => host,
None => {
// If the host flag wasn't set, check if the registry supports the login
// interface.
use cargo::util::ToUrl;

// Get the registry source ID
let sid = match options.flag_registry {
Some(ref registry) => {
let ops::RegistryConfig {
index, ..
} = ops::registry_configuration(config, Some(registry.clone()))?;
match index {
Some(index) => SourceId::for_registry(&index.to_url()?)?,
None => {
let err_msg = format!("registry `{}` not configured", registry);
return Err(CargoError::from(err_msg).into())
}
}
}
None => SourceId::crates_io(config)?,
};

// Update the registry and access its configuration
let mut src = RegistrySource::remote(&sid, config);
src.update().chain_err(|| format!("failed to update {}", sid))?;
let src_cfg = src.config()?.unwrap();

// If the registry supports the v1 login flow, you can use its
// api root as the host.
if src_cfg.commands.get("login").map_or(false, |vs| vs.iter().any(|v| v == "v1")) {
src_cfg.api.unwrap()
} else {
return Err(CargoError::from("token must be provided when --registry is provided.").into());
}
}
};

println!("please visit {}me and paste the API Token below", host);
let mut line = String::new();
let input = io::stdin();
Expand Down
7 changes: 4 additions & 3 deletions src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,16 @@ pub fn registry(config: &Config,
(Some(index), _) | (None, Some(index)) => SourceId::for_registry(&index.to_url()?)?,
(None, None) => SourceId::crates_io(config)?,
};
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
3 changes: 3 additions & 0 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,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
26 changes: 24 additions & 2 deletions src/crates-io/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,16 @@ body:
NotFound {
display("cannot find crate")
}
UnsupportedCommand(command: String) {
description("unsupported command")
display("unsupported command: {}", command)
}
}
}

pub struct Registry {
host: String,
commands: BTreeMap<String, Vec<String>>,
token: Option<String>,
handle: Easy,
}
Expand Down Expand Up @@ -125,21 +130,24 @@ pub struct Warnings {
#[derive(Deserialize)] struct TotalCrates { total: u32 }
#[derive(Deserialize)] struct Crates { crates: Vec<Crate>, 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: BTreeMap<String, Vec<String>>, token: Option<String>) -> Registry {
Registry::new_handle(host, commands, token, Easy::new())
}

pub fn new_handle(host: String,
commands: BTreeMap<String, Vec<String>>,
token: Option<String>,
handle: Easy) -> Registry {
Registry {
host: host,
commands: commands,
token: token,
handle: 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())?;
Expand All @@ -148,6 +156,7 @@ impl Registry {
}

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()))?;
Expand All @@ -156,12 +165,14 @@ impl Registry {
}

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 @@ -233,6 +244,7 @@ impl Registry {
}

pub fn search(&mut self, query: &str, limit: u8) -> Result<(Vec<Crate>, u32)> {
self.supports_command("search", "v1")?;
let formated_query = percent_encode(query.as_bytes(), QUERY_ENCODE_SET);
let body = self.req(
format!("/crates?q={}&per_page={}", formated_query, limit),
Expand All @@ -244,19 +256,29 @@ 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, vers: &str) -> Result<()> {
if self.commands.get(cmd).map_or(false, |vs| vs.iter().any(|v| v == vers)) {
Ok(())
} else {
Err(Error::from_kind(ErrorKind::UnsupportedCommand(cmd.to_string())))
}
}

fn put(&mut self, path: String, b: &[u8]) -> Result<String> {
self.handle.put(true)?;
self.req(path, Some(b), Auth::Authorized)
Expand Down
6 changes: 4 additions & 2 deletions tests/cargotest/support/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::io::prelude::*;
use std::fs::{self, File};

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

use url::Url;
Expand All @@ -19,8 +20,9 @@ pub fn setup() -> Repository {
repo(&registry_path())
.file("config.json", &format!(r#"{{
"dl": "{0}",
"api": "{0}"
}}"#, upload()))
"api": "{0}",
"commands": {1}
}}"#, upload(), registry::COMMANDS))
.build()
}

Expand Down
16 changes: 12 additions & 4 deletions tests/cargotest/support/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ pub fn alt_registry() -> Url { Url::from_file_path(&*alt_registry_path()).ok().u
pub fn alt_dl_path() -> PathBuf { paths::root().join("alt_dl") }
pub fn alt_dl_url() -> Url { Url::from_file_path(&*alt_dl_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 @@ -68,16 +76,16 @@ pub fn init() {
// Init a new registry
let _ = repo(&registry_path())
.file("config.json", &format!(r#"
{{"dl":"{0}","api":"{0}"}}
"#, dl_url()))
{{"dl":"{0}","api":"{0}","commands":{1}}}
"#, dl_url(), COMMANDS))
.build();
fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap();

// Init an alt registry
repo(&alt_registry_path())
.file("config.json", &format!(r#"
{{"dl":"{0}","api":"{0}"}}
"#, alt_dl_url()))
{{"dl":"{0}","api":"{0}","commands":{1}}}
"#, alt_dl_url(), COMMANDS))
.build();
fs::create_dir_all(alt_dl_path().join("api/v1/crates")).unwrap();
}
Expand Down
6 changes: 4 additions & 2 deletions tests/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use cargo::util::ProcessBuilder;
use cargotest::support::execs;
use cargotest::support::git::repo;
use cargotest::support::paths;
use cargotest::support::registry::COMMANDS;
use hamcrest::assert_that;
use url::Url;

Expand All @@ -27,8 +28,9 @@ fn setup() {
let _ = repo(&registry_path())
.file("config.json", &format!(r#"{{
"dl": "{0}",
"api": "{0}"
}}"#, api()))
"api": "{0}",
"commands": {1}
}}"#, api(), COMMANDS))
.build();
}

Expand Down