runner_core/
env.rs

1use std::env;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use anyhow::{Context, Result, anyhow, bail};
6use url::Url;
7
8/// Environment-driven configuration for pack management.
9#[derive(Debug, Clone)]
10pub struct PackConfig {
11    pub source: PackSource,
12    pub index_location: IndexLocation,
13    pub cache_dir: PathBuf,
14    pub public_key: Option<String>,
15}
16
17impl PackConfig {
18    /// Build a [`PackConfig`] by reading the documented PACK_* variables.
19    pub fn from_env() -> Result<Self> {
20        let source = env::var("PACK_SOURCE")
21            .ok()
22            .map(|value| PackSource::from_str(&value))
23            .transpose()?
24            .unwrap_or(PackSource::Fs);
25
26        let index_raw = env::var("PACK_INDEX_URL")
27            .context("PACK_INDEX_URL is required to locate the pack index")?;
28        let index_location = IndexLocation::from_value(&index_raw)?;
29
30        let cache_dir = env::var("PACK_CACHE_DIR")
31            .map(PathBuf::from)
32            .unwrap_or_else(|_| PathBuf::from(".packs"));
33
34        let public_key = env::var("PACK_PUBLIC_KEY").ok();
35
36        Ok(Self {
37            source,
38            index_location,
39            cache_dir,
40            public_key,
41        })
42    }
43}
44
45/// Location of the pack index document (supports file paths and HTTP/S URLs).
46#[derive(Debug, Clone)]
47pub enum IndexLocation {
48    File(PathBuf),
49    Remote(Url),
50}
51
52impl IndexLocation {
53    pub fn from_value(value: &str) -> Result<Self> {
54        if value.starts_with("http://") || value.starts_with("https://") {
55            let url = Url::parse(value).context("PACK_INDEX_URL is not a valid URL")?;
56            return Ok(Self::Remote(url));
57        }
58        if value.starts_with("file://") {
59            let url = Url::parse(value).context("PACK_INDEX_URL is not a valid file:// URL")?;
60            let path = url
61                .to_file_path()
62                .map_err(|_| anyhow!("PACK_INDEX_URL points to an invalid file URI"))?;
63            return Ok(Self::File(path));
64        }
65        Ok(Self::File(PathBuf::from(value)))
66    }
67
68    pub fn display(&self) -> String {
69        match self {
70            Self::File(path) => path.display().to_string(),
71            Self::Remote(url) => url.to_string(),
72        }
73    }
74}
75
76/// Supported default sources for packs when the index omits the URI scheme.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum PackSource {
79    Fs,
80    Http,
81    Oci,
82    S3,
83    Gcs,
84    AzBlob,
85}
86
87impl PackSource {
88    pub fn scheme(self) -> &'static str {
89        match self {
90            Self::Fs => "fs",
91            Self::Http => "http",
92            Self::Oci => "oci",
93            Self::S3 => "s3",
94            Self::Gcs => "gcs",
95            Self::AzBlob => "azblob",
96        }
97    }
98}
99
100impl FromStr for PackSource {
101    type Err = anyhow::Error;
102
103    fn from_str(value: &str) -> Result<Self> {
104        match value.to_ascii_lowercase().as_str() {
105            "fs" => Ok(Self::Fs),
106            "http" | "https" => Ok(Self::Http),
107            "oci" => Ok(Self::Oci),
108            "s3" => Ok(Self::S3),
109            "gcs" => Ok(Self::Gcs),
110            "azblob" | "azure" | "azureblob" => Ok(Self::AzBlob),
111            other => bail!("unsupported PACK_SOURCE `{other}`"),
112        }
113    }
114}