docker_image_pusher/
config.rs

1//! Configuration structures and utilities
2
3use serde::{Deserialize, Serialize};
4use crate::error::{Result, PusherError};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AuthConfig {
8    pub username: String,
9    pub password: String,
10}
11
12impl AuthConfig {
13    pub fn new(username: String, password: String) -> Self {
14        Self { username, password }
15    }
16
17    pub fn from_optional(username: Option<String>, password: Option<String>) -> Result<Self> {
18        match (username, password) {
19            (Some(u), Some(p)) => Ok(Self::new(u, p)),
20            (None, _) => Err(PusherError::Config("Username is required for authentication".to_string())),
21            (_, None) => Err(PusherError::Config("Password is required for authentication".to_string())),
22        }
23    }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct RegistryConfig {
28    pub address: String,
29    pub repository: String,
30    pub tag: String,
31    pub auth: Option<AuthConfig>,
32    pub skip_tls: bool,
33    pub timeout: u64,
34}
35
36impl RegistryConfig {
37    pub fn new(address: String, repository: String, tag: String) -> Self {
38        Self {
39            address,
40            repository,
41            tag,
42            auth: None,
43            skip_tls: false,
44            timeout: 7200, // 2 hours default
45        }
46    }
47
48    pub fn with_auth(mut self, auth: AuthConfig) -> Self {
49        self.auth = Some(auth);
50        self
51    }
52
53    pub fn with_skip_tls(mut self, skip_tls: bool) -> Self {
54        self.skip_tls = skip_tls;
55        self
56    }
57
58    pub fn with_timeout(mut self, timeout: u64) -> Self {
59        self.timeout = timeout;
60        self
61    }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct AppConfig {
66    pub registry: RegistryConfig,
67    pub file_path: String,
68    pub large_layer_threshold: u64,
69    pub max_concurrent: usize,
70    pub retry_attempts: usize,
71    pub verbose: bool,
72    pub dry_run: bool,
73}
74
75impl AppConfig {
76    pub fn new(
77        registry_url: &str,
78        repository: &str,
79        tag: &str,
80        file_path: String,
81    ) -> Result<Self> {
82        let registry_config = RegistryConfig::new(
83            registry_url.to_string(),
84            repository.to_string(),
85            tag.to_string(),
86        );
87
88        Ok(Self {
89            registry: registry_config,
90            file_path,
91            large_layer_threshold: 1024 * 1024 * 1024, // 1GB default
92            max_concurrent: 1,
93            retry_attempts: 3,
94            verbose: false,
95            dry_run: false,
96        })
97    }
98
99    pub fn with_auth(mut self, username: String, password: String) -> Self {
100        let auth = AuthConfig::new(username, password);
101        self.registry = self.registry.with_auth(auth);
102        self
103    }
104
105    pub fn with_optional_auth(mut self, username: Option<String>, password: Option<String>) -> Result<Self> {
106        if let (Some(u), Some(p)) = (username, password) {
107            self = self.with_auth(u, p);
108        }
109        Ok(self)
110    }
111
112    pub fn has_auth(&self) -> bool {
113        self.registry.auth.is_some()
114    }
115
116    pub fn with_large_layer_threshold(mut self, threshold: u64) -> Self {
117        self.large_layer_threshold = threshold;
118        self
119    }
120
121    pub fn with_timeout(mut self, timeout: u64) -> Self {
122        self.registry = self.registry.with_timeout(timeout);
123        self
124    }
125
126    pub fn with_skip_tls(mut self, skip_tls: bool) -> Self {
127        self.registry = self.registry.with_skip_tls(skip_tls);
128        self
129    }
130
131    pub fn with_verbose(mut self, verbose: bool) -> Self {
132        self.verbose = verbose;
133        self
134    }
135
136    pub fn with_dry_run(mut self, dry_run: bool) -> Self {
137        self.dry_run = dry_run;
138        self
139    }
140
141    pub fn with_max_concurrent(mut self, max_concurrent: usize) -> Self {
142        self.max_concurrent = max_concurrent;
143        self
144    }
145
146    pub fn with_retry_attempts(mut self, retry_attempts: usize) -> Self {
147        self.retry_attempts = retry_attempts;
148        self
149    }
150
151    pub fn parse_repository_url(url: &str) -> Result<(String, String, String)> {
152        let parsed_url = url::Url::parse(url)
153            .map_err(|e| PusherError::Config(format!("Invalid repository URL: {}", e)))?;
154
155        let registry_address = format!("{}://{}", 
156            parsed_url.scheme(), 
157            parsed_url.host_str().unwrap_or("localhost"));
158
159        let path = parsed_url.path().trim_start_matches('/');
160        let (repository, tag) = if let Some(colon_pos) = path.rfind(':') {
161            let (repo, tag_part) = path.split_at(colon_pos);
162            (repo, &tag_part[1..]) // Remove the ':' prefix
163        } else {
164            (path, "latest")
165        };
166
167        Ok((registry_address, repository.to_string(), tag.to_string()))
168    }
169}