1use crate::ResourceLifecycle;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct ResourceOwnershipPolicy {
5 default_lifecycle: ResourceLifecycle,
6 allow_frozen: bool,
7 allow_live: bool,
8 emit_in_setup: bool,
9 requires_management_permissions: bool,
10 runtime_cleanup_before_teardown: bool,
11}
12
13impl ResourceOwnershipPolicy {
14 pub const fn new(
15 default_lifecycle: ResourceLifecycle,
16 allow_frozen: bool,
17 allow_live: bool,
18 emit_in_setup: bool,
19 requires_management_permissions: bool,
20 runtime_cleanup_before_teardown: bool,
21 ) -> Self {
22 Self {
23 default_lifecycle,
24 allow_frozen,
25 allow_live,
26 emit_in_setup,
27 requires_management_permissions,
28 runtime_cleanup_before_teardown,
29 }
30 }
31
32 pub const fn default_lifecycle(self) -> ResourceLifecycle {
33 self.default_lifecycle
34 }
35
36 pub const fn allows_frozen(self) -> bool {
37 self.allow_frozen
38 }
39
40 pub const fn allows_live(self) -> bool {
41 self.allow_live
42 }
43
44 pub const fn allows_lifecycle(self, lifecycle: ResourceLifecycle) -> bool {
45 match lifecycle {
46 ResourceLifecycle::Frozen => self.allow_frozen,
47 ResourceLifecycle::Live => self.allow_live,
48 }
49 }
50
51 pub const fn should_emit_in_setup(self, lifecycle: ResourceLifecycle) -> bool {
52 self.emit_in_setup && matches!(lifecycle, ResourceLifecycle::Frozen)
53 }
54
55 pub const fn requires_management_permissions(self) -> bool {
56 self.requires_management_permissions
57 }
58
59 pub const fn has_runtime_cleanup_before_teardown(self) -> bool {
60 self.runtime_cleanup_before_teardown
61 }
62
63 pub fn allowed_lifecycles(self) -> &'static str {
64 match (self.allow_frozen, self.allow_live) {
65 (true, true) => "Frozen or Live",
66 (true, false) => "Frozen",
67 (false, true) => "Live",
68 (false, false) => "no lifecycle",
69 }
70 }
71}
72
73pub fn ownership_policy_for_resource_type(resource_type: &str) -> ResourceOwnershipPolicy {
74 match resource_type {
75 "function" | "container-cluster" => removed_resource_type(),
76 "worker" | "daemon" | "container" => live_only(),
77 "compute-cluster" => frozen_with_runtime_cleanup(),
78 "artifact-registry" => frozen_with_management(),
79 "build"
80 | "network"
81 | "remote-stack-management"
82 | "resource-access"
83 | "service-account"
84 | "service_activation"
85 | "service-activation"
86 | "azure_resource_group"
87 | "azure-resource-group"
88 | "azure_storage_account"
89 | "azure-storage-account"
90 | "azure_container_apps_environment"
91 | "azure-container-apps-environment"
92 | "azure_service_bus_namespace"
93 | "azure-service-bus-namespace"
94 | "email" => frozen_only(),
97 "experimental/aws-opensearch" => frozen_only(),
100 "storage" | "queue" | "kv" | "vault" | "postgres" | "ai" => user_choice(),
101 _ => user_choice(),
102 }
103}
104
105const fn frozen_only() -> ResourceOwnershipPolicy {
106 ResourceOwnershipPolicy::new(ResourceLifecycle::Frozen, true, false, true, false, false)
107}
108
109const fn frozen_with_management() -> ResourceOwnershipPolicy {
110 ResourceOwnershipPolicy::new(ResourceLifecycle::Frozen, true, false, true, true, false)
111}
112
113const fn frozen_with_runtime_cleanup() -> ResourceOwnershipPolicy {
114 ResourceOwnershipPolicy::new(ResourceLifecycle::Frozen, true, false, true, true, true)
115}
116
117const fn live_only() -> ResourceOwnershipPolicy {
118 ResourceOwnershipPolicy::new(ResourceLifecycle::Live, false, true, false, false, false)
119}
120
121const fn removed_resource_type() -> ResourceOwnershipPolicy {
122 ResourceOwnershipPolicy::new(ResourceLifecycle::Live, false, false, false, false, false)
123}
124
125const fn user_choice() -> ResourceOwnershipPolicy {
126 ResourceOwnershipPolicy::new(ResourceLifecycle::Frozen, true, true, true, false, false)
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn workload_resources_are_live_only() {
135 for resource_type in ["worker", "daemon", "container"] {
136 let policy = ownership_policy_for_resource_type(resource_type);
137 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Live);
138 assert!(!policy.allows_lifecycle(ResourceLifecycle::Frozen));
139 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
140 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
141 }
142 }
143
144 #[test]
145 fn compute_cluster_is_frozen_with_runtime_cleanup() {
146 let policy = ownership_policy_for_resource_type("compute-cluster");
147 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
148 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
149 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
150 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
151 assert!(policy.requires_management_permissions());
152 assert!(policy.has_runtime_cleanup_before_teardown());
153 }
154
155 #[test]
156 fn artifact_registry_is_frozen_with_management() {
157 let policy = ownership_policy_for_resource_type("artifact-registry");
158 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
159 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
160 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
161 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
162 assert!(policy.requires_management_permissions());
163 assert!(!policy.has_runtime_cleanup_before_teardown());
164 }
165
166 #[test]
167 fn removed_resource_type_tags_are_not_normal_policy_entries() {
168 for resource_type in ["function", "container-cluster"] {
169 let policy = ownership_policy_for_resource_type(resource_type);
170 assert!(!policy.allows_lifecycle(ResourceLifecycle::Frozen));
171 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
172 assert!(!policy.requires_management_permissions());
173 assert!(!policy.has_runtime_cleanup_before_teardown());
174 }
175 }
176
177 #[test]
178 fn data_resources_can_be_frozen_or_live() {
179 for resource_type in ["storage", "queue", "kv", "vault", "postgres", "ai"] {
180 let policy = ownership_policy_for_resource_type(resource_type);
181 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
182 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
183 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
184 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
185 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
186 }
187 }
188
189 #[test]
190 fn experimental_aws_opensearch_is_frozen_only() {
191 let policy = ownership_policy_for_resource_type("experimental/aws-opensearch");
192 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
193 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
194 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
195 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
196 assert!(!policy.requires_management_permissions());
197 }
198
199 #[test]
200 fn setup_resources_are_frozen_only() {
201 for resource_type in [
202 "build",
203 "network",
204 "remote-stack-management",
205 "resource-access",
206 "service-account",
207 "service_activation",
208 "azure_resource_group",
209 "azure_storage_account",
210 "azure_container_apps_environment",
211 "azure_service_bus_namespace",
212 "email",
213 ] {
214 let policy = ownership_policy_for_resource_type(resource_type);
215 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
216 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
217 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
218 }
219 }
220}