1use std::{collections::HashMap, path::PathBuf, sync::Arc};
4
5use super::{AppManifest, AppManifestValidated};
6use crate::prelude::*;
7
8#[allow(missing_docs)]
9mod error;
10pub use error::*;
11use futures::future::join_all;
12
13#[cfg(test)]
14mod tests;
15
16#[derive(Debug, Serialize, Deserialize, Clone, derive_more::From, shrinkwraprs::Shrinkwrap)]
18pub struct AppBundle(mr_bundle::Bundle<AppManifest>);
19
20impl AppBundle {
21 pub async fn new<R: IntoIterator<Item = (PathBuf, DnaBundle)>>(
23 manifest: AppManifest,
24 resources: R,
25 root_dir: PathBuf,
26 ) -> AppBundleResult<Self> {
27 let resources = join_all(resources.into_iter().map(|(path, dna_bundle)| async move {
28 dna_bundle.encode().map(|bytes| (path, bytes.into()))
29 }))
30 .await
31 .into_iter()
32 .collect::<Result<Vec<_>, _>>()?;
33 Ok(mr_bundle::Bundle::new(manifest, resources, root_dir)?.into())
34 }
35
36 pub fn decode(bytes: &[u8]) -> AppBundleResult<Self> {
38 mr_bundle::Bundle::decode(bytes)
39 .map(Into::into)
40 .map_err(Into::into)
41 }
42
43 pub fn into_inner(self) -> mr_bundle::Bundle<AppManifest> {
45 self.0
46 }
47
48 pub fn get_all_dnas_from_store(&self, dna_store: &impl DnaStore) -> HashMap<DnaHash, DnaFile> {
50 self.manifest()
51 .app_roles()
52 .iter()
53 .flat_map(|role| role.dna.installed_hash.to_owned())
54 .map(Into::into)
55 .flat_map(|hash| dna_store.get_dna(&hash).map(|dna| (hash, dna)))
56 .collect()
57 }
58
59 pub async fn resolve_cells(
62 self,
63 dna_store: &impl DnaStore,
64 membrane_proofs: MemproofMap,
65 existing_cells: ExistingCellsMap,
66 ) -> AppBundleResult<AppRoleResolution> {
67 let AppManifestValidated { name: _, roles } = self.manifest().clone().validate()?;
68 let bundle = Arc::new(self);
69 let tasks = roles.into_iter().map(|(role_name, role)| async {
70 let bundle = bundle.clone();
71 Ok((
72 role_name.clone(),
73 bundle
74 .resolve_cell(dna_store, role_name, role, &existing_cells)
75 .await?,
76 ))
77 });
78
79 futures::future::join_all(tasks)
80 .await
81 .into_iter()
82 .collect::<AppBundleResult<Vec<_>>>()?
83 .into_iter()
84 .try_fold(
85 AppRoleResolution::default(),
86 |mut resolution: AppRoleResolution, (role_name, op)| {
87 match op {
88 CellProvisioningOp::CreateFromDnaFile(dna, clone_limit) => {
89 let dna_hash = dna.dna_hash().clone();
90 let role = AppRolePrimary::new(dna_hash, true, clone_limit).into();
91 let proof = membrane_proofs.get(&role_name).cloned();
93 resolution.dnas_to_register.push((dna, proof));
94 resolution.role_assignments.push((role_name, role));
95 }
96
97 CellProvisioningOp::Existing(cell_id, protected) => {
98 let role = AppRoleDependency { cell_id, protected }.into();
99 resolution.role_assignments.push((role_name, role));
100 }
101
102 CellProvisioningOp::ProvisionOnly(dna, clone_limit) => {
103 let dna_hash = dna.dna_hash().clone();
104
105 let proof = membrane_proofs.get(&role_name).cloned();
107 resolution.dnas_to_register.push((dna, proof));
108 resolution.role_assignments.push((
109 role_name,
110 AppRolePrimary::new(dna_hash, false, clone_limit).into(),
111 ));
112 }
113 }
114
115 Ok(resolution)
116 },
117 )
118 }
119
120 async fn resolve_cell(
121 &self,
122 dna_store: &impl DnaStore,
123 role_name: RoleName,
124 role: AppRoleManifestValidated,
125 existing_cells: &ExistingCellsMap,
126 ) -> AppBundleResult<CellProvisioningOp> {
127 match role {
128 AppRoleManifestValidated::Create {
129 location,
130 installed_hash,
131 clone_limit,
132 modifiers,
133 deferred: _,
134 } => {
135 let dna = self
136 .resolve_dna(
137 role_name,
138 dna_store,
139 &location,
140 installed_hash.as_ref(),
141 modifiers,
142 )
143 .await?;
144 Ok(CellProvisioningOp::CreateFromDnaFile(dna, clone_limit))
145 }
146
147 AppRoleManifestValidated::UseExisting {
148 compatible_hash,
149 protected,
150 } => {
151 if let Some(cell_id) = existing_cells.get(&role_name) {
152 Ok(CellProvisioningOp::Existing(cell_id.clone(), protected))
153 } else {
154 Err(AppBundleError::CellResolutionFailure(
155 role_name,
156 format!("No existing cell was specified for the role with DNA {compatible_hash}"),
157 ))
158 }
159 }
160
161 AppRoleManifestValidated::CloneOnly {
162 clone_limit,
163 location,
164 modifiers,
165 installed_hash,
166 } => {
167 let dna = self
168 .resolve_dna(
169 role_name,
170 dna_store,
171 &location,
172 installed_hash.as_ref(),
173 modifiers,
174 )
175 .await?;
176 Ok(CellProvisioningOp::ProvisionOnly(dna, clone_limit))
177 }
178 }
179 }
180
181 async fn resolve_dna(
182 &self,
183 role_name: RoleName,
184 dna_store: &impl DnaStore,
185 location: &mr_bundle::Location,
186 expected_hash: Option<&DnaHashB64>,
187 modifiers: DnaModifiersOpt,
188 ) -> AppBundleResult<DnaFile> {
189 let dna_file = if let Some(expected_hash) = expected_hash {
190 let expected_hash = expected_hash.clone().into();
191 let (dna_file, original_hash) =
192 if let Some(mut dna_file) = dna_store.get_dna(&expected_hash) {
193 let original_hash = dna_file.dna_hash().clone();
194 dna_file = dna_file.update_modifiers(modifiers);
195 (dna_file, original_hash)
196 } else {
197 self.resolve_location(location, modifiers).await?
198 };
199 if expected_hash != original_hash {
200 return Err(AppBundleError::CellResolutionFailure(
201 role_name,
202 format!("Hash mismatch: {} != {}", expected_hash, original_hash),
203 ));
204 }
205 dna_file
206 } else {
207 self.resolve_location(location, modifiers).await?.0
208 };
209 Ok(dna_file)
210 }
211
212 async fn resolve_location(
213 &self,
214 location: &mr_bundle::Location,
215 modifiers: DnaModifiersOpt,
216 ) -> AppBundleResult<(DnaFile, DnaHash)> {
217 let bytes = self.resolve(location).await?;
218 let dna_bundle: DnaBundle = mr_bundle::Bundle::decode(&bytes)?.into();
219 let (dna_file, original_hash) = dna_bundle.into_dna_file(modifiers).await?;
220 Ok((dna_file, original_hash))
221 }
222}
223
224#[allow(missing_docs)]
229#[derive(PartialEq, Eq, Debug, Default)]
230pub struct AppRoleResolution {
231 pub dnas_to_register: Vec<(DnaFile, Option<MembraneProof>)>,
232 pub role_assignments: Vec<(RoleName, AppRoleAssignment)>,
233}
234
235#[allow(missing_docs)]
236impl AppRoleResolution {
237 pub fn cells_to_create(&self, agent_key: AgentPubKey) -> Vec<(CellId, Option<MembraneProof>)> {
240 let provisioned = self
241 .role_assignments
242 .iter()
243 .filter_map(|(_name, role)| {
244 let role = role.as_primary()?;
245 if role.is_provisioned {
246 Some(CellId::new(role.dna_hash().clone(), agent_key.clone()))
247 } else {
248 None
249 }
250 })
251 .collect::<std::collections::HashSet<_>>();
252
253 self.dnas_to_register
254 .iter()
255 .filter_map(|(dna, proof)| {
256 let cell_id = CellId::new(dna.dna_hash().clone(), agent_key.clone());
257 if provisioned.contains(&cell_id) {
258 Some((cell_id, proof.clone()))
259 } else {
260 None
261 }
262 })
263 .collect()
264 }
265}
266
267#[warn(missing_docs)]
269#[derive(Debug)]
270pub enum CellProvisioningOp {
271 CreateFromDnaFile(DnaFile, u32),
273 Existing(CellId, bool),
275 ProvisionOnly(DnaFile, u32),
278}