Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust/agama-lib/src/manager/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl ManagerHTTPClient {
/// Returns path to logs
pub async fn store(&self, path: &Path) -> Result<PathBuf, ManagerHTTPClientError> {
// 1) response with logs
let response = self.client.get_raw("/manager/logs/store").await?;
let response = self.client.get_raw("/v2/private/download_logs").await?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether this should be part of the private API. However, until we take a decision, it is good to keep it in the private part.

Additionally, I would name it as /v2/private/logs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, CLI has second param logs_list...not sure how much it is needed or useful, but I want to prevent ambitious names.


// 2) find out the destination file name
let ext = &response.headers().get(CONTENT_ENCODING).ok_or(
Expand Down
61 changes: 59 additions & 2 deletions rust/agama-server/src/server/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
//! This module implements Agama's HTTP API.

use crate::server::config_schema;
use agama_lib::error::ServiceError;
use agama_lib::{error::ServiceError, logs};
use agama_manager::{self as manager, message};
use agama_software::Resolvable;
use agama_utils::{
Expand All @@ -36,14 +36,17 @@ use agama_utils::{
progress, question,
};
use axum::{
body::Body,
extract::{Path, Query, State},
http::HeaderValue,
response::{IntoResponse, Response},
routing::{get, post, put},
Json, Router,
};
use hyper::StatusCode;
use hyper::{header, HeaderMap, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tokio_util::io::ReaderStream;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -123,6 +126,7 @@ pub fn server_with_state(state: ServerState) -> Result<Router, ServiceError> {
)
.route("/private/solve_storage_model", get(solve_storage_model))
.route("/private/resolvables/:id", put(set_resolvables))
.route("/private/download_logs", get(download_logs))
.with_state(state))
}

Expand Down Expand Up @@ -494,3 +498,56 @@ fn to_option_response<T: Serialize>(value: Option<T>) -> Response {
None => StatusCode::NOT_FOUND.into_response(),
}
}

/// Solves a storage config model.
#[utoipa::path(
get,
path = "/private/download_logs",
context_path = "/api/v2",
params(query::SolveStorageModel),
responses(
(status = 200, description = "Compressed Agama logs", content_type="application/octet-stream", body = String),
(status = 500, description = "Cannot collect the logs"),
(status = 507, description = "Server is probably out of space"),
)
)]
async fn download_logs() -> impl IntoResponse {
let mut headers = HeaderMap::new();
let err_response = (headers.clone(), Body::empty());

match logs::store() {
Ok(path) => {
if let Ok(file) = tokio::fs::File::open(path.clone()).await {
let stream = ReaderStream::new(file);
let body = Body::from_stream(stream);
let _ = std::fs::remove_file(path.clone());

// See RFC2046, RFC2616 and
// https://www.iana.org/assignments/media-types/media-types.xhtml
// or /etc/mime.types
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/x-compressed-tar"),
);
if let Some(file_name) = path.file_name() {
let disposition =
format!("attachment; filename=\"{}\"", &file_name.to_string_lossy());
headers.insert(
header::CONTENT_DISPOSITION,
HeaderValue::from_str(&disposition)
.unwrap_or_else(|_| HeaderValue::from_static("attachment")),
);
}
headers.insert(
header::CONTENT_ENCODING,
HeaderValue::from_static(logs::DEFAULT_COMPRESSION.1),
);

(StatusCode::OK, (headers, body))
} else {
(StatusCode::INSUFFICIENT_STORAGE, err_response)
}
}
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, err_response),
}
}
Loading