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}