use std::{
io::Read,
process::{Command, Stdio},
};
use anyhow::Context;
use cargo_credential::{
Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret,
};
pub struct BasicProcessCredential {}
impl Credential for BasicProcessCredential {
fn perform(
&self,
registry: &RegistryInfo<'_>,
action: &Action<'_>,
args: &[&str],
) -> Result<CredentialResponse, cargo_credential::Error> {
match action {
Action::Get(_) => {
let mut args = args.iter();
let exe = args.next()
.ok_or("The first argument to `cargo:token-from-stdout` must be a command that prints a token on stdout")?;
let args = args.map(|arg| arg.replace("{index_url}", registry.index_url));
let mut cmd = Command::new(exe);
cmd.args(args)
.env("CARGO_REGISTRY_INDEX_URL", registry.index_url);
if let Some(name) = registry.name {
cmd.env("CARGO_REGISTRY_NAME_OPT", name);
}
cmd.stdout(Stdio::piped());
let mut child = cmd.spawn().context("failed to spawn credential process")?;
let mut buffer = String::new();
child
.stdout
.take()
.unwrap()
.read_to_string(&mut buffer)
.context("failed to read from credential provider")?;
if let Some(end) = buffer.find('\n') {
if buffer.len() > end + 1 {
return Err(format!(
"process `{}` returned more than one line of output; \
expected a single token",
exe
)
.into());
}
buffer.truncate(end);
}
let status = child.wait().context("credential process never started")?;
if !status.success() {
return Err(format!("process `{}` failed with status `{status}`", exe).into());
}
Ok(CredentialResponse::Get {
token: Secret::from(buffer),
cache: CacheControl::Session,
operation_independent: true,
})
}
_ => Err(cargo_credential::Error::OperationNotSupported),
}
}
}