corgea-cli 1.8.7__tar.gz → 1.8.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/Cargo.lock +5 -6
  2. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/Cargo.toml +1 -1
  3. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/PKG-INFO +1 -1
  4. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scan.rs +33 -26
  5. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/utils/api.rs +158 -0
  6. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/.github/workflows/npm-publish.yml +0 -0
  7. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/.github/workflows/release-binaries.yml +0 -0
  8. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/.github/workflows/release.yml +0 -0
  9. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/.github/workflows/test.yml +0 -0
  10. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/.github/workflows/update_docs..yml +0 -0
  11. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/.gitignore +0 -0
  12. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/LICENSE +0 -0
  13. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/README.md +0 -0
  14. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/bin/corgea.js +0 -0
  15. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/build_release.sh +0 -0
  16. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/package.json +0 -0
  17. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/pyproject.toml +0 -0
  18. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/scripts/npm/bundle-binaries.js +0 -0
  19. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/skills/corgea/SKILL.md +0 -0
  20. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/authorize.rs +0 -0
  21. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/cicd.rs +0 -0
  22. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/config.rs +0 -0
  23. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/inspect.rs +0 -0
  24. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/list.rs +0 -0
  25. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/log.rs +0 -0
  26. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/main.rs +0 -0
  27. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/blast.rs +0 -0
  28. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/fortify.rs +0 -0
  29. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/parsers/checkmarx.rs +0 -0
  30. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/parsers/coverity.rs +0 -0
  31. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/parsers/mod.rs +0 -0
  32. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/parsers/sarif.rs +0 -0
  33. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/scanners/parsers/semgrep.rs +0 -0
  34. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/setup_hooks.rs +0 -0
  35. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/targets.rs +0 -0
  36. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/utils/generic.rs +0 -0
  37. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/utils/terminal.rs +0 -0
  38. {corgea_cli-1.8.7 → corgea_cli-1.8.8}/src/wait.rs +0 -0
@@ -340,7 +340,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
340
340
 
341
341
  [[package]]
342
342
  name = "corgea"
343
- version = "1.8.7"
343
+ version = "1.8.8"
344
344
  dependencies = [
345
345
  "chrono",
346
346
  "clap",
@@ -1321,15 +1321,14 @@ dependencies = [
1321
1321
 
1322
1322
  [[package]]
1323
1323
  name = "openssl"
1324
- version = "0.10.73"
1324
+ version = "0.10.80"
1325
1325
  source = "registry+https://github.com/rust-lang/crates.io-index"
1326
- checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
1326
+ checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
1327
1327
  dependencies = [
1328
1328
  "bitflags",
1329
1329
  "cfg-if",
1330
1330
  "foreign-types",
1331
1331
  "libc",
1332
- "once_cell",
1333
1332
  "openssl-macros",
1334
1333
  "openssl-sys",
1335
1334
  ]
@@ -1362,9 +1361,9 @@ dependencies = [
1362
1361
 
1363
1362
  [[package]]
1364
1363
  name = "openssl-sys"
1365
- version = "0.9.109"
1364
+ version = "0.9.116"
1366
1365
  source = "registry+https://github.com/rust-lang/crates.io-index"
1367
- checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
1366
+ checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
1368
1367
  dependencies = [
1369
1368
  "cc",
1370
1369
  "libc",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "corgea"
3
- version = "1.8.7"
3
+ version = "1.8.8"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: corgea-cli
3
- Version: 1.8.7
3
+ Version: 1.8.8
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -186,14 +186,15 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
186
186
  let mut success = false;
187
187
 
188
188
  while attempts < 3 && !success {
189
- let form = reqwest::blocking::multipart::Form::new()
190
- .file("file", fp)
191
- .expect("Failed to read file");
192
-
193
189
  debug(&format!("POST: {}", src_upload_url));
194
- let res = client.post(&src_upload_url)
195
- .multipart(form)
196
- .send();
190
+ let res = utils::api::retry_on_network_error("file upload", || {
191
+ let form = reqwest::blocking::multipart::Form::new()
192
+ .file("file", fp)
193
+ .expect("Failed to read file");
194
+ client.post(&src_upload_url)
195
+ .multipart(form)
196
+ .send()
197
+ });
197
198
 
198
199
  match res {
199
200
  Ok(response) => {
@@ -211,8 +212,9 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
211
212
  }
212
213
  }
213
214
  Err(e) => {
214
- eprintln!("Failed to send request: {}", e);
215
- std::process::exit(1);
215
+ upload_error_count += 1;
216
+ eprintln!("Failed to upload file {} after network retries: {}", path, e);
217
+ break;
216
218
  }
217
219
  }
218
220
  }
@@ -258,12 +260,14 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
258
260
 
259
261
  for (index, chunk) in input_bytes.chunks(chunk_size).enumerate() {
260
262
  debug(&format!("POST: {} (chunk {}/{})", scan_upload_url, index + 1, total_chunks));
261
- let response = client.post(&scan_upload_url)
262
- .header(header::CONTENT_TYPE, "application/json")
263
- .header("Upload-Offset", offset.to_string())
264
- .header("Upload-Length", input_size.to_string())
265
- .body(chunk.to_vec())
266
- .send();
263
+ let response = utils::api::retry_on_network_error("scan chunk upload", || {
264
+ client.post(&scan_upload_url)
265
+ .header(header::CONTENT_TYPE, "application/json")
266
+ .header("Upload-Offset", offset.to_string())
267
+ .header("Upload-Length", input_size.to_string())
268
+ .body(chunk.to_vec())
269
+ .send()
270
+ });
267
271
 
268
272
  let should_break = match &response {
269
273
  Ok(res) => {
@@ -308,10 +312,12 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
308
312
  last_response.expect("Failed to upload scan.")
309
313
  } else {
310
314
  debug(&format!("POST: {}", scan_upload_url));
311
- client.post(&scan_upload_url)
312
- .header(header::CONTENT_TYPE, "application/json")
313
- .body(input.clone())
314
- .send()
315
+ utils::api::retry_on_network_error("scan upload", || {
316
+ client.post(&scan_upload_url)
317
+ .header(header::CONTENT_TYPE, "application/json")
318
+ .body(input.clone())
319
+ .send()
320
+ })
315
321
  };
316
322
 
317
323
  let mut sast_scan_id: Option<String> = None;
@@ -381,14 +387,15 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
381
387
 
382
388
  if git_config_path.exists() {
383
389
  debug("Uploading .git/config");
384
- let form = reqwest::blocking::multipart::Form::new()
385
- .file("file", git_config_path)
386
- .expect("Failed to read file");
387
-
388
390
  debug(&format!("POST: {}", git_config_upload_url));
389
- let res = client.post(&git_config_upload_url)
390
- .multipart(form)
391
- .send();
391
+ let res = utils::api::retry_on_network_error("git config upload", || {
392
+ let form = reqwest::blocking::multipart::Form::new()
393
+ .file("file", git_config_path)
394
+ .expect("Failed to read file");
395
+ client.post(&git_config_upload_url)
396
+ .multipart(form)
397
+ .send()
398
+ });
392
399
 
393
400
  match res {
394
401
  Ok(response) => {
@@ -144,6 +144,34 @@ pub fn http_client() -> HttpClient {
144
144
  HttpClient { inner: SHARED_CLIENT.clone() }
145
145
  }
146
146
 
147
+ #[cfg(not(test))]
148
+ const RETRY_BACKOFF_SECS: &[u64] = &[1, 2, 4, 8, 16, 32];
149
+
150
+ #[cfg(test)]
151
+ const RETRY_BACKOFF_SECS: &[u64] = &[0, 0, 0, 0, 0, 0];
152
+
153
+ pub fn retry_on_network_error<F, T>(operation: &str, mut make_request: F) -> reqwest::Result<T>
154
+ where
155
+ F: FnMut() -> reqwest::Result<T>,
156
+ {
157
+ let mut attempt = 0usize;
158
+ loop {
159
+ match make_request() {
160
+ Ok(result) => return Ok(result),
161
+ Err(e) if (e.is_connect() || e.is_timeout()) && attempt < RETRY_BACKOFF_SECS.len() => {
162
+ let delay = RETRY_BACKOFF_SECS[attempt];
163
+ eprintln!(
164
+ "Network error during {}: {}. Retrying in {}s... ({}/{})",
165
+ operation, e, delay, attempt + 1, RETRY_BACKOFF_SECS.len()
166
+ );
167
+ std::thread::sleep(std::time::Duration::from_secs(delay));
168
+ attempt += 1;
169
+ }
170
+ Err(e) => return Err(e),
171
+ }
172
+ }
173
+ }
174
+
147
175
  fn check_for_warnings(headers: &HeaderMap, status: StatusCode) {
148
176
  if let Some(warning) = headers.get("warning") {
149
177
  let warnings = warning.to_str().unwrap().split(',');
@@ -913,3 +941,133 @@ pub struct SCAIssuesResponse {
913
941
  pub total_pages: u32,
914
942
  pub total_issues: u32,
915
943
  }
944
+
945
+ #[cfg(test)]
946
+ mod tests {
947
+ use super::*;
948
+ use std::cell::Cell;
949
+ use std::net::TcpListener;
950
+ use std::thread;
951
+ use std::time::Duration;
952
+
953
+ fn connection_refused_error() -> reqwest::Error {
954
+ let listener = TcpListener::bind("127.0.0.1:0").expect("failed to bind ephemeral port");
955
+ let port = listener.local_addr().expect("failed to get listener addr").port();
956
+ drop(listener);
957
+
958
+ reqwest::blocking::Client::builder()
959
+ .connect_timeout(Duration::from_secs(1))
960
+ .build()
961
+ .expect("failed to build client")
962
+ .get(format!("http://127.0.0.1:{port}"))
963
+ .send()
964
+ .expect_err("expected connection error")
965
+ }
966
+
967
+ fn timeout_error() -> reqwest::Error {
968
+ let listener = TcpListener::bind("127.0.0.1:0").expect("failed to bind ephemeral port");
969
+ let port = listener.local_addr().expect("failed to get listener addr").port();
970
+
971
+ thread::spawn(move || {
972
+ if let Ok((_, _)) = listener.accept() {
973
+ thread::sleep(Duration::from_secs(30));
974
+ }
975
+ });
976
+
977
+ reqwest::blocking::Client::builder()
978
+ .timeout(Duration::from_millis(200))
979
+ .build()
980
+ .expect("failed to build client")
981
+ .get(format!("http://127.0.0.1:{port}"))
982
+ .send()
983
+ .expect_err("expected timeout error")
984
+ }
985
+
986
+ fn non_retryable_error() -> reqwest::Error {
987
+ let err = reqwest::blocking::Client::new()
988
+ .get("http://[::1:bad")
989
+ .send()
990
+ .expect_err("expected request error");
991
+
992
+ assert!(
993
+ !err.is_connect() && !err.is_timeout(),
994
+ "expected a non-retryable reqwest error, got: {err}"
995
+ );
996
+ err
997
+ }
998
+
999
+ #[test]
1000
+ fn retry_on_network_error_returns_ok_on_first_success() {
1001
+ let attempts = Cell::new(0);
1002
+
1003
+ let result = retry_on_network_error("test operation", || {
1004
+ attempts.set(attempts.get() + 1);
1005
+ Ok("success")
1006
+ });
1007
+
1008
+ assert_eq!(result.unwrap(), "success");
1009
+ assert_eq!(attempts.get(), 1);
1010
+ }
1011
+
1012
+ #[test]
1013
+ fn retry_on_network_error_retries_connect_errors_then_succeeds() {
1014
+ let attempts = Cell::new(0);
1015
+
1016
+ let result = retry_on_network_error("test operation", || {
1017
+ let attempt = attempts.get() + 1;
1018
+ attempts.set(attempt);
1019
+ if attempt < 3 {
1020
+ Err(connection_refused_error())
1021
+ } else {
1022
+ Ok(42)
1023
+ }
1024
+ });
1025
+
1026
+ assert_eq!(result.unwrap(), 42);
1027
+ assert_eq!(attempts.get(), 3);
1028
+ }
1029
+
1030
+ #[test]
1031
+ fn retry_on_network_error_retries_timeout_errors() {
1032
+ let attempts = Cell::new(0);
1033
+
1034
+ let result = retry_on_network_error("test operation", || {
1035
+ let attempt = attempts.get() + 1;
1036
+ attempts.set(attempt);
1037
+ if attempt == 1 {
1038
+ Err(timeout_error())
1039
+ } else {
1040
+ Ok("recovered")
1041
+ }
1042
+ });
1043
+
1044
+ assert_eq!(result.unwrap(), "recovered");
1045
+ assert_eq!(attempts.get(), 2);
1046
+ }
1047
+
1048
+ #[test]
1049
+ fn retry_on_network_error_does_not_retry_non_network_errors() {
1050
+ let attempts = Cell::new(0);
1051
+
1052
+ let result: reqwest::Result<()> = retry_on_network_error("test operation", || {
1053
+ attempts.set(attempts.get() + 1);
1054
+ Err(non_retryable_error())
1055
+ });
1056
+
1057
+ assert!(result.is_err());
1058
+ assert_eq!(attempts.get(), 1);
1059
+ }
1060
+
1061
+ #[test]
1062
+ fn retry_on_network_error_gives_up_after_max_retries() {
1063
+ let attempts = Cell::new(0);
1064
+
1065
+ let result: reqwest::Result<()> = retry_on_network_error("test operation", || {
1066
+ attempts.set(attempts.get() + 1);
1067
+ Err(connection_refused_error())
1068
+ });
1069
+
1070
+ assert!(result.is_err());
1071
+ assert_eq!(attempts.get(), RETRY_BACKOFF_SECS.len() + 1);
1072
+ }
1073
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes