wildland_catlib/lib.rs
1//
2// Wildland Project
3//
4// Copyright © 2022 Golem Foundation
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License version 3 as published by
8// the Free Software Foundation.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! Catalog client library
19//!
20//! This library is used by Wildland Core to allow persistent storage for Wildland manifests that
21//! describe Wildland entities such as Containers, Storages, Bridges etc.
22//!
23//! The library acts as a database client depending on the database backend used. The current
24//! version of CatLib stores manifests in a local single-file nosql, unstructured database.
25//! Location of the database file depends on the platform where the application runs, these are:
26//!
27//! - `Linux: /home/alice/.config/catlib`
28//! - `Windows: C:\Users\Alice\AppData\Roaming\com.wildland.Cargo\catlib`
29//! - `macOS: /Users/Alice/Library/Application Support/com.wildland.Cargo/catlib`
30//!
31//! ## Entities relationship
32//!
33//! ```none
34//! +------------+ +------------+
35//! | Forest | -------> | Bridge |
36//! +------------+ +------------+
37//! |
38//! | +-------------+
39//! +-----> | Container |
40//! +-------------+
41//! |
42//! | +-----------+
43//! +-----> | Storage |
44//! +-----------+
45//! ```
46//! ## Example usage
47//!
48//! ```no_run
49//! # use wildland_catlib::CatLib;
50//! # use wildland_corex::entities::Identity;
51//! # use wildland_corex::interface::CatLib as ICatLib;
52//! # use wildland_corex::StorageTemplate;
53//! # use std::collections::{HashSet, HashMap};
54//! # use uuid::Uuid;
55//! let forest_owner = Identity([1; 32]);
56//! let signer = Identity([2; 32]);
57//!
58//! let catlib = CatLib::default();
59//! let forest = catlib.create_forest(
60//! forest_owner,
61//! HashSet::from([signer]),
62//! vec![],
63//! ).unwrap();
64//! let storage_template = StorageTemplate::try_new(
65//! "FoundationStorage",
66//! HashMap::from([
67//! (
68//! "field1".to_owned(),
69//! "Some value with container name: {{ CONTAINER_NAME }}".to_owned(),
70//! ),
71//! (
72//! "parameter in key: {{ OWNER }}".to_owned(),
73//! "enum: {{ ACCESS_MODE }}".to_owned(),
74//! ),
75//! ("uuid".to_owned(), "{{ CONTAINER_UUID }}".to_owned()),
76//! ("paths".to_owned(), "{{ PATHS }}".to_owned()),
77//! ]),
78//! )
79//! .unwrap();
80//! let path = "/some/path".to_owned();
81//! let container = forest.lock().unwrap().create_container("container name".to_owned(), &storage_template, path).unwrap();
82//! container.lock().unwrap().add_path("/foo/bar".to_string());
83//! container.lock().unwrap().add_path("/bar/baz".to_string());
84//!
85//! ```
86//!
87
88use std::path::PathBuf;
89use std::rc::Rc;
90use std::sync::{Arc, Mutex};
91
92use bridge::Bridge;
93pub use common::*;
94use container::Container;
95use db::*;
96use directories::ProjectDirs;
97use error::*;
98use forest::{Forest, ForestData};
99use rustbreak::PathDatabase;
100use storage::{StorageData, StorageEntity};
101use uuid::Uuid;
102use wildland_corex::catlib_service::entities::{
103 ContainerManifest,
104 ForestManifest,
105 Identity,
106 Signers,
107 StorageManifest,
108};
109use wildland_corex::catlib_service::interface::CatLib as ICatLib;
110
111mod bridge;
112mod common;
113mod container;
114mod db;
115mod error;
116mod forest;
117mod storage;
118
119#[derive(Clone)]
120pub struct CatLib {
121 db: Rc<StoreDb>,
122}
123
124impl CatLib {
125 pub fn new(path: PathBuf) -> Self {
126 let db = PathDatabase::create_at_path(path.clone(), CatLibData::new());
127
128 if db.is_err() {
129 let path_str = path.to_str().unwrap();
130 panic!("Could not create CatLib database at {path_str}");
131 }
132
133 CatLib {
134 db: Rc::new(db.unwrap()),
135 }
136 }
137}
138
139impl ICatLib for CatLib {
140 /// ## Errors
141 ///
142 /// Returns `RustbreakError` cast on [`CatlibResult`] upon failure to save to the database.
143 ///
144 /// ## Example
145 ///
146 /// ```rust
147 /// # use wildland_catlib::CatLib;
148 /// # use wildland_corex::catlib_service::entities::Identity;
149 /// # use wildland_corex::catlib_service::interface::CatLib as ICatLib;
150 /// # use std::collections::HashSet;
151 /// let forest_owner = Identity([1; 32]);
152 /// let signer = Identity([2; 32]);
153 ///
154 /// let catlib = CatLib::default();
155 /// let forest = catlib.create_forest(
156 /// forest_owner,
157 /// HashSet::from([signer]),
158 /// vec![],
159 /// ).unwrap();
160 /// ```
161 #[tracing::instrument(level = "debug", skip_all)]
162 fn create_forest(
163 &self,
164 owner: Identity,
165 signers: Signers,
166 data: Vec<u8>,
167 ) -> CatlibResult<Arc<Mutex<dyn ForestManifest>>> {
168 let forest = Forest::new(owner, signers, data, self.db.clone());
169 forest.save()?;
170 Ok(Arc::new(Mutex::new(forest)))
171 }
172
173 fn get_forest(&self, uuid: &Uuid) -> CatlibResult<Arc<Mutex<dyn ForestManifest>>> {
174 fetch_forest_by_uuid(self.db.clone(), uuid)
175 }
176
177 /// ## Errors
178 ///
179 /// - Returns [`CatlibError::NoRecordsFound`] if no [`Forest`] was found.
180 /// - Returns [`CatlibError::MalformedDatabaseRecord`] if more than one [`Forest`] was found.
181 /// - Returns `RustbreakError` cast on [`CatlibResult`] upon failure to save to the database.
182 #[tracing::instrument(level = "debug", skip_all)]
183 fn find_forest(&self, owner: &Identity) -> CatlibResult<Arc<Mutex<dyn ForestManifest>>> {
184 self.db.load().map_err(to_catlib_error)?;
185 let data = self.db.read(|db| db.clone()).map_err(to_catlib_error)?;
186
187 let forests: Vec<_> = data
188 .iter()
189 .filter(|(id, _)| id.starts_with("forest-"))
190 .map(|(_, forest_str)| Forest {
191 data: ForestData::from(forest_str.as_str()),
192 db: self.db.clone(),
193 })
194 .filter(|forest| &forest.owner() == owner)
195 .map(|forest| Arc::new(Mutex::new(forest)))
196 .collect();
197
198 match forests.len() {
199 0 => Err(CatlibError::NoRecordsFound),
200 1 => Ok(forests[0].clone()),
201 _ => Err(CatlibError::MalformedDatabaseRecord),
202 }
203 }
204
205 fn get_container(&self, uuid: &Uuid) -> CatlibResult<Arc<Mutex<dyn ContainerManifest>>> {
206 fetch_container_by_uuid(self.db.clone(), uuid)
207 }
208
209 /// ## Errors
210 ///
211 /// - Returns [`CatlibError::NoRecordsFound`] if no [`Storage`] was found.
212 /// - Returns `RustbreakError` cast on [`CatlibResult`] upon failure to save to the database.
213 #[tracing::instrument(level = "debug", skip_all)]
214 fn find_storages_with_template(
215 &self,
216 template_id: &Uuid,
217 ) -> CatlibResult<Vec<Arc<Mutex<dyn StorageManifest>>>> {
218 self.db.load().map_err(to_catlib_error)?;
219 let data = self.db.read(|db| db.clone()).map_err(to_catlib_error)?;
220
221 let storages: Vec<_> = data
222 .iter()
223 .filter(|(id, _)| id.starts_with("storage-"))
224 .map(|(_, storage_str)| StorageEntity {
225 data: StorageData::from(storage_str.as_str()),
226 db: self.db.clone(),
227 })
228 .filter(
229 |storage| matches!(storage.as_ref().template_uuid, Some(val) if val == *template_id),
230 )
231 .map(|storage| Arc::new(Mutex::new(storage)) as Arc<Mutex<dyn StorageManifest>>)
232 .collect();
233
234 match storages.len() {
235 0 => Err(CatlibError::NoRecordsFound),
236 _ => Ok(storages),
237 }
238 }
239
240 /// ## Errors
241 ///
242 /// - Returns [`CatlibError::NoRecordsFound`] if no [`Container`] was found.
243 /// - Returns `RustbreakError` cast on [`CatlibResult`] upon failure to save to the database.
244 #[tracing::instrument(level = "debug", skip_all)]
245 fn find_containers_with_template(
246 &self,
247 template_id: &Uuid,
248 ) -> CatlibResult<Vec<Arc<Mutex<dyn ContainerManifest>>>> {
249 let storages = self.find_storages_with_template(template_id)?;
250 storages
251 .iter()
252 .map(|storage| storage.lock().expect("Poisoned Mutex").container())
253 .collect()
254 }
255
256 #[tracing::instrument(level = "debug", skip_all)]
257 fn save_storage_template(&self, template_id: &Uuid, value: String) -> CatlibResult<()> {
258 save_model(
259 self.db.clone(),
260 format!("template-storage-{template_id}"),
261 value,
262 )
263 }
264
265 #[tracing::instrument(level = "debug", skip_all)]
266 fn get_storage_templates_data(&self) -> CatlibResult<Vec<String>> {
267 self.db.load().map_err(to_catlib_error)?;
268 let data = self.db.read(|db| db.clone()).map_err(to_catlib_error)?;
269
270 let storages: Vec<_> = data
271 .iter()
272 .filter(|(id, _)| id.starts_with("template-storage-"))
273 .map(|(_, storage_str)| storage_str)
274 .cloned()
275 .collect();
276 Ok(storages)
277 }
278}
279
280impl Default for CatLib {
281 fn default() -> Self {
282 let project_dirs = ProjectDirs::from("com", "wildland", "Cargo");
283
284 let db_file = if let Some(project_dirs) = project_dirs {
285 let db_dir = project_dirs.data_local_dir().join("catlib");
286
287 if !db_dir.exists() {
288 std::fs::create_dir_all(&db_dir).unwrap();
289 }
290
291 db_dir.join("catlib.database")
292 } else {
293 tracing::info!("Could not create ProjectDirs. Using working directory.");
294 "./catlib.database".into()
295 };
296
297 CatLib {
298 db: Rc::new(PathDatabase::load_from_path_or_default(db_file).unwrap()),
299 }
300 }
301}