#
cargo/util/credential/
adaptor.rs

    1//! Credential provider that launches an external process that only outputs a credential
    2
    3use 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}