1use serde::Deserialize;
2use anyhow::Context;
3
4#[derive(Debug, Clone, Deserialize)]
5pub struct ServerConfig {
6 #[serde(default = "default_port")]
7 pub port: u16,
8 #[serde(default)]
9 pub read_only: bool,
10}
11
12fn default_port() -> u16 { 8080 }
13
14#[derive(Debug, Clone, Deserialize)]
15#[serde(tag = "type", rename_all = "lowercase")]
16pub enum StorageConfig {
17 S3 {
18 endpoint: String,
19 bucket: String,
20 access_key: String,
21 secret_key: String,
22 #[serde(default = "default_region")]
23 region: String,
24 },
25 Filesystem {
26 path: String,
27 },
28}
29
30fn default_region() -> String { "us-east-1".to_string() }
31
32#[derive(Debug, Clone, Deserialize)]
33pub struct DatabaseConfig {
34 pub path: String,
35}
36
37#[derive(Debug, Clone, Deserialize)]
38pub struct AuthConfig {
39 #[serde(default)]
40 pub pull_requires_auth: bool,
41 pub admin_token: String,
42}
43
44#[derive(Debug, Clone, Deserialize)]
45pub struct LogConfig {
46 #[serde(default = "default_log_level")]
47 pub level: String,
48}
49
50impl Default for LogConfig {
51 fn default() -> Self {
52 Self { level: default_log_level() }
53 }
54}
55
56fn default_log_level() -> String { "info".to_string() }
57
58#[derive(Debug, Clone, Deserialize)]
59pub struct RegistryConfig {
60 pub server: ServerConfig,
61 pub storage: StorageConfig,
62 pub database: DatabaseConfig,
63 pub auth: AuthConfig,
64 #[serde(default)]
65 pub log: LogConfig,
66}
67
68impl RegistryConfig {
69 pub fn load(path: &str) -> anyhow::Result<Self> {
70 let content = std::fs::read_to_string(path)
71 .with_context(|| format!("cannot read config file: {}", path))?;
72 serde_yaml::from_str(&content)
73 .with_context(|| format!("cannot parse config file: {}", path))
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_parse_filesystem_config() {
83 let yaml = r#"
84server:
85 port: 9090
86 read_only: false
87storage:
88 type: filesystem
89 path: /tmp/layers
90database:
91 path: /tmp/registry.db
92auth:
93 pull_requires_auth: false
94 admin_token: "phrt_test"
95"#;
96 let cfg: RegistryConfig = serde_yaml::from_str(yaml).unwrap();
97 assert_eq!(cfg.server.port, 9090);
98 assert!(!cfg.server.read_only);
99 match &cfg.storage {
100 StorageConfig::Filesystem { path } => assert_eq!(path, "/tmp/layers"),
101 _ => panic!("expected filesystem storage"),
102 }
103 assert_eq!(cfg.auth.admin_token, "phrt_test");
104 }
105
106 #[test]
107 fn test_parse_s3_config() {
108 let yaml = r#"
109server:
110 port: 8080
111storage:
112 type: s3
113 endpoint: http://minio:9000
114 bucket: prompthub
115 access_key: admin
116 secret_key: secret
117database:
118 path: /data/registry.db
119auth:
120 pull_requires_auth: true
121 admin_token: "phrt_admin"
122"#;
123 let cfg: RegistryConfig = serde_yaml::from_str(yaml).unwrap();
124 match &cfg.storage {
125 StorageConfig::S3 { endpoint, bucket, .. } => {
126 assert_eq!(endpoint, "http://minio:9000");
127 assert_eq!(bucket, "prompthub");
128 }
129 _ => panic!("expected s3 storage"),
130 }
131 assert!(cfg.auth.pull_requires_auth);
132 }
133}