use jiff::Timestamp;
use miette::Result;
use reqwest::StatusCode;
use serde::Serialize;
use crate::{
Redacted,
client::CanopyHttpError,
schema::{BackupTarget, CredentialProcessOutput},
};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct ContainerCreds {
pub access_key_id: String,
pub secret_access_key: Redacted<String>,
pub token: Redacted<String>,
pub expiration: Timestamp,
}
impl From<&CredentialProcessOutput> for ContainerCreds {
fn from(c: &CredentialProcessOutput) -> Self {
Self {
access_key_id: c.access_key_id.clone(),
secret_access_key: c.secret_access_key.clone(),
token: c.session_token.clone(),
expiration: c.expiration,
}
}
}
#[derive(Debug, Clone)]
pub enum TargetOutcome {
Ready(BackupTarget),
Dormant,
}
impl TargetOutcome {
pub fn from_result(result: Result<BackupTarget>) -> Result<Self> {
match result {
Ok(target) => Ok(Self::Ready(target)),
Err(report) => match report.downcast_ref::<CanopyHttpError>() {
Some(err)
if err.status == StatusCode::PRECONDITION_FAILED
|| err.status == StatusCode::CONFLICT =>
{
Ok(Self::Dormant)
}
_ => Err(report),
},
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn container_creds_translate_session_token_to_token() {
let creds: CredentialProcessOutput = serde_json::from_value(json!({
"Version": 1,
"AccessKeyId": "AKIA",
"SecretAccessKey": "secret",
"SessionToken": "session-token",
"Expiration": "2026-05-21T13:00:00Z",
}))
.unwrap();
let container = ContainerCreds::from(&creds);
let out = serde_json::to_value(&container).unwrap();
assert_eq!(
out,
json!({
"AccessKeyId": "AKIA",
"SecretAccessKey": "secret",
"Token": "session-token",
"Expiration": "2026-05-21T13:00:00Z",
})
);
assert!(out.get("SessionToken").is_none());
}
#[test]
fn redacted_debug_does_not_leak() {
let creds = ContainerCreds {
access_key_id: "AKIA".to_owned(),
secret_access_key: Redacted("aws-sk-value-123".to_owned()),
token: Redacted("aws-token-value-456".to_owned()),
expiration: "2026-05-21T13:00:00Z".parse().unwrap(),
};
let debug = format!("{creds:?}");
assert!(!debug.contains("aws-sk-value-123"));
assert!(!debug.contains("aws-token-value-456"));
assert!(debug.contains("<redacted>"));
}
}