1use greentic_config_types::PathsConfig;
2use std::path::{Path, PathBuf};
3use std::str::FromStr;
4
5use anyhow::{Context, Result, anyhow, bail};
6use url::Url;
7
8#[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 pub network: Option<greentic_config_types::NetworkConfig>,
16}
17
18impl PackConfig {
19 pub fn default_for_paths(paths: &PathsConfig) -> Result<Self> {
21 let cache_dir = paths.cache_dir.join("packs");
22 let default_index = paths.greentic_root.join("index.json");
23 let index_location = if default_index.exists() {
24 IndexLocation::File(default_index)
25 } else if Path::new("examples/index.json").exists() {
26 IndexLocation::File(PathBuf::from("examples/index.json"))
27 } else {
28 IndexLocation::File(default_index)
29 };
30 Ok(Self {
31 source: PackSource::Fs,
32 index_location,
33 cache_dir,
34 public_key: None,
35 network: None,
36 })
37 }
38
39 pub fn from_packs(cfg: &greentic_config_types::PacksConfig) -> Result<Self> {
41 let index_location = match &cfg.source {
42 greentic_config_types::PackSourceConfig::LocalIndex { path } => {
43 IndexLocation::File(path.clone())
44 }
45 greentic_config_types::PackSourceConfig::HttpIndex { url } => {
46 IndexLocation::from_value(url)?
47 }
48 greentic_config_types::PackSourceConfig::OciRegistry { reference } => {
49 IndexLocation::from_value(reference)?
50 }
51 };
52 let public_key = cfg
53 .trust
54 .as_ref()
55 .and_then(|trust| trust.public_keys.first().cloned());
56 Ok(Self {
57 source: PackSource::Fs,
58 index_location,
59 cache_dir: cfg.cache_dir.clone(),
60 public_key,
61 network: None,
62 })
63 }
64}
65
66#[derive(Debug, Clone)]
68pub enum IndexLocation {
69 File(PathBuf),
70 Remote(Url),
71}
72
73impl IndexLocation {
74 pub fn from_value(value: &str) -> Result<Self> {
75 if value.starts_with("http://") || value.starts_with("https://") {
76 let url = Url::parse(value).context("PACK_INDEX_URL is not a valid URL")?;
77 return Ok(Self::Remote(url));
78 }
79 if value.starts_with("file://") {
80 let url = Url::parse(value).context("PACK_INDEX_URL is not a valid file:// URL")?;
81 let path = url
82 .to_file_path()
83 .map_err(|_| anyhow!("PACK_INDEX_URL points to an invalid file URI"))?;
84 return Ok(Self::File(path));
85 }
86 Ok(Self::File(PathBuf::from(value)))
87 }
88
89 pub fn display(&self) -> String {
90 match self {
91 Self::File(path) => path.display().to_string(),
92 Self::Remote(url) => url.to_string(),
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum PackSource {
100 Fs,
101 Http,
102 Oci,
103 S3,
104 Gcs,
105 AzBlob,
106}
107
108impl PackSource {
109 pub fn scheme(self) -> &'static str {
110 match self {
111 Self::Fs => "fs",
112 Self::Http => "http",
113 Self::Oci => "oci",
114 Self::S3 => "s3",
115 Self::Gcs => "gcs",
116 Self::AzBlob => "azblob",
117 }
118 }
119}
120
121impl FromStr for PackSource {
122 type Err = anyhow::Error;
123
124 fn from_str(value: &str) -> Result<Self> {
125 match value.to_ascii_lowercase().as_str() {
126 "fs" => Ok(Self::Fs),
127 "http" | "https" => Ok(Self::Http),
128 "oci" => Ok(Self::Oci),
129 "s3" => Ok(Self::S3),
130 "gcs" => Ok(Self::Gcs),
131 "azblob" | "azure" | "azureblob" => Ok(Self::AzBlob),
132 other => bail!("unsupported PACK_SOURCE `{other}`"),
133 }
134 }
135}