docker_image_pusher/cli/
config.rs

1//! Configuration management module
2
3use crate::error::{RegistryError, Result};
4use serde::{Deserialize, Serialize};
5
6/// Authentication configuration
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AuthConfig {
9    pub username: String,
10    pub password: String,
11}
12
13impl AuthConfig {
14    pub fn new(username: String, password: String) -> Self {
15        Self { username, password }
16    }
17
18    pub fn validate(&self) -> Result<()> {
19        if self.username.is_empty() {
20            return Err(RegistryError::Validation(
21                "Username cannot be empty".to_string(),
22            ));
23        }
24        if self.password.is_empty() {
25            return Err(RegistryError::Validation(
26                "Password cannot be empty".to_string(),
27            ));
28        }
29        Ok(())
30    }
31}
32
33/// Registry configuration
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct RegistryConfig {
36    pub address: String,
37    pub skip_tls: bool,
38    pub timeout: u64,
39}
40
41impl RegistryConfig {
42    pub fn new(address: String) -> Self {
43        Self {
44            address,
45            skip_tls: false,
46            timeout: 7200,
47        }
48    }
49
50    pub fn with_skip_tls(mut self, skip_tls: bool) -> Self {
51        self.skip_tls = skip_tls;
52        self
53    }
54
55    pub fn with_timeout(mut self, timeout: u64) -> Self {
56        self.timeout = timeout;
57        self
58    }
59
60    pub fn validate(&self) -> Result<()> {
61        if self.address.is_empty() {
62            return Err(RegistryError::Validation(
63                "Registry address cannot be empty".to_string(),
64            ));
65        }
66
67        if !self.address.starts_with("http://") && !self.address.starts_with("https://") {
68            return Err(RegistryError::Validation(format!(
69                "Invalid registry address: {}. Must start with http:// or https://",
70                self.address
71            )));
72        }
73
74        if self.timeout == 0 {
75            return Err(RegistryError::Validation(
76                "Timeout must be greater than 0".to_string(),
77            ));
78        }
79
80        Ok(())
81    }
82}
83
84/// Application configuration
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct AppConfig {
87    pub cache_dir: String,
88    pub timeout: u64,
89    pub max_concurrent: usize,
90    pub retry_attempts: usize,
91    pub large_layer_threshold: u64,
92    pub skip_tls: bool,
93    pub verbose: bool,
94}
95
96impl Default for AppConfig {
97    fn default() -> Self {
98        Self {
99            cache_dir: ".cache".to_string(),
100            timeout: 7200,
101            max_concurrent: 1,
102            retry_attempts: 3,
103            large_layer_threshold: 1024 * 1024 * 1024, // 1GB
104            skip_tls: false,
105            verbose: false,
106        }
107    }
108}
109
110impl AppConfig {
111    pub fn validate(&self) -> Result<()> {
112        if self.max_concurrent == 0 {
113            return Err(RegistryError::Validation(
114                "max_concurrent must be greater than 0".to_string(),
115            ));
116        }
117        if self.timeout == 0 {
118            return Err(RegistryError::Validation(
119                "timeout must be greater than 0".to_string(),
120            ));
121        }
122        if self.large_layer_threshold == 0 {
123            return Err(RegistryError::Validation(
124                "large_layer_threshold must be greater than 0".to_string(),
125            ));
126        }
127        if self.retry_attempts == 0 {
128            return Err(RegistryError::Validation(
129                "retry_attempts must be greater than 0".to_string(),
130            ));
131        }
132        Ok(())
133    }
134
135    /// Create config from environment variables and defaults
136    pub fn from_env() -> Self {
137        let mut config = Self::default();
138
139        if let Ok(val) = std::env::var("DOCKER_PUSHER_CACHE_DIR") {
140            config.cache_dir = val;
141        }
142        if let Ok(val) = std::env::var("DOCKER_PUSHER_TIMEOUT") {
143            if let Ok(timeout) = val.parse() {
144                config.timeout = timeout;
145            }
146        }
147        if let Ok(val) = std::env::var("DOCKER_PUSHER_MAX_CONCURRENT") {
148            if let Ok(max_concurrent) = val.parse() {
149                config.max_concurrent = max_concurrent;
150            }
151        }
152        if let Ok(val) = std::env::var("DOCKER_PUSHER_VERBOSE") {
153            config.verbose = val.to_lowercase() == "true" || val == "1";
154        }
155
156        config
157    }
158
159    /// Merge with another config, preferring non-default values
160    pub fn merge(mut self, other: &AppConfig) -> Self {
161        // Only override if the other value is not the default
162        let default = AppConfig::default();
163
164        if other.cache_dir != default.cache_dir {
165            self.cache_dir = other.cache_dir.clone();
166        }
167        if other.timeout != default.timeout {
168            self.timeout = other.timeout;
169        }
170        if other.max_concurrent != default.max_concurrent {
171            self.max_concurrent = other.max_concurrent;
172        }
173        if other.retry_attempts != default.retry_attempts {
174            self.retry_attempts = other.retry_attempts;
175        }
176        if other.large_layer_threshold != default.large_layer_threshold {
177            self.large_layer_threshold = other.large_layer_threshold;
178        }
179        if other.skip_tls != default.skip_tls {
180            self.skip_tls = other.skip_tls;
181        }
182        if other.verbose != default.verbose {
183            self.verbose = other.verbose;
184        }
185
186        self
187    }
188}