greentic_component/store/
mod.rs

1use 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;