Skip to main content

greentic_operator/
secrets_client.rs

1use crate::dev_store_path;
2use anyhow::{Result as AnyhowResult, anyhow};
3use async_trait::async_trait;
4use greentic_secrets_lib::{
5    Result as SecretResult, SecretError, SecretsManager, SecretsStore,
6    core::{Error as CoreError, seed::DevStore},
7};
8use std::{
9    path::{Path, PathBuf},
10    sync::Arc,
11};
12
13pub struct SecretsClient {
14    store: Arc<DevStore>,
15    store_path: Option<PathBuf>,
16}
17
18impl SecretsClient {
19    pub fn open(bundle_root: &Path) -> AnyhowResult<Self> {
20        let override_path = dev_store_path::override_path();
21        if let Some(path) =
22            dev_store_path::find_existing_with_override(bundle_root, override_path.as_deref())
23        {
24            return Self::open_with_path(path);
25        }
26        let store_path = dev_store_path::ensure_path(bundle_root)?;
27        let store = DevStore::with_path(store_path.clone())
28            .map_err(|err| anyhow!("failed to open dev secrets store: {err}"))?;
29        Ok(Self {
30            store: Arc::new(store),
31            store_path: Some(store_path),
32        })
33    }
34
35    pub fn open_with_path(path: PathBuf) -> AnyhowResult<Self> {
36        let store = DevStore::with_path(path.clone())
37            .map_err(|err| anyhow!("failed to open dev secrets store: {err}"))?;
38        Ok(Self {
39            store: Arc::new(store),
40            store_path: Some(path),
41        })
42    }
43
44    pub fn store_path(&self) -> Option<&Path> {
45        self.store_path.as_deref()
46    }
47}
48
49#[async_trait]
50impl SecretsManager for SecretsClient {
51    async fn read(&self, path: &str) -> SecretResult<Vec<u8>> {
52        let result = self.store.get(path).await;
53        match result {
54            Ok(value) => Ok(value),
55            Err(CoreError::NotFound { entity }) => Err(SecretError::NotFound(entity)),
56            Err(err) => Err(SecretError::Backend(err.to_string().into())),
57        }
58    }
59
60    async fn write(&self, _: &str, _: &[u8]) -> SecretResult<()> {
61        Err(SecretError::Permission(
62            "dev secrets store is read-only".into(),
63        ))
64    }
65
66    async fn delete(&self, _: &str) -> SecretResult<()> {
67        Err(SecretError::Permission(
68            "dev secrets store is read-only".into(),
69        ))
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use greentic_secrets_lib::{
77        SecretFormat, SeedDoc, SeedEntry, SeedValue,
78        core::seed::{ApplyOptions, DevStore, apply_seed},
79    };
80    use tempfile::tempdir;
81    use tokio::runtime::Runtime;
82
83    #[test]
84    fn reads_seeded_secret_from_dev_store() -> anyhow::Result<()> {
85        let dir = tempdir()?;
86        let store_path = dir.path().join("secrets.env");
87        let store = DevStore::with_path(store_path.clone())?;
88        let seed = SeedDoc {
89            entries: vec![SeedEntry {
90                uri: "secrets://demo/acme/_/mypack/my_secret".to_string(),
91                format: SecretFormat::Text,
92                value: SeedValue::Text {
93                    text: "hello world".to_string(),
94                },
95                description: None,
96            }],
97        };
98        let runtime = Runtime::new()?;
99        let report =
100            runtime.block_on(async { apply_seed(&store, &seed, ApplyOptions::default()).await });
101        assert_eq!(report.ok, 1);
102        let client = SecretsClient::open_with_path(store_path.clone())?;
103        let value = runtime
104            .block_on(async { client.read("secrets://demo/acme/_/mypack/my_secret").await })?;
105        assert_eq!(value, b"hello world".to_vec());
106        Ok(())
107    }
108}