use crate::ownership_policy_for_resource_type;
pub const SECRETS_VAULT_ID: &str = "secrets";
const STACK_DERIVED_TYPES: &[&str] = &[
"build",
"artifact-registry",
"service-account",
"compute-cluster",
"kubernetes-cluster",
"network",
"remote-stack-management",
"service_activation",
"service-activation",
"azure_resource_group",
"azure-resource-group",
"azure_storage_account",
"azure-storage-account",
"azure_container_apps_environment",
"azure-container-apps-environment",
"azure_service_bus_namespace",
"azure-service-bus-namespace",
];
const NOT_YET_GENERIC_TYPES: &[&str] = &[];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GateRefusal {
ReservedSecretsVault,
DerivedFromStack,
NotYetGeneric,
}
impl GateRefusal {
pub fn reason(self) -> &'static str {
match self {
GateRefusal::ReservedSecretsVault => {
"it is the deployment secrets vault. Workers and compute clusters are wired to \
it automatically after compile-time checks run, so a deployer who says no would \
leave them resolving a binding for a vault that was never created. Its presence \
cannot be optional. Give a vault you want to gate a different id"
}
GateRefusal::DerivedFromStack => {
"Alien derives this resource from the stack itself, so it cannot be optional"
}
GateRefusal::NotYetGeneric => {
"this resource type's conditional setup render has not been validated yet, so \
the resource would be created regardless of the deployer's answer"
}
}
}
}
pub fn gate_refusal(resource_type: &str, resource_id: &str) -> Option<GateRefusal> {
if resource_id == SECRETS_VAULT_ID {
return Some(GateRefusal::ReservedSecretsVault);
}
if STACK_DERIVED_TYPES.contains(&resource_type) {
return Some(GateRefusal::DerivedFromStack);
}
if NOT_YET_GENERIC_TYPES.contains(&resource_type) {
return Some(GateRefusal::NotYetGeneric);
}
None
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TypeGateability {
pub frozen: bool,
pub live: bool,
}
pub fn type_gateability(resource_type: &str) -> TypeGateability {
let policy = ownership_policy_for_resource_type(resource_type);
let gateable = gate_refusal(resource_type, "").is_none();
TypeGateability {
frozen: gateable && policy.allows_frozen(),
live: gateable && policy.allows_live(),
}
}
pub const MANIFEST_TYPES: &[&str] = &[
"kv",
"storage",
"queue",
"vault",
"postgres",
"ai",
"worker",
"daemon",
"container",
"email",
"experimental/aws-opensearch",
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stores_are_gateable_in_both_lifecycles() {
for store in ["kv", "storage", "queue", "vault", "ai"] {
assert_eq!(gate_refusal(store, "analytics"), None, "{store}");
let gateability = type_gateability(store);
assert!(gateability.frozen && gateability.live, "{store}");
}
}
#[test]
fn postgres_is_live_gateable_only() {
assert_eq!(gate_refusal("postgres", "db"), None);
let gateability = type_gateability("postgres");
assert!(gateability.live);
}
#[test]
fn compute_is_live_gateable() {
for compute in ["worker", "daemon", "container"] {
assert_eq!(gate_refusal(compute, "api"), None, "{compute}");
let gateability = type_gateability(compute);
assert!(!gateability.frozen, "{compute} cannot be frozen");
assert!(gateability.live, "{compute} gates as a live resource");
}
}
#[test]
fn stack_derived_types_are_refused() {
for framework in STACK_DERIVED_TYPES {
assert_eq!(
gate_refusal(framework, "x"),
Some(GateRefusal::DerivedFromStack),
"{framework}"
);
}
}
#[test]
fn the_reserved_secrets_vault_is_refused_by_id() {
assert_eq!(
gate_refusal("vault", SECRETS_VAULT_ID),
Some(GateRefusal::ReservedSecretsVault)
);
assert_eq!(gate_refusal("vault", "app-tokens"), None);
}
#[test]
fn email_and_opensearch_gate_as_frozen_resources() {
for setup_owned in ["email", "experimental/aws-opensearch"] {
assert_eq!(gate_refusal(setup_owned, "x"), None, "{setup_owned}");
let gateability = type_gateability(setup_owned);
assert!(gateability.frozen, "{setup_owned} gates at setup");
assert!(!gateability.live, "{setup_owned} has no runtime controller");
}
}
#[test]
fn extension_types_default_to_gateable() {
assert_eq!(gate_refusal("acme-widgets", "widgets"), None);
let gateability = type_gateability("acme-widgets");
assert!(gateability.frozen && gateability.live);
}
}