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