cargo/util/credential/
adaptor.rs
1use std::{
4 io::Read,
5 process::{Command, Stdio},
6};
7
8use anyhow::Context;
9use cargo_credential::{
10 Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret,
11};
12
13pub struct BasicProcessCredential {}
14
15impl Credential for BasicProcessCredential {
16 fn perform(
17 &self,
18 registry: &RegistryInfo<'_>,
19 action: &Action<'_>,
20 args: &[&str],
21 ) -> Result<CredentialResponse, cargo_credential::Error> {
22 match action {
23 Action::Get(_) => {
24 let mut args = args.iter();
25 let exe = args.next()
26 .ok_or("The first argument to `cargo:token-from-stdout` must be a command that prints a token on stdout")?;
27 let args = args.map(|arg| arg.replace("{index_url}", registry.index_url));
28
29 let mut cmd = Command::new(exe);
30 cmd.args(args)
31 .env("CARGO_REGISTRY_INDEX_URL", registry.index_url);
32 if let Some(name) = registry.name {
33 cmd.env("CARGO_REGISTRY_NAME_OPT", name);
34 }
35 cmd.stdout(Stdio::piped());
36 let mut child = cmd.spawn().context("failed to spawn credential process")?;
37 let mut buffer = String::new();
38 child
39 .stdout
40 .take()
41 .unwrap()
42 .read_to_string(&mut buffer)
43 .context("failed to read from credential provider")?;
44 if let Some(end) = buffer.find('\n') {
45 if buffer.len() > end + 1 {
46 return Err(format!(
47 "process `{}` returned more than one line of output; \
48 expected a single token",
49 exe
50 )
51 .into());
52 }
53 buffer.truncate(end);
54 }
55 let status = child.wait().context("credential process never started")?;
56 if !status.success() {
57 return Err(format!("process `{}` failed with status `{status}`", exe).into());
58 }
59 Ok(CredentialResponse::Get {
60 token: Secret::from(buffer),
61 cache: CacheControl::Session,
62 operation_independent: true,
63 })
64 }
65 _ => Err(cargo_credential::Error::OperationNotSupported),
66 }
67 }
68}