tidy pigeon.sh a little

This commit is contained in:
2026-10-16 01:18:37 -04:00
parent 8b550cfe10
commit 015c70bc74
2 changed files with 42 additions and 40 deletions
+4 -3
View File
@@ -119,16 +119,17 @@
]; ];
} }
]); ]);
pigeon-webhook = pkgs.writeShellApplication { pigeon-webhook = pkgs.writeShellApplication {
name = "pigeon-webhook"; name = "pigeon-webhook";
runtimeInputs = with pkgs; [ coreutils nq sqlite webhook ]; runtimeInputs = with pkgs; [ coreutils jq nq sqlite webhook ];
text = '' text = ''
: "''${PIGEON_GITLAB_SECRET:?}" : "''${PIGEON_GITLAB_SECRET:?}"
: "''${PIGEON_GITLAB_TOKEN:?}" : "''${PIGEON_GITLAB_TOKEN:?}"
: "''${PIGEON_PROJECTS:?}" : "''${PIGEON_PROJECTS:?}"
: "''${PIGEON_STATE}"
export NQDIR=./queue mkdir -p "$PIGEON_STATE"
export NQDIR="$PIGEON_STATE/nqdir"
exec webhook -hooks ${hooks} -template -port 3000 -verbose -debug exec webhook -hooks ${hooks} -template -port 3000 -verbose -debug
''; '';
}; };
+38 -37
View File
@@ -1,70 +1,71 @@
ID="$1" PROJECT_ID="$1"
NAMESPACE="$2" PROJECT_NAMESPACE="$2"
PROJECT="$3" PROJECT_NAME="$3"
COMMIT="$4" RELEASE_COMMIT="$4"
TAG="$5" RELEASE_TAG="$5"
CREATED_AT="$6" RELEASE_TIME="$6"
: "${PIGEON_GITLAB_TOKEN:?}" : "${PIGEON_GITLAB_TOKEN:?}"
: "${PIGEON_PROJECTS:?}" : "${PIGEON_PROJECTS:?}"
: "${PIGEON_DB:?}" : "${PIGEON_STATE:?}"
LIMIT="${PIGEON_RATE_LIMIT:-3}" # graded attempts per student per project per hour
PIGEON_DB="$(realpath -m "$PIGEON_DB")" RATE_LIMIT=3
db() { sqlite3 -bail -cmd '.timeout 10000' "$PIGEON_DB" "$@"; }
# quote a value as a SQL string literal PIGEON_DATABASE="$(realpath -m "$PIGEON_STATE/db")"
q() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/''/g")"; } RELEASE_TIME="$(date -d "$RELEASE_TIME" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || true)"
report() { report() {
curl -s --show-error --out-null --request PUT \ curl -s --show-error --out-null --request PUT \
--header "PRIVATE-TOKEN: $PIGEON_GITLAB_TOKEN" \ --header "PRIVATE-TOKEN: $PIGEON_GITLAB_TOKEN" \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--data "{\"description\": \"$1\"}" \ --data "$(jq -n --arg d "$1" '{"description": $d}')" \
--url "https://gitlab.com/api/v4/projects/$ID/releases/$TAG" --url "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/$RELEASE_TAG"
}
db() {
sqlite3 -bail -cmd '.timeout 10000' "$PIGEON_DATABASE" "$@"
}
q() {
printf "'%s'" "$(printf %s "$1" | sed "s/'/''/g")"
} }
mkdir -p "$(dirname "$PIGEON_DB")"
db << 'EOF' db << 'EOF'
CREATE TABLE IF NOT EXISTS results ( CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
student TEXT NOT NULL, student TEXT NOT NULL,
project TEXT NOT NULL, project TEXT NOT NULL,
commit_sha TEXT NOT NULL, hash TEXT NOT NULL,
time TEXT NOT NULL DEFAULT (datetime('now')), time TEXT NOT NULL DEFAULT (datetime('now')),
score INTEGER CHECK (score >= 0) score INTEGER CHECK (score >= 0)
); );
EOF EOF
# GitLab's "2026-10-16 02:27:46 UTC" -> SQLite's "2026-10-16 02:27:46". ROW="$(db <<EOF
# If it's missing or unparseable, fall back to the current time. INSERT INTO results (student, project, hash, time)
CREATED="$(date -u -d "$CREATED_AT" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || true)" SELECT $(q "$PROJECT_NAMESPACE"), $(q "$PROJECT_NAME"), $(q "$RELEASE_COMMIT"),
COALESCE(NULLIF($(q "$RELEASE_TIME"), ''), datetime('now'))
WHERE (SELECT COUNT(*) FROM results
WHERE student = $(q "$PROJECT_NAMESPACE") AND project = $(q "$PROJECT_NAME")
AND time > datetime('now', '-1 day')) < $RATE_LIMIT
RETURNING id;
EOF
)"
# Rate limit and record the submission in one atomic statement. if [ -z "$ROW" ] ; then
# No row comes back if the student is over the limit. report "Rate limit exceeded!"
ROW="$(db "INSERT INTO results (student, project, commit_sha, time) exit
SELECT $(q "$NAMESPACE"), $(q "$PROJECT"), $(q "$COMMIT"),
COALESCE(NULLIF($(q "$CREATED"), ''), datetime('now'))
WHERE (SELECT COUNT(*) FROM results
WHERE student = $(q "$NAMESPACE") AND project = $(q "$PROJECT")
AND time > datetime('now', '-1 hour')) < $LIMIT
RETURNING id;")"
if [ -z "$ROW" ]; then
report "Rate limit reached: at most $LIMIT submissions per hour. Try again later."
exit 0
fi fi
DIRECTORY="$(mktemp -d /tmp/pigeon.XXXXXXXXXX)" DIRECTORY="$(mktemp -d /tmp/pigeon.XXXXXXXXXX)"
trap 'rm -rf "$DIRECTORY"' EXIT trap 'rm -rf "$DIRECTORY"' EXIT
cd "$DIRECTORY" cd "$DIRECTORY"
git -c advice.detachedHead=false clone -q --rev "$COMMIT" --depth 1 \ git -c advice.detachedHead=false clone -q --rev "$RELEASE_COMMIT" --depth 1 \
"https://oauth2:$PIGEON_GITLAB_TOKEN@gitlab.com/$NAMESPACE/$PROJECT.git" student/ "https://oauth2:$PIGEON_GITLAB_TOKEN@gitlab.com/$PROJECT_NAMESPACE/$PROJECT_NAME.git" student/
rm -rf student/.git rm -rf student/.git
mkdir -p project/tests project/build mkdir -p project/tests project/build
cp -a "$PIGEON_PROJECTS/$PROJECT"/tests/*.sh project/tests/ cp -a "$PIGEON_PROJECTS/$PROJECT_NAME"/tests/*.sh project/tests/
cp -a "$PIGEON_PROJECTS/$PROJECT"/build/. project/build/ cp -a "$PIGEON_PROJECTS/$PROJECT_NAME"/build/. project/build/
mkfs.ext4 -q -F -d student student.img 16M mkfs.ext4 -q -F -d student student.img 16M
mkfs.ext4 -q -F -d project project.img 16M mkfs.ext4 -q -F -d project project.img 16M
@@ -90,7 +91,7 @@ firecracker --no-api --level Error --config-file vm.json
RESULT="" RESULT=""
SCORE=0 SCORE=0
TESTS="$PIGEON_PROJECTS/$PROJECT/tests" TESTS="$PIGEON_PROJECTS/$PROJECT_NAME/tests"
for TEST in "$TESTS"/*.sh ; do for TEST in "$TESTS"/*.sh ; do
debugfs -R "cat /$(basename "$TEST" .sh)" results.img >scratch 2>/dev/null debugfs -R "cat /$(basename "$TEST" .sh)" results.img >scratch 2>/dev/null
if cmp -s scratch "$TESTS/$(basename "$TEST" .sh).expected" ; then if cmp -s scratch "$TESTS/$(basename "$TEST" .sh).expected" ; then