greentic_component/store/
mod.rs1use std::collections::HashMap;
2use std::path::PathBuf;
3
4#[cfg(not(feature = "oci"))]
5use anyhow::bail;
6use anyhow::{anyhow, Result};
7use bytes::Bytes;
8use serde::{Deserialize, Serialize};
9use tracing::instrument;
10
11use self::cache::Cache;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct ComponentId(pub String);
15
16#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub enum ComponentLocator {
18 Fs { path: PathBuf },
19 Oci { reference: String },
20}
21
22#[derive(Clone, Debug)]
23pub struct ComponentBytes {
24 pub id: ComponentId,
25 pub bytes: Bytes,
26 pub meta: MetaInfo,
27}
28
29pub type SourceId = String;
30
31#[derive(Clone, Debug)]
32pub struct ComponentStore {
33 sources: HashMap<SourceId, ComponentLocator>,
34 cache: Cache,
35 compat: CompatPolicy,
36}
37
38impl Default for ComponentStore {
39 fn default() -> Self {
40 Self::with_cache_dir(None, CompatPolicy::default())
41 }
42}
43
44impl ComponentStore {
45 pub fn with_cache_dir(cache_dir: Option<PathBuf>, compat: CompatPolicy) -> Self {
46 Self {
47 sources: HashMap::new(),
48 cache: Cache::new(cache_dir),
49 compat,
50 }
51 }
52
53 pub fn add_fs(&mut self, id: impl Into<SourceId>, path: impl Into<PathBuf>) -> &mut Self {
54 self.sources
55 .insert(id.into(), ComponentLocator::Fs { path: path.into() });
56 self
57 }
58
59 pub fn add_oci(&mut self, id: impl Into<SourceId>, reference: impl Into<String>) -> &mut Self {
60 self.sources.insert(
61 id.into(),
62 ComponentLocator::Oci {
63 reference: reference.into(),
64 },
65 );
66 self
67 }
68
69 #[instrument(level = "trace", skip_all, fields(source = %source_id))]
70 pub async fn get(&self, source_id: &str) -> Result<ComponentBytes> {
71 let loc = self
72 .sources
73 .get(source_id)
74 .ok_or_else(|| anyhow!("unknown source id: {source_id}"))?;
75
76 if let Some(hit) = self.cache.try_load(loc).await? {
77 compat::check(&self.compat, &hit.meta).map_err(anyhow::Error::new)?;
78 return Ok(hit);
79 }
80
81 let bytes = match loc {
82 ComponentLocator::Fs { path } => fs_source::fetch(path).await?,
83 ComponentLocator::Oci { reference } => {
84 #[cfg(feature = "oci")]
85 {
86 oci_source::fetch(reference).await?
87 }
88 #[cfg(not(feature = "oci"))]
89 {
90 bail!("OCI support disabled: enable the `oci` feature to fetch {reference}");
91 }
92 }
93 };
94
95 let (id, meta) = meta::compute_id_and_meta(bytes.as_ref()).await?;
96 let cb = ComponentBytes { id, bytes, meta };
97
98 compat::check(&self.compat, &cb.meta).map_err(anyhow::Error::new)?;
99 self.cache.store(loc, &cb).await?;
100 Ok(cb)
101 }
102}
103
104mod cache;
105mod compat;
106mod fs_source;
107mod meta;
108#[cfg(feature = "oci")]
109mod oci_source;
110
111pub use compat::{CompatError, CompatPolicy};
112pub use meta::MetaInfo;