tidy pigeon.sh a little
This commit is contained in:
@@ -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
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user