#!/bin/sh
set -eu
usage() {
cat <<-'EOF'
Usage:
aux/ci/report.sh -o OUTDIR
aux/ci/report.sh -h
EOF
}
help() {
cat <<-'EOF'
Options:
-o OUTDIR the directory where to place the generated files
-h, --help show this message
Gather data from Git Notes, and generate an HTML report on CI runs.
Two refs with notes are expected:
1. refs/notes/ci-data: contains metadata abount the CI runs,
with timestamps, filenames and exit status;
2. refs/notes/ci-logs: contains the content of the log.
When reconstructing the CI run, the $FILENAME present in
the refs/notes/ci-data ref names the file, and its content comes
from refs/notes/ci-logs.
On a CI run that generated the numbers from 1 to 10, for a file named
'my-ci-run-2020-01-01-deadbeef.log' that exited successfully, ran for
15 seconds and was deployed to production, the expected output on the
target directory "public" is:
$ tree public/
public/
index.html
data/
2020-01-01T01:00:00-deadbeef.log
...
logs/
2020-01-01T01:00:00-deadbeef.log
...
$ cat public/data/2020-01-01T01:00:00-deadbeef.log
status 0
sha deadbeef
filename deadbeef 2020-01-01T01:00:00-deadbeef.log
duration 15
timestamp 2020-01-01T01:00:00
to-prod true
refname refs/heads/main
$ cat public/logs/2020-01-01T01:00:00-deadbeef.log
1
2
3
4
5
6
7
8
9
10
The generated 'index.html' is a webpage with the list of all known
CI runs, their status, a link to the commit and a link to the
log and data files.
To enable fetching these refs by default, do so in the git config:
$ git config --add remote.origin.fetch '+refs/notes/*:refs/notes/*'
Examples:
Generate the report on the 'www' directory:
$ sh aux/ci/report.sh -o www
EOF
}
for flag in "$@"; do
case "$flag" in
--)
break
;;
--help)
usage
help
exit
;;
*)
;;
esac
done
while getopts 'o:h' flag; do
case "$flag" in
o)
OUTDIR="$OPTARG"
;;
h)
usage
help
exit
;;
*)
exit 2
;;
esac
done
shift $((OPTIND - 1))
. aux/lib.sh
eval "$(assert_arg "${OUTDIR:-}" '-o OUTDIR')"
esc() {
sed \
-e 's|&|\&|g' \
-e 's|<|\<|g' \
-e 's|>|\>|g' \
-e 's|"|\"|g' \
-e "s|'|\'|g"
}
mkdir -p "$OUTDIR"
cd "$OUTDIR"
mkdir -p logs data
for c in $(git notes list | cut -d' ' -f2); do
git notes --ref=refs/notes/ci-data show "$c" > data/FILENAME-tmp
FILENAME="$(grep '^filename ' data/FILENAME-tmp | cut -d' ' -f2-)"
mv data/FILENAME-tmp data/"$FILENAME"
git notes --ref=refs/notes/ci-logs show "$c" > logs/"$FILENAME"
done
{
cat <<-EOF
<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="CI logs for $NAME" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>$NAME - CI logs</title>
<style>
body {
max-width: 800px;
margin: 0 auto;
}
code {
display: block;
margin: 1em 0em 3em 3em;
overflow: auto;
}
pre {
display: inline;
}
ol {
list-style-type: disc;
}
pre, code {
background-color: #ddd;
}
@media(prefers-color-scheme: dark) {
:root {
color: white;
background-color: black;
}
a {
color: hsl(211, 100%, 60%);
}
a:visited {
color: hsl(242, 100%, 80%);
}
pre, code {
background-color: #222;
}
}
</style>
</head>
<body>
<main>
<h1>
CI logs for
<a href="https://$DOMAIN/s/$NAME/">$NAME</a>
</h1>
<ol>
EOF
PASS='✅' # ✅
WARN='🐌' # 🐌
FAIL='❌' # ❌
find data/ -type f | LANG=C.UTF-8 sort -r | while read -r f; do
STATUS="$( grep '^status ' "$f" | cut -d' ' -f2- | esc)"
SHA="$( grep '^sha ' "$f" | cut -d' ' -f2- | esc)"
FILENAME="$(grep '^filename ' "$f" | cut -d' ' -f2- | esc)"
DURATION="$(grep '^duration ' "$f" | cut -d' ' -f2- | cut -d'"' -f1 | esc)"
MESSAGE="$({
git log -1 --format=%B "$SHA" || {
git fetch origin "$SHA"
git log -1 --format=%B "$SHA"
}
} | esc)"
if [ "$STATUS" = 0 ]; then
if [ "$DURATION" -le 60 ]; then
STATUS_MARKER="$PASS"
else
STATUS_MARKER="$WARN"
fi
else
STATUS_MARKER="$FAIL"
fi
cat <<-EOF
<li id="$FILENAME">
<a href="#$FILENAME"><pre>#</pre></a>
$STATUS_MARKER - <pre>${DURATION:-?}s</pre>
<pre>(<a href="https://$DOMAIN/git/$NAME/commit/?id=$SHA">commit</a>)</pre>
<a href="logs/$FILENAME"><pre>$FILENAME</pre></a>
<pre>(<a href="data/$FILENAME">data</a>)</pre>
<br />
<code><pre>$MESSAGE</pre></code>
</li>
EOF
done
cat <<-EOF
</ol>
</main>
</body>
</html>
EOF
} > index.html