#!/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 $NAME - CI logs

CI logs for $NAME

    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
  1. #
    $STATUS_MARKER -
    ${DURATION:-?}s
    (commit)
    $FILENAME
    (data)

    $MESSAGE
  2. EOF done cat <<-EOF
EOF } > index.html