trellis_core/
resource_reconcile.rs1use crate::{
2 Graph, GraphError, GraphResult, ResourceCommand, ResourceCommandCause, ResourceKey,
3 ResourcePlan, ScopeId,
4};
5use std::collections::BTreeSet;
6
7impl<C, O> Graph<C, O> {
8 pub(crate) fn produce_resource_plan(
9 &mut self,
10 closed_scopes: &[ScopeId],
11 ) -> GraphResult<ResourcePlan<C>> {
12 self.audit.pending_resource_causes.clear();
13 let planners = self.resource_planners.clone();
14 let mut plan = ResourcePlan::new();
15 for planner in planners {
16 if self.collection_diffs.contains_key(&planner.collection) {
17 let planned = planner.run(self)?;
18 let cause = ResourceCommandCause::Planner {
19 collection: planner.collection,
20 };
21 let reconciled = self.reconcile_resource_plan(planner.scope, planned, cause)?;
22 plan.append(reconciled);
23 }
24 }
25
26 for scope in closed_scopes {
27 let close_plan = self.close_scope_resources(*scope);
28 plan.append(close_plan);
29 }
30
31 Ok(plan)
32 }
33
34 fn reconcile_resource_plan(
35 &mut self,
36 planner_scope: ScopeId,
37 plan: ResourcePlan<C>,
38 cause: ResourceCommandCause,
39 ) -> GraphResult<ResourcePlan<C>> {
40 let mut reconciled = ResourcePlan::new();
41 for command in plan.into_commands() {
42 if command.scope() != planner_scope {
43 return Err(GraphError::ResourceScopeMismatch(command.scope()));
44 }
45 self.reconcile_resource_command(command, &mut reconciled, cause)?;
46 }
47 Ok(reconciled)
48 }
49
50 fn reconcile_resource_command(
51 &mut self,
52 command: ResourceCommand<C>,
53 plan: &mut ResourcePlan<C>,
54 cause: ResourceCommandCause,
55 ) -> GraphResult<()> {
56 match command {
57 ResourceCommand::Open {
58 key,
59 scope,
60 command,
61 } => self.reconcile_open(key, scope, command, plan, cause),
62 ResourceCommand::Close { key, scope } => {
63 self.remove_resource_owner(&key, scope, plan, cause);
64 Ok(())
65 }
66 ResourceCommand::Replace {
67 key,
68 scope,
69 command,
70 } => {
71 self.require_scope_open(scope)?;
72 self.require_resource_owner(&key, scope)?;
73 self.resource_owners
74 .entry(key.clone())
75 .or_default()
76 .insert(scope);
77 plan.replace(key, scope, command);
78 self.audit.pending_resource_causes.push(cause);
79 Ok(())
80 }
81 ResourceCommand::Refresh {
82 key,
83 scope,
84 command,
85 } => {
86 self.require_scope_open(scope)?;
87 self.require_resource_owner(&key, scope)?;
88 self.resource_owners
89 .entry(key.clone())
90 .or_default()
91 .insert(scope);
92 plan.refresh(key, scope, command);
93 self.audit.pending_resource_causes.push(cause);
94 Ok(())
95 }
96 }
97 }
98
99 fn reconcile_open(
100 &mut self,
101 key: ResourceKey,
102 scope: ScopeId,
103 command: C,
104 plan: &mut ResourcePlan<C>,
105 cause: ResourceCommandCause,
106 ) -> GraphResult<()> {
107 self.require_scope_open(scope)?;
108 let owners = self.resource_owners.entry(key.clone()).or_default();
109 let was_empty = owners.is_empty();
110 owners.insert(scope);
111 if was_empty {
112 plan.open(key, scope, command);
113 self.audit.pending_resource_causes.push(cause);
114 }
115 Ok(())
116 }
117
118 fn close_scope_resources(&mut self, scope: ScopeId) -> ResourcePlan<C> {
119 let keys: Vec<ResourceKey> = self.resource_owners.keys().cloned().collect();
120 let mut plan = ResourcePlan::new();
121 let cause = ResourceCommandCause::ScopeClosed { scope };
122 for key in keys {
123 self.remove_resource_owner(&key, scope, &mut plan, cause);
124 }
125 plan
126 }
127
128 fn remove_resource_owner(
129 &mut self,
130 key: &ResourceKey,
131 scope: ScopeId,
132 plan: &mut ResourcePlan<C>,
133 cause: ResourceCommandCause,
134 ) {
135 let Some(owners) = self.resource_owners.get_mut(key) else {
136 return;
137 };
138 owners.remove(&scope);
139 if owners.is_empty() {
140 self.resource_owners.remove(key);
141 plan.close(key.clone(), scope);
142 self.audit.pending_resource_causes.push(cause);
143 }
144 }
145
146 fn require_resource_owner(&self, key: &ResourceKey, scope: ScopeId) -> GraphResult<()> {
147 let Some(owners) = self.resource_owners.get(key) else {
148 return Err(GraphError::ResourceNotOwned);
149 };
150 if !owners.contains(&scope) {
151 return Err(GraphError::ResourceNotOwned);
152 }
153 Ok(())
154 }
155
156 pub(crate) fn require_scope_open(&self, scope: ScopeId) -> GraphResult<()> {
157 let scope_meta = self
158 .scope_meta(scope)
159 .ok_or(GraphError::UnknownScope(scope))?;
160 if scope_meta.is_closed() {
161 return Err(GraphError::ScopeAlreadyClosed(scope));
162 }
163 Ok(())
164 }
165
166 pub fn resource_owners(&self, key: &ResourceKey) -> Option<&BTreeSet<ScopeId>> {
168 self.resource_owners.get(key)
169 }
170}