runner_core/packs/resolver/
mod.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use anyhow::{Result, anyhow, bail};
6use tempfile::TempPath;
7
8mod azblob;
9mod fs;
10mod gcs;
11mod http;
12mod oci;
13mod s3;
14
15pub use azblob::AzBlobResolver;
16pub use fs::FsResolver;
17pub use gcs::GcsResolver;
18pub use http::HttpResolver;
19pub use oci::OciResolver;
20pub use s3::S3Resolver;
21
22/// Response from a resolver indicating where the artifact was stored.
23pub struct FetchResponse {
24    location: FetchLocation,
25}
26
27impl FetchResponse {
28    pub fn from_path(path: PathBuf) -> Self {
29        Self {
30            location: FetchLocation::Permanent(path),
31        }
32    }
33
34    pub fn from_temp(path: TempPath) -> Self {
35        Self {
36            location: FetchLocation::Temporary(path),
37        }
38    }
39
40    pub fn path(&self) -> &Path {
41        match &self.location {
42            FetchLocation::Permanent(path) => path,
43            FetchLocation::Temporary(path) => path.as_ref(),
44        }
45    }
46}
47
48enum FetchLocation {
49    Permanent(PathBuf),
50    Temporary(TempPath),
51}
52
53pub trait PackResolver: Send + Sync {
54    fn scheme(&self) -> &'static str;
55    fn fetch(&self, locator: &str) -> Result<FetchResponse>;
56}
57
58#[derive(Default)]
59pub struct ResolverRegistry {
60    resolvers: HashMap<String, Arc<dyn PackResolver>>,
61}
62
63impl ResolverRegistry {
64    pub fn register(&mut self, resolver: impl PackResolver + 'static) {
65        self.resolvers
66            .insert(resolver.scheme().to_string(), Arc::new(resolver));
67    }
68
69    pub fn register_builtin(
70        &mut self,
71        fs_root: PathBuf,
72        network: Option<&greentic_config_types::NetworkConfig>,
73    ) -> Result<()> {
74        self.register(FsResolver::new(fs_root));
75        self.register(HttpResolver::new("http", network)?);
76        self.register(HttpResolver::new("https", network)?);
77        self.register(OciResolver::new(network)?);
78        self.register(S3Resolver::new(network)?);
79        self.register(GcsResolver::new(network)?);
80        self.register(AzBlobResolver::new(network)?);
81        Ok(())
82    }
83
84    pub fn fetch(&self, reference: &str) -> Result<FetchResponse> {
85        let parsed = ParsedReference::parse(reference)?;
86        let resolver = self
87            .resolvers
88            .get(&parsed.scheme)
89            .ok_or_else(|| anyhow!("no resolver registered for scheme `{}`", parsed.scheme))?;
90        resolver.fetch(&parsed.locator)
91    }
92}
93
94struct ParsedReference {
95    scheme: String,
96    locator: String,
97}
98
99impl ParsedReference {
100    fn parse(input: &str) -> Result<Self> {
101        let (scheme_part, rest) = input
102            .split_once("://")
103            .ok_or_else(|| anyhow!("reference `{input}` is missing a URI scheme"))?;
104        if rest.is_empty() {
105            bail!("reference `{input}` is missing a locator");
106        }
107        if let Some((logical, actual)) = scheme_part.split_once('+') {
108            let locator = format!("{actual}://{rest}");
109            return Ok(Self {
110                scheme: logical.to_ascii_lowercase(),
111                locator,
112            });
113        }
114        Ok(Self {
115            scheme: scheme_part.to_ascii_lowercase(),
116            locator: input.to_string(),
117        })
118    }
119}