runner_core/packs/resolver/
mod.rs1use 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
22pub 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}