cuenv_core/secrets/
mod.rs

1//! Secret and resolver types
2//!
3//! Based on schema/secrets.cue
4//!
5//! This module provides:
6//! - `Secret`: CUE-compatible secret definition with resolver-based resolution
7//! - Re-exports from `cuenv_secrets`: Trait-based secret resolution system
8
9use crate::{Error, Result};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13
14// Re-export core secret resolution types from cuenv-secrets
15pub use cuenv_secrets::{
16    BatchResolver, ResolvedSecrets, SaltConfig, SecretError, SecretResolver, SecretSpec,
17    compute_secret_fingerprint,
18};
19
20// Re-export resolver implementations
21pub use cuenv_secrets::resolvers::{EnvSecretResolver, ExecSecretResolver};
22
23// Re-export 1Password resolver from its dedicated crate
24pub use cuenv_1password::secrets::{OnePasswordConfig, OnePasswordResolver};
25
26/// Resolver for executing commands to retrieve secret values
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28pub struct ExecResolver {
29    /// Command to execute
30    pub command: String,
31
32    /// Arguments to pass to the command
33    pub args: Vec<String>,
34}
35
36/// Secret definition with resolver
37///
38/// This is the CUE-compatible secret type used for Dagger secrets and environment
39/// variable resolution. Supports multiple resolver types:
40/// - `exec`: Execute a command to get the secret
41/// - `onepassword`: Resolve from 1Password using `ref` field
42/// - `aws`, `gcp`, `vault`: Cloud provider secrets
43///
44/// Resolution is delegated to the trait-based [`SecretResolver`] system.
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46pub struct Secret {
47    /// Resolver type: "exec", "onepassword", "aws", "gcp", "vault"
48    pub resolver: String,
49
50    /// Command to execute (for exec resolver)
51    #[serde(default, skip_serializing_if = "String::is_empty")]
52    pub command: String,
53
54    /// Arguments to pass to the command (for exec resolver)
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub args: Vec<String>,
57
58    /// 1Password reference (for onepassword resolver), e.g., "op://vault/item/field"
59    #[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
60    pub op_ref: Option<String>,
61
62    /// Additional fields for extensibility
63    #[serde(flatten)]
64    pub extra: HashMap<String, Value>,
65}
66
67impl Secret {
68    /// Create a new exec secret
69    #[must_use]
70    pub fn new(command: String, args: Vec<String>) -> Self {
71        Secret {
72            resolver: "exec".to_string(),
73            command,
74            args,
75            op_ref: None,
76            extra: HashMap::new(),
77        }
78    }
79
80    /// Create a 1Password secret
81    #[must_use]
82    pub fn onepassword(reference: impl Into<String>) -> Self {
83        Secret {
84            resolver: "onepassword".to_string(),
85            command: String::new(),
86            args: Vec::new(),
87            op_ref: Some(reference.into()),
88            extra: HashMap::new(),
89        }
90    }
91
92    /// Create a secret with additional fields
93    #[must_use]
94    pub fn with_extra(command: String, args: Vec<String>, extra: HashMap<String, Value>) -> Self {
95        Secret {
96            resolver: "exec".to_string(),
97            command,
98            args,
99            op_ref: None,
100            extra,
101        }
102    }
103
104    /// Get the resolver/provider name
105    #[must_use]
106    pub fn provider(&self) -> &str {
107        &self.resolver
108    }
109
110    /// Convert to a SecretSpec for use with the trait-based resolver system
111    #[must_use]
112    pub fn to_spec(&self) -> SecretSpec {
113        let source = match self.resolver.as_str() {
114            "onepassword" => self.op_ref.clone().unwrap_or_default(),
115            "exec" => serde_json::json!({
116                "command": self.command,
117                "args": self.args
118            })
119            .to_string(),
120            // For other resolvers, serialize the whole secret
121            _ => serde_json::to_string(self).unwrap_or_default(),
122        };
123        SecretSpec::new(source)
124    }
125
126    /// Resolve the secret value using the trait-based resolver system
127    ///
128    /// # Errors
129    /// Returns error if resolution fails
130    pub async fn resolve(&self) -> Result<String> {
131        let spec = self.to_spec();
132
133        match self.resolver.as_str() {
134            "onepassword" => {
135                let resolver = OnePasswordResolver::new().map_err(|e| {
136                    Error::configuration(format!("Failed to initialize 1Password resolver: {e}"))
137                })?;
138                resolver
139                    .resolve("secret", &spec)
140                    .await
141                    .map_err(|e| Error::configuration(format!("{e}")))
142            }
143            "exec" => {
144                let resolver = ExecSecretResolver::new();
145                resolver
146                    .resolve("secret", &spec)
147                    .await
148                    .map_err(|e| Error::configuration(format!("{e}")))
149            }
150            other => Err(Error::configuration(format!(
151                "Unsupported secret resolver: {other}"
152            ))),
153        }
154    }
155}