docker_image_pusher/cli/
args.rs1use clap::Parser;
4
5#[derive(Parser)]
6#[command(name = "docker-image-pusher")]
7#[command(about = "A tool to push Docker image tar packages to registries")]
8#[command(version, author)]
9pub struct Args {
10 #[arg(
12 long = "repository-url",
13 short = 'r',
14 help = "Full repository URL including registry, project, and tag"
15 )]
16 pub repository_url: String,
17
18 #[arg(
20 long = "file",
21 short = 'f',
22 help = "Path to the Docker image tar file"
23 )]
24 pub file: String,
25
26 #[arg(
28 long = "username",
29 short = 'u',
30 help = "Username for registry authentication"
31 )]
32 pub username: Option<String>,
33
34 #[arg(
36 long = "password",
37 short = 'p',
38 help = "Password for registry authentication"
39 )]
40 pub password: Option<String>,
41
42 #[arg(
44 long = "chunk-size",
45 short = 'c',
46 default_value = "10485760",
47 help = "Chunk size for upload in bytes"
48 )]
49 pub chunk_size: usize,
50
51 #[arg(
53 long = "concurrency",
54 short = 'j',
55 default_value = "4",
56 help = "Number of concurrent upload workers"
57 )]
58 pub concurrency: usize,
59
60 #[arg(
62 long = "skip-tls",
63 short = 'k',
64 default_value = "false",
65 help = "Skip TLS certificate verification"
66 )]
67 pub skip_tls: bool,
68
69 #[arg(
71 long = "verbose",
72 short = 'v',
73 help = "Enable verbose output"
74 )]
75 pub verbose: bool,
76
77 #[arg(
79 long = "timeout",
80 short = 't',
81 default_value = "300",
82 help = "Timeout for network operations in seconds"
83 )]
84 pub timeout: u64,
85
86 #[arg(
88 long = "retry",
89 default_value = "3",
90 help = "Number of retry attempts for failed operations"
91 )]
92 pub retry: usize,
93
94 #[arg(
96 long = "registry-type",
97 default_value = "auto",
98 help = "Registry type: auto, docker, harbor, aws, gcp, azure"
99 )]
100 pub registry_type: String,
101
102 #[arg(
104 long = "force",
105 help = "Force overwrite existing image"
106 )]
107 pub force: bool,
108
109 #[arg(
111 long = "dry-run",
112 short = 'n',
113 help = "Perform a dry run without actually uploading"
114 )]
115 pub dry_run: bool,
116
117 #[arg(
119 long = "output",
120 short = 'o',
121 default_value = "text",
122 help = "Output format: text, json, yaml"
123 )]
124 pub output: String,
125
126 #[arg(
128 long = "config",
129 help = "Path to configuration file"
130 )]
131 pub config: Option<String>,
132}
133
134impl Args {
135 pub fn parse_args() -> Self {
136 Args::parse()
137 }
138
139 pub fn validate(&self) -> Result<(), String> {
141 if !std::path::Path::new(&self.file).exists() {
143 return Err(format!("File does not exist: {}", self.file));
144 }
145
146 if !self.repository_url.starts_with("http://") && !self.repository_url.starts_with("https://") {
148 return Err("Repository URL must start with http:// or https://".to_string());
149 }
150
151 if self.chunk_size == 0 {
153 return Err("Chunk size must be greater than 0".to_string());
154 }
155
156 if self.concurrency == 0 {
158 return Err("Concurrency must be greater than 0".to_string());
159 }
160
161 if self.timeout == 0 {
163 return Err("Timeout must be greater than 0".to_string());
164 }
165
166 match self.output.as_str() {
168 "text" | "json" | "yaml" => {}
169 _ => return Err("Output format must be one of: text, json, yaml".to_string()),
170 }
171
172 match self.registry_type.as_str() {
174 "auto" | "docker" | "harbor" | "aws" | "gcp" | "azure" => {}
175 _ => return Err("Registry type must be one of: auto, docker, harbor, aws, gcp, azure".to_string()),
176 }
177
178 Ok(())
179 }
180
181 pub fn print_examples() {
183 println!("Examples:");
184 println!(" # Basic usage with short options");
185 println!(" docker-image-pusher -r https://registry.example.com/myproject/myimage:v1.0 -f image.tar");
186 println!();
187 println!(" # With authentication using short options");
188 println!(" docker-image-pusher -r https://harbor.example.com/project/app:latest \\");
189 println!(" -f app.tar -u myuser -p mypassword");
190 println!();
191 println!(" # With custom settings using long options");
192 println!(" docker-image-pusher --repository-url https://registry.example.com/project/app:v2.0 \\");
193 println!(" --file app.tar --username admin --password secret \\");
194 println!(" --chunk-size 5242880 --concurrency 8 --verbose");
195 println!();
196 println!(" # Dry run to validate without uploading");
197 println!(" docker-image-pusher -r https://registry.example.com/test/app:latest \\");
198 println!(" -f test.tar --dry-run --verbose");
199 println!();
200 println!(" # Skip TLS verification for self-signed certificates");
201 println!(" docker-image-pusher -r https://internal-registry.com/app:latest \\");
202 println!(" -f app.tar -u user -p pass --skip-tls");
203 println!();
204 println!(" # Using environment variables for sensitive data");
205 println!(" export DOCKER_PUSHER_USERNAME=myuser");
206 println!(" export DOCKER_PUSHER_PASSWORD=mypassword");
207 println!(" docker-image-pusher -r https://registry.example.com/app:latest -f app.tar");
208 }
209
210 pub fn from_env(mut self) -> Self {
212 if self.username.is_none() {
213 self.username = std::env::var("DOCKER_PUSHER_USERNAME").ok();
214 }
215
216 if self.password.is_none() {
217 self.password = std::env::var("DOCKER_PUSHER_PASSWORD").ok();
218 }
219
220 if let Ok(timeout) = std::env::var("DOCKER_PUSHER_TIMEOUT") {
222 if let Ok(t) = timeout.parse() {
223 self.timeout = t;
224 }
225 }
226
227 if let Ok(chunk_size) = std::env::var("DOCKER_PUSHER_CHUNK_SIZE") {
228 if let Ok(c) = chunk_size.parse() {
229 self.chunk_size = c;
230 }
231 }
232
233 if let Ok(concurrency) = std::env::var("DOCKER_PUSHER_CONCURRENCY") {
234 if let Ok(c) = concurrency.parse() {
235 self.concurrency = c;
236 }
237 }
238
239 if std::env::var("DOCKER_PUSHER_VERBOSE").is_ok() {
240 self.verbose = true;
241 }
242
243 if std::env::var("DOCKER_PUSHER_SKIP_TLS").is_ok() {
244 self.skip_tls = true;
245 }
246
247 self
248 }
249}