Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8eada18
chore: migrate from pip-compile to uv for dependency management
blarghmatey Mar 11, 2026
1d5db68
fix: remove AppArmor/codejail hard dependency; make codejail optional
blarghmatey Mar 11, 2026
37100ad
feat: add ContainerGrader for Kubernetes/Docker-based sandboxed grading
blarghmatey Mar 11, 2026
a66b703
feat: add grader base Docker image and container entrypoint
blarghmatey Mar 11, 2026
01c65ba
feat: add Kubernetes deployment manifests and Docker Compose local dev
blarghmatey Mar 11, 2026
18c7151
fix: correct grader path handling in ContainerGrader and entrypoint
blarghmatey Mar 11, 2026
0be0926
fix(tests): skip jailed grader tests when codejail is not installed
blarghmatey Mar 11, 2026
3f7944d
fix: address PR review feedback
blarghmatey Mar 11, 2026
1491b29
refactor: replace path-py with stdlib pathlib
blarghmatey Mar 11, 2026
cbdcf15
feat: add edx-codejail as optional dependency; document container iso…
blarghmatey Mar 11, 2026
193c043
fix: address second round of PR review feedback
blarghmatey Mar 11, 2026
d28da94
refactor: move full grading pipeline into container; add ContainerGra…
blarghmatey Mar 11, 2026
2c3b82f
refactor: replace statsd/newrelic with OpenTelemetry; add 12-factor s…
blarghmatey Mar 17, 2026
22d311c
chore: remove planning doc from git tracking
blarghmatey Mar 17, 2026
d524236
chore: remove codecov upload from CI
blarghmatey Mar 17, 2026
16e034b
fix: address PR #14 review feedback
blarghmatey Mar 18, 2026
0495e75
fix: add venv bin to PATH so xqueue-watcher entrypoint resolves
blarghmatey Mar 18, 2026
c247b52
feat: add configure_logging() for 12-factor stdout logging
blarghmatey Mar 18, 2026
2e7ab2d
fix: symlink xqueue-watcher into /usr/local/bin for reliable resolution
blarghmatey Mar 18, 2026
5502d68
feat: add env-based defaults for ContainerGrader configuration
blarghmatey Mar 19, 2026
91d6ab1
feat(containergrader): add ImageDigestPoller and image pull policy su…
blarghmatey Mar 19, 2026
37daee9
fix: normalise imagePullPolicy to title-case before K8s API call
blarghmatey Mar 19, 2026
1a92430
feat: add strip_path_components to ContainerGrader for legacy path pr…
blarghmatey Mar 19, 2026
c8d2bf6
fix: install gettext into builtins before loading grader module
blarghmatey Mar 19, 2026
9a402da
fix: normalize mixed tab/space indentation before exec
blarghmatey Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,17 @@ jobs:
matrix:
os:
- ubuntu-latest
python-version: ['3.12']
python-version: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: setup python
uses: actions/setup-python@v5

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
python-version: ${{ matrix.python-version }}
enable-cache: true

- name: Install requirements and Run Tests
run: make test
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Run Coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
- name: Run Tests
run: uv run --python ${{ matrix.python-version }} pytest tests
69 changes: 69 additions & 0 deletions .github/workflows/publish-grader-base-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Publish grader base image

# Builds grader_support/Dockerfile.base and pushes
# ghcr.io/mitodl/xqueue-watcher-grader-base to GHCR.
#
# Downstream grader images (e.g. graders-mit-600x) extend this base; keeping
# it in GHCR lets the Concourse grader-image pipeline pull it as a trigger
# without requiring DockerHub credentials.

on:
push:
branches:
- master
paths:
- "grader_support/**"
schedule:
# Weekly rebuild to pick up base Python/OS security patches (Sunday 00:00 UTC)
- cron: "0 0 * * 0"
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: mitodl/xqueue-watcher-grader-base

jobs:
build-and-push:
name: Build and push grader base image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up QEMU (for multi-platform builds)
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Extract image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,format=short

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: grader_support/Dockerfile.base
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
44 changes: 24 additions & 20 deletions .github/workflows/upgrade-python-requirements.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
name: Upgrade Python Requirements
name: Update Dependencies

on:
schedule:
- cron: "15 15 1/14 * *"
workflow_dispatch:
inputs:
branch:
description: "Target branch against which to create requirements PR"
required: true
default: 'master'

jobs:
call-upgrade-python-requirements-workflow:
uses: openedx/.github/.github/workflows/upgrade-python-requirements.yml@master
with:
branch: ${{ github.event.inputs.branch || 'master' }}
# optional parameters below; fill in if you'd like github or email notifications
# user_reviewers: ""
# team_reviewers: ""
email_address: "aurora-requirements-update@2u-internal.opsgenie.net"
send_success_notification: true
secrets:
requirements_bot_github_token: ${{ secrets.REQUIREMENTS_BOT_GITHUB_TOKEN }}
requirements_bot_github_email: ${{ secrets.REQUIREMENTS_BOT_GITHUB_EMAIL }}
edx_smtp_username: ${{ secrets.EDX_SMTP_USERNAME }}
edx_smtp_password: ${{ secrets.EDX_SMTP_PASSWORD }}
update-dependencies:
runs-on: ubuntu-24.04
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Update uv.lock
run: uv lock --upgrade

- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.REQUIREMENTS_BOT_GITHUB_TOKEN }}
commit-message: "chore: update uv.lock with latest dependency versions"
title: "chore: update dependencies"
body: "Automated dependency update via `uv lock --upgrade`."
branch: "chore/update-dependencies"
delete-branch: true
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,10 @@ reports/
\#*\#
*.egg-info
.idea/

# uv
.venv/

# Kubernetes secrets — never commit real values
deploy/kubernetes/secret.yaml
Automated code Graders With xqueue-watcher.md
48 changes: 31 additions & 17 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
FROM ubuntu:xenial as openedx
FROM python:3.11-slim AS base

RUN apt update && \
apt install -y git-core language-pack-en apparmor apparmor-utils python python-pip python-dev && \
pip install --upgrade pip setuptools && \
rm -rf /var/lib/apt/lists/*
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8

RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN apt-get update && \
apt-get install -y --no-install-recommends git-core && \
rm -rf /var/lib/apt/lists/*

RUN useradd -m --shell /bin/false app

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

WORKDIR /edx/app/xqueue_watcher
COPY requirements /edx/app/xqueue_watcher/requirements
RUN pip install -r requirements/production.txt

CMD python -m xqueue_watcher -d /edx/etc/xqueue_watcher
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project

COPY . /edx/app/xqueue_watcher
RUN uv sync --frozen --no-dev && \
ln -s /edx/app/xqueue_watcher/.venv/bin/xqueue-watcher /usr/local/bin/xqueue-watcher
# Note: the `codejail` optional extra (edx-codejail) is intentionally omitted
# from this image. In the Kubernetes deployment, student code runs inside an
# isolated container (ContainerGrader) — the container boundary provides the
# sandbox via Linux namespaces, cgroups, capability dropping, network isolation,
# and a read-only filesystem. codejail (AppArmor + OS-level user-switching)
# requires host-level AppArmor configuration that is unavailable inside
# Kubernetes pods and adds no meaningful security benefit on top of container
# isolation. Install the `codejail` extra only when running the legacy
# JailedGrader on a bare-metal or VM host with AppArmor configured.

RUN useradd -m --shell /bin/false app
USER app

COPY . /edx/app/xqueue_watcher
CMD ["xqueue-watcher", "-d", "/etc/xqueue-watcher"]

FROM openedx as edx.org
RUN pip install newrelic
CMD newrelic-admin run-program python -m xqueue_watcher -d /edx/etc/xqueue_watcher
FROM base AS edx.org
USER app
CMD ["xqueue-watcher", "-d", "/etc/xqueue-watcher"]
56 changes: 19 additions & 37 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,47 +1,29 @@
NODE_BIN=./node_modules/.bin

help:
@echo ' '
@echo 'Makefile for the xqueue-watcher '
@echo ' '
@echo 'Usage: '
@echo ' make requirements install requirements for local development '
@echo ' make test run python unit-tests '
@echo ' make clean delete generated byte code and coverage reports '
@echo ' '

COMMON_CONSTRAINTS_TXT=requirements/common_constraints.txt
.PHONY: $(COMMON_CONSTRAINTS_TXT)
$(COMMON_CONSTRAINTS_TXT):
wget -O "$(@)" https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt || touch "$(@)"

upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade
upgrade: $(COMMON_CONSTRAINTS_TXT)
## update the requirements/*.txt files with the latest packages satisfying requirements/*.in
pip install -q -r requirements/pip_tools.txt
pip-compile --allow-unsafe --rebuild --upgrade -o requirements/pip.txt requirements/pip.in
pip-compile --upgrade -o requirements/pip_tools.txt requirements/pip_tools.in
pip install -q -r requirements/pip.txt
pip install -q -r requirements/pip_tools.txt
pip-compile --upgrade -o requirements/base.txt requirements/base.in
pip-compile --upgrade -o requirements/production.txt requirements/production.in
pip-compile --upgrade -o requirements/test.txt requirements/test.in
pip-compile --upgrade -o requirements/ci.txt requirements/ci.in
@echo ''
@echo 'Makefile for the xqueue-watcher'
@echo ''
@echo 'Usage:'
@echo ' make requirements sync dev dependencies with uv'
@echo ' make test run python unit-tests'
@echo ' make docker-build build the grader base Docker image'
@echo ' make local-run run locally with docker-compose'
@echo ' make clean delete generated byte code'
@echo ''

requirements:
pip install -qr requirements/production.txt --exists-action w
uv sync

test.requirements:
pip install -q -r requirements/test.txt --exists-action w
test: requirements
uv run pytest --cov=xqueue_watcher --cov-report=xml tests

ci.requirements:
pip install -q -r requirements/ci.txt --exists-action w
docker-build:
docker build -t xqueue-watcher:local .
docker build -t grader-base:local -f grader_support/Dockerfile.base .

test: test.requirements
pytest --cov=xqueue_watcher --cov-report=xml tests
local-run:
docker compose up

clean:
find . -name '*.pyc' -delete

# Targets in a Makefile which do not produce an output file with the same name as the target name
.PHONY: help requirements clean
.PHONY: help requirements test docker-build local-run clean
10 changes: 7 additions & 3 deletions conf.d/600.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
"AUTH": ["lms", "lms"],
"HANDLERS": [
{
"HANDLER": "xqueue_watcher.grader.Grader",
"HANDLER": "xqueue_watcher.containergrader.ContainerGrader",
"KWARGS": {
"grader_root": "../data/6.00x/graders/",
"gradepy": "../data/6.00x/graders/grade.py"
"grader_root": "/graders/",
"image": "grader-base:local",
"backend": "docker",
"cpu_limit": "500m",
"memory_limit": "256Mi",
"timeout": 20
}
}
]
Expand Down
Loading
Loading