use std::{
io::{BufRead, BufReader, Write},
path::PathBuf,
process::{Child, Command, Stdio},
};
use anyhow::Context;
use cargo_credential::{
Action, Credential, CredentialHello, CredentialRequest, CredentialResponse, Error, RegistryInfo,
};
pub struct CredentialProcessCredential {
path: PathBuf,
}
impl<'a> CredentialProcessCredential {
pub fn new(path: &str) -> Self {
Self {
path: PathBuf::from(path),
}
}
fn run(
&self,
child: &mut Child,
action: &Action<'_>,
registry: &RegistryInfo<'_>,
args: &[&str],
) -> Result<Result<CredentialResponse, Error>, Error> {
let mut output_from_child = BufReader::new(child.stdout.take().unwrap());
let mut input_to_child = child.stdin.take().unwrap();
let mut buffer = String::new();
output_from_child
.read_line(&mut buffer)
.context("failed to read hello from credential provider")?;
let credential_hello: CredentialHello =
serde_json::from_str(&buffer).context("failed to deserialize hello")?;
tracing::debug!("credential-process > {credential_hello:?}");
if !credential_hello
.v
.contains(&cargo_credential::PROTOCOL_VERSION_1)
{
return Err(format!(
"credential provider supports protocol versions {:?}, while Cargo supports {:?}",
credential_hello.v,
[cargo_credential::PROTOCOL_VERSION_1]
)
.into());
}
let req = CredentialRequest {
v: cargo_credential::PROTOCOL_VERSION_1,
action: action.clone(),
registry: registry.clone(),
args: args.to_vec(),
};
let request = serde_json::to_string(&req).context("failed to serialize request")?;
tracing::debug!("credential-process < {req:?}");
writeln!(input_to_child, "{request}").context("failed to write to credential provider")?;
buffer.clear();
output_from_child
.read_line(&mut buffer)
.context("failed to read response from credential provider")?;
let response: Result<CredentialResponse, Error> =
serde_json::from_str(&buffer).context("failed to deserialize response")?;
tracing::debug!("credential-process > {response:?}");
drop(input_to_child);
let status = child.wait().context("credential process never started")?;
if !status.success() {
return Err(anyhow::anyhow!(
"credential process `{}` failed with status {}`",
self.path.display(),
status
)
.into());
}
tracing::trace!("credential process exited successfully");
Ok(response)
}
}
impl<'a> Credential for CredentialProcessCredential {
fn perform(
&self,
registry: &RegistryInfo<'_>,
action: &Action<'_>,
args: &[&str],
) -> Result<CredentialResponse, Error> {
let mut cmd = Command::new(&self.path);
cmd.stdout(Stdio::piped());
cmd.stdin(Stdio::piped());
cmd.arg("--cargo-plugin");
tracing::debug!("credential-process: {cmd:?}");
let mut child = cmd.spawn().context("failed to spawn credential process")?;
match self.run(&mut child, action, registry, args) {
Err(e) => {
let _ = child.kill();
Err(e)
}
Ok(response) => response,
}
}
}