Extend documentation

This commit is contained in:
Tim Schubert 2018-09-28 15:00:26 +02:00
parent 0e555d9e8a
commit f356b1e65f
9 changed files with 160 additions and 98 deletions

View file

@ -1,63 +0,0 @@
# Programmieren [1,2] Gitlab
- https://docs.gitlab.com/omnibus/README.html
## Authentication
- use GITZ LDAP for login
- not allow "create new repo"
## Structure
- main repo
+ publish example solutions
+ CI config for checkstyle
+ Protected Runner for JPlag
+ restrict access to branches with example solutions
- student repos
+ forked from main repo
+ one repo per student
+ student has *Developer* Access
+ *tutors* group has *Master* access
+ students can request access (Abgabepartner)
+ *tutors* can grant access
## Checkstyle
- GitLab CI
- [Docker](https://docs.gitlab.com/omnibus/docker/README.html)container
- [Shared Runner](https://docs.gitlab.com/ce/ci/runners/README.html)
- restrict Container to [checkstyle](http://checkstyle.sourceforge.net/)
- disable internet access for container
## JPlag
- Deadline [at,cron]job or schedule via gitlab
- triggers [Protected Runner](https://docs.gitlab.com/ee/ci/runners/README.html#protected-runners)
- creates automatic protected TAG in each repo
- checks out TAG from all repos into /tmp and runs [JPlag](https://jplag.ipd.kit.edu/)
- replace with MOSS? https://github.com/soachishti/moss.py
- deploy key in each repo
## (optional) sync script
- (one-way) sync students and groups from [Stud.IP REST API](http://docs.studip.de/develop/Entwickler/RESTAPI) to [Gitlab REST API](https://docs.gitlab.com/ce/api/)
# Replicate (TODO: ansible playbook)
- install gitlab
- install docker
- copy gitlab.rb
- partially protected
- default project limit = 0
- shared runner for checkstyle
- protected runner for
+ setting protected tags
+ running jplag
- script for creating repos and groups
- SSH deploy key

View file

@ -4,9 +4,8 @@
# You can set these variables from the command line. # You can set these variables from the command line.
SPHINXOPTS = SPHINXOPTS =
SPHINXBUILD = sphinx-build SPHINXBUILD = sphinx-build
SPHINXPROJ = abgabesystem SOURCEDIR = source
SOURCEDIR = . BUILDDIR = build
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
help: help:

View file

@ -7,9 +7,8 @@ REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" ( if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build set SPHINXBUILD=sphinx-build
) )
set SOURCEDIR=. set SOURCEDIR=source
set BUILDDIR=_build set BUILDDIR=build
set SPHINXPROJ=abgabesystem
if "%1" == "" goto help if "%1" == "" goto help

View file

@ -12,9 +12,9 @@
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# #
import os # import os
import sys # import sys
sys.path.insert(0, os.path.abspath('../src')) # sys.path.insert(0, os.path.abspath('.'))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
@ -26,7 +26,7 @@ author = 'Tim Schubert'
# The short X.Y version # The short X.Y version
version = '' version = ''
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '' release = '1.0'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@ -42,7 +42,10 @@ extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'sphinx.ext.doctest', 'sphinx.ext.doctest',
'sphinx.ext.intersphinx', 'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage', 'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -66,11 +69,11 @@ language = None
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path . # This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = None
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
@ -159,9 +162,32 @@ texinfo_documents = [
] ]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration ------------------------------------------------- # -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension --------------------------------------- # -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None} intersphinx_mapping = {'https://docs.python.org/': None}
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

View file

@ -1,5 +1,5 @@
.. abgabesystem documentation master file, created by .. abgabesystem documentation master file, created by
sphinx-quickstart on Fri Jun 1 13:35:35 2018. sphinx-quickstart on Fri Sep 28 14:59:39 2018.
You can adapt this file completely to your liking, but it should at least You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive. contain the root `toctree` directive.
@ -18,18 +18,3 @@ Indices and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`
.. automodule:: abgabesystem
:members:
.. automodule:: abgabesystem.projects
:members:
.. automodule:: abgabesystem.students
:members:
.. automodule:: abgabesystem.commands
:members:
.. autoclass:: abgabesystem.students.Student
:members:

View file

@ -9,6 +9,10 @@ from gitlab.exceptions import GitlabCreateError, GitlabGetError
def enroll_students(gl, args): def enroll_students(gl, args):
"""Creates Gitlab users from exported students list """Creates Gitlab users from exported students list
Args:
gl: API
args: command line arguments
""" """
student_group = get_student_group(gl, args.course) student_group = get_student_group(gl, args.course)
@ -25,6 +29,10 @@ def enroll_students(gl, args):
def projects(gl, args): def projects(gl, args):
"""Creates the projects for all course participants """Creates the projects for all course participants
Args:
gl: API
args: command line arguments
""" """
course = None course = None
for g in gl.groups.list(search=args.course): for g in gl.groups.list(search=args.course):
@ -39,7 +47,12 @@ def projects(gl, args):
def deadline(gl, args): def deadline(gl, args):
"""Checks deadlines for course and triggers deadline if it is reached""" """Checks deadlines for course and triggers deadline if it is reached
Args:
gl: API
args: command line arguments
"""
deadline_name = args.tag_name deadline_name = args.tag_name
try: try:
@ -63,6 +76,10 @@ def deadline(gl, args):
def plagiates(gl, args): def plagiates(gl, args):
"""Runs the plagiarism checker (JPlag) for the solutions with a certain tag """Runs the plagiarism checker (JPlag) for the solutions with a certain tag
Args:
gl: API
args: command line arguments
""" """
solutions_dir = 'input' solutions_dir = 'input'
@ -90,6 +107,10 @@ def plagiates(gl, args):
def course(gl, args): def course(gl, args):
"""Creates the group for the course """Creates the group for the course
Args:
gl: API
args: command line arguments
""" """
try: try:
gl.groups.create({ gl.groups.create({

View file

@ -2,10 +2,21 @@ import logging as log
class InvalidCourse(Exception): class InvalidCourse(Exception):
"""Raised if the selected course is invalid.
"""
pass pass
def create_subgroup(gl, name, parent_group): def create_subgroup(gl, name, parent_group):
"""Creates a group with `parent_group` as its parent.
Args:
gl: gitlab API object
name: name of the group to be created
parent_group: parent group of the created group
"""
log.info("Creating subgroup %s in group %s" % (name, parent_group.name)) log.info("Creating subgroup %s in group %s" % (name, parent_group.name))
return gl.groups.create({ return gl.groups.create({
"name": name, "name": name,
@ -23,6 +34,15 @@ def create_solutions_group(gl, parent_group):
def create_course(gl, course_name): def create_course(gl, course_name):
"""Creates a complete course as required by the `abgabesystem` including
the students and solutions groups.
Args:
gl: gitlab API object
course_name: name of the course, may contain any characters from
[0-9,a-z,A-Z,_, ]
"""
group = gl.groups.create({ group = gl.groups.create({
"name": course_name, "name": course_name,
"path": course_name.lower().replace(" ", "_"), "path": course_name.lower().replace(" ", "_"),

View file

@ -10,7 +10,12 @@ def create_tag(project, tag, ref):
"""Creates protected tag on ref """Creates protected tag on ref
The tag is used by the abgabesystem to mark the state of a solution at the The tag is used by the abgabesystem to mark the state of a solution at the
deadline deadline.
Args:
project: GIT repository to create the tag in
tag: name of the tag to be created
ref: name of the red (branch / commit) to create the new tag on
""" """
print('Project %s. Creating tag %s' % (project.path, tag)) print('Project %s. Creating tag %s' % (project.path, tag))
@ -21,9 +26,17 @@ def create_tag(project, tag, ref):
}) })
def fork_reference(gl, reference, namespace, deploy_key): def fork_reference(gl, reference, namespace, deploy_key):
"""Create fork of solutions for student. """Create fork of solutions for student.
Returns the created project.
Args:
gl: gitlab API object
reference: project to fork from
namespace: namespace to place the created project into
deploy_key: will be used by the abgabesystem to access the created
project
""" """
fork = reference.forks.create({ fork = reference.forks.create({
@ -46,6 +59,14 @@ def fork_reference(gl, reference, namespace, deploy_key):
def create_project(gl, group, user, reference, deploy_key): def create_project(gl, group, user, reference, deploy_key):
"""Creates a namespace (subgroup) and forks the project with """Creates a namespace (subgroup) and forks the project with
the reference solutions into that namespace the reference solutions into that namespace
Args:
gl: Gitlab API object
group: project will be created in the namespace of this group
user: user to add to the project as a developer
reference: project to fork the new project from
deploy_key: deploy key used by the `abgabesystem` to access the new
project
""" """
subgroup = None subgroup = None
@ -79,6 +100,14 @@ def create_project(gl, group, user, reference, deploy_key):
def create_reference_solution(gl, namespace): def create_reference_solution(gl, namespace):
"""Creates a new project for the reference solutions.
Args:
gl: gitlab API object
namespace: namespace to create the project in (that of the solutions for the course)
"""
reference_project = gl.projects.create({ reference_project = gl.projects.create({
'name': 'solutions', 'name': 'solutions',
'namespace_id': namespace, 'namespace_id': namespace,
@ -100,7 +129,12 @@ def create_reference_solution(gl, namespace):
def setup_projects(gl, course, deploy_key): def setup_projects(gl, course, deploy_key):
"""Sets up the internal structure for the group for use with the course """Sets up the internal structure for the group for use with the course.
Args:
gl: gitlab API object
course: course to set up projects for
deploy_key: will be used to access the solutions from the abgabesystem
""" """
solutions = None solutions = None

View file

@ -5,10 +5,17 @@ from gitlab import GUEST_ACCESS
class MissingStudentsGroup(Exception): class MissingStudentsGroup(Exception):
"""Raised if a the group for the students has not already been created
inside the course.
"""
pass pass
class MissingCourseGroup(Exception): class MissingCourseGroup(Exception):
"""Raised if the group for the course is missing.
"""
pass pass
@ -18,6 +25,12 @@ class Student():
Students are read from the CSV file that was exported from Stud.IP. Students are read from the CSV file that was exported from Stud.IP.
For each user, a dummy LDAP user is created in Gitlab. For each user, a dummy LDAP user is created in Gitlab.
Upon the first login Gitlab fetches the complete user using LDAP. Upon the first login Gitlab fetches the complete user using LDAP.
Args:
user: user name
mail: mail address of the user
name: full name of the user
group: tutorial group of the user
""" """
def __init__(self, user, mail, name, group): def __init__(self, user, mail, name, group):
@ -27,7 +40,11 @@ class Student():
self.group = group self.group = group
def from_csv(csvfile): def from_csv(csvfile):
"""Creates an iterable containing the users""" """Creates an iterable containing the users
Args:
csvfile: CSV file from Stud.IP (latin-1)
"""
reader = csv.DictReader(csvfile, delimiter=';', quotechar='"') reader = csv.DictReader(csvfile, delimiter=';', quotechar='"')
for line in reader: for line in reader:
@ -36,7 +53,12 @@ class Student():
def get_students_csv(gl, students_csv): def get_students_csv(gl, students_csv):
"""Returns already existing GitLab users for students from provided CSV file that have an account. """Returns already existing GitLab users for students from provided CSV
file that have an account.
Args:
gl: Gitlab API object
students_csv: CSV file from Stud.IP
""" """
for student in Student.from_csv(students_csv): for student in Student.from_csv(students_csv):
@ -47,6 +69,10 @@ def get_students_csv(gl, students_csv):
def enrolled_students(gl, course): def enrolled_students(gl, course):
"""Returns the students enrolled in the course """Returns the students enrolled in the course
Args:
gl: Gitlab API object
course: course the students are enrolled in
""" """
students = None students = None
@ -66,6 +92,12 @@ def enrolled_students(gl, course):
def create_user(gl, student, ldap_base, ldap_provider): def create_user(gl, student, ldap_base, ldap_provider):
"""Creates a GitLab user account student. """Creates a GitLab user account student.
Requires admin privileges. Requires admin privileges.
Args:
gl: Gitlab API object
student: student to create user for
ldap_base: the search base string for the LDAP query
ldap_provider: LDAP provider configured for Gitlab (usually `main`)
""" """
user = gl.users.create({ user = gl.users.create({
@ -84,6 +116,10 @@ def create_user(gl, student, ldap_base, ldap_provider):
def get_student_group(gl, course_name): def get_student_group(gl, course_name):
"""Gets the `students` subgroup for the course """Gets the `students` subgroup for the course
Args:
gl: Gitlab API objects
course_name: name of the course
""" """
course = None course = None
@ -108,6 +144,11 @@ def get_student_group(gl, course_name):
def enroll_student(gl, user, subgroup): def enroll_student(gl, user, subgroup):
"""Adds a student to the course """Adds a student to the course
Args:
gl: Gitlab API object
user: user to add to the course
subgroup: student will become member of this group
""" """
subgroup.members.create({ subgroup.members.create({