Skip to main content

alien_core/
setup_package.rs

1use crate::{
2    ownership_policy_for_resource_type, ErrorData, Key, ManagementPermissions, Platform,
3    ResourceEntry, Result, Sandbox, SetupManagement, Stack, Storage,
4};
5use alien_error::AlienError;
6
7/// Refuse resources whose setup-owned scaffolding cannot yet be created by runtime.
8/// Check both package generation and deployment planning, including later additions.
9pub 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            // Setup must be able to grant whatever a later release adds.
26            ManagementPermissions::Override(_) => {
27                return Err(unsupported(
28                    "management permission override",
29                    "permissions.management",
30                ));
31            }
32            // The AWS management role may not change its own policies after setup, so a
33            // grant scoped to one resource could never be attached by the runtime.
34            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
62/// Automatic packages emit only access and secret storage, so every other resource is
63/// created by its runtime controller. Return the resources that controller cannot create
64/// as declared.
65fn automatic_setup_unsupported_resource(
66    entry: &ResourceEntry,
67    platform: Platform,
68) -> Option<String> {
69    if entry.config.downcast_ref::<Key>().is_some() {
70        return Some("key".to_string());
71    }
72    let resource_type = entry.config.resource_type();
73    // These controllers refuse runtime creation: only a setup package can provision them.
74    if matches!(
75        resource_type.as_ref(),
76        "email" | "experimental/aws-opensearch" | "kubernetes-cluster"
77    ) {
78        return Some(resource_type.as_ref().to_string());
79    }
80    // Only the CloudFormation template applies S3 CORS rules. The runtime storage
81    // controller would create the bucket without them, so browser reads would fail.
82    if platform == Platform::Aws
83        && entry
84            .config
85            .downcast_ref::<Storage>()
86            .is_some_and(|storage| !storage.cors_allowed_origins.is_empty())
87    {
88        return Some("storage with CORS origins".to_string());
89    }
90    None
91}
92
93/// Controls configuration exposure, independently of installation authority.
94/// Existing generator entry points retain the complete Advanced interface.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum SetupConfigurationProfile {
97    Standard,
98    Advanced,
99}
100
101/// Automatic packages install access and secret storage, then let the runtime
102/// create application infrastructure. Cloud API activation and Azure's containing
103/// resource group must exist before their management identity can be installed.
104pub fn emits_package_scaffolding(entry: &ResourceEntry, management: SetupManagement) -> bool {
105    let resource_type = entry.config.resource_type();
106    if management == SetupManagement::Automatic {
107        is_automatic_setup_package_resource_type(resource_type.as_ref())
108    } else {
109        ownership_policy_for_resource_type(resource_type.as_ref())
110            .emits_setup_scaffolding(entry.lifecycle)
111    }
112}
113
114/// Resources whose state is replaced by an automatic setup package import.
115/// Application resources are managed through the installed identity and must
116/// retain their controller state when that package is reimported.
117pub fn is_automatic_setup_package_resource_type(resource_type: &str) -> bool {
118    matches!(
119        resource_type,
120        "remote-stack-management" | "vault" | "azure_resource_group" | "service_activation"
121    )
122}