Skip to main content

alien_core/
stack_settings.rs

1//!
2//! Defines stack-level settings and management configurations for different cloud platforms.
3//! These settings customize deployment behavior and cross-account/cross-tenant access patterns.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// AWS management configuration extracted from stack settings
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
11#[serde(rename_all = "camelCase")]
12pub struct AwsManagementConfig {
13    /// The managing AWS IAM role ARN that can assume cross-account roles
14    pub managing_role_arn: String,
15}
16
17/// GCP management configuration extracted from stack settings
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
19#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
20#[serde(rename_all = "camelCase")]
21pub struct GcpManagementConfig {
22    /// Service account email for management roles
23    pub service_account_email: String,
24}
25
26/// Azure management configuration extracted from stack settings
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
29#[serde(rename_all = "camelCase")]
30pub struct AzureManagementConfig {
31    /// The managing Azure Tenant ID for cross-tenant access
32    pub managing_tenant_id: String,
33    /// OIDC issuer URL for federated identity credential creation
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub oidc_issuer: Option<String>,
36    /// OIDC subject claim for federated identity credential creation
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub oidc_subject: Option<String>,
39    /// Management service principal object ID for local development fallback
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub management_principal_id: Option<String>,
42}
43
44/// Management configuration for different cloud platforms.
45///
46/// Platform-derived configuration for cross-account/cross-tenant access.
47/// This is NOT user-specified - it's derived from the Manager's ServiceAccount.
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
50#[serde(rename_all = "camelCase", tag = "platform")]
51pub enum ManagementConfig {
52    /// AWS management configuration
53    Aws(AwsManagementConfig),
54    /// GCP management configuration  
55    Gcp(GcpManagementConfig),
56    /// Azure management configuration
57    Azure(AzureManagementConfig),
58    /// Kubernetes management configuration (minimal for now)
59    Kubernetes,
60}
61
62/// Network configuration for the stack.
63///
64/// Controls how VPC/VNet networking is provisioned. Users configure this in
65/// `StackSettings`; the Network resource itself is auto-generated by preflights.
66///
67/// ## Egress policy
68///
69/// Container cluster VMs are configured for egress based on the mode:
70///
71/// - `UseDefault` → VMs get ephemeral public IPs (no NAT is provisioned)
72/// - `Create` → VMs use private IPs; Alien provisions a NAT gateway for outbound access
73/// - `ByoVpc*` / `ByoVnet*` → no public IPs assigned; customer manages egress
74///
75/// For production workloads, use `Create`. For fast dev/test iteration, `UseDefault` is
76/// sufficient. For environments with existing VPCs, use the appropriate `ByoVpc*` variant.
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
78#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
79#[serde(rename_all = "camelCase", tag = "type")]
80pub enum NetworkSettings {
81    /// Use the cloud provider's default VPC/network.
82    ///
83    /// Designed for fast dev/test provisioning. No isolated VPC is created, so there
84    /// is nothing to wait for or clean up. VMs receive ephemeral public IPs for internet
85    /// access — no NAT gateway is provisioned.
86    ///
87    /// - **AWS**: Discovers the account's default VPC. Subnets are public with auto-assigned IPs.
88    /// - **GCP**: Discovers the project's `default` network and regional subnet. Instance
89    ///   templates include an `AccessConfig` to assign an ephemeral external IP.
90    /// - **Azure**: Azure has no default VNet, so one is created along with a NAT Gateway.
91    ///   VMs stay private and use NAT for egress.
92    ///
93    /// Not recommended for production. Use `Create` instead.
94    #[serde(rename = "use-default")]
95    UseDefault,
96
97    /// Create a new isolated VPC/VNet with a managed NAT gateway.
98    ///
99    /// All networking infrastructure is provisioned by Alien and cleaned up on delete.
100    /// VMs use private IPs only; all outbound traffic routes through the NAT gateway.
101    ///
102    /// Recommended for production deployments.
103    #[serde(rename = "create")]
104    Create {
105        /// VPC/VNet CIDR block. If not specified, auto-generated from stack ID
106        /// to reduce conflicts (e.g., "10.{hash}.0.0/16").
107        #[serde(skip_serializing_if = "Option::is_none")]
108        cidr: Option<String>,
109
110        /// Number of availability zones (default: 2).
111        #[serde(default = "default_availability_zones")]
112        availability_zones: u8,
113    },
114
115    /// Use an existing VPC (AWS).
116    ///
117    /// Alien validates the references but creates no networking infrastructure.
118    /// The customer is responsible for routing and egress (NAT, proxy, VPN, etc.).
119    #[serde(rename = "byo-vpc-aws")]
120    ByoVpcAws {
121        /// The ID of the existing VPC
122        vpc_id: String,
123        /// IDs of public subnets (required for public ingress)
124        public_subnet_ids: Vec<String>,
125        /// IDs of private subnets
126        private_subnet_ids: Vec<String>,
127        /// Optional security group IDs to use
128        #[serde(default)]
129        security_group_ids: Vec<String>,
130    },
131
132    /// Use an existing VPC (GCP).
133    ///
134    /// Alien validates the references but creates no networking infrastructure.
135    /// The customer is responsible for routing and egress (Cloud NAT, proxy, VPN, etc.).
136    #[serde(rename = "byo-vpc-gcp")]
137    ByoVpcGcp {
138        /// The name of the existing VPC network
139        network_name: String,
140        /// The name of the subnet to use
141        subnet_name: String,
142        /// The region of the subnet
143        region: String,
144    },
145
146    /// Use an existing VNet (Azure).
147    ///
148    /// Alien validates the references but creates no networking infrastructure.
149    /// The customer is responsible for routing and egress (NAT Gateway, proxy, VPN, etc.).
150    #[serde(rename = "byo-vnet-azure")]
151    ByoVnetAzure {
152        /// The full resource ID of the existing VNet
153        vnet_resource_id: String,
154        /// Name of the public subnet within the VNet
155        public_subnet_name: String,
156        /// Name of the private subnet within the VNet
157        private_subnet_name: String,
158    },
159}
160
161fn default_availability_zones() -> u8 {
162    2
163}
164
165/// Deployment model: how updates are delivered to the remote environment.
166#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
167#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
168#[serde(rename_all = "camelCase")]
169pub enum DeploymentModel {
170    /// Manager pushes updates via cross-account access.
171    /// Available for AWS, GCP, Azure only.
172    #[default]
173    Push,
174    /// Agent in remote environment pulls updates.
175    /// Available for all platforms (AWS, GCP, Azure, Kubernetes, Local).
176    Pull,
177}
178
179/// How updates are delivered to the deployment.
180#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
181#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
182#[serde(rename_all = "kebab-case")]
183pub enum UpdatesMode {
184    /// Updates deploy automatically (default).
185    #[default]
186    Auto,
187    /// Updates require explicit approval before deployment.
188    ApprovalRequired,
189}
190
191/// How telemetry (logs, metrics, traces) is handled.
192#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
193#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
194#[serde(rename_all = "kebab-case")]
195pub enum TelemetryMode {
196    /// No telemetry permissions. Data will not be collected.
197    Off,
198    /// Telemetry flows automatically (default).
199    #[default]
200    Auto,
201    /// Telemetry requires explicit approval before collection begins.
202    ApprovalRequired,
203}
204
205impl TelemetryMode {
206    /// Returns true if telemetry is enabled (Auto or ApprovalRequired).
207    pub fn is_enabled(&self) -> bool {
208        !matches!(self, TelemetryMode::Off)
209    }
210}
211
212/// How heartbeat health checks are handled.
213#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
214#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
215#[serde(rename_all = "kebab-case")]
216pub enum HeartbeatsMode {
217    /// No heartbeat permissions. Health checks disabled.
218    Off,
219    /// Heartbeat enabled (default).
220    #[default]
221    On,
222}
223
224impl HeartbeatsMode {
225    /// Returns true if heartbeat is enabled.
226    pub fn is_enabled(&self) -> bool {
227        matches!(self, HeartbeatsMode::On)
228    }
229}
230
231/// Domain configuration for the stack.
232///
233/// When `custom_domains` is set, the specified resources use customer-provided
234/// domains and certificates. Otherwise, Alien auto-generates domains.
235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
236#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
237#[serde(rename_all = "camelCase")]
238pub struct DomainSettings {
239    /// Custom domain configuration per resource ID.
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub custom_domains: Option<HashMap<String, CustomDomainConfig>>,
242}
243
244/// Custom domain configuration for a single resource.
245#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
246#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
247#[serde(rename_all = "camelCase")]
248pub struct CustomDomainConfig {
249    /// Fully qualified domain name to use.
250    pub domain: String,
251    /// Customer-provided certificate reference.
252    pub certificate: CustomCertificateConfig,
253}
254
255/// Platform-specific certificate references for custom domains.
256#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
257#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
258#[serde(rename_all = "camelCase")]
259pub struct CustomCertificateConfig {
260    /// AWS ACM certificate ARN
261    #[serde(default, skip_serializing_if = "Option::is_none")]
262    pub aws: Option<AwsCustomCertificateConfig>,
263    /// GCP Certificate Manager certificate name
264    #[serde(default, skip_serializing_if = "Option::is_none")]
265    pub gcp: Option<GcpCustomCertificateConfig>,
266    /// Azure Key Vault certificate ID
267    #[serde(default, skip_serializing_if = "Option::is_none")]
268    pub azure: Option<AzureCustomCertificateConfig>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
272#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
273#[serde(rename_all = "camelCase")]
274pub struct AwsCustomCertificateConfig {
275    pub certificate_arn: String,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
279#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
280#[serde(rename_all = "camelCase")]
281pub struct GcpCustomCertificateConfig {
282    pub certificate_name: String,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
286#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
287#[serde(rename_all = "camelCase")]
288pub struct AzureCustomCertificateConfig {
289    pub key_vault_certificate_id: String,
290}
291
292/// User-customizable deployment settings specified at deploy time.
293///
294/// These settings are provided by the customer via CloudFormation parameters,
295/// Terraform attributes, CLI flags, or Helm values. They customize how the
296/// deployment runs and what capabilities are enabled.
297///
298/// **Key distinction**: StackSettings is user-customizable, while ManagementConfig
299/// is platform-derived (from the Manager's ServiceAccount).
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
301#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
302#[serde(rename_all = "camelCase")]
303pub struct StackSettings {
304    /// Network configuration for the stack (VPC/VNet settings).
305    /// If `None`, an isolated VPC with NAT is auto-created when the stack has resources
306    /// that require networking (e.g., containers). Set explicitly to customize:
307    /// `UseDefault` for the provider's default network (fast, dev/test only),
308    /// `Create` for an isolated VPC with managed NAT (production), or `ByoVpc*`
309    /// to reference an existing customer-managed VPC.
310    #[serde(default, skip_serializing_if = "Option::is_none")]
311    pub network: Option<NetworkSettings>,
312
313    /// Domain configuration (future).
314    #[serde(default, skip_serializing_if = "Option::is_none")]
315    pub domains: Option<DomainSettings>,
316
317    /// Deployment model: push (Manager) or pull (Agent).
318    /// Default: Push.
319    /// - Push: Manager drives updates. For cloud platforms, requires cross-account
320    ///   credentials established during initial setup. For push-mode local
321    ///   deployments (currently `alien dev`), the manager has direct access —
322    ///   no bootstrap needed.
323    /// - Pull: Agent in the target environment drives updates via polling.
324    ///   Required for Kubernetes and remote local deployments.
325    #[serde(default, skip_serializing_if = "is_default_deployment_model")]
326    pub deployment_model: DeploymentModel,
327
328    /// How updates are delivered.
329    /// - auto: Updates deploy automatically (default)
330    /// - approval-required: Updates wait for explicit approval
331    #[serde(default, skip_serializing_if = "is_default_updates_mode")]
332    pub updates: UpdatesMode,
333
334    /// How telemetry (logs, metrics, traces) is handled.
335    /// - off: No telemetry permissions
336    /// - auto: Telemetry flows automatically (default)
337    /// - approval-required: Telemetry waits for explicit approval
338    #[serde(default, skip_serializing_if = "is_default_telemetry_mode")]
339    pub telemetry: TelemetryMode,
340
341    /// How heartbeat health checks are handled.
342    /// - off: No heartbeat permissions
343    /// - on: Heartbeat enabled (default)
344    #[serde(default, skip_serializing_if = "is_default_heartbeats_mode")]
345    pub heartbeats: HeartbeatsMode,
346
347    /// External bindings for pre-existing infrastructure.
348    /// Allows using existing resources (MinIO, Redis, shared Container Apps
349    /// Environment, etc.) instead of having Alien provision them.
350    /// Required for Kubernetes platform, optional for cloud platforms.
351    #[serde(default, skip_serializing_if = "Option::is_none")]
352    #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
353    pub external_bindings: Option<crate::ExternalBindings>,
354}
355
356fn is_default_deployment_model(model: &DeploymentModel) -> bool {
357    *model == DeploymentModel::default()
358}
359
360fn is_default_updates_mode(mode: &UpdatesMode) -> bool {
361    *mode == UpdatesMode::default()
362}
363
364fn is_default_telemetry_mode(mode: &TelemetryMode) -> bool {
365    *mode == TelemetryMode::default()
366}
367
368fn is_default_heartbeats_mode(mode: &HeartbeatsMode) -> bool {
369    *mode == HeartbeatsMode::default()
370}