From 0289220aabffbdd12ba119d66496dc77fc8f563f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20Gonz=C3=A1lez?= Date: Fri, 8 May 2026 17:56:49 -0400 Subject: [PATCH] feat!: support for verawood --- pyproject.toml | 16 +- tutorcodejail/patches/cms-env | 2 +- tutorcodejail/patches/k8s-deployments | 52 +--- tutorcodejail/patches/k8s-services | 9 +- .../patches/kustomization-configmapgenerator | 9 +- tutorcodejail/patches/lms-env | 2 +- .../patches/local-docker-compose-dev-services | 8 - .../patches/local-docker-compose-services | 25 +- tutorcodejail/plugin.py | 87 +++---- .../apps/codejail-service-v2/tutor.py | 34 --- .../templates/codejail/apps/codejail/tutor.py | 42 ++- .../build/codejail-service/Dockerfile | 173 ------------- .../codejail/build/codejail/Dockerfile | 239 ++++++++++++------ uv.lock | 19 +- 14 files changed, 265 insertions(+), 452 deletions(-) delete mode 100644 tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py delete mode 100644 tutorcodejail/templates/codejail/build/codejail-service/Dockerfile diff --git a/pyproject.toml b/pyproject.toml index 9b489da..1718255 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tutor-contrib-codejail" -version = "21.0.1" +version = "22.0.0" description = "Codejail plugin for Tutor" readme = "README.md" license-files = ["LICENSE"] @@ -9,7 +9,7 @@ authors = [ ] requires-python = ">=3.10" dependencies = [ - "tutor~=21.0" + "tutor~=22.0" ] classifiers = [ "Development Status :: 3 - Alpha", @@ -34,6 +34,17 @@ codejail = "tutorcodejail.plugin" module-root = "." module-name = "tutorcodejail" +[tool.uv.sources] +tutor = { + git = "https://github.com/overhangio/tutor", + rev = "59264ca9f9c6a8e67622ae72080684ffc6ec7e2a", # Verawood +} +tutor-mfe = { + git = "https://github.com/overhangio/tutor-mfe", + rev = "72370dd4ace29e1a7a646da57eabc8e098ecee67", # Verawood +} + + [build-system] requires = ["uv_build>=0.9.5,<0.12.0"] build-backend = "uv_build" @@ -44,6 +55,7 @@ dev = [ "ruff", "scriv>=1.8.0", "ty>=0.0.34", + "tutor-mfe", ] [tool.ruff] diff --git a/tutorcodejail/patches/cms-env b/tutorcodejail/patches/cms-env index 10a52f8..4c8b040 100644 --- a/tutorcodejail/patches/cms-env +++ b/tutorcodejail/patches/cms-env @@ -1,4 +1,4 @@ ENABLE_CODEJAIL_REST_SERVICE: true -CODE_JAIL_REST_SERVICE_HOST: "http://{{ CODEJAIL_HOST }}:8550" +CODE_JAIL_REST_SERVICE_HOST: "http://codejail:8000" CODE_JAIL_REST_SERVICE_CONNECT_TIMEOUT: 0.5 CODE_JAIL_REST_SERVICE_READ_TIMEOUT: 3.5 diff --git a/tutorcodejail/patches/k8s-deployments b/tutorcodejail/patches/k8s-deployments index f22d9e5..f9dec16 100644 --- a/tutorcodejail/patches/k8s-deployments +++ b/tutorcodejail/patches/k8s-deployments @@ -1,27 +1,26 @@ --- -{% if CODEJAIL_USE_SERVICE_V2 %} apiVersion: apps/v1 kind: Deployment metadata: - name: codejailservice + name: codejail labels: - app.kubernetes.io/name: codejailservice + app.kubernetes.io/name: codejail spec: selector: matchLabels: - app.kubernetes.io/name: codejailservice + app.kubernetes.io/name: codejail template: metadata: labels: - app.kubernetes.io/name: codejailservice + app.kubernetes.io/name: codejail spec: securityContext: appArmorProfile: type: Localhost localhostProfile: openedx_codejail_service containers: - - name: codejailservice - image: {{ CODEJAIL_DOCKER_IMAGE_V2 }} + - name: codejail + image: {{ CODEJAIL_DOCKER_IMAGE }} ports: - containerPort: 8550 env: @@ -35,45 +34,6 @@ spec: - name: settings-codejail configMap: name: settings-codejail -{% else %} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: codejailservice - labels: - app.kubernetes.io/name: codejailservice -spec: - selector: - matchLabels: - app.kubernetes.io/name: codejailservice - template: - metadata: - labels: - app.kubernetes.io/name: codejailservice - spec: - {% if CODEJAIL_ENFORCE_APPARMOR %} - securityContext: - appArmorProfile: - type: Localhost - localhostProfile: docker-edx-sandbox - {% endif %} - containers: - - name: codejailservice - image: {{ CODEJAIL_DOCKER_IMAGE }} - ports: - - containerPort: 8550 - env: - - name: FLASK_APP_SETTINGS - value: codejailservice.tutor.ProductionConfig - volumeMounts: - - mountPath: /openedx/codejailservice/codejailservice/tutor.py - name: settings-codejail - subPath: tutor.py - volumes: - - name: settings-codejail - configMap: - name: settings-codejail -{% endif %} {% if CODEJAIL_ENABLE_K8S_DAEMONSET %} --- apiVersion: apps/v1 diff --git a/tutorcodejail/patches/k8s-services b/tutorcodejail/patches/k8s-services index 7b259c1..49736e2 100644 --- a/tutorcodejail/patches/k8s-services +++ b/tutorcodejail/patches/k8s-services @@ -2,13 +2,14 @@ apiVersion: v1 kind: Service metadata: - name: codejailservice + name: codejail labels: - app.kubernetes.io/name: codejailservice + app.kubernetes.io/name: codejail spec: type: ClusterIP ports: - - port: 8550 + - port: 8000 protocol: TCP + name: http selector: - app.kubernetes.io/name: codejailservice + app.kubernetes.io/name: codejail diff --git a/tutorcodejail/patches/kustomization-configmapgenerator b/tutorcodejail/patches/kustomization-configmapgenerator index e1adba7..f232d94 100644 --- a/tutorcodejail/patches/kustomization-configmapgenerator +++ b/tutorcodejail/patches/kustomization-configmapgenerator @@ -1,17 +1,12 @@ - name: codejail-profile files: - - plugins/codejail/apps/profiles/docker-edx-sandbox.profile - plugins/codejail/apps/profiles/openedx-codejail-service.profile options: labels: app.kubernetes.io/name: codejail-aa-loader -- name: settings-codejail +- name: codejail-settings files: - {% if CODEJAIL_USE_SERVICE_V2 %} - - plugins/codejail/apps/codejail-service-v2/tutor.py - {% else %} - plugins/codejail/apps/codejail/tutor.py - {% endif %} options: labels: - app.kubernetes.io/name: codejailservice + app.kubernetes.io/name: codejail diff --git a/tutorcodejail/patches/lms-env b/tutorcodejail/patches/lms-env index 10a52f8..4c8b040 100644 --- a/tutorcodejail/patches/lms-env +++ b/tutorcodejail/patches/lms-env @@ -1,4 +1,4 @@ ENABLE_CODEJAIL_REST_SERVICE: true -CODE_JAIL_REST_SERVICE_HOST: "http://{{ CODEJAIL_HOST }}:8550" +CODE_JAIL_REST_SERVICE_HOST: "http://codejail:8000" CODE_JAIL_REST_SERVICE_CONNECT_TIMEOUT: 0.5 CODE_JAIL_REST_SERVICE_READ_TIMEOUT: 3.5 diff --git a/tutorcodejail/patches/local-docker-compose-dev-services b/tutorcodejail/patches/local-docker-compose-dev-services index b092da1..e69de29 100644 --- a/tutorcodejail/patches/local-docker-compose-dev-services +++ b/tutorcodejail/patches/local-docker-compose-dev-services @@ -1,8 +0,0 @@ -codejailservice: - command: flask run --host 0.0.0.0 --port 8550 - environment: - FLASK_ENV: development - FLASK_APP_SETTINGS: codejailservice.tutor.DevelopmentConfig - ports: - - "8550:8550" - restart: unless-stopped diff --git a/tutorcodejail/patches/local-docker-compose-services b/tutorcodejail/patches/local-docker-compose-services index a40ad88..e101e83 100644 --- a/tutorcodejail/patches/local-docker-compose-services +++ b/tutorcodejail/patches/local-docker-compose-services @@ -1,35 +1,16 @@ #############Codejail service -{% if CODEJAIL_USE_SERVICE_V2 %} -codejailservice: - image: {{ CODEJAIL_DOCKER_IMAGE_V2 }} - ports: - - 8550:8550 +codejail: + image: {{ CODEJAIL_DOCKER_IMAGE }} environment: DJANGO_SETTINGS_MODULE: codejail_service.settings.tutor security_opt: - apparmor:openedx_codejail_service volumes: - - ../plugins/codejail/apps/codejail-service-v2/tutor.py:/app/codejail_service/settings/tutor.py:ro - restart: unless-stopped - depends_on: - codejail-apparmor-loader: - condition: service_completed_successfully -{% else %} -codejailservice: - image: {{ CODEJAIL_DOCKER_IMAGE }} - environment: - FLASK_APP_SETTINGS: codejailservice.tutor.ProductionConfig - {% if CODEJAIL_ENFORCE_APPARMOR %} - security_opt: - - apparmor:docker-edx-sandbox - {% endif %} - volumes: - - ../plugins/codejail/apps/codejail/tutor.py:/openedx/codejailservice/codejailservice/tutor.py:ro + - ../plugins/codejail/apps/codejail/tutor.py:/app/codejail_service/settings/tutor.py:ro restart: unless-stopped depends_on: codejail-apparmor-loader: condition: service_completed_successfully -{% endif %} codejail-apparmor-loader: image: {{ CODEJAIL_APPARMOR_DOCKER_IMAGE }} diff --git a/tutorcodejail/plugin.py b/tutorcodejail/plugin.py index 483c908..f0468be 100644 --- a/tutorcodejail/plugin.py +++ b/tutorcodejail/plugin.py @@ -8,7 +8,6 @@ import importlib_resources from tutor import hooks -from tutor.types import Config from .__about__ import __version__ @@ -21,18 +20,13 @@ "defaults": { "APPARMOR_DOCKER_IMAGE": "docker.io/ednxops/codejail_apparmor_loader:apparmor-4", "DOCKER_IMAGE": f"docker.io/ednxops/codejailservice:{__version__}", - "DOCKER_IMAGE_V2": "{{ CODEJAIL_DOCKER_IMAGE }}-v2", "ENABLE_K8S_DAEMONSET": False, "ENFORCE_APPARMOR": True, "EXTRA_PIP_REQUIREMENTS": [], - "HOST": "codejailservice", - "SANDBOX_PYTHON_VERSION": "3.11.14", - "SERVICE_REPOSITORY": "https://github.com/edunext/codejailservice.git", - "SERVICE_V2_REPOSITORY": "https://github.com/openedx/codejail-service.git", - "SERVICE_V2_VERSION": "{{ OPENEDX_COMMON_VERSION }}", + "SANDBOX_PYTHON_VERSION": "3.12", + "SERVICE_REPOSITORY": "https://github.com/openedx/codejail-service.git", "SERVICE_VERSION": "{{ OPENEDX_COMMON_VERSION }}", "SKIP_INIT": False, - "USE_SERVICE_V2": False, "VERSION": __version__, }, "overrides": {}, @@ -71,60 +65,47 @@ def get_apparmor_abi(): ] ) +hooks.Filters.IMAGES_BUILD.add_item( + ( + "codejail", + ("plugins", "codejail", "build", "codejail"), + "{{ CODEJAIL_DOCKER_IMAGE }}", + (), + ), +) +hooks.Filters.IMAGES_PULL.add_item( + ( + "codejail", + "{{ CODEJAIL_DOCKER_IMAGE }}", + ) +) +hooks.Filters.IMAGES_PUSH.add_item( + ( + "codejail", + "{{ CODEJAIL_DOCKER_IMAGE }}", + ) +) -@hooks.Filters.IMAGES_BUILD.add() -def _build_codejail_images( - images: list[tuple[str, str | tuple[str, ...], str, tuple[str, ...]]], - tutor_config: Config, -): - """Choose the appropiate build context when using CODEJAIL_USE_SERVICE_V2.""" - # TODO: Remove after the Verawood update - if tutor_config.get("CODEJAIL_USE_SERVICE_V2"): - codejail_img = ( - "codejail", - "plugins/codejail/build/codejail-service", - "{{ CODEJAIL_DOCKER_IMAGE_V2 }}", - (), - ) - else: - codejail_img = ( - "codejail", - "plugins/codejail/build/codejail", - "{{ CODEJAIL_DOCKER_IMAGE }}", - (), - ) - apparmor_img = ( +hooks.Filters.IMAGES_BUILD.add_item( + ( "codejail_apparmor", ("plugins", "codejail", "build", "codejail_apparmor"), "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", (), + ), +) +hooks.Filters.IMAGES_PULL.add_item( + ( + "codejail_apparmor", + "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", ) - - return images + [codejail_img, apparmor_img] - - -@hooks.Filters.IMAGES_PUSH.add() -def _push_codejail_images( - images: list[tuple[str, str]], - tutor_config: Config, -): - """Choose the appropiate image tag when using CODEJAIL_USE_SERVICE_V2.""" - # TODO: Remove after the Verawood update - if tutor_config.get("CODEJAIL_USE_SERVICE_V2"): - codejail_img = ( - "codejail", - "{{ CODEJAIL_DOCKER_IMAGE_V2 }}", - ) - else: - codejail_img = ( - "codejail", - "{{ CODEJAIL_DOCKER_IMAGE }}", - ) - apparmor_img = ( +) +hooks.Filters.IMAGES_PUSH.add_item( + ( "codejail_apparmor", "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", ) - return images + [codejail_img, apparmor_img] +) # Boilerplate code diff --git a/tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py b/tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py deleted file mode 100644 index 172a72b..0000000 --- a/tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py +++ /dev/null @@ -1,34 +0,0 @@ -from codejail_service.settings.local import * # pylint: disable=wildcard-import - -ALLOWED_HOSTS = [ - 'codejailservice', - 'localhost', -] - -CODEJAIL_ENABLED = True -SECRET_KEY = '{{ CODEJAIL_SECRET_KEY }}' - -CODE_JAIL = { - 'python_bin': '/sandbox/venv/bin/python', - 'user': 'sandbox', - - # Configurable limits. - 'limits': { - # CPU-seconds - 'CPU': 3, - # Clock seconds - 'REALTIME': 3, - # Need at least 300 MiB memory for matplotlib alone. 512 MiB should be - # enough headroom in general. - 'VMEM': 512 * 1024 * 1024, - # 10 MB file size limit - 'FSIZE': 10 * 1024 * 1024, - # 15 processes and threads (codejail default) - 'NPROC': 15, - # Don't use a proxy process to spawn subprocesses. - 'PROXY': 0, - }, -} - -{{ patch("codejail-common-settings") }} -{{ patch("codejail-production-settings") }} diff --git a/tutorcodejail/templates/codejail/apps/codejail/tutor.py b/tutorcodejail/templates/codejail/apps/codejail/tutor.py index df72ab4..592643d 100644 --- a/tutorcodejail/templates/codejail/apps/codejail/tutor.py +++ b/tutorcodejail/templates/codejail/apps/codejail/tutor.py @@ -1,18 +1,34 @@ -"""Module with the configuration of config classes.""" -from codejailservice.config import DevelopmentConfig, ProductionConfig +from codejail_service.settings.local import * # pylint: disable=wildcard-import +ALLOWED_HOSTS = [ + 'codejail', + 'localhost', +] -class DevelopmentConfig(DevelopmentConfig): - """Class to use for development context that inherits from DevelopmentConfig.""" +CODEJAIL_ENABLED = True +SECRET_KEY = '{{ CODEJAIL_SECRET_KEY }}' - CODE_JAIL = DevelopmentConfig.CODE_JAIL - {{patch("codejail-common-settings") | indent(4)}} - {{patch("codejail-development-settings") | indent(4)}} +CODE_JAIL = { + 'python_bin': '/sandbox/venv/bin/python', + 'user': 'sandbox', + # Configurable limits. + 'limits': { + # CPU-seconds + 'CPU': 3, + # Clock seconds + 'REALTIME': 3, + # Need at least 300 MiB memory for matplotlib alone. 512 MiB should be + # enough headroom in general. + 'VMEM': 512 * 1024 * 1024, + # 10 MB file size limit + 'FSIZE': 10 * 1024 * 1024, + # 15 processes and threads (codejail default) + 'NPROC': 15, + # Don't use a proxy process to spawn subprocesses. + 'PROXY': 0, + }, +} -class ProductionConfig(ProductionConfig): - """Class to use for production context that inherits from ProductionConfig.""" - - CODE_JAIL = ProductionConfig.CODE_JAIL - {{patch("codejail-common-settings") | indent(4)}} - {{patch("codejail-production-settings") | indent(4)}} +{{ patch("codejail-common-settings") }} +{{ patch("codejail-production-settings") }} diff --git a/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile b/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile deleted file mode 100644 index a62f8cd..0000000 --- a/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile +++ /dev/null @@ -1,173 +0,0 @@ -FROM scratch AS codejail-service-code -ARG CODEJAIL_SERVICE_REPO={{ CODEJAIL_SERVICE_V2_REPOSITORY }} -ARG CODEJAIL_SERVICE_VERSION={{ CODEJAIL_SERVICE_V2_VERSION }} -ADD ${CODEJAIL_SERVICE_REPO}#${CODEJAIL_SERVICE_VERSION} / - -FROM scratch AS sandbox-dependencies -# Where to get the Python dependencies lockfile for installing -# packages into the sandbox environment. Defaults to the codejail -# dependencies in edx-platform. -ARG SANDBOX_DEPS_REPO={{ EDX_PLATFORM_REPOSITORY }} -ARG SANDBOX_DEPS_VERSION={{ CODEJAIL_SERVICE_V2_VERSION }} -# Path to the lockfile in the deps repo, as dir + filename. -# -# The path base.txt will get the latest dependencies, but this needs -# to be coordinated with SANDBOX_PY_VER as each release has a -# different Python support window. -ARG SANDBOX_DEPS_SRC_DIR=requirements/edx-sandbox -ADD ${SANDBOX_DEPS_REPO}#${SANDBOX_DEPS_VERSION}:${SANDBOX_DEPS_SRC_DIR} / - -FROM docker.io/ubuntu:24.04 AS app -ARG APP_PY_VER=3.12 -# See codejail-service deployment and configuration docs for why we need to select -# a UID/GID that is unlikely to collide with anything on the host. (Short answer: -# RLIMIT_NPROC UID-global usage pool, and Docker not isolating UIDs.) -# -# Selected via: python3 -c 'import random; print(random.randrange(3000, 2 ** 31))' -ARG APP_UID=15826504 -ARG APP_GID=$APP_UID - -ARG SANDBOX_DEPS_SRC_FILE=base.txt - -# Python version for sandboxed executions. This must be coordinated with -# `SANDBOX_DEPS_SRC_*` to ensure compatibility. -ARG SANDBOX_PY_VER={{ '.'.join(CODEJAIL_SANDBOX_PYTHON_VERSION.split('.')[:2]) }} - - -##### Base app installation ##### - -ENV DEBIAN_FRONTEND=noninteractive -ARG APT_INSTALL="apt-get install --quiet --yes --no-install-recommends" - -# The codejail library specifies a certain structure to how the sandboxing is -# performed. (See the documentation in the codejail library README: -# https://github.com/openedx/codejail). -# -# Some of this structure can be changed, and some cannot. Any changes that are -# possible will also need to be coordinated with changes to the apparmor profile -# as well as to the `CODE_JAIL` Django settings. Accordingly, it's best to just -# *avoid* making changes to this part. - -# The location of the virtualenv that code executions in the sandbox will use. -# This is a critical path, as SAND_VENV/bin/python is what is targeted by the -# AppArmor confinement. It must also match the Django setting -# `CODE_JAIL.python_bin`. The codejail docs refer to this as ``. -ARG SAND_VENV=/sandbox/venv -# The user account that will run code executions, described just as "the sandbox -# user" in codejail docs. This needs to match the Django setting -# `CODE_JAIL.user` and the sudoers file. -ARG SAND_USER=sandbox -# Same situation as for APP_UID -ARG SAND_UID=33552349 -ARG SAND_GID=$SAND_UID -# The user account that runs the regular web app, described in codejail docs as -# ``. Needs to match the sudoers file. -ARG APP_USER=app - -# The codejail-service API tests check for the visibility of this environment -# variable from the sandbox. (It should not be visible.) This helps test for -# environment leakage into the sandbox. -ENV CJS_TEST_ENV_LEAKAGE=yes - -# Packages installed: -# -# - language-pack-en, locales: Ubuntu locale support so that system utilities -# have a consistent language and time zone. -# - sudo: Web user (`APP_USER`) needs to be able to sudo as `SAND_USER` -# - python*: Specific versions of Python -- the service runs with a recent version, but -# the sandboxed code will usually need a different (older) version. This is also why -# we need to pull in the deadsnakes PPA. -# - python*-dev: Header files for python extensions, required by many source wheels -# - python*-venv: Allow creation of virtualenvs -# -# We also have to do a bit of bootstrapping here installing the -# `software-properties-common` package gives us `add-apt-repository`, which -# allows us to add the deadsnakes PPA more easily (that is, without messing -# about with repository keys). -RUN <`. +ARG SAND_VENV=/sandbox/venv +# The user account that will run code executions, described just as "the sandbox +# user" in codejail docs. This needs to match the Django setting +# `CODE_JAIL.user` and the sudoers file. +ARG SAND_USER=sandbox +# Same situation as for APP_UID +ARG SAND_UID=33552349 +ARG SAND_GID=$SAND_UID +# The user account that runs the regular web app, described in codejail docs as +# ``. Needs to match the sudoers file. +ARG APP_USER=app + +# The codejail-service API tests check for the visibility of this environment +# variable from the sandbox. (It should not be visible.) This helps test for +# environment leakage into the sandbox. +ENV CJS_TEST_ENV_LEAKAGE=yes + +# Packages installed: +# +# - language-pack-en, locales: Ubuntu locale support so that system utilities +# have a consistent language and time zone. +# - sudo: Web user (`APP_USER`) needs to be able to sudo as `SAND_USER` +# - python*: Specific versions of Python -- the service runs with a recent version, but +# the sandboxed code will usually need a different (older) version. This is also why +# we need to pull in the deadsnakes PPA. +# - python*-dev: Header files for python extensions, required by many source wheels +# - python*-venv: Allow creation of virtualenvs +# +# We also have to do a bit of bootstrapping here installing the +# `software-properties-common` package gives us `add-apt-repository`, which +# allows us to add the deadsnakes PPA more easily (that is, without messing +# about with repository keys). +RUN <