mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-25 11:14:46 +00:00
Add more docs and example for cargo-credential
This commit is contained in:
parent
abc115972e
commit
af95711ae5
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -339,6 +339,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snapbox",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
@ -867,6 +868,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "escargot"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "768064bd3a0e2bedcba91dc87ace90beea91acc41b6a01a3ca8e9aa8827461bf"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
@ -3003,6 +3016,7 @@ dependencies = [
|
||||
"anstyle",
|
||||
"content_inspector",
|
||||
"dunce",
|
||||
"escargot",
|
||||
"filetime",
|
||||
"normalize-line-endings",
|
||||
"similar",
|
||||
|
@ -12,3 +12,6 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
snapbox = { workspace = true, features = ["examples"] }
|
||||
|
90
credential/cargo-credential/examples/file-provider.rs
Normal file
90
credential/cargo-credential/examples/file-provider.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! Example credential provider that stores credentials in a JSON file.
|
||||
//! This is not secure
|
||||
|
||||
use cargo_credential::{
|
||||
Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret,
|
||||
};
|
||||
use std::{collections::HashMap, fs::File, io::ErrorKind};
|
||||
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
|
||||
struct FileCredential;
|
||||
|
||||
impl Credential for FileCredential {
|
||||
fn perform(
|
||||
&self,
|
||||
registry: &RegistryInfo,
|
||||
action: &Action,
|
||||
_args: &[&str],
|
||||
) -> Result<CredentialResponse, cargo_credential::Error> {
|
||||
if registry.index_url != "https://github.com/rust-lang/crates.io-index" {
|
||||
// Restrict this provider to only work for crates.io. Cargo will skip it and attempt
|
||||
// another provider for any other registry.
|
||||
//
|
||||
// If a provider supports any registry, then this check should be omitted.
|
||||
return Err(cargo_credential::Error::UrlNotSupported);
|
||||
}
|
||||
|
||||
// `Error::Other` takes a boxed `std::error::Error` type that causes Cargo to show the error.
|
||||
let mut creds = FileCredential::read().map_err(cargo_credential::Error::Other)?;
|
||||
|
||||
match action {
|
||||
Action::Get(_) => {
|
||||
// Cargo requested a token, look it up.
|
||||
if let Some(token) = creds.get(registry.index_url) {
|
||||
Ok(CredentialResponse::Get {
|
||||
token: token.clone(),
|
||||
cache: CacheControl::Session,
|
||||
operation_independent: true,
|
||||
})
|
||||
} else {
|
||||
// Credential providers should respond with `NotFound` when a credential can not be
|
||||
// found, allowing Cargo to attempt another provider.
|
||||
Err(cargo_credential::Error::NotFound)
|
||||
}
|
||||
}
|
||||
Action::Login(login_options) => {
|
||||
// The token for `cargo login` can come from the `login_options` parameter or i
|
||||
// interactively reading from stdin.
|
||||
//
|
||||
// `cargo_credential::read_token` automatically handles this.
|
||||
let token = cargo_credential::read_token(login_options, registry)?;
|
||||
creds.insert(registry.index_url.to_string(), token);
|
||||
|
||||
FileCredential::write(&creds).map_err(cargo_credential::Error::Other)?;
|
||||
|
||||
// Credentials were successfully stored.
|
||||
Ok(CredentialResponse::Login)
|
||||
}
|
||||
Action::Logout => {
|
||||
if creds.remove(registry.index_url).is_none() {
|
||||
// If the user attempts to log out from a registry that has no credentials
|
||||
// stored, then NotFound is the appropriate error.
|
||||
Err(cargo_credential::Error::NotFound)
|
||||
} else {
|
||||
// Credentials were successfully erased.
|
||||
Ok(CredentialResponse::Logout)
|
||||
}
|
||||
}
|
||||
// If a credential provider doesn't support a given operation, it should respond with `OperationNotSupported`.
|
||||
_ => Err(cargo_credential::Error::OperationNotSupported),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileCredential {
|
||||
fn read() -> Result<HashMap<String, Secret<String>>, Error> {
|
||||
match File::open("cargo-credentials.json") {
|
||||
Ok(f) => Ok(serde_json::from_reader(f)?),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Ok(HashMap::new()),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
}
|
||||
fn write(value: &HashMap<String, Secret<String>>) -> Result<(), Error> {
|
||||
let file = File::create("cargo-credentials.json")?;
|
||||
Ok(serde_json::to_writer_pretty(file, value)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
cargo_credential::main(FileCredential);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
//! Helper library for writing Cargo credential processes.
|
||||
//! Helper library for writing Cargo credential providers.
|
||||
//!
|
||||
//! A credential process should have a `struct` that implements the `Credential` trait.
|
||||
//! The `main` function should be called with an instance of that struct, such as:
|
||||
@ -8,6 +8,34 @@
|
||||
//! cargo_credential::main(MyCredential);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! While in the `perform` function, stdin and stdout will be re-attached to the
|
||||
//! active console. This allows credential providers to be interactive if necessary.
|
||||
//!
|
||||
//! ## Error handling
|
||||
//! ### [`Error::UrlNotSupported`]
|
||||
//! A credential provider may only support some registry URLs. If this is the case
|
||||
//! and an unsupported index URL is passed to the provider, it should respond with
|
||||
//! [`Error::UrlNotSupported`]. Other credential providers may be attempted by Cargo.
|
||||
//!
|
||||
//! ### [`Error::NotFound`]
|
||||
//! When attempting an [`Action::Get`] or [`Action::Logout`], if a credential can not
|
||||
//! be found, the provider should respond with [`Error::NotFound`]. Other credential
|
||||
//! providers may be attempted by Cargo.
|
||||
//!
|
||||
//! ### [`Error::OperationNotSupported`]
|
||||
//! A credential provider might not support all operations. For example if the provider
|
||||
//! only supports [`Action::Get`], [`Error::OperationNotSupported`] should be returned
|
||||
//! for all other requests.
|
||||
//!
|
||||
//! ### [`Error::Other`]
|
||||
//! All other errors go here. The error will be shown to the user in Cargo, including
|
||||
//! the full error chain using [`std::error::Error::source`].
|
||||
//!
|
||||
//! ## Example
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../examples/file-provider.rs")]
|
||||
//! ```
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
28
credential/cargo-credential/tests/examples.rs
Normal file
28
credential/cargo-credential/tests/examples.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use std::path::Path;
|
||||
|
||||
use snapbox::cmd::Command;
|
||||
|
||||
#[test]
|
||||
fn file_provider() {
|
||||
let bin = snapbox::cmd::compile_example("file-provider", []).unwrap();
|
||||
|
||||
let hello = r#"{"v":[1]}"#;
|
||||
let login_request = r#"{"v": 1,"registry": {"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind": "login","token": "s3krit","args": []}"#;
|
||||
let login_response = r#"{"Ok":{"kind":"login"}}"#;
|
||||
|
||||
let get_request = r#"{"v": 1,"registry": {"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind": "get","operation": "read","args": []}"#;
|
||||
let get_response =
|
||||
r#"{"Ok":{"kind":"get","token":"s3krit","cache":"session","operation_independent":true}}"#;
|
||||
|
||||
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join("cargo-credential-tests");
|
||||
std::fs::create_dir(&dir).unwrap();
|
||||
Command::new(bin)
|
||||
.current_dir(&dir)
|
||||
.stdin(format!("{login_request}\n{get_request}\n"))
|
||||
.arg("--cargo-plugin")
|
||||
.assert()
|
||||
.stdout_eq(format!("{hello}\n{login_response}\n{get_response}\n"))
|
||||
.stderr_eq("")
|
||||
.success();
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user