1use 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, }
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, 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..]) } else {
164 (path, "latest")
165 };
166
167 Ok((registry_address, repository.to_string(), tag.to_string()))
168 }
169}