docker_image_pusher/
config.rs

1//! Configuration module for managing application settings and URL parsing
2
3use anyhow::{Result, anyhow};
4use serde::{Deserialize, Serialize};
5use std::env;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RegistryConfig {
9    pub url: String,
10    pub repository: String,
11    pub tag: String,
12    pub skip_tls: bool,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct AuthConfig {
17    pub username: Option<String>,
18    pub password: Option<String>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct UploadConfig {
23    pub chunk_size: usize,
24    pub concurrency: usize,
25    pub verbose: bool,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct AppConfig {
30    pub registry: RegistryConfig,
31    pub auth: AuthConfig,
32    pub upload: UploadConfig,
33    pub tar_file_path: String,
34}
35
36impl AppConfig {
37    pub fn new(
38        repository_url: String,
39        tar_file_path: String,
40        username: Option<String>,
41        password: Option<String>,
42        chunk_size: usize,
43        concurrency: usize,
44        skip_tls: bool,
45        verbose: bool,
46    ) -> Result<Self> {
47        let registry = RegistryConfig::parse_url(&repository_url, skip_tls)?;
48        let auth = AuthConfig { username, password };
49        let upload = UploadConfig { chunk_size, concurrency, verbose };
50
51        Ok(AppConfig {
52            registry,
53            auth,
54            upload,
55            tar_file_path,
56        })
57    }
58
59    pub fn has_auth(&self) -> bool {
60        self.auth.username.is_some() && self.auth.password.is_some()
61    }
62}
63
64impl RegistryConfig {
65    pub fn parse_url(url: &str, skip_tls: bool) -> Result<Self> {
66        // Parse URL like: https://harbortest.sinosig.com/g480_mqmanage/dufs:v1
67        let (protocol, remaining) = if let Some(pos) = url.find("://") {
68            (&url[..pos + 3], &url[pos + 3..])
69        } else {
70            ("https://", url)
71        };
72
73        let (host, path) = if let Some(pos) = remaining.find('/') {
74            (&remaining[..pos], &remaining[pos + 1..])
75        } else {
76            return Err(anyhow!("Invalid repository URL format. Expected: https://registry/project/repo:tag"));
77        };
78
79        let registry_url = format!("{}{}", protocol, host);
80
81        let (repository, tag) = if let Some(colon_pos) = path.rfind(':') {
82            (&path[..colon_pos], &path[colon_pos + 1..])
83        } else {
84            (path, "latest")
85        };
86
87        if repository.is_empty() {
88            return Err(anyhow!("Repository name cannot be empty"));
89        }
90
91        Ok(RegistryConfig {
92            url: registry_url,
93            repository: repository.to_string(),
94            tag: tag.to_string(),
95            skip_tls,
96        })
97    }
98}
99
100#[derive(Debug)]
101pub struct Config {
102    pub registry_address: String,
103    pub username: Option<String>,
104    pub password: Option<String>,
105    pub project: Option<String>,
106    pub skip_tls: bool,
107    pub chunk_size: usize,
108}
109
110impl Config {
111    pub fn new() -> Result<Self, &'static str> {
112        let registry_address = env::var("REGISTRY_ADDRESS").map_err(|_| "REGISTRY_ADDRESS not set")?;
113        let username = env::var("REGISTRY_USERNAME").ok();
114        let password = env::var("REGISTRY_PASSWORD").ok();
115        let project = env::var("REGISTRY_PROJECT").ok();
116        let skip_tls = env::var("SKIP_TLS").map_or(false, |v| v == "true");
117        let chunk_size = env::var("CHUNK_SIZE")
118            .map(|v| v.parse::<usize>().unwrap_or(1048576)) // Default to 1MB
119            .unwrap_or(1048576); // Default to 1MB
120
121        Ok(Config {
122            registry_address,
123            username,
124            password,
125            project,
126            skip_tls,
127            chunk_size,
128        })
129    }
130}