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}