
This should be more reliable if another group structure is desired later. (e.g. student projects in the user namespace of the each student)
299 lines
9.5 KiB
Python
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()
|