use super::{proxy_to_role, proxy_upgrade_to_role, resolve_container_role, resolve_exec_role};
use crate::api::AppState;
use crate::error::{DockerError, Result};
use axum::body::Body;
use axum::extract::{OriginalUri, State};
use axum::http::{Request, header};
use axum::response::Response;
crate::handlers::exec_proxy_handler!(exec_resize);
crate::handlers::exec_proxy_handler!(exec_inspect);
pub async fn exec_create(
State(state): State<AppState>,
OriginalUri(uri): OriginalUri,
req: Request<Body>,
) -> Result<Response> {
let role = resolve_container_role(&state, &uri).await?;
let response = proxy_to_role(&state, role, &uri, req).await?;
if !response.status().is_success() {
return Ok(response);
}
let (parts, body) = response.into_parts();
let body_bytes = http_body_util::BodyExt::collect(body)
.await
.map_err(|e| DockerError::Server(format!("failed to read exec create response: {e}")))?
.to_bytes();
if let Some(exec_id) = parse_exec_create_response_id(&body_bytes) {
tracing::debug!(
utility_vm = role.as_str(),
exec_id = %exec_id,
"recorded exec role binding",
);
state.workload_roles.record(exec_id, role).await;
} else {
tracing::warn!(
utility_vm = role.as_str(),
"exec create response missing exec ID; follow-up calls will fall back to native"
);
}
Ok(Response::from_parts(parts, Body::from(body_bytes)))
}
pub async fn exec_start(
State(state): State<AppState>,
OriginalUri(uri): OriginalUri,
req: Request<Body>,
) -> Result<Response> {
let role = resolve_exec_role(&state, &uri).await?;
let wants_upgrade = req.headers().get(header::UPGRADE).is_some()
|| req
.headers()
.get(header::CONNECTION)
.and_then(|v| v.to_str().ok())
.is_some_and(|v| v.to_ascii_lowercase().contains("upgrade"));
if wants_upgrade {
proxy_upgrade_to_role(&state, role, &uri, req).await
} else {
proxy_to_role(&state, role, &uri, req).await
}
}
fn parse_exec_create_response_id(body: &[u8]) -> Option<String> {
let value: serde_json::Value = serde_json::from_slice(body).ok()?;
value.get("Id")?.as_str().map(String::from)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_exec_id_from_create_response() {
let json = br#"{"Id":"exec-abc123"}"#;
assert_eq!(
parse_exec_create_response_id(json).as_deref(),
Some("exec-abc123"),
);
}
#[test]
fn returns_none_when_exec_create_response_missing_id() {
assert_eq!(parse_exec_create_response_id(b"{}"), None);
assert_eq!(parse_exec_create_response_id(b"not json"), None);
}
}