Skip to content

Commit 903fab1

Browse files
authored
Merge pull request #781 from maherthomsi/index_out_of_bounds_fix
fix: improve status code extraction from API client error messages
2 parents c0d2f62 + 608721f commit 903fab1

File tree

3 files changed

+89
-9
lines changed

3 files changed

+89
-9
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ apiserver = { path = "../apiserver", version = "0.1.0", default-features = false
1414

1515
futures = { workspace = true }
1616
governor = { workspace = true }
17+
http = { workspace = true }
1718
lazy_static = { workspace = true }
1819
nonzero_ext = { workspace = true }
1920
tracing = { workspace = true }

agent/src/apiclient.rs

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)