greentic-runner-host 0.5.21

Host runtime shim for Greentic runner: config, pack loading, activity handling
Documentation
use std::collections::HashMap;
use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use greentic_runner_host::secrets::{
    DynSecretsManager, read_secret_blocking, scoped_secret_path_for_pack,
};
use greentic_secrets_lib::{SecretError, SecretsManager};
use greentic_types::{EnvId, TenantCtx, TenantId, UserId};
use parking_lot::RwLock;

const TEST_PACK_ID: &str = "scoping";

#[derive(Default)]
struct MemorySecretsManager {
    values: RwLock<HashMap<String, Vec<u8>>>,
}

#[async_trait]
impl SecretsManager for MemorySecretsManager {
    async fn read(&self, path: &str) -> Result<Vec<u8>, SecretError> {
        self.values
            .read()
            .get(path)
            .cloned()
            .ok_or_else(|| SecretError::NotFound(path.to_string()))
    }

    async fn write(&self, path: &str, bytes: &[u8]) -> Result<(), SecretError> {
        self.values.write().insert(path.to_string(), bytes.to_vec());
        Ok(())
    }

    async fn delete(&self, path: &str) -> Result<(), SecretError> {
        self.values.write().remove(path);
        Ok(())
    }
}

fn tenant_ctx(env: &str, tenant: &str, user: Option<&str>) -> TenantCtx {
    let env_id = EnvId::new(env).expect("env");
    let tenant_id = TenantId::new(tenant).expect("tenant");
    let mut ctx = TenantCtx::new(env_id, tenant_id);
    if let Some(user) = user {
        let user_id = UserId::new(user).expect("user");
        ctx = ctx.with_user(Some(user_id));
    }
    ctx
}

#[test]
fn tenant_scoped_secret_reads_do_not_cross() -> Result<()> {
    let manager_impl: Arc<MemorySecretsManager> = Arc::new(MemorySecretsManager::default());
    let manager: DynSecretsManager = manager_impl.clone();
    let key = "API_KEY";
    let ctx_a = tenant_ctx("local", "tenant-a", None);
    let ctx_b = tenant_ctx("local", "tenant-b", None);

    let scoped_a = scoped_secret_path_for_pack(&ctx_a, TEST_PACK_ID, key)?;
    manager_impl
        .values
        .write()
        .insert(scoped_a.clone(), b"alpha".to_vec());

    let value = read_secret_blocking(&manager, &ctx_a, TEST_PACK_ID, key)?;
    assert_eq!(value, b"alpha".to_vec());
    assert!(read_secret_blocking(&manager, &ctx_b, TEST_PACK_ID, key).is_err());
    Ok(())
}

#[test]
fn user_context_does_not_change_provider_secret_scope() -> Result<()> {
    let manager_impl: Arc<MemorySecretsManager> = Arc::new(MemorySecretsManager::default());
    let manager: DynSecretsManager = manager_impl.clone();
    let key = "SESSION_TOKEN";
    let ctx = tenant_ctx("local", "tenant-a", Some("user-1"));

    let scoped = scoped_secret_path_for_pack(&ctx, TEST_PACK_ID, key)?;
    manager_impl
        .values
        .write()
        .insert(scoped.clone(), b"token".to_vec());

    let value = read_secret_blocking(&manager, &ctx, TEST_PACK_ID, key)?;
    assert_eq!(value, b"token".to_vec());
    Ok(())
}