@@ -110,11 +110,12 @@ pub(super) mod api {
110110 state::{InMemoryState, NotKeyed},
111111 Quota, RateLimiter,
112112 };
113+ use http::StatusCode;
113114 use lazy_static::lazy_static;
114115 use nonzero_ext::nonzero;
115116 use semver::Version;
116117 use serde::Deserialize;
117- use snafu::ResultExt;
118+ use snafu::{OptionExt, ResultExt} ;
118119 use std::process::{Command, Output};
119120 use tokio::time::Duration;
120121 use tokio_retry::{
@@ -124,7 +125,7 @@ pub(super) mod api {
124125 use tracing::{event, instrument, Level};
125126
126127 const API_CLIENT_BIN: &str = "apiclient";
127- const UPDATE_API_BUSY_STATUSCODE: &str = "423" ;
128+ const UPDATE_API_BUSY_STATUSCODE: StatusCode = StatusCode::LOCKED ;
128129 const ACTIVATE_UPDATES_URI: &str = "/actions/activate-update";
129130 const OS_URI: &str = "/os";
130131 const PREPARE_UPDATES_URI: &str = "/actions/prepare-update";
@@ -232,12 +233,22 @@ pub(super) mod api {
232233 /// Extract error statuscode from stderr string
233234 /// Error Example:
234235 /// "Failed POST request to '/actions/refresh-updates': Status 423 when POSTing /actions/refresh-updates: Update lock held\n"
235- fn extract_status_code_from_error(error: &str) -> &str {
236- let error_content_split_by_status: Vec<&str> = error.split("Status").collect();
237- let error_content_split_by_whitespace: Vec<&str> = error_content_split_by_status[1]
236+ pub fn extract_status_code_from_error(error: &str) -> Result<StatusCode> {
237+ let status_str = error
238+ .split("Status ")
239+ .nth(1)
240+ .context(apiclient_error::StatusCodeParsingSnafu)?;
241+
242+ let code_str = status_str
238243 .split_whitespace()
239- .collect();
240- error_content_split_by_whitespace[0]
244+ .next()
245+ .context(apiclient_error::StatusCodeParsingSnafu)?;
246+
247+ let code = code_str
248+ .parse::<u16>()
249+ .context(apiclient_error::StatusCodeIntParsingSnafu)?;
250+
251+ StatusCode::from_u16(code).context(apiclient_error::InvalidStatusCodeSnafu)
241252 }
242253
243254 /// Wait time between invoking the Bottlerocket API
@@ -294,7 +305,7 @@ pub(super) mod api {
294305 let error_statuscode = extract_status_code_from_error(&error_content);
295306
296307 match error_statuscode {
297- UPDATE_API_BUSY_STATUSCODE => {
308+ Ok( UPDATE_API_BUSY_STATUSCODE) => {
298309 event!(
299310 Level::DEBUG,
300311 "The lock for the update API is held by another process ..."
@@ -307,7 +318,10 @@ pub(super) mod api {
307318 apiclient_error::BadHttpResponseSnafu {
308319 args: args.clone(),
309320 error_content: &error_content,
310- statuscode: error_statuscode,
321+ statuscode: error_statuscode.map_or_else(
322+ |_| "None".to_string(),
323+ |s| s.as_u16().to_string(),
324+ ),
311325 }
312326 .fail()
313327 }
@@ -447,5 +461,69 @@ pub mod apiclient_error {
447461
448462 #[snafu(display("Unable to parse version information: '{}'", source))]
449463 VersionParseError { source: semver::Error },
464+
465+ #[snafu(display("Failed to parse status code from error string"))]
466+ StatusCodeParsing,
467+
468+ #[snafu(display("Failed to parse integer from status code string"))]
469+ StatusCodeIntParsing { source: std::num::ParseIntError },
470+
471+ #[snafu(display("Invalid HTTP status code"))]
472+ InvalidStatusCode {
473+ source: http::status::InvalidStatusCode,
474+ },
475+ }
476+ }
477+
478+ #[cfg(test)]
479+ mod tests {
480+ use super::api::*;
481+ use super::apiclient_error::Error;
482+ use http::StatusCode;
483+
484+ #[test]
485+ fn test_extract_status_code_from_error_success() {
486+ let error = "Failed POST request to '/actions/refresh-updates': Status 423 when POSTing /actions/refresh-updates: Update lock held";
487+ let result = extract_status_code_from_error(error);
488+ assert!(result.is_ok());
489+ assert_eq!(result.unwrap(), StatusCode::LOCKED);
490+ }
491+
492+ #[test]
493+ fn test_extract_status_code_from_error_no_status() {
494+ let error = "Some error without status code";
495+ let result = extract_status_code_from_error(error);
496+ assert!(result.is_err());
497+ assert!(matches!(result.unwrap_err(), Error::StatusCodeParsing));
498+ }
499+
500+ #[test]
501+ fn test_extract_status_code_from_error_invalid_number() {
502+ let error = "Failed POST request: Status abc when POSTing";
503+ let result = extract_status_code_from_error(error);
504+ assert!(result.is_err());
505+ assert!(matches!(
506+ result.unwrap_err(),
507+ Error::StatusCodeIntParsing { .. }
508+ ));
509+ }
510+
511+ #[test]
512+ fn test_extract_status_code_from_error_invalid_status_code() {
513+ let error = "Failed POST request: Status 1000 when POSTing";
514+ let result = extract_status_code_from_error(error);
515+ assert!(result.is_err());
516+ assert!(matches!(
517+ result.unwrap_err(),
518+ Error::InvalidStatusCode { .. }
519+ ));
520+ }
521+
522+ #[test]
523+ fn test_extract_status_code_from_error_no_whitespace() {
524+ let error = "Status 200";
525+ let result = extract_status_code_from_error(error);
526+ assert!(result.is_ok());
527+ assert_eq!(result.unwrap(), StatusCode::OK);
450528 }
451529}
0 commit comments