1use std::{
2 collections::HashMap,
3 sync::{LazyLock, Mutex},
4};
5
6use bon::Builder;
7use clap::Args;
8use docker_credential::DockerCredential;
9use log::trace;
10
11use crate::{
12 constants::{
13 BB_PASSWORD, BB_REGISTRY, BB_USERNAME, CI_REGISTRY, CI_REGISTRY_PASSWORD, CI_REGISTRY_USER,
14 GITHUB_ACTIONS, GITHUB_ACTOR, GITHUB_TOKEN,
15 },
16 get_env_var,
17 secret::SecretValue,
18 string,
19};
20
21static CREDS: LazyLock<Mutex<HashMap<String, Credentials>>> =
22 LazyLock::new(|| Mutex::new(HashMap::default()));
23
24#[derive(Debug, Clone)]
26pub enum Credentials {
27 Basic {
28 username: String,
29 password: SecretValue,
30 },
31 Token(SecretValue),
32}
33
34impl Credentials {
35 pub fn init(args: CredentialsArgs) {
45 trace!("Credentials::init()");
46 let mut creds = CREDS.lock().expect("Must lock CREDS");
47
48 let CredentialsArgs {
49 username,
50 password,
51 registry,
52 } = args;
53
54 let registry = match (
55 registry,
56 get_env_var(CI_REGISTRY).ok(),
57 get_env_var(GITHUB_ACTIONS).ok(),
58 ) {
59 (Some(registry), _, _) | (_, Some(registry), _) if !registry.is_empty() => registry,
60 (_, _, Some(_)) => string!("ghcr.io"),
61 _ => return,
62 };
63
64 let cred = match (
65 (username, password),
66 docker_credential::get_credential(®istry).ok(),
67 docker_credential::get_podman_credential(®istry).ok(),
68 (
69 get_env_var(CI_REGISTRY_USER).ok(),
70 get_env_var(CI_REGISTRY_PASSWORD)
71 .ok()
72 .map(SecretValue::from),
73 ),
74 (
75 get_env_var(GITHUB_ACTOR).ok(),
76 get_env_var(GITHUB_TOKEN).ok().map(SecretValue::from),
77 ),
78 ) {
79 ((Some(username), Some(password)), _, _, _, _) => Self::Basic { username, password },
80 (_, Some(DockerCredential::UsernamePassword(username, password)), _, _, _)
81 | (_, _, Some(DockerCredential::UsernamePassword(username, password)), _, _) => {
82 Self::Basic {
83 username,
84 password: password.into(),
85 }
86 }
87 (_, Some(DockerCredential::IdentityToken(token)), _, _, _)
88 | (_, _, Some(DockerCredential::IdentityToken(token)), _, _) => {
89 Self::Token(token.into())
90 }
91 (_, _, _, (Some(username), Some(password)), _)
92 | (_, _, _, _, (Some(username), Some(password)))
93 if !username.is_empty() && !password.is_empty() =>
94 {
95 Self::Basic { username, password }
96 }
97 _ => return,
98 };
99
100 let _ = creds.insert(registry, cred);
101 drop(creds);
102 }
103
104 #[must_use]
109 pub fn get(registry: &str) -> Option<Self> {
110 trace!("credentials::get({registry})");
111
112 let mut creds = CREDS.lock().expect("Must lock CREDS");
113
114 match (
115 creds.get(registry),
116 docker_credential::get_credential(registry).ok(),
117 docker_credential::get_podman_credential(registry).ok(),
118 ) {
119 (Some(creds), _, _) => Some(creds.clone()),
120 (None, Some(DockerCredential::IdentityToken(token)), _)
121 | (None, None, Some(DockerCredential::IdentityToken(token))) => {
122 let cred = Self::Token(SecretValue::from(token));
123 let _ = creds.insert(registry.into(), cred.clone());
124 drop(creds);
125 Some(cred)
126 }
127 (None, Some(DockerCredential::UsernamePassword(username, password)), _)
128 | (None, None, Some(DockerCredential::UsernamePassword(username, password))) => {
129 let cred = Self::Basic {
130 username,
131 password: password.into(),
132 };
133 let _ = creds.insert(registry.into(), cred.clone());
134 drop(creds);
135 Some(cred)
136 }
137 (None, None, None) => None,
138 }
139 }
140}
141
142#[derive(Debug, Default, Clone, Builder, Args)]
143#[builder(on(String, into))]
144pub struct CredentialsArgs {
145 #[arg(long, env = BB_REGISTRY)]
147 pub registry: Option<String>,
148
149 #[arg(short = 'U', long, env = BB_USERNAME, hide_env_values = true)]
152 pub username: Option<String>,
153
154 #[arg(short = 'P', long, env = BB_PASSWORD, hide_env_values = true)]
157 pub password: Option<SecretValue>,
158}