Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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', '3.14']
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
67 changes: 67 additions & 0 deletions .github/workflows/publish-grader-base-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Publish grader base image

# Builds grader_support/Dockerfile.base and pushes to:
# - GHCR: ghcr.io/openedx/xqueue-watcher-grader-base
#
# One image is published per supported Python version, tagged as:
# py<version>-latest (e.g. py3.12-latest, py3.13-latest, py3.14-latest)
# The newest version is also tagged as `latest`.

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:
IMAGE_NAME: openedx/xqueue-watcher-grader-base
# Newest version also receives the `latest` tag
LATEST_PYTHON: "3.14"

jobs:
build-and-push:
name: Build and push grader base image (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.12', '3.13', '3.14']
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: ghcr.io
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: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: grader_support/Dockerfile.base
platforms: linux/amd64,linux/arm64
push: true
build-args: |
PYTHON_VERSION=${{ matrix.python-version }}
tags: |
ghcr.io/${{ env.IMAGE_NAME }}:py${{ matrix.python-version }}-latest
${{ matrix.python-version == env.LATEST_PYTHON && format('ghcr.io/{0}:latest', env.IMAGE_NAME) || '' }}
cache-from: type=gha
cache-to: type=gha,mode=max
46 changes: 26 additions & 20 deletions .github/workflows/upgrade-python-requirements.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
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'

permissions: {}

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
54 changes: 37 additions & 17 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
FROM ubuntu:xenial as openedx
ARG UV_VERSION=0.10.7
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv

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/*
FROM python:3.11-slim AS base

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
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \
LC_ALL=C.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=uv /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
# 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.

# Put the venv on PATH so `xqueue-watcher` and any other installed scripts are
# available without a symlink.
ENV PATH="/edx/app/xqueue_watcher/.venv/bin:$PATH"

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
Loading
Loading