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(&mut self) -> Result<()> {
70 self.register(FsResolver::new());
71 self.register(HttpResolver::new("http")?);
72 self.register(HttpResolver::new("https")?);
73 self.register(OciResolver::new()?);
74 self.register(S3Resolver::new()?);
75 self.register(GcsResolver::new()?);
76 self.register(AzBlobResolver::new()?);
77 Ok(())
78 }
79
80 pub fn fetch(&self, reference: &str) -> Result<FetchResponse> {
81 let parsed = ParsedReference::parse(reference)?;
82 let resolver = self
83 .resolvers
84 .get(&parsed.scheme)
85 .ok_or_else(|| anyhow!("no resolver registered for scheme `{}`", parsed.scheme))?;
86 resolver.fetch(&parsed.locator)
87 }
88}
89
90struct ParsedReference {
91 scheme: String,
92 locator: String,
93}
94
95impl ParsedReference {
96 fn parse(input: &str) -> Result<Self> {
97 let (scheme_part, rest) = input
98 .split_once("://")
99 .ok_or_else(|| anyhow!("reference `{input}` is missing a URI scheme"))?;
100 if rest.is_empty() {
101 bail!("reference `{input}` is missing a locator");
102 }
103 if let Some((logical, actual)) = scheme_part.split_once('+') {
104 let locator = format!("{actual}://{rest}");
105 return Ok(Self {
106 scheme: logical.to_ascii_lowercase(),
107 locator,
108 });
109 }
110 Ok(Self {
111 scheme: scheme_part.to_ascii_lowercase(),
112 locator: input.to_string(),
113 })
114 }
115}