Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
# Ensure that each python version will be tested even if one of them fails.
fail-fast: false
matrix:
python_version: ["3.10", "3.11", "3.12", "3.13"]
python_version: ["3.12", "3.13"]

runs-on: ubuntu-24.04
services:
Expand Down
2 changes: 0 additions & 2 deletions deploy/agent-docker.containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ COPY inginious/agent/__init__.py inginious/agent/
COPY inginious/agent/docker_agent/ inginious/agent/docker_agent/
COPY inginious/scripts inginious/scripts

RUN dnf install -y gcc python3.11-devel

# See https://github.com/pypa/setuptools_scm/#usage-from-docker
RUN --mount=source=.git,target=.git,type=bind \
pip3 install --no-cache-dir -e .
Expand Down
26 changes: 25 additions & 1 deletion deploy/db_setup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from cryptography.hazmat.primitives.serialization import load_ssh_private_key
import argparse
import os

from pymongo import MongoClient
from gridfs import GridFS

Expand Down Expand Up @@ -37,6 +41,24 @@ def try_mongodb_opts(host="localhost", database_name='INGInious'):
email = "[email protected]"
password = "superadmin"

parser = argparse.ArgumentParser()
parser.add_argument(
"--key",
help="Path towards the superadmin's private SSH key.",
default=None
)
parser.add_argument("--ssh_pwd", help="The SSH key's password, if needed.", default=None)
args = parser.parse_args()

ssh_key = None
if args.key is None:
ssh_key = Ed25519PrivateKey.generate()
else:
if os.path.isfile(args.key):
with open(args.key, 'rb') as fd:
key = fd.read()
ssh_key = load_ssh_private_key(key, password=args.ssh_pwd.encode('utf-8'))

print('Initial DB setup.')

database = try_mongodb_opts('db')
Expand All @@ -47,5 +69,7 @@ def try_mongodb_opts(host="localhost", database_name='INGInious'):
"password": UserManager.hash_password(password),
"bindings": {},
"language": "en",
"code_indentation": "4"})
"code_indentation": "4",
"ssh_key": ssh_key.private_bytes_raw()})

print('Superadmin user added!')
5 changes: 2 additions & 3 deletions deploy/frontend.containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ COPY inginious/frontend inginious/frontend/
COPY inginious/client inginious/client/
COPY inginious/scripts inginious/scripts

RUN dnf install -y git gcc python3.11-devel

# See https://github.com/pypa/setuptools_scm/#usage-from-docker
RUN --mount=source=.git,target=.git,type=bind \
pip3 install --no-cache-dir -e .

COPY deploy/db_setup.py /tmp/db_setup.py
COPY deploy/gitolite /tmp/gitolite

CMD ["sh", "-c", "python3 /tmp/db_setup.py; python3 -m inginious.scripts.webapp"]
CMD ["sh", "-c", "python3 /tmp/db_setup.py --key /tmp/gitolite --ssh_pwd=test; python3 -m inginious.scripts.webapp"]
18 changes: 18 additions & 0 deletions deploy/gitolite.containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM rockylinux/rockylinux:10

RUN dnf update -y && dnf install -y python3.12 python3.12-pip git findutils openssh-server perl-core
COPY deploy/sshd_config /etc/ssh/sshd_config
RUN ssh-keygen -A
WORKDIR /opt
RUN git clone -b v3.6.14 https://github.com/sitaramc/gitolite
RUN useradd -p test -m admin
RUN chown admin:admin -R gitolite
USER admin
RUN git config --global init.defaultBranch main
RUN mkdir ~/bin
RUN /opt/gitolite/install -ln
COPY deploy/gitolite.pub /tmp/superadmin.pub
WORKDIR /home/admin
COPY deploy/gitolite.sh .
USER root
ENTRYPOINT ["./gitolite.sh"]
9 changes: 9 additions & 0 deletions deploy/gitolite.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /bin/bash -xe

if [[ ! -d ~/.gitolite ]]
then
chown -R admin:admin /home/admin/repositories
su - admin -c "~/bin/gitolite setup -pk /tmp/superadmin.pub"
fi

/usr/sbin/sshd -D
27 changes: 25 additions & 2 deletions deploy/inginious-base.containerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
# While https://github.com/libgit2/pygit2/pull/1395 is not available through PYPI,
# we have to custom build this patched version of pygit2 which requires libgit2.
# Build libgit2 from sources as headers are not correctly included in Rocky
# Devel package and the packaged version is too old.
FROM rockylinux/rockylinux:10 AS builder
RUN dnf update -y &&\
dnf install -y git gcc python3.12-devel python3.12-setuptools cmake openssl-devel &&\
python3 -m pip install build

# libssh2 is a dependency of libgit2, it is not embedded in RL10.
RUN git clone -b libssh2-1.11.1 https://github.com/libssh2/libssh2.git /tmp/libssh2
WORKDIR /tmp/libssh2
RUN cmake -B bld && cmake --build bld

# Build libgit2 with libssh2 support.
RUN git clone -b v1.9.1 https://github.com/libgit2/libgit2.git /tmp/libgit2
WORKDIR /tmp/libgit2
RUN cmake -B build -DUSE_SSH=ON -DLIBSSH2_LIBRARY=/tmp/libssh2/bld/src/libssh2.so -DLIBSSH2_INCLUDE_DIR=/tmp/libssh2/include/ && cmake --build build

# Base core container. This is not a service per-se but is used as a common layer for the core services.
FROM rockylinux:8
FROM rockylinux/rockylinux:10

RUN dnf update -y && dnf install -y python3.11 python3.11-pip git
RUN dnf update -y && dnf install -y python3.12 python3.12-pip git gcc python3.12-devel

WORKDIR /inginious
COPY --from=builder /tmp/libssh2/bld/src/libssh2.so* /usr/local/lib64/
COPY --from=builder /tmp/libgit2/include /usr/local/include
COPY --from=builder /tmp/libgit2/build/libgit2.* /usr/local/lib64/
COPY pyproject.toml README.rst ./
COPY inginious/common/ inginious/common/
COPY inginious/__init__.py inginious/
ENV LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH

# See https://github.com/pypa/setuptools_scm/#usage-from-docker
RUN --mount=source=.git,target=.git,type=bind \
Expand Down
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ services:
networks:
- inginious

gitolite:
build:
# When building the container, the public SSH key of the instance
# administrator is expected at deploy/gitolite.pub.
# See deploy/gitolite.containerfile for more details.
dockerfile: deploy/gitolite.containerfile
networks:
- inginious
volumes:
- ./gitolite:/home/admin/repositories
ports:
- 2222:22

backend:
image: ${REGISTRY}/inginious/core-backend:${VERSION}
depends_on:
Expand Down
4 changes: 3 additions & 1 deletion inginious/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import re

import inginious.common.custom_yaml
from collections import OrderedDict
from collections import OrderedDict, namedtuple


GitInfo = namedtuple("GitInfo", ["realname", "email", "username", "key"])

def id_checker(id_to_test):
"""Checks if a id is correct"""
return bool(re.match(r'[a-z0-9\-\._]+$', id_to_test, re.IGNORECASE))
Expand Down
3 changes: 2 additions & 1 deletion inginious/common/entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import sys

from inginious.common.filesystems.local import LocalFSProvider
from inginious.common.filesystems.git import GitFSProvider

def get_filesystems_providers():
""" Returns a dictionnary of {"fs_name": fs_class}, for each usable FileSystemProvider"""
providers = {"local": LocalFSProvider}
providers = {"local": LocalFSProvider, "git": GitFSProvider}
try:
plugged_providers = importlib.metadata.entry_points(group="inginious.filesystems")
except: # < python3.10
Expand Down
40 changes: 35 additions & 5 deletions inginious/common/filesystems/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
from __future__ import annotations
from abc import ABCMeta, abstractmethod
from datetime import datetime
from enum import StrEnum

from inginious.common.base import GitInfo


class FsType(StrEnum):
course = 'course'
task = 'task'
other = 'other'

class FileSystemProvider(metaclass=ABCMeta):
""" Provides tools to access a given filesystem. The filesystem may be distant, and subclasses of FileSystemProvider should take care of
Expand Down Expand Up @@ -52,6 +60,28 @@ def _checkpath(self, path):
if path.startswith("/") or ".." in path or path.strip() != path:
raise FileNotFoundError()

@abstractmethod
def try_stage(self, filepath: str) -> None:
"""
For versioned filesystems, try staging `filepath` if it points to
modified content. Otherwise, do nothing.

:param filepath: The path towards items to stage if modified.
"""

@abstractmethod
def try_commit(self, filepath: str, msg: str=None, user: GitInfo=None):
"""
For versioned filesystems, add `filepath` content to the history with
`msg` message from `user` author. Otherwise, do nothing.
If `user` is not provided, `filepath` content is only staged, if needed,
and not committed.

:param filepath: Path towards item(s) to commit.
:param msg: An optional commit message.
:param user: Optional authorship information for the commit.
"""

@abstractmethod
def from_subfolder(self, subfolder: str) -> FileSystemProvider:
"""
Expand All @@ -69,11 +99,11 @@ def exists(self, path: str=None) -> bool:
"""

@abstractmethod
def ensure_exists(self) -> None:
def ensure_exists(self, type: FsType=FsType.other, user: GitInfo=None, push: bool=True) -> None:
""" Ensure that the current prefix exists. If it is not the case, creates the directory. """

@abstractmethod
def put(self, filepath, content):
def put(self, filepath: str, content, msg: str=None, user: GitInfo=None):
""" Write `content` in `filepath`"""

@abstractmethod
Expand All @@ -89,7 +119,7 @@ def get_fd(self, filepath: str, timestamp:datetime=None):
"""

@abstractmethod
def get(self, filepath, timestamp:datetime=None):
def get(self, filepath: str, timestamp:datetime=None):
""" Get the content of a file.
If timestamp is not None, it gives an indication to the cache that the file must have been retrieved from the (possibly distant)
filesystem since the timestamp.
Expand All @@ -109,7 +139,7 @@ def list(self, folders: bool=True, files: bool=True, recursive: bool=False) -> l
"""

@abstractmethod
def delete(self, filepath: str=None):
def delete(self, filepath: str=None, msg: str=None, user: GitInfo=None, push: bool=True) -> None:
""" Delete a path recursively. If filepath is None, then the prefix will be deleted.

:param filepath: The prefix entry to delete.
Expand All @@ -122,7 +152,7 @@ def get_last_modification_time(self, filepath):
""" Get a timestamp representing the time of the last modification of the file at filepath """

@abstractmethod
def move(self, src, dest):
def move(self, src, dest, msg: str=None, user: GitInfo=None, push: bool=None) -> None:
""" Move path src to path dest, recursively. """

@abstractmethod
Expand Down
Loading
Loading