systemprompt_cli/commands/cloud/deploy/pipeline/
request.rs1use std::path::PathBuf;
12
13use systemprompt_cloud::CloudCredentials;
14use systemprompt_identifiers::{ProfileName, TenantId};
15use systemprompt_manifest::env::contains_placeholder;
16use systemprompt_manifest::profile::{SecretsConfig, SecretsSource, VaultAuth, VaultSecretsConfig};
17
18pub const VAULT_ADDR_ENV: &str = "VAULT_ADDR";
19pub const VAULT_NAMESPACE_ENV: &str = "VAULT_NAMESPACE";
20
21#[derive(Debug)]
22pub struct DeployRequest {
23 pub tenant_id: TenantId,
24 pub tenant_name: String,
25 pub profile_name: ProfileName,
26 pub project_root: PathBuf,
27 pub credentials: CloudCredentials,
28 pub secrets: DeploySecretsSource,
29 pub signing_key_path: PathBuf,
30 pub options: DeployOptions,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum DeploySecretsSource {
35 EnvFromFile { path: PathBuf },
36 Vault { bootstrap_env: Vec<String> },
37}
38
39impl DeploySecretsSource {
40 #[must_use]
41 pub fn from_profile(secrets: Option<&SecretsConfig>, secrets_path: PathBuf) -> Self {
42 match secrets {
43 Some(config) if matches!(config.source, SecretsSource::Vault) => {
44 let bootstrap_env = config
45 .vault
46 .as_ref()
47 .map(bootstrap_env_names)
48 .unwrap_or_default();
49 Self::Vault { bootstrap_env }
50 },
51 Some(_) | None => Self::EnvFromFile { path: secrets_path },
52 }
53 }
54}
55
56#[must_use]
57pub fn bootstrap_env_names(vault: &VaultSecretsConfig) -> Vec<String> {
58 let mut names = match &vault.auth {
59 VaultAuth::Token {
60 token_env,
61 token_file,
62 } => {
63 if token_file.is_some() {
64 Vec::new()
65 } else {
66 vec![token_env.clone()]
67 }
68 },
69 VaultAuth::AppRole {
70 role_id_env,
71 secret_id_env,
72 ..
73 } => vec![role_id_env.clone(), secret_id_env.clone()],
74 VaultAuth::Kubernetes { .. } => Vec::new(),
75 };
76
77 if contains_placeholder(&vault.address) {
78 names.push(placeholder_var(&vault.address, VAULT_ADDR_ENV));
79 }
80 if let Some(namespace) = vault.namespace.as_deref()
81 && contains_placeholder(namespace)
82 {
83 names.push(placeholder_var(namespace, VAULT_NAMESPACE_ENV));
84 }
85
86 names.sort();
87 names.dedup();
88 names
89}
90
91fn placeholder_var(value: &str, fallback: &str) -> String {
92 value
93 .split_once("${")
94 .and_then(|(_, rest)| rest.split_once('}'))
95 .map(|(name, _)| name.split(":-").next().unwrap_or(name).to_owned())
96 .filter(|name| !name.is_empty())
97 .unwrap_or_else(|| fallback.to_owned())
98}
99
100#[derive(Debug, Clone, Copy)]
101pub struct DeployOptions {
102 pub skip_push: bool,
103}
104
105#[derive(Debug)]
106pub struct DeployReport {
107 pub image: String,
108 pub status: String,
109 pub app_url: Option<String>,
110}