abgabesystem/abgabesystem.py
Tim Schubert d7a8440e11 Rewrite plagiates and deadlines functions to use fork relationship instead of groups.
This should be more reliable if another group structure is desired later. (e.g. student projects in the user namespace of the each student)
2018-08-15 12:13:01 +02:00

299 lines
9.5 KiB
Python

#!/usr/bin/env python3
import argparse
import gitlab
import logging as log
import csv
import secrets
import subprocess
import os
class Student():
"""A Gitlab user
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.
"""
def __init__(self, user, mail, name, group):
self.user = user
self.email = mail
self.name = name
self.group = group
def from_csv(csvfile):
"""Creates an iterable containing the users"""
reader = csv.DictReader(csvfile, delimiter=';', quotechar='"')
for line in reader:
yield Student(line['Nutzernamen'], line['E-Mail'], line['Vorname']
+ ' ' + line['Nachname'], line['Gruppe'])
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
"""
print('Project %s. Creating tag %s' % (project.name, tag))
project.tags.create({
'tag_name': tag,
'ref': ref
})
def get_students(gl, students_csv):
"""Returns already existing GitLab users for students from provided CSV file that have an account.
"""
for student in Student.from_csv(students_csv):
users = gl.users.list(search=student.user)
if len(users) > 0:
yield users[0]
def create_user(gl, student, ldap_base, ldap_provider):
"""Creates a GitLab user account student.
Requires admin privileges.
"""
user = gl.users.create({
'email': student.email,
'username': student.user,
'name': student.name,
'provider': ldap_provider,
'skip_confirmation': True,
'extern_uid': 'uid=%s,%s' % (student.user, ldap_base),
'password': secrets.token_urlsafe(nbytes=32)
})
user.customattributes.set('group', student.group)
return user
def create_users(gl, args):
with open(args.students, encoding='iso8859') as students_csv:
for student in Student.from_csv(students_csv):
try:
create_user(gl, student, args.ldap_base, args.ldap_provider)
except gitlab.exceptions.GitlabCreateError:
log.warn('Failed to create user: %s' % student.user)
def fork_reference(gl, reference, namespace, deploy_key):
"""Create fork of solutions for student.
"""
fork = reference.forks.create({
'namespace': namespace.id
})
project = gl.projects.get(fork.id)
project.visibility = 'private'
project.container_registry_enabled = False
project.lfs_enabled = False
deploy_key = project.keys.create({
'title': "Deploy Key",
'key': deploy_key
})
project.keys.enable(deploy_key.id)
project.save()
return project
def create_project(gl, group, user, reference, deploy_key):
"""Creates a namespace (subgroup) and forks the project with
the reference solutions into that namespace
"""
subgroup = None
try:
subgroup = gl.groups.create({
'name': user.username,
'path': user.username,
'parent_id': group.id
})
except gitlab.exceptions.GitlabError as e:
subgroups = group.subgroups.list(search=user.username)
if len(subgroups) > 0 and subgroup[0].name == user.username:
subgroup = subgroups[0]
subgroup = gl.groups.get(subgroup.id, lazy=True)
else:
raise(e)
try:
subgroup.members.create({
'user_id': user.id,
'access_level': gitlab.DEVELOPER_ACCESS,
})
except gitlab.exceptions.GitlabError:
log.warning('Failed to add student %s to its own group' % user.username)
try:
fork_reference(gl, reference, subgroup, deploy_key)
except gitlab.exceptions.GitlabCreateError as e:
log.warning(e.error_message)
def setup_course(gl, group, students_csv, deploy_key):
solution = None
reference_project = None
try:
solution = gl.groups.create({
'name': 'solutions',
'path': 'solutions',
'parent_id': group.id,
})
except gitlab.exceptions.GitlabCreateError as e:
log.info('Failed to create solutions group. %s' % e.error_message)
solutions = group.subgroups.list(search='solutions')
if len(solutions) > 0 and solutions[0].name == 'solutions':
solution = gl.groups.get(solutions[0].id, lazy=True)
else:
raise(gitlab.exceptions.GitlabCreateError(error_message='Failed to setup solutions subgroup'))
try:
reference_project = gl.projects.create({
'name': 'solutions',
'namespace_id': solution.id
})
except gitlab.exceptions.GitlabCreateError as e:
log.info('Failed to setup group structure. %s' % e.error_message)
projects = solution.projects.list(search='solutions')
if len(projects) > 0 and projects[0].name == 'solutions':
reference_project = gl.projects.get(projects[0].id)
else:
raise(gitlab.exceptions.GitlabCreateError(error_message='Failed to setup reference solutions'))
if solution is None or reference_project is None:
raise(gitlab.exceptions.GitlabCreateError(error_message='Failed to setup course'))
for user in get_students(gl, students_csv):
create_project(gl, solution, user, reference_project, deploy_key)
def projects(gl, args):
groups = gl.groups.list(search=args.course)
if len(groups) == 0 and groups[0].name == args.course:
log.warn('This group does not exist')
else:
group = groups[0]
with open(args.deploy_key, 'r') as key, open(args.students, encoding='iso8859') as students_csv:
key = key.read()
setup_course(gl, group, students_csv, key)
def deadline(gl, args):
"""Checks deadlines for course and triggers deadline if it is reached"""
deadline_name = args.tag_name
try:
reference = gl.projects.get(args.reference, lazy=True)
for fork in reference.forks.list():
project = gl.projects.get(fork.id, lazy=True)
try:
create_tag(project, deadline_name, 'master')
except gitlab.exceptions.GitlabCreateError as e:
print(e.error_message)
except gitlab.exceptions.GitlabGetError as e:
print(e.error_message)
def plagiates(gl, args):
"""Runs the plagiarism checker (JPlag) for the solutions with a certain tag
"""
tag = args.tag_name
try:
reference = gl.projects.get(args.reference, lazy=True)
try:
os.mkdir('solutions')
except os.FileExistsError as e:
print(e)
os.chdir('solutions')
for fork in reference.forks.list():
project = gl.projects.get(fork.id, lazy=True)
try:
subprocess.run(
['git', 'clone', '--branch', tag, project.ssh_url_to_repo, project.path_with_namespace])
os.chdir('..')
subprocess.run(
['java', '-jar', args.jplag_jar, '-s', 'solutions', '-p', 'java', '-r', 'results', '-bc', args.reference, '-l', 'java17'])
def course(gl, args):
try:
group = gl.groups.create({
'name': args.course,
'path': args.course
})
log.info('Created group %s' % args.course)
except gitlab.exceptions.GitlabCreateError as e:
log.warning('Failed to create group %s. %s' % (args.course, e.error_message))
if __name__ == '__main__':
gl = gitlab.Gitlab.from_config()
gl.auth()
log.info('authenticated')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands')
user_parser = subparsers.add_parser(
'users',
help='Creates users from LDAP')
user_parser.set_defaults(func=create_users)
user_parser.add_argument('-s', '--students', dest='students')
user_parser.add_argument('-b', '--ldap-base', dest='ldap_base')
user_parser.add_argument('-p', '--ldap-provider', dest='ldap_provider')
course_parser = subparsers.add_parser(
'courses',
help='Create course')
course_parser.set_defaults(func=course)
course_parser.add_argument('-c', '--course', dest='course')
projects_parser = subparsers.add_parser(
'projects',
help='Setup projects')
projects_parser.set_defaults(func=projects)
projects_parser.add_argument('-c', '--course', dest='course')
projects_parser.add_argument('-d', '--deploy-key', dest='deploy_key')
projects_parser.add_argument('-s', '--students', dest='students')
deadline_parser = subparsers.add_parser(
'deadline',
description='set tags at deadline')
deadline_parser.set_defaults(func=deadline)
deadline_parser.add_argument('-t', '--tag-name', dest='tag_name')
deadline_parser.add_argument('-r', '--reference', dest='reference')
plagiates_parser = subparsers.add_parser(
'plagiates',
description='set tags at plagiates')
plagiates_parser.set_defaults(func=plagiates)
plagiates_parser.add_argument('-t', '--tag-name', dest='tag_name')
plagiates_parser.add_argument('-r', '--reference', dest='reference')
plagiates_parser.add_argument('-j', '--jplag-jar', dest='jplag_jar')
args = parser.parse_args()
log.basicConfig(filename='example.log', filemode='w', level=log.DEBUG)
if 'func' in args:
args.func(gl, args)
else:
parser.print_help()