summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2025-03-09 17:37:09 -0300
committerEuAndreh <eu@euandre.org>2025-03-09 17:37:09 -0300
commitcd93af12b34c17be3bde287e85accc11d821077e (patch)
treed9787249daa05e004a4c95dc908ca0514c6be4e3
parentsrc/cicd: Fix where getopts(1) is called (diff)
downloadcicd-cd93af12b34c17be3bde287e85accc11d821077e.tar.gz
cicd-cd93af12b34c17be3bde287e85accc11d821077e.tar.xz
Add src/cireport
-rw-r--r--deps.mk1
-rwxr-xr-xsrc/cireport260
2 files changed, 261 insertions, 0 deletions
diff --git a/deps.mk b/deps.mk
index 922440e..858d01a 100644
--- a/deps.mk
+++ b/deps.mk
@@ -3,4 +3,5 @@ sources.sh = \
src/cicd-post-receive \
src/cicd-run \
src/cicdd \
+ src/cireport \
diff --git a/src/cireport b/src/cireport
new file mode 100755
index 0000000..e14e40a
--- /dev/null
+++ b/src/cireport
@@ -0,0 +1,260 @@
+#!/bin/sh
+set -eu
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ report [-C REPO] -o DIRECTORY
+ report -h
+ EOF
+}
+
+help() {
+ cat <<-'EOF'
+
+
+ Options:
+ -C REPO change to REPO when doing Git operations (default: $PWD)
+ -o DIRECTORY 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, the
+ expected output on the target directory "public" is:
+
+ $ tree public/
+ public/
+ index.html
+ data/
+ my-ci-run-2020-01-01-deadbeef.log
+ ...
+ logs/
+ my-ci-run-2020-01-01-deadbeef.log
+ ...
+
+ $ cat public/data/my-ci-run-2020-01-01-deadbeef.log
+ 0 deadbeef my-ci-run-2020-01-01-deadbeef.log
+
+ $ cat public/logs/my-ci-run-2020-01-01-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 file.
+
+ 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:
+
+ $ report -o www
+ EOF
+}
+
+
+for flag in "$@"; do
+ case "$flag" in
+ --)
+ break
+ ;;
+ --help)
+ usage
+ help
+ exit
+ ;;
+ *)
+ ;;
+ esac
+done
+
+REPO="$PWD"
+while getopts 'C:o:h' flag; do
+ case "$flag" in
+ C)
+ REPO="$OPTARG"
+ ;;
+ o)
+ OUTDIR="$OPTARG"
+ ;;
+ h)
+ usage
+ help
+ exit
+ ;;
+ *)
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+if [ -z "${OUTDIR:-}" ]; then
+ printf 'Missing -o OUTDIR.\n\n' >&2
+ usage >&2
+ exit 2
+fi
+
+if [ -r src/infrastructure/config/conf.env ]; then
+ CONF=src/infrastructure/config/conf.env
+else
+ CONF=/etc/conf.env
+fi
+
+# shellcheck source=/dev/null
+. "$CONF"
+
+
+esc() {
+ sed \
+ -e 's|&|\&amp;|g' \
+ -e 's|<|\&lt;|g' \
+ -e 's|>|\&gt;|g' \
+ -e 's|"|\&quot;|g' \
+ -e "s|'|\&#39;|g"
+}
+
+mkdir -p "$OUTDIR"
+cd "$OUTDIR"
+mkdir -p logs data
+
+for c in $(git -C "$REPO" notes list | cut -d' ' -f2); do
+ git -C "$REPO" 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 -C "$REPO" notes --ref=refs/notes/ci-logs show "$c" > logs/"$FILENAME"
+done
+
+{
+ cat <<-EOF
+ <!DOCTYPE html>
+ <html lang="en">
+ <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="$HOMEPAGE">$NAME</a>
+ </h1>
+ <ol>
+ EOF
+
+
+ PASS='&#x2705;' # ✅
+ WARN='&#x1F40C;' # 🐌
+ FAIL='&#x274C;' # ❌
+ 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 -C "$REPO" log -1 --format=%B "$SHA" || {
+ git fetch origin "$SHA"
+ git -C "$REPO" 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="${CGIT_URL}${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