diff --git a/doc/notes.md b/doc/notes.md deleted file mode 100644 index e4ca6bb..0000000 --- a/doc/notes.md +++ /dev/null @@ -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 diff --git a/doc/Makefile b/docs/Makefile similarity index 88% rename from doc/Makefile rename to docs/Makefile index 3f2faaa..69fe55e 100644 --- a/doc/Makefile +++ b/docs/Makefile @@ -4,9 +4,8 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -SPHINXPROJ = abgabesystem -SOURCEDIR = . -BUILDDIR = _build +SOURCEDIR = source +BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: diff --git a/doc/make.bat b/docs/make.bat similarity index 87% rename from doc/make.bat rename to docs/make.bat index 325fa1e..543c6b1 100644 --- a/doc/make.bat +++ b/docs/make.bat @@ -7,9 +7,8 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=abgabesystem +set SOURCEDIR=source +set BUILDDIR=build if "%1" == "" goto help diff --git a/doc/conf.py b/docs/source/conf.py similarity index 86% rename from doc/conf.py rename to docs/source/conf.py index 085ff35..ad507da 100644 --- a/doc/conf.py +++ b/docs/source/conf.py @@ -12,9 +12,9 @@ # 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. # -import os -import sys -sys.path.insert(0, os.path.abspath('../src')) +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- @@ -26,7 +26,7 @@ author = 'Tim Schubert' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags -release = '' +release = '1.0' # -- General configuration --------------------------------------------------- @@ -42,7 +42,10 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', ] # 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 # directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = None # -- 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 ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. 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 \ No newline at end of file diff --git a/doc/index.rst b/docs/source/index.rst similarity index 56% rename from doc/index.rst rename to docs/source/index.rst index 8836464..ff85a24 100644 --- a/doc/index.rst +++ b/docs/source/index.rst @@ -1,5 +1,5 @@ .. 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 contain the root `toctree` directive. @@ -18,18 +18,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - -.. automodule:: abgabesystem - :members: - -.. automodule:: abgabesystem.projects - :members: - -.. automodule:: abgabesystem.students - :members: - -.. automodule:: abgabesystem.commands - :members: - -.. autoclass:: abgabesystem.students.Student - :members: diff --git a/src/abgabesystem/commands.py b/src/abgabesystem/commands.py index df0a746..b5fa625 100644 --- a/src/abgabesystem/commands.py +++ b/src/abgabesystem/commands.py @@ -9,6 +9,10 @@ from gitlab.exceptions import GitlabCreateError, GitlabGetError def enroll_students(gl, args): """Creates Gitlab users from exported students list + + Args: + gl: API + args: command line arguments """ student_group = get_student_group(gl, args.course) @@ -25,6 +29,10 @@ def enroll_students(gl, args): def projects(gl, args): """Creates the projects for all course participants + + Args: + gl: API + args: command line arguments """ course = None for g in gl.groups.list(search=args.course): @@ -39,7 +47,12 @@ def projects(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 try: @@ -63,6 +76,10 @@ def deadline(gl, args): def plagiates(gl, args): """Runs the plagiarism checker (JPlag) for the solutions with a certain tag + + Args: + gl: API + args: command line arguments """ solutions_dir = 'input' @@ -90,6 +107,10 @@ def plagiates(gl, args): def course(gl, args): """Creates the group for the course + + Args: + gl: API + args: command line arguments """ try: gl.groups.create({ diff --git a/src/abgabesystem/course.py b/src/abgabesystem/course.py index 3d3feff..e42493c 100644 --- a/src/abgabesystem/course.py +++ b/src/abgabesystem/course.py @@ -2,10 +2,21 @@ import logging as log class InvalidCourse(Exception): + """Raised if the selected course is invalid. + """ + pass 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)) return gl.groups.create({ "name": name, @@ -23,6 +34,15 @@ def create_solutions_group(gl, parent_group): 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({ "name": course_name, "path": course_name.lower().replace(" ", "_"), diff --git a/src/abgabesystem/projects.py b/src/abgabesystem/projects.py index ab7334f..8826fbb 100644 --- a/src/abgabesystem/projects.py +++ b/src/abgabesystem/projects.py @@ -10,7 +10,12 @@ def create_tag(project, tag, ref): """Creates protected tag on ref 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)) @@ -21,9 +26,17 @@ def create_tag(project, tag, ref): }) - def fork_reference(gl, reference, namespace, deploy_key): """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({ @@ -46,6 +59,14 @@ def fork_reference(gl, reference, namespace, deploy_key): def create_project(gl, group, user, reference, deploy_key): """Creates a namespace (subgroup) and forks the project with 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 @@ -79,6 +100,14 @@ def create_project(gl, group, user, reference, deploy_key): 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({ 'name': 'solutions', 'namespace_id': namespace, @@ -100,7 +129,12 @@ def create_reference_solution(gl, namespace): 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 diff --git a/src/abgabesystem/students.py b/src/abgabesystem/students.py index 3ff440f..4c86889 100644 --- a/src/abgabesystem/students.py +++ b/src/abgabesystem/students.py @@ -5,10 +5,17 @@ from gitlab import GUEST_ACCESS class MissingStudentsGroup(Exception): + """Raised if a the group for the students has not already been created + inside the course. + """ + pass class MissingCourseGroup(Exception): + """Raised if the group for the course is missing. + """ + pass @@ -18,6 +25,12 @@ class Student(): Students are read from the CSV file that was exported from Stud.IP. For each user, a dummy LDAP user is created in Gitlab. 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): @@ -27,7 +40,11 @@ class Student(): self.group = group 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='"') for line in reader: @@ -36,7 +53,12 @@ class Student(): 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): @@ -47,6 +69,10 @@ def get_students_csv(gl, students_csv): def enrolled_students(gl, course): """Returns the students enrolled in the course + + Args: + gl: Gitlab API object + course: course the students are enrolled in """ students = None @@ -66,6 +92,12 @@ def enrolled_students(gl, course): def create_user(gl, student, ldap_base, ldap_provider): """Creates a GitLab user account student. 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({ @@ -84,6 +116,10 @@ def create_user(gl, student, ldap_base, ldap_provider): def get_student_group(gl, course_name): """Gets the `students` subgroup for the course + + Args: + gl: Gitlab API objects + course_name: name of the course """ course = None @@ -108,6 +144,11 @@ def get_student_group(gl, course_name): def enroll_student(gl, user, subgroup): """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({