1use serde::{Deserialize, Serialize};
2use spin_expressions::async_trait::async_trait;
3use spin_factors::anyhow::{self, Context as _};
4use tracing::{instrument, Level};
5use vaultrs::{
6 client::{VaultClient, VaultClientSettingsBuilder},
7 error::ClientError,
8 kv2,
9};
10
11use spin_expressions::{Key, Provider};
12
13#[derive(Debug, Default, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct VaultVariablesProvider {
17 url: String,
19 token: String,
21 mount: String,
23 #[serde(default)]
25 prefix: Option<String>,
26}
27
28#[async_trait]
29impl Provider for VaultVariablesProvider {
30 #[instrument(name = "spin_variables.get_from_vault", level = Level::DEBUG, skip(self), err(level = Level::INFO), fields(otel.kind = "client"))]
31 async fn get(&self, key: &Key) -> anyhow::Result<Option<String>> {
32 let client = VaultClient::new(
33 VaultClientSettingsBuilder::default()
34 .address(&self.url)
35 .token(&self.token)
36 .build()?,
37 )?;
38 let path = match &self.prefix {
39 Some(prefix) => format!("{}/{}", prefix, key.as_str()),
40 None => key.as_str().to_string(),
41 };
42
43 #[derive(Deserialize, Serialize)]
44 struct Secret {
45 value: String,
46 }
47 match kv2::read::<Secret>(&client, &self.mount, &path).await {
48 Ok(secret) => Ok(Some(secret.value)),
49 Err(ClientError::APIError { code: 404, .. }) => Ok(None),
51 Err(e) => Err(e).context("Failed to check Vault for config"),
53 }
54 }
55}