Skip to content

Commit

Permalink
Moved new logs HTTP API under /manager
Browse files Browse the repository at this point in the history
so http://.../api/logs is now http://.../api/manager/logs
  • Loading branch information
mchf committed Nov 4, 2024
1 parent 1fd3f87 commit 79e4595
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 96 deletions.
2 changes: 1 addition & 1 deletion rust/agama-lib/src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const DEFAULT_PATHS: [&str; 14] = [
"/linuxrc.config",
];

const DEFAULT_RESULT: &str = "/tmp/agama-logs";
const DEFAULT_RESULT: &str = "/run/agama/agama-logs";
// what compression is used by default:
// (<compression as distinguished by tar>, <an extension for resulting archive>)
pub const DEFAULT_COMPRESSION: (&str, &str) = ("gzip", "tar.gz");
Expand Down
4 changes: 2 additions & 2 deletions rust/agama-lib/src/logs/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl HTTPClient {
/// Returns path to logs
pub async fn store(&self, path: &Path) -> Result<PathBuf, ServiceError> {
// 1) response with logs
let response = self.client.get_raw("/logs/store").await?;
let response = self.client.get_raw("/manager/logs/store").await?;

// 2) find out the destination file name
let ext =
Expand Down Expand Up @@ -74,6 +74,6 @@ impl HTTPClient {
/// Asks backend for lists of log files and commands used for creating logs archive returned by
/// store (/logs/store) backed HTTP API command
pub async fn list(&self) -> Result<LogsLists, ServiceError> {
Ok(self.client.get("/logs/list").await?)
Ok(self.client.get("/manager/logs/list").await?)
}
}
83 changes: 51 additions & 32 deletions rust/agama-server/src/manager/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,26 @@
//! * `manager_service` which returns the Axum service.
//! * `manager_stream` which offers an stream that emits the manager events coming from D-Bus.
use agama_lib::logs::{
list as listLogs, store as storeLogs, LogOptions, LogsLists, DEFAULT_COMPRESSION,
};
use agama_lib::{
error::ServiceError,
manager::{InstallationPhase, ManagerClient},
proxies::Manager1Proxy,
};
use axum::{
extract::{Request, State},
http::StatusCode,
body::Body,
extract::State,
http::{header, HeaderMap, HeaderValue},
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use rand::distributions::{Alphanumeric, DistString};
use serde::Serialize;
use std::pin::Pin;
use tokio::process::Command;
use tokio_stream::{Stream, StreamExt};
use tower_http::services::ServeFile;
use tokio_util::io::ReaderStream;

use crate::{
error::Error,
Expand Down Expand Up @@ -116,7 +118,7 @@ pub async fn manager_service(dbus: zbus::Connection) -> Result<Router, ServiceEr
.route("/install", post(install_action))
.route("/finish", post(finish_action))
.route("/installer", get(installer_status))
.route("/logs.tar.gz", get(download_logs))
.nest("/logs", logs_router())
.merge(status_router)
.merge(progress_router)
.with_state(state))
Expand Down Expand Up @@ -226,36 +228,53 @@ async fn installer_status(
Ok(Json(status))
}

/// Returns agama logs
#[utoipa::path(get, path = "/api/manager/logs", responses(
(status = 200, description = "Download logs blob.")
/// Creates router for handling /logs/* endpoints
fn logs_router() -> Router<ManagerState<'static>> {
Router::new()
.route("/store", get(download_logs))
.route("/list", get(list_logs))
}

#[utoipa::path(get, path = "/logs/store", responses(
(status = 200, description = "Compressed Agama logs", content_type="application/octet-stream"),
))]

pub async fn download_logs() -> impl IntoResponse {
let path = generate_logs().await;
let Ok(path) = path else {
return (StatusCode::INTERNAL_SERVER_ERROR).into_response();
};
async fn download_logs() -> impl IntoResponse {
let mut headers = HeaderMap::new();

match ServeFile::new(path)
.try_call(Request::new(axum::body::Body::empty()))
.await
{
Ok(res) => res.into_response(),
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR).into_response(),
}
}
match storeLogs(LogOptions::default()) {
Ok(path) => {
let file = tokio::fs::File::open(path.clone()).await.unwrap();
let stream = ReaderStream::new(file);
let body = Body::from_stream(stream);

async fn generate_logs() -> Result<String, Error> {
let random_name: String = Alphanumeric.sample_string(&mut rand::thread_rng(), 8);
let path = format!("/run/agama/logs_{random_name}");
// Cleanup - remove temporary file, no one cares it it fails
let _ = std::fs::remove_file(path.clone());

Command::new("agama")
.args(["logs", "store", "-d", path.as_str()])
.status()
.await
.map_err(|e| ServiceError::CannotGenerateLogs(e.to_string()))?;
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/toml; charset=utf-8"),
);
headers.insert(
header::CONTENT_DISPOSITION,
HeaderValue::from_static("attachment; filename=\"agama-logs\""),
);
headers.insert(
header::CONTENT_ENCODING,
HeaderValue::from_static(DEFAULT_COMPRESSION.1),
);

let full_path = format!("{path}.tar.gz");
Ok(full_path)
(headers, body)
}
Err(_) => {
// fill in with meaningful headers
(headers, Body::empty())
}
}
}
#[utoipa::path(get, path = "/logs/list", responses(
(status = 200, description = "Lists of collected logs", body = LogsLists)
))]
pub async fn list_logs() -> Json<LogsLists> {
Json(listLogs(LogOptions::default()))
}
60 changes: 1 addition & 59 deletions rust/agama-server/src/web/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,76 +22,18 @@
use super::{auth::AuthError, state::ServiceState};
use agama_lib::auth::{AuthToken, TokenClaims};
use agama_lib::logs::{
list as listLogs, store as storeLogs, LogOptions, LogsLists, DEFAULT_COMPRESSION,
};
use axum::{
body::Body,
extract::{Query, State},
http::{header, HeaderMap, HeaderValue, StatusCode},
response::IntoResponse,
routing::get,
Json, Router,
Json,
};
use axum_extra::extract::cookie::CookieJar;
use pam::Client;
use serde::{Deserialize, Serialize};
use tokio_util::io::ReaderStream;
use utoipa::ToSchema;

/// Creates router for handling /logs/* endpoints
pub fn logs_router() -> Router<ServiceState> {
Router::new()
.route("/store", get(logs_store))
.route("/list", get(logs_list))
}

#[utoipa::path(get, path = "/logs/store", responses(
(status = 200, description = "Compressed Agama logs", content_type="application/octet-stream"),
(status = 404, description = "Agama logs not available")
))]
async fn logs_store() -> impl IntoResponse {
// TODO: require authorization
let mut headers = HeaderMap::new();

match storeLogs(LogOptions::default()) {
Ok(path) => {
let file = tokio::fs::File::open(path.clone()).await.unwrap();
let stream = ReaderStream::new(file);
let body = Body::from_stream(stream);

// Cleanup - remove temporary file, no one cares it it fails
let _ = std::fs::remove_file(path.clone());

headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/toml; charset=utf-8"),
);
headers.insert(
header::CONTENT_DISPOSITION,
HeaderValue::from_static("attachment; filename=\"agama-logs\""),
);
headers.insert(
header::CONTENT_ENCODING,
HeaderValue::from_static(DEFAULT_COMPRESSION.1),
);

(headers, body)
}
Err(_) => {
// fill in with meaningful headers
(headers, Body::empty())
}
}
}

#[utoipa::path(get, path = "/logs/list", responses(
(status = 200, description = "Lists of collected logs", body = LogsLists)
))]
pub async fn logs_list() -> Json<LogsLists> {
Json(listLogs(LogOptions::default()))
}

#[derive(Serialize, ToSchema)]
pub struct PingResponse {
/// API status
Expand Down
3 changes: 1 addition & 2 deletions rust/agama-server/src/web/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ impl MainServiceBuilder {
state.clone(),
))
.route("/ping", get(super::http::ping))
.route("/auth", post(login).get(session).delete(logout))
.nest("/logs", super::http::logs_router());
.route("/auth", post(login).get(session).delete(logout));

tracing::info!("Serving static files from {}", self.public_dir.display());
let serve = ServeDir::new(self.public_dir).precompressed_gzip();
Expand Down

0 comments on commit 79e4595

Please sign in to comment.