1use http::StatusCode;
11use jiff::Timestamp;
12use miette::{IntoDiagnostic, Result};
13use serde::Serialize;
14
15use crate::{
16 Redacted,
17 schema::{BackupTarget, CredentialProcessOutput},
18};
19
20#[derive(Debug, Clone, Serialize)]
23#[serde(rename_all = "PascalCase")]
24pub struct ContainerCreds {
25 pub access_key_id: String,
26 pub secret_access_key: Redacted<String>,
27 pub token: Redacted<String>,
28 pub expiration: Timestamp,
29}
30
31impl From<&CredentialProcessOutput> for ContainerCreds {
32 fn from(c: &CredentialProcessOutput) -> Self {
33 Self {
34 access_key_id: c.access_key_id.clone(),
35 secret_access_key: c.secret_access_key.clone(),
36 token: c.session_token.clone(),
37 expiration: c.expiration,
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
46pub enum TargetOutcome {
47 Ready(BackupTarget),
48 Dormant,
49}
50
51impl TargetOutcome {
52 pub fn from_result(result: bes_canopy_api::Result<BackupTarget>) -> Result<Self> {
57 match result {
58 Ok(target) => Ok(Self::Ready(target)),
59 Err(err)
60 if matches!(
61 err.status(),
62 Some(StatusCode::PRECONDITION_FAILED | StatusCode::CONFLICT)
63 ) =>
64 {
65 Ok(Self::Dormant)
66 }
67 Err(err) => Err(err).into_diagnostic(),
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use serde_json::json;
75
76 use super::*;
77
78 #[test]
79 fn container_creds_translate_session_token_to_token() {
80 let creds: CredentialProcessOutput = serde_json::from_value(json!({
81 "Version": 1,
82 "AccessKeyId": "AKIA",
83 "SecretAccessKey": "secret",
84 "SessionToken": "session-token",
85 "Expiration": "2026-05-21T13:00:00Z",
86 }))
87 .unwrap();
88 let container = ContainerCreds::from(&creds);
89 let out = serde_json::to_value(&container).unwrap();
90 assert_eq!(
91 out,
92 json!({
93 "AccessKeyId": "AKIA",
94 "SecretAccessKey": "secret",
95 "Token": "session-token",
96 "Expiration": "2026-05-21T13:00:00Z",
97 })
98 );
99 assert!(out.get("SessionToken").is_none());
101 }
102
103 #[test]
104 fn redacted_debug_does_not_leak() {
105 let creds = ContainerCreds {
106 access_key_id: "AKIA".to_owned(),
107 secret_access_key: Redacted("aws-sk-value-123".to_owned()),
108 token: Redacted("aws-token-value-456".to_owned()),
109 expiration: "2026-05-21T13:00:00Z".parse().unwrap(),
110 };
111 let debug = format!("{creds:?}");
112 assert!(!debug.contains("aws-sk-value-123"));
113 assert!(!debug.contains("aws-token-value-456"));
114 assert!(debug.contains("<redacted>"));
115 }
116}