Skip to content

Commit 8800ee6

Browse files
committed
Add enhanced GitHub Actions raw logs viewer
1 parent 9e585b5 commit 8800ee6

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed

src/gha_logs.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::github;
2+
use crate::handlers::Context;
3+
use anyhow::Context as _;
4+
use hyper::header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE};
5+
use hyper::{Body, Response, StatusCode};
6+
use std::str::FromStr;
7+
use std::sync::Arc;
8+
use uuid::Uuid;
9+
10+
const ANSI_UP_URL: &str = "https://cdn.jsdelivr.net/npm/ansi_up@6.0.6/+esm";
11+
12+
pub async fn gha_logs(
13+
ctx: Arc<Context>,
14+
owner: &str,
15+
repo: &str,
16+
log_id: &str,
17+
) -> Result<Response<Body>, hyper::Error> {
18+
let res = process_logs(ctx, owner, repo, log_id).await;
19+
let res = match res {
20+
Ok(r) => r,
21+
Err(e) => {
22+
tracing::error!("gha_logs: unable to serve logs for {owner}/{repo}#{log_id}: {e:?}");
23+
return Ok(Response::builder()
24+
.status(StatusCode::INTERNAL_SERVER_ERROR)
25+
.body(Body::from(format!("{:?}", e)))
26+
.unwrap());
27+
}
28+
};
29+
30+
Ok(res)
31+
}
32+
33+
async fn process_logs(
34+
ctx: Arc<Context>,
35+
owner: &str,
36+
repo: &str,
37+
log_id: &str,
38+
) -> anyhow::Result<Response<Body>> {
39+
let log_id = u128::from_str(log_id).context("log_id is not a number")?;
40+
41+
let repos = ctx
42+
.team
43+
.repos()
44+
.await
45+
.context("unable to retrieve team repos")?;
46+
47+
let Some(repos) = repos.repos.get(owner) else {
48+
anyhow::bail!("Organization `{owner}` is not part of team repos")
49+
};
50+
51+
if !repos.iter().any(|r| r.name == repo) {
52+
anyhow::bail!("Repository `{repo}` is not part of team repos");
53+
}
54+
55+
let logs = ctx
56+
.github
57+
.raw_job_logs(
58+
&github::IssueRepository {
59+
organization: owner.to_string(),
60+
repository: repo.to_string(),
61+
},
62+
log_id,
63+
)
64+
.await
65+
.context("unable to get the raw logs")?;
66+
67+
let json_logs = serde_json::to_string(&logs).context("unable to JSON-ify the raw logs")?;
68+
69+
let nonce = Uuid::new_v4().to_hyphenated().to_string();
70+
71+
let html = format!(
72+
r#"<!DOCTYPE html>
73+
<html>
74+
<head>
75+
<title>{owner}/{repo}${log_id} - triagebot</title>
76+
<meta charset="UTF-8">
77+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78+
<link rel="icon" sizes="32x32" type="image/png" href="https://rust-lang.org/static/images/favicon-32x32.png">
79+
<style>
80+
body {{
81+
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
82+
background: #0C0C0C;
83+
color: #CCCCCC;
84+
white-space: pre;
85+
}}
86+
</style>
87+
<script type="module" nonce="{nonce}">
88+
import {{ AnsiUp }} from '{ANSI_UP_URL}'
89+
90+
var logs = {json_logs};
91+
var ansi_up = new AnsiUp();
92+
93+
var html = ansi_up.ansi_to_html(logs);
94+
95+
var cdiv = document.getElementById("console");
96+
cdiv.innerHTML = html;
97+
</script>
98+
</head>
99+
<body id="console">
100+
</body>
101+
</html>"#,
102+
);
103+
104+
tracing::info!("gha_logs: serving logs for {owner}/{repo}#{log_id}");
105+
106+
return Ok(Response::builder()
107+
.status(StatusCode::OK)
108+
.header(CONTENT_TYPE, "text/html; charset=utf-8")
109+
.header(
110+
CONTENT_SECURITY_POLICY,
111+
format!("script-src 'nonce-{nonce}' {ANSI_UP_URL}"),
112+
)
113+
.body(Body::from(html))?);
114+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod agenda;
1313
mod changelogs;
1414
mod config;
1515
pub mod db;
16+
pub mod gha_logs;
1617
pub mod github;
1718
pub mod handlers;
1819
mod interactions;

src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async fn serve_req(
4444
let mut router = Router::new();
4545
router.add("/triage", "index".to_string());
4646
router.add("/triage/:owner/:repo", "pulls".to_string());
47+
router.add("/gha-logs/:owner/:repo/:log-id", "gha-logs".to_string());
4748
let (req, body_stream) = req.into_parts();
4849

4950
if let Ok(matcher) = router.recognize(req.uri.path()) {
@@ -52,8 +53,14 @@ async fn serve_req(
5253
let owner = params.find("owner");
5354
let repo = params.find("repo");
5455
return triagebot::triage::pulls(ctx, owner.unwrap(), repo.unwrap()).await;
55-
} else {
56+
} else if matcher.handler().as_str() == "index" {
5657
return triagebot::triage::index();
58+
} else if matcher.handler().as_str() == "gha-logs" {
59+
let params = matcher.params();
60+
let owner = params.find("owner").unwrap();
61+
let repo = params.find("repo").unwrap();
62+
let log_id = params.find("log-id").unwrap();
63+
return triagebot::gha_logs::gha_logs(ctx, owner, repo, log_id).await;
5764
}
5865
}
5966

0 commit comments

Comments
 (0)