Skip to main content

systemprompt_cloud/deploy/provider/
mod.rs

1//! The provider seam behind `cloud deploy`.
2//!
3//! [`DeployProvider`] is the part of a deploy that differs by where the
4//! tenant runs: where the image goes, how secrets reach the instance, what
5//! "roll out" means, and how readiness is read back. The image build, the
6//! artifact checks and the secret collection stay in the caller's pipeline.
7//! [`SystempromptCloud`] drives the managed control plane; [`GitOpsDeploy`]
8//! pushes to an operator-owned registry and pins the image digest in an ops
9//! repository's values file for the cluster's controller to apply.
10//! [`DeployProviderKind::for_provider`] is the one place a
11//! [`HostingProvider`] selects its pipeline.
12//!
13//! Copyright (c) systemprompt.io — Business Source License 1.1.
14//! See <https://systemprompt.io> for licensing details.
15
16mod gitops;
17mod health;
18mod systemprompt;
19mod values_file;
20
21use std::collections::HashMap;
22use std::future::Future;
23
24use systemprompt_manifest::HostingProvider;
25
26use crate::error::{CloudError, CloudResult};
27
28pub use gitops::{GitOpsDeploy, GitOpsDeployParams};
29pub use health::{TenantHealth, probe_tenant, tenant_base_url, wait_until_ready};
30pub use systemprompt::SystempromptCloud;
31pub use values_file::{ValuesFileError, set_yaml_scalar};
32
33/// One tenant's deploy target. Every method acts on the tenant the provider
34/// was built for.
35pub trait DeployProvider: Send + Sync {
36    fn provider(&self) -> HostingProvider;
37
38    fn secrets_transport(&self) -> SecretsTransport;
39
40    fn registry_credentials(&self) -> impl Future<Output = CloudResult<RegistryTarget>> + Send;
41
42    fn push_secrets(
43        &self,
44        batch: SecretBatch,
45    ) -> impl Future<Output = CloudResult<SecretsOutcome>> + Send;
46
47    fn rollout(
48        &self,
49        image: &BuiltImage,
50    ) -> impl Future<Output = CloudResult<RolloutReport>> + Send;
51
52    fn status(&self) -> impl Future<Output = CloudResult<TenantHealth>> + Send;
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum DeployProviderKind {
57    SystempromptCloud,
58    GitOps,
59}
60
61impl DeployProviderKind {
62    pub const fn for_provider(provider: HostingProvider) -> CloudResult<Self> {
63        match provider {
64            HostingProvider::Systemprompt => Ok(Self::SystempromptCloud),
65            HostingProvider::Kubernetes | HostingProvider::Gke => Ok(Self::GitOps),
66            HostingProvider::DockerLocal => Err(CloudError::ProviderNotDeployable { provider }),
67        }
68    }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum SecretsTransport {
73    Push,
74    External,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct RegistryTarget {
79    pub image: String,
80    pub login: Option<RegistryLogin>,
81}
82
83#[derive(Clone, PartialEq, Eq)]
84pub struct RegistryLogin {
85    pub registry: String,
86    pub username: String,
87    pub token: String,
88}
89
90impl std::fmt::Debug for RegistryLogin {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_struct("RegistryLogin")
93            .field("registry", &self.registry)
94            .field("username", &self.username)
95            .finish_non_exhaustive()
96    }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum SecretPurpose {
101    Profile,
102    VaultBootstrap,
103    PlatformCredentials,
104    ProfilePath,
105}
106
107#[derive(Clone, PartialEq, Eq)]
108pub enum SecretBatch {
109    Values {
110        purpose: SecretPurpose,
111        entries: HashMap<String, String>,
112    },
113    Declared {
114        purpose: SecretPurpose,
115        keys: Vec<String>,
116    },
117}
118
119impl SecretBatch {
120    #[must_use]
121    pub const fn purpose(&self) -> SecretPurpose {
122        match self {
123            Self::Values { purpose, .. } | Self::Declared { purpose, .. } => *purpose,
124        }
125    }
126
127    #[must_use]
128    pub fn keys(&self) -> Vec<String> {
129        let mut keys: Vec<String> = match self {
130            Self::Values { entries, .. } => entries.keys().cloned().collect(),
131            Self::Declared { keys, .. } => keys.clone(),
132        };
133        keys.sort();
134        keys.dedup();
135        keys
136    }
137}
138
139impl std::fmt::Debug for SecretBatch {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        f.debug_struct("SecretBatch")
142            .field("purpose", &self.purpose())
143            .field("keys", &self.keys())
144            .finish()
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub enum SecretsOutcome {
150    Pushed { keys: Vec<String> },
151    External { required: Vec<String> },
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct BuiltImage {
156    pub reference: String,
157    pub pushed: bool,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct RolloutReport {
162    pub image: String,
163    pub status: String,
164    pub app_url: Option<String>,
165    pub commit: Option<String>,
166}