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 requires_management_permissions(self) -> bool {
94 self.requires_management_permissions
95 }
96
97 pub const fn has_runtime_cleanup_before_teardown(self) -> bool {
98 self.runtime_cleanup_before_teardown
99 }
100
101 pub fn allowed_lifecycles(self) -> &'static str {
102 match (self.allow_frozen, self.allow_live) {
103 (true, true) => "Frozen or Live",
104 (true, false) => "Frozen",
105 (false, true) => "Live",
106 (false, false) => "no lifecycle",
107 }
108 }
109}
110
111pub fn ownership_policy_for_resource_type(resource_type: &str) -> ResourceOwnershipPolicy {
112 match resource_type {
113 "function" | "container-cluster" => removed_resource_type(),
114 "worker" | "daemon" | "container" => live_only(),
115 "compute-cluster" => frozen_with_runtime_cleanup(),
116 "sandbox" => sandbox_lifecycle(),
117 "artifact-registry" | "key" => frozen_with_management(),
118 "build"
119 | "network"
120 | "remote-stack-management"
121 | "resource-access"
122 | "service-account"
123 | "service_activation"
124 | "service-activation"
125 | "azure_resource_group"
126 | "azure-resource-group"
127 | "azure_storage_account"
128 | "azure-storage-account"
129 | "azure_container_apps_environment"
130 | "azure-container-apps-environment"
131 | "azure_service_bus_namespace"
132 | "azure-service-bus-namespace"
133 | "email" => frozen_only(),
136 "experimental/aws-opensearch" => frozen_only(),
139 "storage" | "queue" | "kv" | "vault" | "postgres" | "ai" => user_choice(),
140 _ => user_choice(),
141 }
142}
143
144const fn frozen_only() -> ResourceOwnershipPolicy {
145 ResourceOwnershipPolicy::new(
146 ResourceLifecycle::Frozen,
147 true,
148 false,
149 SetupEmission::WhenFrozen,
150 false,
151 false,
152 )
153}
154
155const fn frozen_with_management() -> ResourceOwnershipPolicy {
156 ResourceOwnershipPolicy::new(
157 ResourceLifecycle::Frozen,
158 true,
159 false,
160 SetupEmission::WhenFrozen,
161 true,
162 false,
163 )
164}
165
166const fn frozen_with_runtime_cleanup() -> ResourceOwnershipPolicy {
167 ResourceOwnershipPolicy::new(
168 ResourceLifecycle::Frozen,
169 true,
170 false,
171 SetupEmission::WhenFrozen,
172 true,
173 true,
174 )
175}
176
177const fn sandbox_lifecycle() -> ResourceOwnershipPolicy {
183 ResourceOwnershipPolicy::new(
184 ResourceLifecycle::Frozen,
185 true,
186 true,
187 SetupEmission::Always,
188 true,
189 true,
190 )
191}
192
193const fn live_only() -> ResourceOwnershipPolicy {
194 ResourceOwnershipPolicy::new(
195 ResourceLifecycle::Live,
196 false,
197 true,
198 SetupEmission::Never,
199 false,
200 false,
201 )
202}
203
204const fn removed_resource_type() -> ResourceOwnershipPolicy {
205 ResourceOwnershipPolicy::new(
206 ResourceLifecycle::Live,
207 false,
208 false,
209 SetupEmission::Never,
210 false,
211 false,
212 )
213}
214
215const fn user_choice() -> ResourceOwnershipPolicy {
216 ResourceOwnershipPolicy::new(
217 ResourceLifecycle::Frozen,
218 true,
219 true,
220 SetupEmission::WhenFrozen,
221 false,
222 false,
223 )
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn workload_resources_are_live_only() {
232 for resource_type in ["worker", "daemon", "container"] {
233 let policy = ownership_policy_for_resource_type(resource_type);
234 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Live);
235 assert!(!policy.allows_lifecycle(ResourceLifecycle::Frozen));
236 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
237 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
238 }
239 }
240
241 #[test]
242 fn compute_cluster_is_frozen_with_runtime_cleanup() {
243 let policy = ownership_policy_for_resource_type("compute-cluster");
244 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
245 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
246 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
247 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
248 assert!(policy.requires_management_permissions());
249 assert!(policy.has_runtime_cleanup_before_teardown());
250 }
251
252 #[test]
253 fn sandbox_defaults_to_frozen_but_may_be_live() {
254 let policy = ownership_policy_for_resource_type("sandbox");
255 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
256 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
257 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
258 assert!(policy.requires_management_permissions());
259 assert!(policy.has_runtime_cleanup_before_teardown());
260 }
261
262 #[test]
266 fn a_live_sandbox_is_scaffolded_by_setup_but_not_owned_by_it() {
267 let policy = ownership_policy_for_resource_type("sandbox");
268
269 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
270 assert!(policy.emits_setup_scaffolding(ResourceLifecycle::Frozen));
271
272 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
273 assert!(policy.emits_setup_scaffolding(ResourceLifecycle::Live));
274 }
275
276 #[test]
280 fn only_the_sandbox_separates_ownership_from_scaffolding() {
281 let types = crate::gateability::MANIFEST_TYPES.iter().copied().chain([
282 "function",
283 "container-cluster",
284 "compute-cluster",
285 "artifact-registry",
286 "key",
287 "build",
288 "network",
289 "remote-stack-management",
290 "resource-access",
291 "service-account",
292 "service_activation",
293 "service-activation",
294 "azure_resource_group",
295 "azure-resource-group",
296 "azure_storage_account",
297 "azure-storage-account",
298 "azure_container_apps_environment",
299 "azure-container-apps-environment",
300 "azure_service_bus_namespace",
301 "azure-service-bus-namespace",
302 "an-unregistered-extension-type",
303 ]);
304
305 for resource_type in types {
306 if resource_type == "sandbox" {
307 continue;
308 }
309 let policy = ownership_policy_for_resource_type(resource_type);
310 for lifecycle in [ResourceLifecycle::Frozen, ResourceLifecycle::Live] {
311 assert_eq!(
312 policy.should_emit_in_setup(lifecycle),
313 policy.emits_setup_scaffolding(lifecycle),
314 "'{resource_type}' answers the two questions differently under {lifecycle:?}"
315 );
316 }
317 }
318 }
319
320 #[test]
321 fn artifact_registry_is_frozen_with_management() {
322 let policy = ownership_policy_for_resource_type("artifact-registry");
323 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
324 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
325 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
326 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
327 assert!(policy.requires_management_permissions());
328 assert!(!policy.has_runtime_cleanup_before_teardown());
329 }
330
331 #[test]
332 fn removed_resource_type_tags_are_not_normal_policy_entries() {
333 for resource_type in ["function", "container-cluster"] {
334 let policy = ownership_policy_for_resource_type(resource_type);
335 assert!(!policy.allows_lifecycle(ResourceLifecycle::Frozen));
336 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
337 assert!(!policy.requires_management_permissions());
338 assert!(!policy.has_runtime_cleanup_before_teardown());
339 }
340 }
341
342 #[test]
343 fn data_resources_can_be_frozen_or_live() {
344 for resource_type in ["storage", "queue", "kv", "vault", "postgres", "ai"] {
345 let policy = ownership_policy_for_resource_type(resource_type);
346 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
347 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
348 assert!(policy.allows_lifecycle(ResourceLifecycle::Live));
349 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
350 assert!(!policy.should_emit_in_setup(ResourceLifecycle::Live));
351 }
352 }
353
354 #[test]
355 fn experimental_aws_opensearch_is_frozen_only() {
356 let policy = ownership_policy_for_resource_type("experimental/aws-opensearch");
357 assert_eq!(policy.default_lifecycle(), ResourceLifecycle::Frozen);
358 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
359 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
360 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
361 assert!(!policy.requires_management_permissions());
362 }
363
364 #[test]
365 fn setup_resources_are_frozen_only() {
366 for resource_type in [
367 "build",
368 "network",
369 "remote-stack-management",
370 "resource-access",
371 "service-account",
372 "service_activation",
373 "azure_resource_group",
374 "azure_storage_account",
375 "azure_container_apps_environment",
376 "azure_service_bus_namespace",
377 "email",
378 ] {
379 let policy = ownership_policy_for_resource_type(resource_type);
380 assert!(policy.allows_lifecycle(ResourceLifecycle::Frozen));
381 assert!(!policy.allows_lifecycle(ResourceLifecycle::Live));
382 assert!(policy.should_emit_in_setup(ResourceLifecycle::Frozen));
383 }
384 }
385}