alien_core/
setup_package.rs1use crate::{
2 ownership_policy_for_resource_type, ErrorData, Key, ManagementPermissions, Platform,
3 ResourceEntry, ResourceLifecycle, Result, Sandbox, SetupManagement, Stack, Storage,
4};
5use alien_error::AlienError;
6
7pub fn validate_automatic_setup_resources(
10 stack: &Stack,
11 platform: Platform,
12 management: SetupManagement,
13) -> Result<()> {
14 if management == SetupManagement::Automatic
15 && matches!(platform, Platform::Aws | Platform::Gcp | Platform::Azure)
16 {
17 let unsupported = |resource_type: &str, resource_id: &str| {
18 AlienError::new(ErrorData::AutomaticSetupResourceUnsupported {
19 resource_type: resource_type.to_string(),
20 resource_id: resource_id.to_string(),
21 platform,
22 })
23 };
24 match stack.management() {
25 ManagementPermissions::Override(_) => {
27 return Err(unsupported(
28 "management permission override",
29 "permissions.management",
30 ));
31 }
32 ManagementPermissions::Extend(profile) if platform == Platform::Aws => {
35 if let Some(scope) = profile.0.keys().find(|scope| scope.as_str() != "*") {
36 return Err(unsupported("resource-scoped management permission", scope));
37 }
38 }
39 _ => {}
40 }
41 for (id, entry) in stack.resources() {
42 if let Some(resource_type) = automatic_setup_unsupported_resource(entry, platform) {
43 return Err(unsupported(&resource_type, id));
44 }
45 }
46 }
47 if management == SetupManagement::Automatic && platform == Platform::Azure {
48 for (id, entry) in stack.resources() {
49 if entry.config.downcast_ref::<Sandbox>().is_some() {
50 return Err(AlienError::new(ErrorData::SandboxCapabilityUnsupported {
51 capability: format!(
52 "automatic infrastructure management for sandbox '{id}'; use manual setup to create and import its sandbox group"
53 ),
54 platform: platform.to_string(),
55 }));
56 }
57 }
58 }
59 Ok(())
60}
61
62fn automatic_setup_unsupported_resource(
65 entry: &ResourceEntry,
66 platform: Platform,
67) -> Option<String> {
68 if entry.config.downcast_ref::<Key>().is_some() {
69 return Some("key".to_string());
70 }
71 let resource_type = entry.config.resource_type();
72 if matches!(
74 resource_type.as_ref(),
75 "email" | "experimental/aws-opensearch" | "kubernetes-cluster"
76 ) {
77 return Some(resource_type.as_ref().to_string());
78 }
79 if platform == Platform::Azure
82 && entry
83 .config
84 .downcast_ref::<Storage>()
85 .is_some_and(|storage| storage.has_cors())
86 {
87 return Some("storage with CORS origins".to_string());
88 }
89 None
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum SetupConfigurationProfile {
96 Standard,
97 Advanced,
98}
99
100pub fn emits_package_scaffolding(
106 entry: &ResourceEntry,
107 management: SetupManagement,
108 platform: Platform,
109) -> bool {
110 let resource_type = entry.config.resource_type();
111 if management == SetupManagement::Automatic {
112 is_automatic_setup_package_resource_type(resource_type.as_ref(), platform)
113 } else {
114 ownership_policy_for_resource_type(resource_type.as_ref())
115 .emits_setup_scaffolding(entry.lifecycle)
116 }
117}
118
119pub fn is_automatic_setup_package_resource_type(resource_type: &str, platform: Platform) -> bool {
123 matches!(
124 resource_type,
125 "remote-stack-management" | "vault" | "azure_resource_group" | "service_activation"
126 ) || (platform == Platform::Azure
127 && matches!(resource_type, "service-account" | "resource-access"))
128}
129
130pub fn removed_by_runtime_cleanup(
139 resource_type: &str,
140 lifecycle: ResourceLifecycle,
141 management: SetupManagement,
142 platform: Platform,
143) -> bool {
144 if management == SetupManagement::Automatic {
145 return !is_automatic_setup_package_resource_type(resource_type, platform);
146 }
147 lifecycle == ResourceLifecycle::Live
148 || ownership_policy_for_resource_type(resource_type).has_runtime_cleanup_before_teardown()
149}
150
151#[cfg(test)]
152mod cors_tests {
153 use super::*;
154
155 #[test]
156 fn automatic_cors_requires_an_implementing_controller() {
157 let stack = Stack::new("browser-storage".into())
158 .add(
159 Storage::new("assets".into())
160 .cors_allowed_origins(vec!["https://app.example.com".into()])
161 .build(),
162 ResourceLifecycle::Live,
163 )
164 .build();
165 for platform in [Platform::Aws, Platform::Gcp] {
166 validate_automatic_setup_resources(&stack, platform, SetupManagement::Automatic)
167 .unwrap();
168 }
169 let error =
170 validate_automatic_setup_resources(&stack, Platform::Azure, SetupManagement::Automatic)
171 .unwrap_err();
172 assert!(matches!(
173 error.error,
174 Some(ErrorData::AutomaticSetupResourceUnsupported { .. })
175 ));
176 }
177}