photoncloud-monorepo/deployer/crates/deployer-server/src/bootstrap_assets.rs

133 lines
3.9 KiB
Rust

use std::sync::Arc;
use axum::{
body::Body,
extract::State,
http::{header, HeaderMap, HeaderValue, StatusCode},
response::IntoResponse,
};
use tokio::fs;
use crate::{auth::require_bootstrap_auth, state::AppState};
/// GET /api/v1/bootstrap/flake-bundle
pub async fn flake_bundle(
State(state): State<Arc<AppState>>,
headers: HeaderMap,
) -> Result<impl IntoResponse, (StatusCode, String)> {
require_bootstrap_auth(&state, &headers)?;
let Some(path) = state.config.bootstrap_flake_bundle_path.as_ref() else {
return Err((
StatusCode::SERVICE_UNAVAILABLE,
"bootstrap flake bundle not configured".to_string(),
));
};
let bytes = fs::read(path).await.map_err(|error| {
let status = if error.kind() == std::io::ErrorKind::NotFound {
StatusCode::NOT_FOUND
} else {
StatusCode::INTERNAL_SERVER_ERROR
};
(
status,
format!(
"failed to read bootstrap flake bundle {}: {}",
path.display(),
error
),
)
})?;
let headers = [
(
header::CONTENT_TYPE,
HeaderValue::from_static("application/gzip"),
),
(
header::CONTENT_DISPOSITION,
HeaderValue::from_static("attachment; filename=\"plasmacloud-flake-bundle.tar.gz\""),
),
];
Ok((headers, Body::from(bytes)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{build_router, config::Config};
use axum::{body::to_bytes, http::Request};
use std::{
fs,
time::{SystemTime, UNIX_EPOCH},
};
use tower::ServiceExt;
fn temp_path(name: &str) -> std::path::PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
std::env::temp_dir().join(format!("{}-{}-{}", name, std::process::id(), nanos))
}
#[tokio::test]
async fn flake_bundle_route_serves_configured_bundle() {
let bundle_path = temp_path("deployer-flake-bundle");
fs::write(&bundle_path, b"bundle-bytes").unwrap();
let mut config = Config::default();
config.bootstrap_token = Some("test-token".to_string());
config.bootstrap_flake_bundle_path = Some(bundle_path.clone());
let state = Arc::new(AppState::with_config(config));
let app = build_router(state);
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/bootstrap/flake-bundle")
.header("x-deployer-token", "test-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response
.headers()
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok()),
Some("application/gzip")
);
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
assert_eq!(body.as_ref(), b"bundle-bytes");
let _ = fs::remove_file(bundle_path);
}
#[tokio::test]
async fn flake_bundle_route_requires_configured_bundle() {
let mut config = Config::default();
config.bootstrap_token = Some("test-token".to_string());
let state = Arc::new(AppState::with_config(config));
let app = build_router(state);
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/bootstrap/flake-bundle")
.header("x-deployer-token", "test-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}
}