1use crate::Config;
2use crate::callback::event_manager::EventManager;
3use crate::callback::filter_manager::FilterManager;
4use crate::callback::preprocess_manager::PreprocessManager;
5#[allow(unused_imports)]
6use crate::config::args::value_parser::{
7 canned_acl, checksum_algorithm, file_exist, human_bytes, metadata, sse, storage_class,
8 storage_path, tagging, url,
9};
10use crate::config::{
11 CLITimeoutConfig, ClientConfig, FilterConfig, ForceRetryConfig, RetryConfig, TracingConfig,
12 TransferConfig,
13};
14#[allow(unused_imports)]
15use crate::types::event_callback::EventType;
16use crate::types::{
17 AccessKeys, ClientConfigLocation, S3Credentials, SseCustomerKey, SseKmsKeyId, StoragePath,
18};
19use aws_sdk_s3::types::{
20 ChecksumAlgorithm, ChecksumMode, ObjectCannedAcl, RequestPayer, ServerSideEncryption,
21 StorageClass,
22};
23use aws_smithy_types::checksum_config::RequestChecksumCalculation;
24use cfg_if::cfg_if;
25use chrono::{DateTime, Utc};
26use clap::Parser;
27use clap::builder::{ArgPredicate, NonEmptyStringValueParser};
28use clap_verbosity_flag::{Verbosity, WarnLevel};
29use fancy_regex::Regex;
30#[cfg(feature = "version")]
31use shadow_rs::shadow;
32use std::ffi::OsString;
33use std::path::PathBuf;
34use std::str::FromStr;
35use std::sync::Arc;
36use tokio::sync::Semaphore;
37
38mod tests;
39mod value_parser;
40
41const EXPRESS_ONEZONE_STORAGE_SUFFIX: &str = "--x-s3";
42
43const DEFAULT_WORKER_SIZE: u16 = 16;
44const DEFAULT_AWS_MAX_ATTEMPTS: u32 = 10;
45const DEFAULT_FORCE_RETRY_COUNT: u32 = 5;
46const DEFAULT_FORCE_RETRY_INTERVAL_MILLISECONDS: u64 = 1000;
47const DEFAULT_INITIAL_BACKOFF_MILLISECONDS: u64 = 100;
48const DEFAULT_JSON_TRACING: bool = false;
49const DEFAULT_AWS_SDK_TRACING: bool = false;
50const DEFAULT_SPAN_EVENTS_TRACING: bool = false;
51const DEFAULT_DISABLE_COLOR_TRACING: bool = false;
52const DEFAULT_MULTIPART_THRESHOLD: &str = "8MiB";
53const DEFAULT_MULTIPART_CHUNKSIZE: &str = "8MiB";
54const DEFAULT_AUTO_CHUNKSIZE: bool = false;
55const DEFAULT_NO_SYNC_SYSTEM_METADATA: bool = false;
56const DEFAULT_NO_SYNC_USER_DEFINED_METADATA: bool = false;
57const DEFAULT_WARN_AS_ERROR: bool = false;
58const DEFAULT_IGNORE_SYMLINKS: bool = false;
59const DEFAULT_FORCE_PATH_STYLE: bool = false;
60const DEFAULT_HEAD_EACH_TARGET: bool = false;
61const DEFAULT_ENABLE_VERSIONING: bool = false;
62const DEFAULT_REMOVE_MODIFIED_FILTER: bool = false;
63const DEFAULT_CHECK_SIZE: bool = false;
64const DEFAULT_CHECK_ETAG: bool = false;
65const DEFAULT_CHECK_MTIME_AND_ETAG: bool = false;
66const DEFAULT_SYNC_WITH_DELETE: bool = false;
67const DEFAULT_DISABLE_TAGGING: bool = false;
68const DEFAULT_SYNC_LATEST_TAGGING: bool = false;
69const DEFAULT_NO_GUESS_MIME_TYPE: bool = false;
70const DEFAULT_SERVER_SIDE_COPY: bool = false;
71const DEFAULT_DISABLE_MULTIPART_VERIFY: bool = false;
72const DEFAULT_DISABLE_ETAG_VERIFY: bool = false;
73const DEFAULT_DISABLE_ADDITIONAL_CHECKSUM_VERIFY: bool = false;
74const DEFAULT_ENABLE_ADDITIONAL_CHECKSUM: bool = false;
75const DEFAULT_DRY_RUN: bool = false;
76const DEFAULT_MAX_KEYS: i32 = 1000;
77const DEFAULT_PUT_LAST_MODIFIED_METADATA: bool = false;
78const DEFAULT_DISABLE_STALLED_STREAM_PROTECTION: bool = false;
79const DEFAULT_DISABLE_PAYLOAD_SIGNING: bool = false;
80const DEFAULT_DISABLE_CONTENT_MD5_HEADER: bool = false;
81const DEFAULT_DELETE_EXCLUDED: bool = false;
82const DEFAULT_FULL_OBJECT_CHECKSUM: bool = false;
83const DEFAULT_DISABLE_EXPRESS_ONE_ZONE_ADDITIONAL_CHECKSUM: bool = false;
84const DEFAULT_MAX_PARALLEL_MULTIPART_UPLOADS: u16 = 16;
85const DEFAULT_MAX_PARALLEL_LISTINGS: u16 = 16;
86const DEFAULT_OBJECT_LISTING_QUEUE_SIZE: u32 = 200000;
87const DEFAULT_PARALLEL_LISTING_MAX_DEPTH: u16 = 2;
88const DEFAULT_ALLOW_PARALLEL_LISTINGS_IN_EXPRESS_ONE_ZONE: bool = false;
89const DEFAULT_ACCELERATE: bool = false;
90const DEFAULT_REQUEST_PAYER: bool = false;
91const DEFAULT_REPORT_SYNC_STATUS: bool = false;
92const DEFAULT_REPORT_METADATA_SYNC_STATUS: bool = false;
93const DEFAULT_REPORT_TAGGING_SYNC_STATUS: bool = false;
94#[allow(dead_code)]
95const DEFAULT_ALLOW_LUA_OS_LIBRARY: bool = false;
96#[allow(dead_code)]
97const DEFAULT_ALLOW_LUA_UNSAFE_VM: bool = false;
98#[allow(dead_code)]
99const DEFAULT_LUA_VM_MEMORY_LIMIT: &str = "64MiB";
100const DEFAULT_SHOW_NO_PROGRESS: bool = false;
101const DEFAULT_IF_MATCH: bool = false;
102const DEFAULT_COPY_SOURCE_IF_MATCH: bool = false;
103const DEFAULT_IGNORE_GLACIER_WARNINGS: bool = false;
104
105const NO_S3_STORAGE_SPECIFIED: &str = "either SOURCE or TARGET must be s3://\n";
106const LOCAL_STORAGE_SPECIFIED: &str = "with --enable-versioning/--sync-latest-tagging/--copy-source-if-match, both storage must be s3://\n";
107const VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
108 "--enable-versioning is not supported with express onezone storage class\n";
109const LOCAL_STORAGE_SPECIFIED_WITH_STORAGE_CLASS: &str =
110 "with --storage-class, target storage must be s3://\n";
111const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SSE: &str =
112 "with --sse/--sse-kms-key-id, target storage must be s3://\n";
113const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACL: &str = "with --acl, target storage must be s3://\n";
114const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENABLE_ADDITIONAL_CHECKSUM: &str =
115 "with --enable-additional-checksum, source storage must be s3://\n";
116const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ADDITIONAL_CHECKSUM_ALGORITHM: &str =
117 "with --additional-checksum-algorithm, target storage must be s3://\n";
118const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_AUTO_CHUNKSIZE: &str =
119 "with --auto-chunksize, source storage must be s3://\n";
120const SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_IGNORE_SYMLINKS: &str =
121 "with --ignore-symlinks, source storage must be local storage\n";
122const SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_NO_GUESS_MIME_TYPE: &str =
123 "with --no-guess-mime-type, source storage must be local storage\n";
124const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_METADATA_OPTION: &str =
125 "with metadata related option, target storage must be s3://\n";
126const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL: &str =
127 "with --source-endpoint-url, source storage must be s3://\n";
128const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL: &str =
129 "with --target-endpoint-url, target storage must be s3://\n";
130const CHECK_SIZE_CONFLICT: &str =
131 "--head-each-target is required for --check-size, or remove --remove-modified-filter\n";
132
133const CHECK_ETAG_CONFLICT: &str =
134 "--head-each-target is required for --check-etag, or remove --remove-modified-filter\n";
135const CHECK_ETAG_CONFLICT_SSE_KMS: &str =
136 "--check-etag is not supported with --sse aws:kms | aws:kms:dsse \n";
137const CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
138 "--check-etag is not supported with express onezone storage class\n";
139
140const CHECK_MTIME_AND_ETAG_CONFLICT_SSE_KMS: &str =
141 "--check-mtime-and-etag is not supported with --sse aws:kms | aws:kms:dsse \n";
142const CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
143 "--check-mtime-and-etag is not supported with express onezone storage class\n";
144const SOURCE_LOCAL_STORAGE_FILE_WITH_DELETE_OPTION: &str =
145 "with --delete, source storage must be directory\n";
146const SOURCE_LOCAL_STORAGE_PATH_NOT_FOUND: &str = "source file/directory not found\n";
147const TARGET_LOCAL_STORAGE_INVALID: &str = "invalid target path\n";
148const SSE_KMS_KEY_ID_ARGUMENTS_CONFLICT: &str =
149 "--sse-kms-key-id must be used with --sse aws:kms | aws:kms:dsse\n";
150const LOCAL_STORAGE_SPECIFIED_WITH_SSE_C: &str =
151 "with --source-sse-c/--target-sse-c, remote storage must be s3://\n";
152const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_PAYLOAD_SIGNING: &str =
153 "with --disable-payload-signing, target storage must be s3://\n";
154const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_CONTENT_MD5_HEADER: &str =
155 "with --disable-content-md5-header, target storage must be s3://\n";
156const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_FULL_OBJECT_CHECKSUM: &str =
157 "with --full-object-checksum, target storage must be s3://\n";
158const FULL_OBJECT_CHECKSUM_NOT_SUPPORTED: &str =
159 "Only CRC32/CRC32C/CRC64NVME supports full object checksum\n";
160const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE: &str =
161 "with --source-accelerate, source storage must be s3://\n";
162const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE: &str =
163 "with --target-accelerate, target storage must be s3://\n";
164const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER: &str =
165 "with --source-request-payer, source storage must be s3://\n";
166const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER: &str =
167 "with --target-request-payer, target storage must be s3://\n";
168const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY: &str =
169 "with --server-side-copy, source storage must be s3://\n";
170const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY: &str =
171 "with --server-side-copy, target storage must be s3://\n";
172
173const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_METADATA_REGEX: &str =
174 "with --filter-include-metadata-regex, source storage must be s3://\n";
175const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_METADATA_REGEX: &str =
176 "with --filter-exclude-metadata-regex, source storage must be s3://\n";
177
178const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_TAG_REGEX: &str =
179 "with --filter-include-tag-regex, source storage must be s3://\n";
180const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_TAG_REGEX: &str =
181 "with --filter-exclude-tag-regex, source storage must be s3://\n";
182
183const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA: &str =
184 "with --no-sync-system-metadata, source storage must be s3://\n";
185const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA: &str =
186 "with --no-sync-system-metadata, target storage must be s3://\n";
187const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA: &str =
188 "with --no-sync-user-defined-metadata, source storage must be s3://\n";
189const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA: &str =
190 "with --no-sync-user-defined-metadata, target storage must be s3://\n";
191const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_POINT_IN_TIME: &str =
192 "with --point-in-time, source storage must be s3://\n";
193const POINT_IN_TIME_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
194 "--point-in-time is not supported with express onezone storage class\n";
195const POINT_IN_TIME_VALUE_INVALID: &str = "--point-in-time value is later than current time\n";
196const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS: &str =
197 "with --report-metadata-sync-status, source storage must be s3://\n";
198const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS: &str =
199 "with --report-metadata-sync-status, target storage must be s3://\n";
200const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS: &str =
201 "with --report-tagging-sync-status, source storage must be s3://\n";
202const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS: &str =
203 "with --report-tagging-sync-status, target storage must be s3://\n";
204const NO_SOURCE_CREDENTIAL_REQUIRED: &str = "no source credential required\n";
205const NO_TARGET_CREDENTIAL_REQUIRED: &str = "no target credential required\n";
206#[allow(dead_code)]
207const LUA_SCRIPT_LOAD_ERROR: &str = "Failed to load and compile Lua script callback: ";
208const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_MATCH: &str =
209 "with --if-match, target storage must be s3://\n";
210const IF_MATCH_CONFLICT: &str =
211 "--head-each-target is required for --if-match, or remove --remove-modified-filter\n";
212
213#[cfg(feature = "version")]
214shadow!(build);
215
216#[derive(Parser, Clone, Debug)]
217#[cfg_attr(feature = "version", command(version=format!("{} ({} {}), {}", build::PKG_VERSION, build::SHORT_COMMIT, build::BUILD_TARGET, build::RUST_VERSION)))]
218pub struct CLIArgs {
219 #[arg(env, help = "s3://<BUCKET_NAME>[/prefix] or local path", value_parser = storage_path::check_storage_path, default_value_if("auto_complete_shell", ArgPredicate::IsPresent, "s3://ignored"), required = false)]
220 source: String,
221
222 #[arg(env, help = "s3://<BUCKET_NAME>[/prefix] or local path", value_parser = storage_path::check_storage_path, default_value_if("auto_complete_shell", ArgPredicate::IsPresent, "s3://ignored"), required = false)]
223 target: String,
224
225 #[arg(long, env, default_value_t = DEFAULT_DRY_RUN, help_heading = "General")]
227 dry_run: bool,
228
229 #[arg(long, env, default_value_t = DEFAULT_SHOW_NO_PROGRESS, help_heading = "General")]
231 show_no_progress: bool,
232
233 #[arg(long, env, default_value_t = DEFAULT_SERVER_SIDE_COPY, help_heading = "General",
234 long_help = r#"Use server-side copy. This option is only available both source and target are S3 storage.
235It cannot work with between different object storages or regions."#)]
236 server_side_copy: bool,
237
238 #[arg(long, env, value_name = "FILE", help_heading = "AWS Configuration")]
240 aws_config_file: Option<PathBuf>,
241
242 #[arg(long, env, value_name = "FILE", help_heading = "AWS Configuration")]
244 aws_shared_credentials_file: Option<PathBuf>,
245
246 #[arg(long, env, conflicts_with_all = ["source_access_key", "source_secret_access_key", "source_session_token"], help_heading = "AWS Configuration")]
248 source_profile: Option<String>,
249
250 #[arg(long, env, conflicts_with_all = ["source_profile"], requires = "source_secret_access_key", help_heading = "AWS Configuration")]
252 source_access_key: Option<String>,
253
254 #[arg(long, env, conflicts_with_all = ["source_profile"], requires = "source_access_key", help_heading = "AWS Configuration")]
256 source_secret_access_key: Option<String>,
257
258 #[arg(long, env, conflicts_with_all = ["source_profile"], requires = "source_access_key", help_heading = "AWS Configuration")]
260 source_session_token: Option<String>,
261
262 #[arg(long, env, value_parser = NonEmptyStringValueParser::new(), help_heading = "Source Options")]
264 source_region: Option<String>,
265
266 #[arg(long, env, value_parser = url::check_scheme, help_heading = "Source Options")]
268 source_endpoint_url: Option<String>,
269
270 #[arg(long, env, default_value_t = DEFAULT_ACCELERATE, help_heading = "Source Options")]
272 source_accelerate: bool,
273
274 #[arg(long, env, default_value_t = DEFAULT_REQUEST_PAYER, help_heading = "Source Options")]
276 source_request_payer: bool,
277
278 #[arg(long, env, default_value_t = DEFAULT_FORCE_PATH_STYLE, help_heading = "Source Options")]
280 source_force_path_style: bool,
281
282 #[arg(long, env, conflicts_with_all = ["target_access_key", "target_secret_access_key", "target_session_token"], help_heading = "AWS Configuration")]
284 target_profile: Option<String>,
285
286 #[arg(long, env, conflicts_with_all = ["target_profile"], requires = "target_secret_access_key", help_heading = "AWS Configuration")]
288 target_access_key: Option<String>,
289
290 #[arg(long, env, conflicts_with_all = ["target_profile"], requires = "target_access_key", help_heading = "AWS Configuration")]
292 target_secret_access_key: Option<String>,
293
294 #[arg(long, env, conflicts_with_all = ["target_profile"], requires = "target_access_key", help_heading = "AWS Configuration")]
296 target_session_token: Option<String>,
297
298 #[arg(long, env, value_parser = NonEmptyStringValueParser::new(), help_heading = "Target Options")]
300 target_region: Option<String>,
301
302 #[arg(long, env, value_parser = url::check_scheme, help_heading = "Target Options")]
304 target_endpoint_url: Option<String>,
305
306 #[arg(long, env, default_value_t = DEFAULT_ACCELERATE, help_heading = "Target Options")]
308 target_accelerate: bool,
309
310 #[arg(long, env, default_value_t = DEFAULT_REQUEST_PAYER,help_heading = "Target Options")]
312 target_request_payer: bool,
313
314 #[arg(long, env, default_value_t = DEFAULT_FORCE_PATH_STYLE, help_heading = "Target Options")]
316 target_force_path_style: bool,
317
318 #[arg(long, env, value_parser = storage_class::parse_storage_class, help_heading = "Target Options",
319 long_help = r#"Type of storage to use for the target object.
320Valid choices: STANDARD | REDUCED_REDUNDANCY | STANDARD_IA | ONE-ZONE_IA | INTELLIGENT_TIERING | GLACIER |
321 DEEP_ARCHIVE | GLACIER_IR | EXPRESS_ONEZONE"#)]
322 storage_class: Option<String>,
323
324 #[arg(
325 long,
326 env,
327 help_heading = "Filtering",
328 long_help = r#"Sync only objects older than given time (RFC3339 datetime).
329Example: 2023-02-19T12:00:00Z"#
330 )]
331 filter_mtime_before: Option<DateTime<Utc>>,
332
333 #[arg(
334 long,
335 env,
336 help_heading = "Filtering",
337 long_help = r#"Sync only objects newer than OR EQUAL TO given time (RFC3339 datetime).
338Example: 2023-02-19T12:00:00Z"#
339 )]
340 filter_mtime_after: Option<DateTime<Utc>>,
341
342 #[arg(long, env, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Filtering", long_help=r#"Sync only objects smaller than given size.
343Allow suffixes: KB, KiB, MB, MiB, GB, GiB, TB, TiB"#)]
344 filter_smaller_size: Option<String>,
345
346 #[arg(long, env, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Filtering", long_help=r#"Sync only objects larger than OR EQUAL TO given size.
347Allow suffixes: KB, KiB, MB, MiB, GB, GiB, TB, TiB"#)]
348 filter_larger_size: Option<String>,
349
350 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering")]
352 filter_include_regex: Option<String>,
353
354 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering")]
356 filter_exclude_regex: Option<String>,
357
358 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
359 long_help = r#"Sync only objects that have Content-Type matching a given regular expression.
360If the source is local storage, Content-Type is guessed by the file extension,
361Unless --no-guess-mime-type is specified.
362It may take an extra API call to get Content-Type of the object.
363"#)]
364 filter_include_content_type_regex: Option<String>,
365
366 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
367 long_help=r#"Do not sync objects that have Content-Type matching a given regular expression.
368If the source is local storage, Content-Type is guessed by the file extension,
369Unless --no-guess-mime-type is specified.
370It may take an extra API call to get Content-Type of the object.
371"#)]
372 filter_exclude_content_type_regex: Option<String>,
373
374 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
375 long_help=r#"Sync only objects that have metadata matching a given regular expression.
376Keys(lowercase) must be sorted in alphabetical order, and comma separated.
377This filter is applied after all other filters(except tag filters).
378It may take an extra API call to get metadata of the object.
379
380Example: "key1=(value1|value2),key2=value2""#)]
381 filter_include_metadata_regex: Option<String>,
382
383 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
384 long_help=r#"Do not sync objects that have metadata matching a given regular expression.
385Keys(lowercase) must be sorted in alphabetical order, and comma separated.
386This filter is applied after all other filters(except tag filters).
387It may take an extra API call to get metadata of the object.
388
389Example: "key1=(value1|value2),key2=value2""#)]
390 filter_exclude_metadata_regex: Option<String>,
391
392 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
393 long_help=r#"Sync only objects that have tag matching a given regular expression.
394Keys must be sorted in alphabetical order, and '&' separated.
395This filter is applied after all other filters.
396It takes an extra API call to get tags of the object.
397
398Example: "key1=(value1|value2)&key2=value2""#)]
399 filter_include_tag_regex: Option<String>,
400
401 #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
402 long_help=r#"Do not sync objects that have tag matching a given regular expression.
403Keys must be sorted in alphabetical order, and '&' separated.
404This filter is applied after all other filters.
405It takes an extra API call to get tags of the object.
406
407Example: "key1=(value1|value2)&key2=value2""#)]
408 filter_exclude_tag_regex: Option<String>,
409
410 #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_REMOVE_MODIFIED_FILTER, help_heading = "Update Checking")]
412 remove_modified_filter: bool,
413
414 #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_etag", "check_mtime_and_etag", "check_mtime_and_additional_checksum"], default_value_t = DEFAULT_CHECK_SIZE, help_heading = "Update Checking")]
416 check_size: bool,
417
418 #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_size", "check_mtime_and_etag", "check_mtime_and_additional_checksum", "source_sse_c_key", "target_sse_c_key"], default_value_t = DEFAULT_CHECK_ETAG, help_heading = "Update Checking",)]
420 check_etag: bool,
421
422 #[arg(long, env, conflicts_with_all = ["enable_versioning", "remove_modified_filter", "check_size", "check_etag", "source_sse_c_key", "target_sse_c_key"], default_value_t = DEFAULT_CHECK_MTIME_AND_ETAG, help_heading = "Update Checking",
423 long_help=r#"Use the modification time and ETag for update checking.
424If the source modification date is newer, check the ETag.
425"#)]
426 check_mtime_and_etag: bool,
427
428 #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_size", "check_etag", "check_mtime_and_etag", "check_mtime_and_additional_checksum"], value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Update Checking")]
430 check_additional_checksum: Option<String>,
431
432 #[arg(long, env, conflicts_with_all = ["enable_versioning", "remove_modified_filter", "check_size", "check_etag", "check_mtime_and_etag", "check_additional_checksum"], value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Update Checking",
433 long_help=r#"Use the modification time and additional checksum for update checking.
434If the source modification date is newer, check the additional checksum.
435"#)]
436 check_mtime_and_additional_checksum: Option<String>,
437
438 #[arg(long, env, value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Verification")]
440 additional_checksum_algorithm: Option<String>,
441
442 #[arg(long, env, default_value_t = DEFAULT_FULL_OBJECT_CHECKSUM, help_heading = "Verification", long_help=r#"Use full object checksum for verification. CRC64NVME automatically use full object checksum.
443This option cannot be used with SHA1/SHA256 additional checksum."#)]
444 full_object_checksum: bool,
445
446 #[arg(long, env, default_value_t = DEFAULT_ENABLE_ADDITIONAL_CHECKSUM, help_heading = "Verification")]
448 enable_additional_checksum: bool,
449
450 #[arg(long, env, default_value_t = DEFAULT_DISABLE_MULTIPART_VERIFY, help_heading = "Verification")]
452 disable_multipart_verify: bool,
453
454 #[arg(long, env, default_value_t = DEFAULT_DISABLE_ETAG_VERIFY, help_heading = "Verification")]
456 disable_etag_verify: bool,
457
458 #[arg(long, env, requires = "additional_checksum_algorithm", default_value_t = DEFAULT_DISABLE_ADDITIONAL_CHECKSUM_VERIFY, help_heading = "Verification",
459 long_help=r#"Disable additional checksum verification
460But use additional checksum for upload (The hash value is stored in the target object)."#)]
461 disable_additional_checksum_verify: bool,
462
463 #[arg(long, env, default_value_t = DEFAULT_WORKER_SIZE, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance")]
465 worker_size: u16,
466
467 #[arg(long, env, default_value_t = DEFAULT_MAX_PARALLEL_MULTIPART_UPLOADS, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance")]
469 max_parallel_uploads: u16,
470
471 #[arg(long, env, default_value_t = DEFAULT_MAX_PARALLEL_LISTINGS, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance", long_help=r#"Maximum number of parallel listings of objects."#)]
472 max_parallel_listings: u16,
473
474 #[arg(long, env, default_value_t = DEFAULT_PARALLEL_LISTING_MAX_DEPTH, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance", long_help=r#"Maximum depth(sub directroy/prefix) of parallel listings."#)]
475 max_parallel_listing_max_depth: u16,
476
477 #[arg(long, env, default_value_t = DEFAULT_OBJECT_LISTING_QUEUE_SIZE, value_parser = clap::value_parser!(u32).range(1..), help_heading = "Performance")]
479 object_listing_queue_size: u32,
480
481 #[arg(long, env, default_value_t = DEFAULT_ALLOW_PARALLEL_LISTINGS_IN_EXPRESS_ONE_ZONE, help_heading = "Performance", long_help=r#"Allow parallel listings in express one zone storage class.
482It may include multipart upload in progress objects in the listing result."#)]
483 allow_parallel_listings_in_express_one_zone: bool,
484
485 #[arg(long, env, value_parser = clap::value_parser!(u32).range(10..), help_heading = "Performance")]
487 rate_limit_objects: Option<u32>,
488
489 #[arg(long, env, value_parser = human_bytes::check_human_bandwidth, help_heading = "Performance")]
491 rate_limit_bandwidth: Option<String>,
492
493 #[arg(long, env, conflicts_with_all = ["auto_chunksize"], default_value = DEFAULT_MULTIPART_THRESHOLD, value_parser = human_bytes::check_human_bytes, help_heading = "Multipart Settings",
494 long_help=r#"Object size threshold that s3sync uses for multipart upload
495Allow suffixes: MB, MiB, GB, GiB.
496The larger the size, the larger the memory usage."#)]
497 multipart_threshold: String,
498
499 #[arg(long, env, conflicts_with_all = ["auto_chunksize"], default_value = DEFAULT_MULTIPART_CHUNKSIZE, value_parser = human_bytes::check_human_bytes, help_heading = "Multipart Settings",
500 long_help=r#"Chunk size that s3sync uses for multipart upload of individual files
501Allow suffixes: MB, MiB, GB, GiB.
502The larger the size, the larger the memory usage."#)]
503 multipart_chunksize: String,
504
505 #[arg(long, env, conflicts_with_all = ["multipart_threshold", "multipart_chunksize"], default_value_t = DEFAULT_AUTO_CHUNKSIZE, help_heading = "Multipart Settings",
506 long_help=r#"Automatically adjusts a chunk size to match the source or target.
507It takes extra HEAD requests(1 API call per part)."#)]
508 auto_chunksize: bool,
509
510 #[arg(long, env, help_heading = "Metadata/Headers")]
512 cache_control: Option<String>,
513
514 #[arg(long, env, help_heading = "Metadata/Headers")]
516 content_disposition: Option<String>,
517
518 #[arg(long, env, help_heading = "Metadata/Headers")]
520 content_encoding: Option<String>,
521
522 #[arg(long, env, help_heading = "Metadata/Headers")]
524 content_language: Option<String>,
525
526 #[arg(long, env, help_heading = "Metadata/Headers")]
528 content_type: Option<String>,
529
530 #[arg(
531 long,
532 env,
533 help_heading = "Metadata/Headers",
534 long_help = r#"Expires HTTP header to set on the target object(RFC3339 datetime)
535Example: 2023-02-19T12:00:00Z"#
536 )]
537 expires: Option<DateTime<Utc>>,
538
539 #[arg(long, env, value_parser = metadata::check_metadata, help_heading = "Metadata/Headers", long_help=r#"Metadata to set on the target object
540Example: key1=value1,key2=value2"#)]
541 metadata: Option<String>,
542
543 #[arg(long, env, help_heading = "Metadata/Headers")]
545 website_redirect: Option<String>,
546
547 #[arg(long, env, default_value_t = DEFAULT_NO_SYNC_SYSTEM_METADATA, help_heading = "Metadata/Headers",
548 long_help= r#"Do not sync system metadata
549System metadata: content-disposition, content-encoding, content-language, content-type,
550 cache-control, expires, website-redirect"#)]
551 no_sync_system_metadata: bool,
552
553 #[arg(long, env, default_value_t = DEFAULT_NO_SYNC_USER_DEFINED_METADATA, help_heading = "Metadata/Headers")]
555 no_sync_user_defined_metadata: bool,
556
557 #[arg(long, env, conflicts_with_all = ["disable_tagging", "sync_latest_tagging"], value_parser = tagging::parse_tagging, help_heading = "Tagging",
558 long_help=r#"Tagging to set on the target object.
559Key/value must be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.
560
561Example: key1=value1&key2=value2"#)]
562 tagging: Option<String>,
563
564 #[arg(long, env, default_value_t = DEFAULT_DISABLE_TAGGING, help_heading = "Tagging")]
566 disable_tagging: bool,
567
568 #[arg(long, env, conflicts_with_all = ["enable_versioning", "disable_tagging"], default_value_t = DEFAULT_SYNC_LATEST_TAGGING, help_heading = "Tagging",
569 long_help=r#"Copy the latest tagging from the source if necessary.
570If this option is enabled, the --remove-modified-filter and
571--head-each-target options are automatically enabled."#)]
572 sync_latest_tagging: bool,
573
574 #[arg(long, env, conflicts_with_all = ["delete", "head_each_target", "remove_modified_filter"], default_value_t = DEFAULT_ENABLE_VERSIONING, help_heading = "Versioning",
575 long_help=r#"Sync all version objects in the source storage to the target versioning storage.
576 "#)]
577 enable_versioning: bool,
578
579 #[arg(
580 long,
581 env,
582 conflicts_with_all = ["delete", "head_each_target", "enable_versioning", "check_etag", "check_mtime_and_etag", "check_size", "check_mtime_and_additional_checksum"],
583 help_heading = "Versioning",
584 long_help = r#"Sync only objects at a specific point in time (RFC3339 datetime).
585The source storage must be a versioning enabled S3 bucket.
586By default, the target storage's objects will always be overwritten with the source's objects.
587
588Example: 2025-07-16T12:00:00Z"#
589 )]
590 point_in_time: Option<DateTime<Utc>>,
591
592 #[arg(long, env, value_parser = sse::parse_sse, help_heading = "Encryption")]
594 sse: Option<String>,
595
596 #[arg(long, env, help_heading = "Encryption")]
598 sse_kms_key_id: Option<String>,
599
600 #[arg(long, env, conflicts_with_all = ["sse", "sse_kms_key_id"], requires = "source_sse_c_key", value_parser = sse::parse_sse_c, help_heading = "Encryption")]
602 source_sse_c: Option<String>,
603
604 #[arg(
606 long,
607 env,
608 requires = "source_sse_c_key_md5",
609 help_heading = "Encryption"
610 )]
611 source_sse_c_key: Option<String>,
612
613 #[arg(long, env, requires = "source_sse_c", help_heading = "Encryption")]
615 source_sse_c_key_md5: Option<String>,
616
617 #[arg(long, env, conflicts_with_all = ["sse", "sse_kms_key_id"], requires = "target_sse_c_key", value_parser = sse::parse_sse_c, help_heading = "Encryption")]
619 target_sse_c: Option<String>,
620
621 #[arg(
623 long,
624 env,
625 requires = "target_sse_c_key_md5",
626 help_heading = "Encryption"
627 )]
628 target_sse_c_key: Option<String>,
629
630 #[arg(long, env, requires = "target_sse_c", help_heading = "Encryption")]
632 target_sse_c_key_md5: Option<String>,
633
634 #[clap(flatten)]
636 verbosity: Verbosity<WarnLevel>,
637
638 #[arg(
639 long,
640 env,
641 default_value_t = DEFAULT_REPORT_SYNC_STATUS,
642 conflicts_with_all = ["dry_run", "delete", "enable_versioning", "head_each_target"],
643 help_heading = "Reporting",
644 long_help = r#"Report sync status to the target storage.
645Default verification is for etag. For additional checksum, use --check-additional-checksum.
646For more precise control, use with --auto-chunksize."#
647 )]
648 report_sync_status: bool,
649
650 #[arg(
651 long,
652 env,
653 default_value_t = DEFAULT_REPORT_METADATA_SYNC_STATUS,
654 requires = "report_sync_status",
655 help_heading = "Reporting",
656 long_help = r#"Report metadata sync status to the target storage.
657It must be used with --report-sync-status.
658Note: s3sync generated user-defined metadata(s3sync_origin_version_id/s3sync_origin_last_modified) were ignored.
659 Because they are usually different from the source storage."#
660 )]
661 report_metadata_sync_status: bool,
662
663 #[arg(
664 long,
665 env,
666 default_value_t = DEFAULT_REPORT_TAGGING_SYNC_STATUS,
667 requires = "report_sync_status",
668 help_heading = "Reporting",
669 long_help = r#"Report tagging sync status to the target storage.
670It must be used with --report-sync-status."#
671 )]
672 report_tagging_sync_status: bool,
673
674 #[arg(long, env, default_value_t = DEFAULT_JSON_TRACING, help_heading = "Tracing/Logging")]
676 json_tracing: bool,
677
678 #[arg(long, env, default_value_t = DEFAULT_AWS_SDK_TRACING, help_heading = "Tracing/Logging")]
680 aws_sdk_tracing: bool,
681
682 #[arg(long, env, default_value_t = DEFAULT_SPAN_EVENTS_TRACING, help_heading = "Tracing/Logging")]
684 span_events_tracing: bool,
685
686 #[arg(long, env, default_value_t = DEFAULT_DISABLE_COLOR_TRACING, help_heading = "Tracing/Logging")]
688 disable_color_tracing: bool,
689
690 #[arg(long, env, default_value_t = DEFAULT_AWS_MAX_ATTEMPTS, value_name = "max_attempts", help_heading = "Retry Options")]
692 aws_max_attempts: u32,
693
694 #[arg(long, env, default_value_t = DEFAULT_INITIAL_BACKOFF_MILLISECONDS, value_name = "initial_backoff", help_heading = "Retry Options",
695 long_help=r#"A multiplier value used when calculating backoff times as part of an exponential backoff with jitter strategy.
696"#)]
697 initial_backoff_milliseconds: u64,
698
699 #[arg(long, env, default_value_t = DEFAULT_FORCE_RETRY_COUNT, help_heading = "Retry Options")]
701 force_retry_count: u32,
702
703 #[arg(long, env, default_value_t = DEFAULT_FORCE_RETRY_INTERVAL_MILLISECONDS, value_name = "force_retry_interval", help_heading = "Retry Options",
704 long_help=r#"Sleep interval (milliseconds) between s3sync force retries on error.
705"#)]
706 force_retry_interval_milliseconds: u64,
707
708 #[arg(
709 long,
710 env,
711 value_name = "operation_timeout",
712 help_heading = "Timeout Options",
713 long_help = r#"Operation timeout (milliseconds). For details, see the AWS SDK for Rust TimeoutConfig documentation.
714The default has no timeout."#
715 )]
716 operation_timeout_milliseconds: Option<u64>,
717
718 #[arg(
719 long,
720 env,
721 value_name = "operation_attempt_timeout",
722 help_heading = "Timeout Options",
723 long_help = r#"Operation attempt timeout (milliseconds). For details, see the AWS SDK for Rust TimeoutConfig documentation.
724The default has no timeout."#
725 )]
726 operation_attempt_timeout_milliseconds: Option<u64>,
727
728 #[arg(
729 long,
730 env,
731 value_name = "connect_timeout",
732 help_heading = "Timeout Options",
733 long_help = r#"Connect timeout (milliseconds).The default has AWS SDK default timeout (Currently 3100 milliseconds).
734"#
735 )]
736 connect_timeout_milliseconds: Option<u64>,
737
738 #[arg(
739 long,
740 env,
741 value_name = "read_timeout",
742 help_heading = "Timeout Options",
743 long_help = r#"Read timeout (milliseconds). The default has no timeout."#
744 )]
745 read_timeout_milliseconds: Option<u64>,
746
747 #[arg(long, env, default_value_t = DEFAULT_WARN_AS_ERROR, help_heading = "Advanced")]
749 warn_as_error: bool,
750
751 #[arg(long, env, default_value_t = DEFAULT_IGNORE_SYMLINKS, help_heading = "Advanced")]
753 ignore_symlinks: bool,
754
755 #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_HEAD_EACH_TARGET, help_heading = "Advanced",
756 long_help=r#"HeadObject is used to check whether an object has been modified in the target storage.
757It reduces the possibility of race condition issue"#)]
758 head_each_target: bool,
759
760 #[arg(long, env, value_parser = canned_acl::parse_canned_acl, help_heading = "Advanced",
761 long_help=r#"ACL for the objects
762Valid choices: private | public-read | public-read-write | authenticated-read | aws-exec-read |
763 bucket-owner-read | bucket-owner-full-control"#)]
764 acl: Option<String>,
765
766 #[arg(long, env, default_value_t = DEFAULT_NO_GUESS_MIME_TYPE, help_heading = "Advanced")]
768 no_guess_mime_type: bool,
769
770 #[arg(long, env, default_value_t = DEFAULT_MAX_KEYS, value_parser = clap::value_parser!(i32).range(1..=32767), help_heading = "Advanced")]
772 max_keys: i32,
773
774 #[arg(long, env, default_value_t = DEFAULT_PUT_LAST_MODIFIED_METADATA, help_heading = "Advanced")]
776 put_last_modified_metadata: bool,
777
778 #[arg(long, env, value_name = "SHELL", value_parser = clap_complete::shells::Shell::from_str, help_heading = "Advanced",
779 long_help=r#"Generate a auto completions script.
780Valid choices: bash, fish, zsh, powershell, elvish."#)]
781 auto_complete_shell: Option<clap_complete::shells::Shell>,
782
783 #[arg(long, env, default_value_t = DEFAULT_DISABLE_STALLED_STREAM_PROTECTION, help_heading = "Advanced")]
785 disable_stalled_stream_protection: bool,
786
787 #[arg(long, env, default_value_t = DEFAULT_DISABLE_PAYLOAD_SIGNING, help_heading = "Advanced")]
789 disable_payload_signing: bool,
790
791 #[arg(long, env, default_value_t = DEFAULT_DISABLE_CONTENT_MD5_HEADER, help_heading = "Advanced",
792 long_help=r#"Disable Content-MD5 header for object uploads. It disables the ETag verification for the uploaded object.
793"#)]
794 disable_content_md5_header: bool,
795
796 #[arg(long, env, default_value_t = DEFAULT_DISABLE_EXPRESS_ONE_ZONE_ADDITIONAL_CHECKSUM, help_heading = "Advanced",
797 long_help=r#"Disable default additional checksum verification in Express One Zone storage class.
798 "#)]
799 disable_express_one_zone_additional_checksum: bool,
800
801 #[arg(long, env, requires = "delete", default_value_t = DEFAULT_DELETE_EXCLUDED, help_heading = "Advanced",
802 long_help=r#"When used in combination with --delete options, supplied --filter-exclude-regex patterns will not prevent an object from being deleted.
803"#)]
804 delete_excluded: bool,
805
806 #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time"], default_value_t = DEFAULT_IF_MATCH, help_heading = "Advanced", long_help=r#"Add an If-Match header for PutObject/CompleteMultipartUpload/DeleteObject requests.
807This is for like an optimistic lock."#)]
808 if_match: bool,
809
810 #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time"], requires = "server_side_copy", default_value_t = DEFAULT_COPY_SOURCE_IF_MATCH, help_heading = "Advanced", long_help=r#"Add an x-amz-copy-source-if-match header for CopyObject/UploadPartCopy requests.
811This is for like an optimistic lock."#)]
812 copy_source_if_match: bool,
813
814 #[arg(long, env, requires = "delete", value_parser = clap::value_parser!(u64).range(1..), help_heading = "Advanced")]
816 max_delete: Option<u64>,
817
818 #[arg(long, env, default_value_t = DEFAULT_IGNORE_GLACIER_WARNINGS, help_heading = "Advanced")]
820 ignore_glacier_warnings: bool,
821
822 #[cfg(feature = "lua_support")]
823 #[arg(
824 long,
825 env,
826 help_heading = "Lua scripting support",
827 value_parser = file_exist::is_file_exist,
828 long_help = r#"Path to the Lua script that is executed as preprocess callback"#
829 )]
830 preprocess_callback_lua_script: Option<String>,
831
832 #[cfg(feature = "lua_support")]
833 #[arg(
834 long,
835 env,
836 help_heading = "Lua scripting support",
837 value_parser = file_exist::is_file_exist,
838 long_help = r#"Path to the Lua script that is executed as event callback"#
839 )]
840 event_callback_lua_script: Option<String>,
841
842 #[cfg(feature = "lua_support")]
843 #[arg(
844 long,
845 env,
846 help_heading = "Lua scripting support",
847 value_parser = file_exist::is_file_exist,
848 long_help = r#"Path to the Lua script that is executed as filter callback"#
849 )]
850 filter_callback_lua_script: Option<String>,
851
852 #[cfg(feature = "lua_support")]
853 #[arg(long, env, conflicts_with_all = ["allow_lua_unsafe_vm"], default_value_t = DEFAULT_ALLOW_LUA_OS_LIBRARY, help_heading = "Lua scripting support", long_help="Allow Lua OS and I/O library functions in the Lua script.")]
854 allow_lua_os_library: bool,
855
856 #[cfg(feature = "lua_support")]
857 #[arg(long, env, default_value = DEFAULT_LUA_VM_MEMORY_LIMIT, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Lua scripting support",
858 long_help=r#"Memory limit for the Lua VM. Allow suffixes: KB, KiB, MB, MiB, GB, GiB.
859Zero means no limit.
860If the memory limit is exceeded, the whole process will be terminated."#)]
861 lua_vm_memory_limit: String,
862
863 #[cfg(feature = "lua_support")]
864 #[arg(long, env, conflicts_with_all = ["allow_lua_os_library"], default_value_t = DEFAULT_ALLOW_LUA_UNSAFE_VM, help_heading = "Dangerous",
865 long_help=r#"Allow unsafe Lua VM functions in the Lua script.
866It allows the Lua script to load unsafe standard libraries or C modules."#)]
867 allow_lua_unsafe_vm: bool,
868
869 #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_SYNC_WITH_DELETE, help_heading = "Dangerous",
870 long_help=r#"Delete objects that exist in the target but not in the source.
871Exclude filters other than --filter-exclude-regex will not prevent an object from being deleted.
872[Warning] Since this can cause data loss, test first with the --dry-run option.
873 "#)]
874 delete: bool,
875
876 #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
878 allow_both_local_storage: bool,
879
880 #[cfg(feature = "e2e_test_dangerous_simulations")]
882 #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
883 test_user_defined_callback: bool,
884
885 #[cfg(feature = "e2e_test_dangerous_simulations")]
887 #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
888 allow_e2e_test_dangerous_simulation: bool,
889
890 #[cfg(feature = "e2e_test_dangerous_simulations")]
892 #[arg(long, hide = true, help_heading = "Dangerous")]
893 cancellation_point: Option<String>,
894
895 #[cfg(feature = "e2e_test_dangerous_simulations")]
897 #[arg(long, hide = true, help_heading = "Dangerous")]
898 panic_simulation_point: Option<String>,
899
900 #[cfg(feature = "e2e_test_dangerous_simulations")]
902 #[arg(long, hide = true, help_heading = "Dangerous")]
903 error_simulation_point: Option<String>,
904}
905
906pub fn parse_from_args<I, T>(args: I) -> Result<CLIArgs, clap::Error>
907where
908 I: IntoIterator<Item = T>,
909 T: Into<OsString> + Clone,
910{
911 CLIArgs::try_parse_from(args)
912}
913
914pub fn build_config_from_args<I, T>(args: I) -> Result<Config, String>
915where
916 I: IntoIterator<Item = T>,
917 T: Into<OsString> + Clone,
918{
919 let config_args = CLIArgs::try_parse_from(args).map_err(|e| e.to_string())?;
920 crate::Config::try_from(config_args)
921}
922
923impl CLIArgs {
924 fn validate_storage_config(&self) -> Result<(), String> {
926 self.check_source_local_storage()?;
927 self.check_target_local_storage()?;
928 self.check_storage_conflict()?;
929 self.check_versioning_option_conflict()?;
930 self.check_tagging_option_conflict()?;
931 self.check_storage_class_conflict()?;
932 self.check_storage_credentials_conflict()?;
933 self.check_sse_conflict()?;
934 self.check_sse_c_conflict()?;
935 self.check_acl_conflict()?;
936 self.check_enable_additional_checksum_conflict()?;
937 self.check_additional_checksum_algorithm_conflict()?;
938 self.check_auto_chunksize_conflict()?;
939 self.check_metadata_conflict()?;
940 self.check_check_size_conflict()?;
941 self.check_check_e_tag_conflict()?;
942 self.check_check_mtime_and_e_tag_conflict()?;
943 self.check_ignore_symlinks_conflict()?;
944 self.check_no_guess_mime_type_conflict()?;
945 self.check_endpoint_url_conflict()?;
946 self.check_disable_payload_signing_conflict()?;
947 self.check_disable_content_md5_header_conflict()?;
948 self.check_full_object_checksum_conflict()?;
949 self.check_accelerate_conflict()?;
950 self.check_request_payer_conflict()?;
951 self.check_server_side_copy_conflict()?;
952 self.check_filter_include_metadata_regex_conflict()?;
953 self.check_filter_exclude_metadata_regex_conflict()?;
954 self.check_filter_include_tag_regex_conflict()?;
955 self.check_filter_exclude_tag_regex_conflict()?;
956 self.check_server_no_sync_system_metadata_conflict()?;
957 self.check_server_no_sync_user_defined_metadata_conflict()?;
958 self.check_point_in_time_conflict()?;
959 self.check_report_metadata_sync_status_conflict()?;
960 self.check_report_tagging_sync_status_conflict()?;
961 self.check_if_match_conflict()?;
962 self.check_copy_source_if_match_conflict()?;
963
964 Ok(())
965 }
966
967 fn check_source_local_storage(&self) -> Result<(), String> {
968 let source = storage_path::parse_storage_path(&self.source);
969
970 if let StoragePath::Local(path) = source {
971 if !path.is_dir() && self.delete {
972 return Err(SOURCE_LOCAL_STORAGE_FILE_WITH_DELETE_OPTION.to_string());
973 }
974 if !path.exists() {
975 return Err(SOURCE_LOCAL_STORAGE_PATH_NOT_FOUND.to_string());
976 }
977 }
978
979 Ok(())
980 }
981
982 fn check_target_local_storage(&self) -> Result<(), String> {
983 let target = storage_path::parse_storage_path(&self.target);
984
985 if let StoragePath::Local(path) = target {
986 let exist_result = path.try_exists();
987 if exist_result.is_err() {
988 return Err(TARGET_LOCAL_STORAGE_INVALID.to_string());
989 }
990 }
991 Ok(())
992 }
993
994 fn check_storage_conflict(&self) -> Result<(), String> {
995 if self.allow_both_local_storage {
996 return Ok(());
997 }
998
999 let source = storage_path::parse_storage_path(&self.source);
1000 let target = storage_path::parse_storage_path(&self.target);
1001
1002 if storage_path::is_both_storage_local(&source, &target) {
1003 return Err(NO_S3_STORAGE_SPECIFIED.to_string());
1004 }
1005
1006 Ok(())
1007 }
1008
1009 fn check_versioning_option_conflict(&self) -> Result<(), String> {
1010 if !self.enable_versioning {
1011 return Ok(());
1012 }
1013
1014 let source = storage_path::parse_storage_path(&self.source);
1015 let target = storage_path::parse_storage_path(&self.target);
1016
1017 if !storage_path::is_both_storage_s3(&source, &target) {
1018 return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1019 }
1020
1021 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1022 if is_express_onezone_storage(&bucket) {
1023 return Err(VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1024 }
1025 }
1026
1027 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1028 if is_express_onezone_storage(&bucket) {
1029 return Err(VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1030 }
1031 }
1032
1033 Ok(())
1034 }
1035
1036 fn check_tagging_option_conflict(&self) -> Result<(), String> {
1037 let source = storage_path::parse_storage_path(&self.source);
1038 let target = storage_path::parse_storage_path(&self.target);
1039
1040 if self.sync_latest_tagging && !storage_path::is_both_storage_s3(&source, &target) {
1041 return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1042 }
1043
1044 Ok(())
1045 }
1046
1047 fn check_storage_class_conflict(&self) -> Result<(), String> {
1048 let target = storage_path::parse_storage_path(&self.target);
1049
1050 if self.storage_class.is_some() && matches!(target, StoragePath::Local(_)) {
1051 return Err(LOCAL_STORAGE_SPECIFIED_WITH_STORAGE_CLASS.to_string());
1052 }
1053
1054 Ok(())
1055 }
1056
1057 fn check_storage_credentials_conflict(&self) -> Result<(), String> {
1058 let source = storage_path::parse_storage_path(&self.source);
1059 let target = storage_path::parse_storage_path(&self.target);
1060
1061 if matches!(source, StoragePath::Local(_))
1062 && (self.source_profile.is_some() || self.source_access_key.is_some())
1063 {
1064 return Err(NO_SOURCE_CREDENTIAL_REQUIRED.to_string());
1065 }
1066
1067 if matches!(target, StoragePath::Local(_))
1068 && (self.target_profile.is_some() || self.target_access_key.is_some())
1069 {
1070 return Err(NO_TARGET_CREDENTIAL_REQUIRED.to_string());
1071 }
1072
1073 Ok(())
1074 }
1075
1076 fn check_sse_conflict(&self) -> Result<(), String> {
1077 if self.sse.is_none() && self.sse_kms_key_id.is_none() {
1078 return Ok(());
1079 }
1080
1081 let target = storage_path::parse_storage_path(&self.target);
1082 if matches!(target, StoragePath::Local(_)) {
1083 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SSE.to_string());
1084 }
1085
1086 if self.sse_kms_key_id.is_some()
1087 && (self.sse.is_none()
1088 || (ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap()
1089 != ServerSideEncryption::AwsKms
1090 && ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap()
1091 != ServerSideEncryption::AwsKmsDsse))
1092 {
1093 return Err(SSE_KMS_KEY_ID_ARGUMENTS_CONFLICT.to_string());
1094 }
1095
1096 Ok(())
1097 }
1098
1099 fn check_sse_c_conflict(&self) -> Result<(), String> {
1100 if self.source_sse_c.is_none() && self.target_sse_c.is_none() {
1101 return Ok(());
1102 }
1103
1104 if self.source_sse_c.is_some() {
1105 let source = storage_path::parse_storage_path(&self.source);
1106 if matches!(source, StoragePath::Local(_)) {
1107 return Err(LOCAL_STORAGE_SPECIFIED_WITH_SSE_C.to_string());
1108 }
1109 }
1110
1111 if self.target_sse_c.is_some() {
1112 let target = storage_path::parse_storage_path(&self.target);
1113 if matches!(target, StoragePath::Local(_)) {
1114 return Err(LOCAL_STORAGE_SPECIFIED_WITH_SSE_C.to_string());
1115 }
1116 }
1117
1118 Ok(())
1119 }
1120
1121 fn check_acl_conflict(&self) -> Result<(), String> {
1122 if self.acl.is_none() {
1123 return Ok(());
1124 }
1125
1126 let target = storage_path::parse_storage_path(&self.target);
1127 if matches!(target, StoragePath::Local(_)) {
1128 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACL.to_string());
1129 }
1130
1131 Ok(())
1132 }
1133
1134 fn check_additional_checksum_algorithm_conflict(&self) -> Result<(), String> {
1135 if self.additional_checksum_algorithm.is_none() {
1136 return Ok(());
1137 }
1138
1139 let target = storage_path::parse_storage_path(&self.target);
1140 if matches!(target, StoragePath::Local(_)) {
1141 return Err(
1142 TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ADDITIONAL_CHECKSUM_ALGORITHM.to_string(),
1143 );
1144 }
1145
1146 Ok(())
1147 }
1148
1149 fn check_enable_additional_checksum_conflict(&self) -> Result<(), String> {
1150 if !self.enable_additional_checksum {
1151 return Ok(());
1152 }
1153
1154 let source = storage_path::parse_storage_path(&self.source);
1155 if matches!(source, StoragePath::Local(_)) {
1156 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENABLE_ADDITIONAL_CHECKSUM.to_string());
1157 }
1158
1159 Ok(())
1160 }
1161
1162 fn check_auto_chunksize_conflict(&self) -> Result<(), String> {
1163 if !self.auto_chunksize {
1164 return Ok(());
1165 }
1166
1167 let source = storage_path::parse_storage_path(&self.source);
1168
1169 if !self.check_etag && !self.check_mtime_and_etag && matches!(source, StoragePath::Local(_))
1170 {
1171 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_AUTO_CHUNKSIZE.to_string());
1172 }
1173
1174 Ok(())
1175 }
1176
1177 fn check_metadata_conflict(&self) -> Result<(), String> {
1178 if self.cache_control.is_none()
1179 && self.content_disposition.is_none()
1180 && self.content_encoding.is_none()
1181 && self.content_language.is_none()
1182 && self.content_type.is_none()
1183 && self.website_redirect.is_none()
1184 && self.expires.is_none()
1185 && self.tagging.is_none()
1186 && !self.put_last_modified_metadata
1187 {
1188 return Ok(());
1189 }
1190
1191 let target = storage_path::parse_storage_path(&self.target);
1192 if matches!(target, StoragePath::Local(_)) {
1193 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_METADATA_OPTION.to_string());
1194 }
1195
1196 Ok(())
1197 }
1198
1199 fn check_check_size_conflict(&self) -> Result<(), String> {
1200 if self.check_size && self.remove_modified_filter && !self.head_each_target {
1201 return Err(CHECK_SIZE_CONFLICT.to_string());
1202 }
1203
1204 Ok(())
1205 }
1206
1207 fn check_check_e_tag_conflict(&self) -> Result<(), String> {
1208 if !self.check_etag {
1209 return Ok(());
1210 }
1211 if self.remove_modified_filter && !self.head_each_target {
1212 return Err(CHECK_ETAG_CONFLICT.to_string());
1213 }
1214
1215 if self.sse.is_some() {
1216 let sse = ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap();
1217 if sse == ServerSideEncryption::AwsKms || sse == ServerSideEncryption::AwsKmsDsse {
1218 return Err(CHECK_ETAG_CONFLICT_SSE_KMS.to_string());
1219 }
1220 }
1221
1222 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1223 if is_express_onezone_storage(&bucket) {
1224 return Err(CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1225 }
1226 }
1227
1228 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1229 if is_express_onezone_storage(&bucket) {
1230 return Err(CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1231 }
1232 }
1233
1234 Ok(())
1235 }
1236
1237 fn check_check_mtime_and_e_tag_conflict(&self) -> Result<(), String> {
1238 if !self.check_mtime_and_etag {
1239 return Ok(());
1240 }
1241
1242 if self.sse.is_some() {
1243 let sse = ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap();
1244 if sse == ServerSideEncryption::AwsKms || sse == ServerSideEncryption::AwsKmsDsse {
1245 return Err(CHECK_MTIME_AND_ETAG_CONFLICT_SSE_KMS.to_string());
1246 }
1247 }
1248
1249 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1250 if is_express_onezone_storage(&bucket) {
1251 return Err(CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1252 }
1253 }
1254
1255 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1256 if is_express_onezone_storage(&bucket) {
1257 return Err(CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1258 }
1259 }
1260
1261 Ok(())
1262 }
1263
1264 fn check_ignore_symlinks_conflict(&self) -> Result<(), String> {
1265 if !self.ignore_symlinks {
1266 return Ok(());
1267 }
1268
1269 let source = storage_path::parse_storage_path(&self.source);
1270 if matches!(source, StoragePath::S3 { .. }) {
1271 return Err(SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_IGNORE_SYMLINKS.to_string());
1272 }
1273
1274 Ok(())
1275 }
1276
1277 fn check_no_guess_mime_type_conflict(&self) -> Result<(), String> {
1278 if !self.no_guess_mime_type {
1279 return Ok(());
1280 }
1281
1282 let source = storage_path::parse_storage_path(&self.source);
1283 if matches!(source, StoragePath::S3 { .. }) {
1284 return Err(SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_NO_GUESS_MIME_TYPE.to_string());
1285 }
1286
1287 Ok(())
1288 }
1289
1290 fn check_endpoint_url_conflict(&self) -> Result<(), String> {
1291 let source = storage_path::parse_storage_path(&self.source);
1292 if matches!(source, StoragePath::Local(_)) && self.source_endpoint_url.is_some() {
1293 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL.to_string());
1294 }
1295
1296 let target = storage_path::parse_storage_path(&self.target);
1297 if matches!(target, StoragePath::Local(_)) && self.target_endpoint_url.is_some() {
1298 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL.to_string());
1299 }
1300
1301 Ok(())
1302 }
1303
1304 fn check_disable_payload_signing_conflict(&self) -> Result<(), String> {
1305 if !self.disable_payload_signing {
1306 return Ok(());
1307 }
1308
1309 let target = storage_path::parse_storage_path(&self.target);
1310 if matches!(target, StoragePath::Local(_)) {
1311 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_PAYLOAD_SIGNING.to_string());
1312 }
1313
1314 Ok(())
1315 }
1316
1317 fn check_disable_content_md5_header_conflict(&self) -> Result<(), String> {
1318 if !self.disable_content_md5_header {
1319 return Ok(());
1320 }
1321
1322 let target = storage_path::parse_storage_path(&self.target);
1323 if matches!(target, StoragePath::Local(_)) {
1324 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_CONTENT_MD5_HEADER.to_string());
1325 }
1326
1327 Ok(())
1328 }
1329
1330 fn check_full_object_checksum_conflict(&self) -> Result<(), String> {
1331 if !self.full_object_checksum {
1332 return Ok(());
1333 }
1334
1335 let target = storage_path::parse_storage_path(&self.target);
1336 if matches!(target, StoragePath::Local(_)) {
1337 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_FULL_OBJECT_CHECKSUM.to_string());
1338 }
1339
1340 if let Some(additional_checksum_algorithm) = &self.additional_checksum_algorithm {
1341 if additional_checksum_algorithm == "SHA1" || additional_checksum_algorithm == "SHA256"
1342 {
1343 return Err(FULL_OBJECT_CHECKSUM_NOT_SUPPORTED.to_string());
1344 }
1345 }
1346
1347 Ok(())
1348 }
1349
1350 fn check_accelerate_conflict(&self) -> Result<(), String> {
1351 let source = storage_path::parse_storage_path(&self.source);
1352 if matches!(source, StoragePath::Local(_)) && self.source_accelerate {
1353 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE.to_string());
1354 }
1355
1356 let target = storage_path::parse_storage_path(&self.target);
1357 if matches!(target, StoragePath::Local(_)) && self.target_accelerate {
1358 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE.to_string());
1359 }
1360
1361 Ok(())
1362 }
1363
1364 fn check_request_payer_conflict(&self) -> Result<(), String> {
1365 let source = storage_path::parse_storage_path(&self.source);
1366 if matches!(source, StoragePath::Local(_)) && self.source_request_payer {
1367 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER.to_string());
1368 }
1369
1370 let target = storage_path::parse_storage_path(&self.target);
1371 if matches!(target, StoragePath::Local(_)) && self.target_request_payer {
1372 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER.to_string());
1373 }
1374
1375 Ok(())
1376 }
1377
1378 fn check_server_side_copy_conflict(&self) -> Result<(), String> {
1379 let source = storage_path::parse_storage_path(&self.source);
1380 if matches!(source, StoragePath::Local(_)) && self.server_side_copy {
1381 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY.to_string());
1382 }
1383
1384 let target = storage_path::parse_storage_path(&self.target);
1385 if matches!(target, StoragePath::Local(_)) && self.server_side_copy {
1386 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY.to_string());
1387 }
1388
1389 Ok(())
1390 }
1391
1392 fn check_filter_include_metadata_regex_conflict(&self) -> Result<(), String> {
1393 let source = storage_path::parse_storage_path(&self.source);
1394 if matches!(source, StoragePath::Local(_)) && self.filter_include_metadata_regex.is_some() {
1395 return Err(
1396 SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_METADATA_REGEX.to_string(),
1397 );
1398 }
1399
1400 Ok(())
1401 }
1402
1403 fn check_filter_exclude_metadata_regex_conflict(&self) -> Result<(), String> {
1404 let source = storage_path::parse_storage_path(&self.source);
1405 if matches!(source, StoragePath::Local(_)) && self.filter_exclude_metadata_regex.is_some() {
1406 return Err(
1407 SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_METADATA_REGEX.to_string(),
1408 );
1409 }
1410
1411 Ok(())
1412 }
1413
1414 fn check_filter_include_tag_regex_conflict(&self) -> Result<(), String> {
1415 let source = storage_path::parse_storage_path(&self.source);
1416 if matches!(source, StoragePath::Local(_)) && self.filter_include_tag_regex.is_some() {
1417 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_TAG_REGEX.to_string());
1418 }
1419
1420 Ok(())
1421 }
1422
1423 fn check_filter_exclude_tag_regex_conflict(&self) -> Result<(), String> {
1424 let source = storage_path::parse_storage_path(&self.source);
1425 if matches!(source, StoragePath::Local(_)) && self.filter_exclude_tag_regex.is_some() {
1426 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_TAG_REGEX.to_string());
1427 }
1428
1429 Ok(())
1430 }
1431
1432 fn check_server_no_sync_system_metadata_conflict(&self) -> Result<(), String> {
1433 let source = storage_path::parse_storage_path(&self.source);
1434 if matches!(source, StoragePath::Local(_)) && self.no_sync_system_metadata {
1435 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA.to_string());
1436 }
1437
1438 let target = storage_path::parse_storage_path(&self.target);
1439 if matches!(target, StoragePath::Local(_)) && self.no_sync_system_metadata {
1440 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA.to_string());
1441 }
1442
1443 Ok(())
1444 }
1445
1446 fn check_server_no_sync_user_defined_metadata_conflict(&self) -> Result<(), String> {
1447 let source = storage_path::parse_storage_path(&self.source);
1448 if matches!(source, StoragePath::Local(_)) && self.no_sync_user_defined_metadata {
1449 return Err(
1450 SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA.to_string(),
1451 );
1452 }
1453
1454 let target = storage_path::parse_storage_path(&self.target);
1455 if matches!(target, StoragePath::Local(_)) && self.no_sync_user_defined_metadata {
1456 return Err(
1457 TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA.to_string(),
1458 );
1459 }
1460
1461 Ok(())
1462 }
1463 fn check_point_in_time_conflict(&self) -> Result<(), String> {
1464 if self.point_in_time.is_none() {
1465 return Ok(());
1466 }
1467
1468 let source = storage_path::parse_storage_path(&self.source);
1469 if matches!(source, StoragePath::Local(_)) {
1470 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_POINT_IN_TIME.to_string());
1471 }
1472
1473 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1474 if is_express_onezone_storage(&bucket) {
1475 return Err(POINT_IN_TIME_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1476 }
1477 }
1478
1479 if Utc::now() < self.point_in_time.unwrap() {
1480 return Err(POINT_IN_TIME_VALUE_INVALID.to_string());
1481 }
1482
1483 Ok(())
1484 }
1485
1486 fn check_report_metadata_sync_status_conflict(&self) -> Result<(), String> {
1487 if !self.report_metadata_sync_status {
1488 return Ok(());
1489 }
1490
1491 let source = storage_path::parse_storage_path(&self.source);
1492 if matches!(source, StoragePath::Local(_)) {
1493 return Err(
1494 SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS.to_string(),
1495 );
1496 }
1497
1498 let target = storage_path::parse_storage_path(&self.target);
1499 if matches!(target, StoragePath::Local(_)) {
1500 return Err(
1501 TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS.to_string(),
1502 );
1503 }
1504
1505 Ok(())
1506 }
1507
1508 fn check_report_tagging_sync_status_conflict(&self) -> Result<(), String> {
1509 if !self.report_tagging_sync_status {
1510 return Ok(());
1511 }
1512
1513 let source = storage_path::parse_storage_path(&self.source);
1514 if matches!(source, StoragePath::Local(_)) {
1515 return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS.to_string());
1516 }
1517
1518 let target = storage_path::parse_storage_path(&self.target);
1519 if matches!(target, StoragePath::Local(_)) {
1520 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS.to_string());
1521 }
1522
1523 Ok(())
1524 }
1525
1526 fn check_if_match_conflict(&self) -> Result<(), String> {
1527 if !self.if_match {
1528 return Ok(());
1529 }
1530
1531 let target = storage_path::parse_storage_path(&self.target);
1532 if matches!(target, StoragePath::Local(_)) {
1533 return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_MATCH.to_string());
1534 }
1535
1536 if self.remove_modified_filter && !self.head_each_target {
1537 return Err(IF_MATCH_CONFLICT.to_string());
1538 }
1539
1540 Ok(())
1541 }
1542
1543 fn check_copy_source_if_match_conflict(&self) -> Result<(), String> {
1544 if !self.copy_source_if_match {
1545 return Ok(());
1546 }
1547
1548 let source = storage_path::parse_storage_path(&self.source);
1549 let target = storage_path::parse_storage_path(&self.target);
1550
1551 if !storage_path::is_both_storage_s3(&source, &target) {
1552 return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1553 }
1554
1555 Ok(())
1556 }
1557
1558 fn build_client_configs(
1559 &self,
1560 request_checksum_calculation: RequestChecksumCalculation,
1561 ) -> (Option<ClientConfig>, Option<ClientConfig>) {
1562 let source_credential = if let Some(source_profile) = self.source_profile.clone() {
1563 Some(S3Credentials::Profile(source_profile))
1564 } else if self.source_access_key.is_some() {
1565 self.source_access_key
1566 .clone()
1567 .map(|access_key| S3Credentials::Credentials {
1568 access_keys: AccessKeys {
1569 access_key,
1570 secret_access_key: self
1571 .source_secret_access_key
1572 .as_ref()
1573 .unwrap()
1574 .to_string(),
1575 session_token: self.source_session_token.clone(),
1576 },
1577 })
1578 } else {
1579 Some(S3Credentials::FromEnvironment)
1580 };
1581
1582 let target_credential = if let Some(target_profile) = self.target_profile.clone() {
1583 Some(S3Credentials::Profile(target_profile))
1584 } else if self.target_access_key.is_some() {
1585 self.target_access_key
1586 .clone()
1587 .map(|access_key| S3Credentials::Credentials {
1588 access_keys: AccessKeys {
1589 access_key,
1590 secret_access_key: self
1591 .target_secret_access_key
1592 .as_ref()
1593 .unwrap()
1594 .to_string(),
1595 session_token: self.target_session_token.clone(),
1596 },
1597 })
1598 } else {
1599 Some(S3Credentials::FromEnvironment)
1600 };
1601
1602 let parallel_upload_semaphore =
1603 Arc::new(Semaphore::new(self.max_parallel_uploads as usize));
1604
1605 let source_request_payer = if self.source_request_payer {
1606 Some(RequestPayer::Requester)
1607 } else {
1608 None
1609 };
1610
1611 let source_client_config = source_credential.map(|source_credential| ClientConfig {
1612 client_config_location: ClientConfigLocation {
1613 aws_config_file: self.aws_config_file.clone(),
1614 aws_shared_credentials_file: self.aws_shared_credentials_file.clone(),
1615 },
1616 credential: source_credential,
1617 region: self.source_region.clone(),
1618 endpoint_url: self.source_endpoint_url.clone(),
1619 force_path_style: self.source_force_path_style,
1620 retry_config: RetryConfig {
1621 aws_max_attempts: self.aws_max_attempts,
1622 initial_backoff_milliseconds: self.initial_backoff_milliseconds,
1623 },
1624 cli_timeout_config: CLITimeoutConfig {
1625 operation_timeout_milliseconds: self.operation_timeout_milliseconds,
1626 operation_attempt_timeout_milliseconds: self.operation_attempt_timeout_milliseconds,
1627 connect_timeout_milliseconds: self.connect_timeout_milliseconds,
1628 read_timeout_milliseconds: self.read_timeout_milliseconds,
1629 },
1630 disable_stalled_stream_protection: self.disable_stalled_stream_protection,
1631 request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
1632 parallel_upload_semaphore: parallel_upload_semaphore.clone(),
1633 accelerate: self.source_accelerate,
1634 request_payer: source_request_payer,
1635 });
1636
1637 let target_request_payer = if self.target_request_payer {
1638 Some(RequestPayer::Requester)
1639 } else {
1640 None
1641 };
1642
1643 let target_client_config = target_credential.map(|target_credential| ClientConfig {
1644 client_config_location: ClientConfigLocation {
1645 aws_config_file: self.aws_config_file.clone(),
1646 aws_shared_credentials_file: self.aws_shared_credentials_file.clone(),
1647 },
1648 credential: target_credential,
1649 region: self.target_region.clone(),
1650 endpoint_url: self.target_endpoint_url.clone(),
1651 force_path_style: self.target_force_path_style,
1652 retry_config: RetryConfig {
1653 aws_max_attempts: self.aws_max_attempts,
1654 initial_backoff_milliseconds: self.initial_backoff_milliseconds,
1655 },
1656 cli_timeout_config: CLITimeoutConfig {
1657 operation_timeout_milliseconds: self.operation_timeout_milliseconds,
1658 operation_attempt_timeout_milliseconds: self.operation_attempt_timeout_milliseconds,
1659 connect_timeout_milliseconds: self.connect_timeout_milliseconds,
1660 read_timeout_milliseconds: self.read_timeout_milliseconds,
1661 },
1662 disable_stalled_stream_protection: self.disable_stalled_stream_protection,
1663 request_checksum_calculation,
1664 parallel_upload_semaphore,
1665 accelerate: self.target_accelerate,
1666 request_payer: target_request_payer,
1667 });
1668
1669 (source_client_config, target_client_config)
1670 }
1671}
1672
1673impl TryFrom<CLIArgs> for Config {
1674 type Error = String;
1675
1676 fn try_from(value: CLIArgs) -> Result<Self, Self::Error> {
1677 value.validate_storage_config()?;
1678
1679 let original_cloned_value = value.clone();
1680
1681 let mut tracing_config = value.verbosity.log_level().map(|log_level| TracingConfig {
1682 tracing_level: log_level,
1683 json_tracing: value.json_tracing,
1684 aws_sdk_tracing: value.aws_sdk_tracing,
1685 span_events_tracing: value.span_events_tracing,
1686 disable_color_tracing: value.disable_color_tracing,
1687 });
1688
1689 let storage_class = value
1690 .storage_class
1691 .map(|storage_class| StorageClass::from_str(&storage_class).unwrap());
1692
1693 let sse = value
1694 .sse
1695 .map(|sse| ServerSideEncryption::from_str(&sse).unwrap());
1696
1697 let canned_acl = value
1698 .acl
1699 .map(|acl| ObjectCannedAcl::from_str(&acl).unwrap());
1700
1701 let include_regex = value
1702 .filter_include_regex
1703 .map(|regex| Regex::new(®ex).unwrap());
1704
1705 let exclude_regex = value
1706 .filter_exclude_regex
1707 .map(|regex| Regex::new(®ex).unwrap());
1708 let include_content_type_regex = value
1709 .filter_include_content_type_regex
1710 .map(|regex| Regex::new(®ex).unwrap());
1711 let exclude_content_type_regex = value
1712 .filter_exclude_content_type_regex
1713 .map(|regex| Regex::new(®ex).unwrap());
1714
1715 let include_metadata_regex = value
1716 .filter_include_metadata_regex
1717 .map(|regex| Regex::new(®ex).unwrap());
1718 let exclude_metadata_regex = value
1719 .filter_exclude_metadata_regex
1720 .map(|regex| Regex::new(®ex).unwrap());
1721 let include_tag_regex = value
1722 .filter_include_tag_regex
1723 .map(|regex| Regex::new(®ex).unwrap());
1724 let exclude_tag_regex = value
1725 .filter_exclude_tag_regex
1726 .map(|regex| Regex::new(®ex).unwrap());
1727
1728 let rate_limit_bandwidth = value
1729 .rate_limit_bandwidth
1730 .map(|bandwidth| human_bytes::parse_human_bandwidth(&bandwidth).unwrap());
1731
1732 let mut additional_checksum_algorithm = value
1733 .additional_checksum_algorithm
1734 .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1735
1736 let check_additional_checksum_algorithm = value
1737 .check_additional_checksum
1738 .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1739
1740 let check_mtime_and_additional_checksum = value
1741 .check_mtime_and_additional_checksum
1742 .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1743
1744 let mut checksum_mode = if value.enable_additional_checksum {
1745 Some(ChecksumMode::Enabled)
1746 } else {
1747 None
1748 };
1749
1750 let tagging = value
1751 .tagging
1752 .map(|tagging| tagging::parse_tagging(&tagging).unwrap());
1753 let filter_larger_size = value
1754 .filter_larger_size
1755 .map(|human_bytes| human_bytes::parse_human_bytes_without_limit(&human_bytes).unwrap());
1756 let filter_smaller_size = value
1757 .filter_smaller_size
1758 .map(|human_bytes| human_bytes::parse_human_bytes_without_limit(&human_bytes).unwrap());
1759
1760 let metadata = if value.metadata.is_some() {
1761 Some(metadata::parse_metadata(&value.metadata.unwrap())?)
1762 } else {
1763 None
1764 };
1765
1766 let mut full_object_checksum = if additional_checksum_algorithm
1767 .as_ref()
1768 .is_some_and(|algorithm| algorithm == &ChecksumAlgorithm::Crc64Nvme)
1769 {
1770 true
1771 } else {
1772 value.full_object_checksum
1773 };
1774
1775 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&value.source) {
1776 if is_express_onezone_storage(&bucket)
1777 && !value.disable_express_one_zone_additional_checksum
1778 {
1779 checksum_mode = Some(ChecksumMode::Enabled);
1780 }
1781 }
1782
1783 let mut request_checksum_calculation = RequestChecksumCalculation::WhenRequired;
1784 if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&value.target) {
1785 if is_express_onezone_storage(&bucket)
1786 && additional_checksum_algorithm.is_none()
1787 && !value.disable_express_one_zone_additional_checksum
1788 {
1789 additional_checksum_algorithm = Some(ChecksumAlgorithm::Crc64Nvme);
1790 full_object_checksum = true;
1791 request_checksum_calculation = RequestChecksumCalculation::WhenSupported;
1792 } else if additional_checksum_algorithm.is_some() {
1793 request_checksum_calculation = RequestChecksumCalculation::WhenSupported;
1794 }
1795 }
1796
1797 let (source_client_config, target_client_config) =
1798 original_cloned_value.build_client_configs(request_checksum_calculation);
1799
1800 #[allow(unused_assignments)]
1801 #[allow(unused_mut)]
1802 let mut allow_e2e_test_dangerous_simulation = false;
1803
1804 #[allow(unused_assignments)]
1805 #[allow(unused_mut)]
1806 let mut cancellation_point = None;
1807 #[allow(unused_assignments)]
1808 #[allow(unused_mut)]
1809 let mut panic_simulation_point = None;
1810 #[allow(unused_assignments)]
1811 #[allow(unused_mut)]
1812 let mut error_simulation_point = None;
1813 #[cfg(feature = "e2e_test_dangerous_simulations")]
1814 {
1815 allow_e2e_test_dangerous_simulation = value.allow_e2e_test_dangerous_simulation;
1816 cancellation_point = value.cancellation_point;
1817 panic_simulation_point = value.panic_simulation_point;
1818 error_simulation_point = value.error_simulation_point;
1819 }
1820
1821 #[allow(unused_assignments)]
1822 #[allow(unused_mut)]
1823 let mut test_user_defined_callback = false;
1824 #[cfg(feature = "e2e_test_dangerous_simulations")]
1825 {
1826 test_user_defined_callback = value.test_user_defined_callback;
1827 }
1828
1829 let mut remove_modified_filter = if value.point_in_time.is_some() {
1832 true
1833 } else {
1834 value.remove_modified_filter
1835 };
1836
1837 let dry_run = if value.report_sync_status {
1839 true
1840 } else {
1841 value.dry_run
1842 };
1843
1844 let warn_as_error;
1847 let check_etag;
1848 let head_each_target;
1849 if value.report_sync_status {
1850 remove_modified_filter = true;
1851 warn_as_error = true;
1852 head_each_target = true;
1853 check_etag = check_additional_checksum_algorithm.is_none();
1854 } else {
1855 warn_as_error = value.warn_as_error;
1856 check_etag = value.check_etag;
1857 head_each_target = value.head_each_target;
1858 }
1859
1860 if dry_run {
1861 if tracing_config.is_none() {
1862 tracing_config = Some(TracingConfig {
1863 tracing_level: log::Level::Info,
1864 json_tracing: DEFAULT_JSON_TRACING,
1865 aws_sdk_tracing: DEFAULT_AWS_SDK_TRACING,
1866 span_events_tracing: DEFAULT_SPAN_EVENTS_TRACING,
1867 disable_color_tracing: DEFAULT_DISABLE_COLOR_TRACING,
1868 });
1869 } else if tracing_config.unwrap().tracing_level < log::Level::Info {
1870 tracing_config = Some(TracingConfig {
1871 tracing_level: log::Level::Info,
1872 json_tracing: tracing_config.unwrap().json_tracing,
1873 aws_sdk_tracing: tracing_config.unwrap().aws_sdk_tracing,
1874 span_events_tracing: tracing_config.unwrap().span_events_tracing,
1875 disable_color_tracing: tracing_config.unwrap().disable_color_tracing,
1876 });
1877 }
1878 }
1879
1880 cfg_if! {
1881 if #[cfg(feature = "lua_support")] {
1882 let preprocess_callback_lua_script = value.preprocess_callback_lua_script.clone();
1883 let event_callback_lua_script = value.event_callback_lua_script.clone();
1884 let filter_callback_lua_script = value.filter_callback_lua_script.clone();
1885 let allow_lua_os_library = value.allow_lua_os_library;
1886 let allow_lua_unsafe_vm = value.allow_lua_unsafe_vm;
1887 let lua_vm_memory_limit = human_bytes::parse_human_bytes_without_low_limit(&value.lua_vm_memory_limit)? as usize;
1888 } else {
1889 let preprocess_callback_lua_script = None;
1890 let event_callback_lua_script = None;
1891 let filter_callback_lua_script = None;
1892 let allow_lua_os_library = false;
1893 let allow_lua_unsafe_vm = false;
1894 let lua_vm_memory_limit = 0;
1895 }
1896 }
1897
1898 #[allow(unused_mut)]
1899 let mut preprocess_manager = PreprocessManager::new();
1900 cfg_if! {
1901 if #[cfg(feature = "lua_support")] {
1902 if let Some(preprocess_callback_lua_script) = preprocess_callback_lua_script.as_ref() {
1903 let mut lua_preprocess_callback = crate::callback::lua_preprocess_callback::LuaPreprocessCallback::new(
1904 lua_vm_memory_limit,
1905 allow_lua_os_library,
1906 allow_lua_unsafe_vm,
1907 );
1908 if let Err(e) =
1909 lua_preprocess_callback.load_and_compile(preprocess_callback_lua_script.as_str())
1910 {
1911 return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
1912 }
1913 preprocess_manager.register_callback(lua_preprocess_callback);
1914 }
1915 }
1916 }
1917
1918 #[allow(unused_mut)]
1919 let mut event_manager = EventManager::new();
1920 cfg_if! {
1921 if #[cfg(feature = "lua_support")] {
1922 if let Some(event_callback_lua_script) = event_callback_lua_script.as_ref() {
1923 let mut lua_event_callback = crate::callback::lua_event_callback::LuaEventCallback::new(
1924 lua_vm_memory_limit,
1925 allow_lua_os_library,
1926 allow_lua_unsafe_vm,
1927 );
1928 if let Err(e) = lua_event_callback.load_and_compile(event_callback_lua_script.as_str())
1929 {
1930 return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
1931 }
1932 event_manager.register_callback(EventType::ALL_EVENTS, lua_event_callback, dry_run);
1934 }
1935 }
1936 }
1937
1938 #[allow(unused_mut)]
1939 let mut filter_manager = FilterManager::new();
1940 cfg_if! {
1941 if #[cfg(feature = "lua_support")] {
1942 if let Some(filter_callback_lua_script) = filter_callback_lua_script.as_ref() {
1943 let mut lua_filter_callback = crate::callback::lua_filter_callback::LuaFilterCallback::new(
1944 lua_vm_memory_limit,
1945 allow_lua_os_library,
1946 allow_lua_unsafe_vm,
1947 );
1948 if let Err(e) =
1949 lua_filter_callback.load_and_compile(filter_callback_lua_script.as_str())
1950 {
1951 return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
1952 }
1953 filter_manager.register_callback(lua_filter_callback);
1954 }
1955 }
1956 }
1957
1958 Ok(Config {
1959 source: storage_path::parse_storage_path(&value.source),
1960 target: storage_path::parse_storage_path(&value.target),
1961
1962 show_no_progress: value.show_no_progress,
1963
1964 source_client_config,
1965 target_client_config,
1966
1967 tracing_config,
1968
1969 force_retry_config: ForceRetryConfig {
1970 force_retry_count: value.force_retry_count,
1971 force_retry_interval_milliseconds: value.force_retry_interval_milliseconds,
1972 },
1973
1974 transfer_config: TransferConfig {
1975 multipart_threshold: human_bytes::parse_human_bytes(&value.multipart_threshold)?,
1976 multipart_chunksize: human_bytes::parse_human_bytes(&value.multipart_chunksize)?,
1977 auto_chunksize: value.auto_chunksize,
1978 },
1979
1980 worker_size: value.worker_size,
1981
1982 warn_as_error,
1983 follow_symlinks: !value.ignore_symlinks,
1984 head_each_target,
1985 sync_with_delete: value.delete,
1986 disable_tagging: value.disable_tagging,
1987 sync_latest_tagging: value.sync_latest_tagging,
1988 server_side_copy: value.server_side_copy,
1989 no_guess_mime_type: value.no_guess_mime_type,
1990 disable_multipart_verify: value.disable_multipart_verify,
1991 disable_etag_verify: value.disable_etag_verify,
1992 disable_additional_checksum_verify: value.disable_additional_checksum_verify,
1993 enable_versioning: value.enable_versioning,
1994 point_in_time: value.point_in_time,
1995 storage_class,
1996 sse,
1997 sse_kms_key_id: SseKmsKeyId {
1998 id: value.sse_kms_key_id,
1999 },
2000 source_sse_c: value.source_sse_c,
2001 source_sse_c_key: SseCustomerKey {
2002 key: value.source_sse_c_key,
2003 },
2004 source_sse_c_key_md5: value.source_sse_c_key_md5,
2005 target_sse_c: value.target_sse_c,
2006 target_sse_c_key: SseCustomerKey {
2007 key: value.target_sse_c_key,
2008 },
2009 target_sse_c_key_md5: value.target_sse_c_key_md5,
2010 canned_acl,
2011 additional_checksum_algorithm,
2012 additional_checksum_mode: checksum_mode,
2013 dry_run,
2014 rate_limit_objects: value.rate_limit_objects,
2015 rate_limit_bandwidth,
2016 max_parallel_listings: value.max_parallel_listings,
2017 object_listing_queue_size: value.object_listing_queue_size,
2018 max_parallel_listing_max_depth: value.max_parallel_listing_max_depth,
2019 allow_parallel_listings_in_express_one_zone: value
2020 .allow_parallel_listings_in_express_one_zone,
2021 cache_control: value.cache_control,
2022 content_disposition: value.content_disposition,
2023 content_encoding: value.content_encoding,
2024 content_language: value.content_language,
2025 content_type: value.content_type,
2026 expires: value.expires,
2027 metadata,
2028 website_redirect: value.website_redirect,
2029 no_sync_system_metadata: value.no_sync_system_metadata,
2030 no_sync_user_defined_metadata: value.no_sync_user_defined_metadata,
2031 tagging,
2032 filter_config: FilterConfig {
2033 before_time: value.filter_mtime_before,
2034 after_time: value.filter_mtime_after,
2035 remove_modified_filter,
2036 check_size: value.check_size,
2037 check_etag,
2038 check_mtime_and_etag: value.check_mtime_and_etag,
2039 check_checksum_algorithm: check_additional_checksum_algorithm,
2040 check_mtime_and_additional_checksum,
2041 include_regex,
2042 exclude_regex,
2043 include_content_type_regex,
2044 exclude_content_type_regex,
2045 include_metadata_regex,
2046 exclude_metadata_regex,
2047 include_tag_regex,
2048 exclude_tag_regex,
2049 larger_size: filter_larger_size,
2050 smaller_size: filter_smaller_size,
2051 filter_manager,
2052 },
2053 max_keys: value.max_keys,
2054 put_last_modified_metadata: value.put_last_modified_metadata,
2055 auto_complete_shell: value.auto_complete_shell,
2056 disable_payload_signing: value.disable_payload_signing,
2057 disable_content_md5_header: value.disable_content_md5_header,
2058 delete_excluded: value.delete_excluded,
2059 full_object_checksum,
2060 allow_e2e_test_dangerous_simulation,
2061 test_user_defined_callback,
2062 cancellation_point,
2063 panic_simulation_point,
2064 error_simulation_point,
2065 source_accelerate: value.source_accelerate,
2066 target_accelerate: value.target_accelerate,
2067 source_request_payer: value.source_request_payer,
2068 target_request_payer: value.target_request_payer,
2069 report_sync_status: value.report_sync_status,
2070 report_metadata_sync_status: value.report_metadata_sync_status,
2071 report_tagging_sync_status: value.report_tagging_sync_status,
2072 event_manager,
2073 preprocess_manager,
2074 preprocess_callback_lua_script,
2075 event_callback_lua_script,
2076 filter_callback_lua_script,
2077 allow_lua_os_library,
2078 allow_lua_unsafe_vm,
2079 lua_vm_memory_limit,
2080 if_match: value.if_match,
2081 copy_source_if_match: value.copy_source_if_match,
2082 max_delete: value.max_delete,
2083 ignore_glacier_warnings: value.ignore_glacier_warnings,
2084 })
2085 }
2086}
2087
2088fn is_express_onezone_storage(bucket: &str) -> bool {
2089 bucket.ends_with(EXPRESS_ONEZONE_STORAGE_SUFFIX)
2090}