Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rewrite logapi in rust #702

Merged
merged 1 commit into from
Dec 30, 2024
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
73 changes: 0 additions & 73 deletions log-api/index.php

This file was deleted.

132 changes: 132 additions & 0 deletions ofborg/src/bin/logapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::{collections::HashMap, error::Error, path::PathBuf};

use hyper::{
header::ContentType,
mime,
server::{Request, Response, Server},
status::StatusCode,
};
use ofborg::config;
use tracing::{error, info, warn};

#[derive(serde::Serialize, Default)]
struct Attempt {
metadata: Option<serde_json::Value>,
result: Option<serde_json::Value>,
log_url: Option<String>,
}

#[derive(serde::Serialize)]
struct LogResponse {
attempts: HashMap<String, Attempt>,
}

fn main() -> Result<(), Box<dyn Error>> {
ofborg::setup_log();

let arg = std::env::args()
.nth(1)
.unwrap_or_else(|| panic!("usage: {} <config>", std::env::args().next().unwrap()));
let Some(cfg) = config::load(arg.as_ref()).log_api_config else {
error!("No LogApi configuration found!");
panic!();
};

let threads = std::thread::available_parallelism()
.map(|x| x.get())
.unwrap_or(1);
info!("Will listen on {} with {threads} threads", cfg.listen);
Server::http(cfg.listen)?.handle_threads(
move |req: Request, mut res: Response| {
if req.method != hyper::Get {
*res.status_mut() = StatusCode::MethodNotAllowed;
return;
}

let uri = req.uri.to_string();
let Some(reqd) = uri.strip_prefix("/logs/").map(ToOwned::to_owned) else {
*res.status_mut() = StatusCode::NotFound;
let _ = res.send(b"invalid uri");
return;
};
let path: PathBuf = [&cfg.logs_path, &reqd].iter().collect();
let Ok(path) = std::fs::canonicalize(&path) else {
*res.status_mut() = StatusCode::NotFound;
let _ = res.send(b"absent");
return;
};
let Ok(iter) = std::fs::read_dir(path) else {
*res.status_mut() = StatusCode::NotFound;
let _ = res.send(b"non dir");
return;
};

let mut attempts = HashMap::<String, Attempt>::new();
for e in iter {
let Ok(e) = e else { continue };
let e_metadata = e.metadata();
if e_metadata.as_ref().map(|v| v.is_dir()).unwrap_or(true) {
*res.status_mut() = StatusCode::InternalServerError;
let _ = res.send(b"dir found");
return;
}

if e_metadata.as_ref().map(|v| v.is_file()).unwrap_or_default() {
let Ok(file_name) = e.file_name().into_string() else {
warn!("entry filename is not a utf-8 string: {:?}", e.file_name());
continue;
};

if file_name.ends_with(".metadata.json") || file_name.ends_with(".result.json")
{
let Ok(file) = std::fs::File::open(e.path()) else {
warn!("could not open file: {file_name}");
continue;
};
let Ok(json) = serde_json::from_reader::<_, serde_json::Value>(file) else {
warn!("file is not a valid json file: {file_name}");
continue;
};
let Some(attempt_id) = json
.get("attempt_id")
.and_then(|v| v.as_str())
.map(ToOwned::to_owned)
else {
warn!("attempt_id not found in file: {file_name}");
continue;
};
let attempt_obj = attempts
.entry(attempt_id)
.or_insert_with(Attempt::default);
if file_name.ends_with(".metadata.json") {
attempt_obj.metadata = Some(json);
} else {
attempt_obj.result = Some(json);
}
} else {
let attempt_obj = attempts
.entry(file_name.clone())
.or_insert_with(Attempt::default);
attempt_obj.log_url =
Some(format!("{}/{reqd}/{file_name}", &cfg.serve_root));
}
}
}

*res.status_mut() = StatusCode::Ok;
res.headers_mut()
.set::<ContentType>(hyper::header::ContentType(mime::Mime(
mime::TopLevel::Application,
mime::SubLevel::Json,
Vec::new(),
)));
let _ = res.send(
serde_json::to_string(&LogResponse { attempts })
.unwrap_or_default()
.as_bytes(),
);
},
threads,
)?;
Ok(())
}
39 changes: 36 additions & 3 deletions ofborg/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use tracing::{debug, error, info, warn};
pub struct Config {
/// Configuration for the webhook receiver
pub github_webhook_receiver: Option<GithubWebhookConfig>,
/// Configuration for the logapi receiver
pub log_api_config: Option<LogApiConfig>,
/// Configuration for the evaluation filter
pub evaluation_filter: Option<EvaluationFilter>,
/// Configuration for the GitHub comment filter
Expand Down Expand Up @@ -44,6 +46,26 @@ pub struct GithubWebhookConfig {
pub rabbitmq: RabbitMqConfig,
}

fn default_logs_path() -> String {
"/var/log/ofborg".into()
}

fn default_serve_root() -> String {
"https://logs.ofborg.org/logfile".into()
}

/// Configuration for logapi
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct LogApiConfig {
/// Listen host/port
pub listen: String,
#[serde(default = "default_logs_path")]
pub logs_path: String,
#[serde(default = "default_serve_root")]
pub serve_root: String,
}

/// Configuration for the evaluation filter
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
Expand Down Expand Up @@ -168,12 +190,23 @@ impl Config {
}

pub fn github(&self) -> Github {
let token = std::fs::read_to_string(self.github_app.clone().expect("No GitHub app configured").oauth_client_secret_file)
.expect("Couldn't read from GitHub app token");
let token = std::fs::read_to_string(
self.github_app
.clone()
.expect("No GitHub app configured")
.oauth_client_secret_file,
)
.expect("Couldn't read from GitHub app token");
let token = token.trim();
Github::new(
"github.com/NixOS/ofborg",
Credentials::Client(self.github_app.clone().expect("No GitHub app configured").oauth_client_id, token.to_owned()),
Credentials::Client(
self.github_app
.clone()
.expect("No GitHub app configured")
.oauth_client_id,
token.to_owned(),
),
)
.expect("Unable to create a github client instance")
}
Expand Down
Loading