docker_image_pusher/cli/
args.rs

1//! Command-line argument parsing
2
3use 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    /// Repository URL (e.g., https://harbortest.sinosig.com/g480_mqmanage/dufs:v1)
11    #[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    /// Path to the Docker image tar file
19    #[arg(
20        long = "file",
21        short = 'f',
22        help = "Path to the Docker image tar file"
23    )]
24    pub file: String,
25
26    /// Registry username
27    #[arg(
28        long = "username",
29        short = 'u',
30        help = "Username for registry authentication"
31    )]
32    pub username: Option<String>,
33
34    /// Registry password
35    #[arg(
36        long = "password",
37        short = 'p',
38        help = "Password for registry authentication"
39    )]
40    pub password: Option<String>,
41
42    /// Chunk size for upload (default: 10MB)
43    #[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    /// Number of concurrent uploads
52    #[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    /// Skip TLS verification
61    #[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    /// Verbose output
70    #[arg(
71        long = "verbose",
72        short = 'v',
73        help = "Enable verbose output"
74    )]
75    pub verbose: bool,
76
77    /// Timeout in seconds for network operations
78    #[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    /// Retry attempts for failed operations
87    #[arg(
88        long = "retry",
89        default_value = "3",
90        help = "Number of retry attempts for failed operations"
91    )]
92    pub retry: usize,
93
94    /// Registry type (docker, harbor, etc.)
95    #[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    /// Force overwrite existing image
103    #[arg(
104        long = "force",
105        help = "Force overwrite existing image"
106    )]
107    pub force: bool,
108
109    /// Dry run mode (validate without uploading)
110    #[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    /// Output format for results
118    #[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    /// Configuration file path
127    #[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    /// Validate arguments
140    pub fn validate(&self) -> Result<(), String> {
141        // Validate file exists
142        if !std::path::Path::new(&self.file).exists() {
143            return Err(format!("File does not exist: {}", self.file));
144        }
145
146        // Validate repository URL format
147        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        // Validate chunk size
152        if self.chunk_size == 0 {
153            return Err("Chunk size must be greater than 0".to_string());
154        }
155
156        // Validate concurrency
157        if self.concurrency == 0 {
158            return Err("Concurrency must be greater than 0".to_string());
159        }
160
161        // Validate timeout
162        if self.timeout == 0 {
163            return Err("Timeout must be greater than 0".to_string());
164        }
165
166        // Validate output format
167        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        // Validate registry type
173        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    /// Print usage examples
182    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    /// Load configuration from environment variables
211    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        // Override with environment variables if present
221        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}