use secrecy::SecretString;
use super::client::SECRETS_FORBIDDEN_MESSAGE;
use super::flow::forget_client_key;
use super::{ApiError, NewSecret, PodCtx, PodError, PodRecord, PodState, RunpodClient};
use crate::runs::{Runs, is_valid_run_id};
pub const HOST_KEY_SECRET_PREFIX: &str = "overbrainer_host_key_";
#[must_use]
pub fn host_key_secret(run_id: &str) -> String {
format!("{HOST_KEY_SECRET_PREFIX}{run_id}")
}
#[must_use]
pub fn host_key_placeholder(run_id: &str) -> String {
format!("{{{{ RUNPOD_SECRET_{} }}}}", host_key_secret(run_id))
}
#[must_use]
pub fn host_key_run(name: &str) -> Option<&str> {
name.strip_prefix(HOST_KEY_SECRET_PREFIX)
.filter(|run_id| is_valid_run_id(run_id))
}
pub async fn store_host_key(
client: &RunpodClient,
run_id: &str,
value: &SecretString,
) -> Result<(), PodError> {
let name = host_key_secret(run_id);
let secret = NewSecret {
name: name.clone(),
value: value.clone(),
description: format!("SSH host key of the pod of overbrainer run {run_id}"),
};
client.redact_also(value.clone());
let stored = match client.create_secret(&secret).await {
Ok(_) => Ok(()),
Err(error) if error.status() == Some(409) => replace(client, &name, value).await,
Err(error) => Err(error),
};
stored.map_err(|error| PodError::HostKeySecret(secret_error(&error)))
}
async fn replace(client: &RunpodClient, name: &str, value: &SecretString) -> Result<(), ApiError> {
let found = client.list_secrets(Some(name)).await?;
let Some(secret) = found.iter().find(|secret| secret.name == name) else {
return Err(ApiError::InvalidResponse(format!(
"Runpod says the secret {name} exists but does not list it"
)));
};
client.update_secret_value(&secret.id, value).await?;
Ok(())
}
fn secret_error(error: &ApiError) -> String {
match error.status() {
Some(403) => SECRETS_FORBIDDEN_MESSAGE.to_string(),
_ => error.to_string(),
}
}
pub async fn drop_host_key(client: &RunpodClient, run_id: &str) -> Result<(), ApiError> {
let name = host_key_secret(run_id);
for secret in client.list_secrets(Some(&name)).await? {
if secret.name == name {
client.delete_secret(&secret.id).await?;
}
}
Ok(())
}
pub async fn drop_host_key_or_warn(client: &RunpodClient, run_id: &str) {
if let Err(error) = drop_host_key(client, run_id).await {
tracing::warn!(
"cannot delete the Runpod secret {} ({error}); `overbrainer pod rm {run_id}` tries again",
host_key_secret(run_id)
);
}
}
pub async fn forget_keys(ctx: &PodCtx<'_>, run_id: &str) {
if !no_pod_left(ctx.runs, run_id) {
tracing::debug!("keeping the keys of run {run_id}: a pod of it may still exist");
return;
}
forget_client_key(ctx.runs, run_id);
drop_host_key_or_warn(ctx.client, run_id).await;
}
fn no_pod_left(runs: &Runs, run_id: &str) -> bool {
match PodRecord::load(runs, run_id) {
Ok(Some(record)) => {
record.stray_pods.is_empty()
&& (record.pod_id.is_none() || record.state == PodState::Deleted)
},
Ok(None) => true,
Err(error) => {
tracing::warn!("cannot read the pod record of run {run_id}: {error}");
false
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_secret_name_and_its_reference_follow_the_run() {
assert_eq!(
host_key_secret("20260922-143005-a1b2"),
"overbrainer_host_key_20260922-143005-a1b2"
);
assert_eq!(
host_key_placeholder("r1"),
"{{ RUNPOD_SECRET_overbrainer_host_key_r1 }}"
);
assert_eq!(host_key_run("overbrainer_host_key_r1"), Some("r1"));
for other in [
"hf-token",
"overbrainer_host_key_",
"overbrainer_host_key_../x",
"overbrainer_host_key_a/b",
] {
assert_eq!(host_key_run(other), None, "{other}");
}
}
#[test]
fn every_name_meets_the_runpod_rules() {
let longest = "a".repeat(crate::runs::RUN_ID_MAX);
let name = host_key_secret(&longest);
assert!(name.len() <= 191);
assert!(name.starts_with(|c: char| c.is_ascii_alphabetic()));
assert!(!name.to_ascii_uppercase().starts_with("RUNPOD"));
}
}