1use crate::ResourceLifecycle;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SetupEmission {
10 Never,
12 WhenFrozen,
14 Always,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct ResourceOwnershipPolicy {
22 default_lifecycle: ResourceLifecycle,
23 allow_frozen: bool,
24 allow_live: bool,
25 emit_in_setup: SetupEmission,
26 requires_management_permissions: bool,
27 runtime_cleanup_before_teardown: bool,
28}
29
30impl ResourceOwnershipPolicy {
31 pub const fn new(
32 default_lifecycle: ResourceLifecycle,
33 allow_frozen: bool,
34 allow_live: bool,
35 emit_in_setup: SetupEmission,
36 requires_management_permissions: bool,
37 runtime_cleanup_before_teardown: bool,
38 ) -> Self {
39 Self {
40 default_lifecycle,
41 allow_frozen,
42 allow_live,
43 emit_in_setup,
44 requires_management_permissions,
45 runtime_cleanup_before_teardown,
46 }
47 }
48
49 pub const fn default_lifecycle(self) -> ResourceLifecycle {
50 self.default_lifecycle
51 }
52
53 pub const fn allows_frozen(self) -> bool {
54 self.allow_frozen
55 }
56
57 pub const fn allows_live(self) -> bool {
58 self.allow_live
59 }
60
61 pub const fn allows_lifecycle(self, lifecycle: ResourceLifecycle) -> bool {
62 match lifecycle {
63 ResourceLifecycle::Frozen => self.allow_frozen,
64 ResourceLifecycle::Live => self.allow_live,
65 }
66 }
67
68 pub const fn should_emit_in_setup(self, lifecycle: ResourceLifecycle) -> bool {
75 !matches!(self.emit_in_setup, SetupEmission::Never)
76 && matches!(lifecycle, ResourceLifecycle::Frozen)
77 }
78
79 pub const fn emits_setup_scaffolding(self, lifecycle: ResourceLifecycle) -> bool {
86 match self.emit_in_setup {
87 SetupEmission::Never => false,
88 SetupEmission::WhenFrozen => matches!(lifecycle, ResourceLifecycle::Frozen),
89 SetupEmission::Always => true,
90 }
91 }
92
93 pub const fn always_emits_setup_scaffolding(self) -> bool {
96 matches!(self.emit_in_setup, SetupEmission::Always)
97 }
98
99 pub const fn requires_management_permissions(self) -> bool {
100 self.requires_management_permissions
101 }
102
103 pub const fn has_runtime_cleanup_before_teardown(self) -> bool {
104 self.runtime_cleanup_before_teardown
105 }
106
107 pub fn allowed_lifecycles(self) -> &'static str {
108 match (self.allow_frozen, self.allow_live) {
109 (true, true) => "Frozen or Live",
110 (true, false) => "Frozen",
111 (false, true) => "Live",
112 (false, false) => "no lifecycle",
113 }
114 }
115}
116
117pub fn ownership_policy_for_resource_type(resource_type: &str) -> ResourceOwnershipPolicy {
118 match resource_type {
119 "function" | "container-cluster" => removed_resource_type(),
120 "worker" | "daemon" | "container" => live_only(),
121 "compute-cluster" => frozen_with_runtime_cleanup(),
122 "sandbox" => sandbox_lifecycle(),
123 "artifact-registry" | "key" => frozen_with_management(),
124 "build"
125 | "network"
126 | "remote-stack-management"
127 | "resource-access"
128 | "service-account"
129 | "service_activation"
130 | "service-activation"
131 | "azure_resource_group"
132 | "azure-resource-group"
133 | "azure_storage_account"
134 | "azure-storage-account"
135 | "azure_container_apps_environment"
136 | "azure-container-apps-environment"
137 | "azure_service_bus_namespace"
138 | "azure-service-bus-namespace"
139 | "email" => frozen_only(),
142 "experimental/aws-opensearch" => frozen_only(),
145 "storage" | "queue" | "kv" | "vault" | "postgres" | "ai" => user_choice(),
146 _ => user_choice(),
147 }
148}
149
150const fn frozen_only() -> ResourceOwnershipPolicy {
151 ResourceOwnershipPolicy::new(
152 ResourceLifecycle::Frozen,
153 true,
154 false,
155 SetupEmission::WhenFrozen,
156 false,
157 false,
158 )
159}
160
161const fn frozen_with_management() -> ResourceOwnershipPolicy {
162 ResourceOwnershipPolicy::new(
163 ResourceLifecycle::Frozen,
164 true,
165 false,
166 SetupEmission::WhenFrozen,
167 true,
168 false,
169 )
170}
171
172const fn frozen_with_runtime_cleanup() -> ResourceOwnershipPolicy {
173 ResourceOwnershipPolicy::new(
174 ResourceLifecycle::Frozen,
175 true,
176 false,
177 SetupEmission::WhenFrozen,
178 true,
179 true,
180 )
181}
182
183const fn sandbox_lifecycle() -> ResourceOwnershipPolicy {
189 ResourceOwnershipPolicy::new(
190 ResourceLifecycle::Frozen,
191 true,
192 true,
193 SetupEmission::Always,
194 true,
195 true,
196 )
197}
198
199const fn live_only() -> ResourceOwnershipPolicy {
200 ResourceOwnershipPolicy::new(
201 ResourceLifecycle::Live,
202 false,
203 true,
204 SetupEmission::Never,
205 false,
206 false,
207 )
208}
209
210const fn removed_resource_type() -> ResourceOwnershipPolicy {
211 ResourceOwnershipPolicy::new(
212 ResourceLifecycle::Live,
213 false,
214 false,
215 SetupEmission::Never,
216 false,
217 false,
218 )
219}
220
221const fn user_choice() -> ResourceOwnershipPolicy {
222 ResourceOwnershipPolicy::new(
223 ResourceLifecycle::Frozen,
224 true,
225 true,
226 SetupEmission::WhenFrozen,
227 false,
228 false,
229 )
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn workload_resources_are_live_only() {
238 for resource_type in ["worker", "daemon", "container"] {
239 let policy = ownership_policy_for_resource_type(resource_type);
240 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Live);
241 assert!(!policy.allows_lifecycle(ResourceLifecycle::Frozen));
242 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
243 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
244 }
245 }
246
247 #[test]
248 fn compute_cluster_is_frozen_with_runtime_cleanup() {
249 let policy = ownership_policy_for_resource_type("compute-cluster");
250 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
251 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
252 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
253 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
254 assert!(policy.requires_management_permissions());
255 assert!(policy.has_runtime_cleanup_before_teardown());
256 }
257
258 #[test]
259 fn sandbox_defaults_to_frozen_but_may_be_live() {
260 let policy = ownership_policy_for_resource_type("sandbox");
261 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
262 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
263 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
264 assert!(policy.requires_management_permissions());
265 assert!(policy.has_runtime_cleanup_before_teardown());
266 }
267
268 #[test]
272 fn a_live_sandbox_is_scaffolded_by_setup_but_not_owned_by_it() {
273 let policy = ownership_policy_for_resource_type("sandbox");
274
275 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
276 assert!(policy.emits_setup_scaffolding(ResourceLifecycle::Frozen));
277
278 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
279 assert!(policy.emits_setup_scaffolding(ResourceLifecycle::Live));
280 }
281
282 #[test]
286 fn only_the_sandbox_separates_ownership_from_scaffolding() {
287 let types = crate::gateability::MANIFEST_TYPES.iter().copied().chain([
288 "function",
289 "container-cluster",
290 "compute-cluster",
291 "artifact-registry",
292 "key",
293 "build",
294 "network",
295 "remote-stack-management",
296 "resource-access",
297 "service-account",
298 "service_activation",
299 "service-activation",
300 "azure_resource_group",
301 "azure-resource-group",
302 "azure_storage_account",
303 "azure-storage-account",
304 "azure_container_apps_environment",
305 "azure-container-apps-environment",
306 "azure_service_bus_namespace",
307 "azure-service-bus-namespace",
308 "an-unregistered-extension-type",
309 ]);
310
311 for resource_type in types {
312 if resource_type == "sandbox" {
313 continue;
314 }
315 let policy = ownership_policy_for_resource_type(resource_type);
316 for lifecycle in [ResourceLifecycle::Frozen, ResourceLifecycle::Live] {
317 assert_eq!(
318 policy.should_emit_in_setup(lifecycle),
319 policy.emits_setup_scaffolding(lifecycle),
320 "'{resource_type}' answers the two questions differently under {lifecycle:?}"
321 );
322 }
323 }
324 }
325
326 #[test]
327 fn artifact_registry_is_frozen_with_management() {
328 let policy = ownership_policy_for_resource_type("artifact-registry");
329 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
330 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
331 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
332 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
333 assert!(policy.requires_management_permissions());
334 assert!(!policy.has_runtime_cleanup_before_teardown());
335 }
336
337 #[test]
338 fn removed_resource_type_tags_are_not_normal_policy_entries() {
339 for resource_type in ["function", "container-cluster"] {
340 let policy = ownership_policy_for_resource_type(resource_type);
341 assert!(!policy.allows_lifecycle(ResourceLifecycle::Frozen));
342 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
343 assert!(!policy.requires_management_permissions());
344 assert!(!policy.has_runtime_cleanup_before_teardown());
345 }
346 }
347
348 #[test]
349 fn data_resources_can_be_frozen_or_live() {
350 for resource_type in ["storage", "queue", "kv", "vault", "postgres", "ai"] {
351 let policy = ownership_policy_for_resource_type(resource_type);
352 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
353 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
354 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
355 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
356 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
357 }
358 }
359
360 #[test]
361 fn experimental_aws_opensearch_is_frozen_only() {
362 let policy = ownership_policy_for_resource_type("experimental/aws-opensearch");
363 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
364 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
365 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
366 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
367 assert!(!policy.requires_management_permissions());
368 }
369
370 #[test]
371 fn setup_resources_are_frozen_only() {
372 for resource_type in [
373 "build",
374 "network",
375 "remote-stack-management",
376 "resource-access",
377 "service-account",
378 "service_activation",
379 "azure_resource_group",
380 "azure_storage_account",
381 "azure_container_apps_environment",
382 "azure_service_bus_namespace",
383 "email",
384 ] {
385 let policy = ownership_policy_for_resource_type(resource_type);
386 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
387 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
388 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
389 }
390 }
391}