abgabesystem/abgabesystem.py
2018-05-17 16:39:01 +02:00

304 lines
9.2 KiB
Python

#!/usr/bin/env python3
import argparse
import yaml
import gitlab
import logging as log
import csv
import secrets
import subprocess
import os
class Course(yaml.YAMLObject):
"""A course"""
yaml_tag = 'Course'
def __init__(self, name, base, plagiates, deadlines, studentsfile):
self.name = name
self.base = base
self.plagiates = plagiates
self.deadlines = deadlines
self.students = studentsfile
def sync_group(self, gl):
found = gl.groups.list(search=self.name)
print(found)
if len(found) > 0:
for g in found:
if g.name == self.name:
log.info('Found existing group %s' % found[0].name)
return g
path = self.name.replace(' ', '_').lower()
log.info('%s: Creating group' % self.name)
group = gl.groups.create({
'name': self.name,
'path': path,
'visibility': 'internal'
})
return group
def sync_base(self, gl):
found = self.group.projects.list(search=self.base)
if len(found) == 0:
self.base = gl.projects.create({
'name': self.base,
'namespace_id': self.group.id,
'visibility': 'internal'
})
log.info('%s: Created project base repo' % self.name)
data = {
'branch': 'master',
'commit_message': 'Initial commit',
'actions': [
{
'action': 'create',
'file_path': 'README.md',
'content': 'README'
}
]
}
self.base.commits.create(data)
def sync_projects(self, gl):
self.sync_base(gl)
class Student():
"""A student"""
def __init__(self, user, mail, name, group):
self.user = user
self.email = mail
self.name = name
self.group = group
def from_csv(csvfile):
reader = csv.DictReader(csvfile, delimiter=';', quotechar='"')
for line in reader:
yield Student(line['Nutzernamen'], line['E-Mail'], line['Vorname']
+ ' ' + line['Nachname'], line['Gruppe'])
def sync_user(self, gl, ldap):
"""Creates a dummy user for users that do not exist in gitlab
but in LDAP and have not logged in yet"""
found = gl.users.list(search=self.user)
user = None
if len(found) > 0:
user = found[0]
else:
log.info('Creating student %s' % self.user)
user = gl.users.create({
'email': self.email,
'username': self.user,
'name': self.name,
'provider': ldap['provider'],
'skip_confirmation': True,
'extern_uid': 'uid=%s,%s' % (self.user, ldap['basedn']),
'password': secrets.token_urlsafe(nbytes=32)
})
# TODO create groups for abgabegruppen
# group is stored in custom attribute
# https://docs.gitlab.com/ee/api/custom_attributes.html
user.customattributes.set('group', self.group)
return user
def sync_project(gl, course, student):
"""Create user projects as forks from course/solutions in namespace of
course and add user as developer (NOT master) user should not be able
to modify protected TAG or force-push on protected branch users can
later invite other users into their projects"""
# tmp TODO
#for project in student.user.projects.list():
# gl.projects.delete(project.id)
projects = course.group.projects.list(search=student.user.username)
project = None
if len(projects) == 0:
base = course.group.projects.list(search=course.base)[0]
base = gl.projects.get(base.id)
log.info('Creating project %s' % student.user.username)
fork = base.forks.create({
'namespace': student.user.username,
'name': student.user.username
})
project = gl.projects.get(fork.id)
project.path = student.user.username
project.name = student.user.username
project.visibility = 'private'
project.save()
course.group.transfer_project(to_project_id=fork.id)
else:
project = gl.projects.get(id=projects[0].id)
try:
student_member = project.members.get(student.user.id)
student_member.access_level = gitlab.DEVELOPER_ACCESS
student_member.save()
except gitlab.exceptions.GitlabGetError as e:
student_member = project.members.create({'user_id': student.user.id, 'access_level':
gitlab.DEVELOPER_ACCESS})
deploy_key = None
for k in gl.deploykeys.list():
if k.key == course.deploy_key:
deploy_key = k
if deploy_key is None:
print('Missing deploy key. Add global deploy key and sync again')
else:
project.keys.enable(deploy_key.id)
project.container_registry_enabled = False
project.lfs_enabled = False
project.save()
def create_tag(project, tag, ref):
"""Create protected tag on ref"""
print('Project %s. Creating tag %s' % (project.name, tag))
project.tags.create({
'tag_name': tag,
'ref': ref
})
def sync(gl, conf, args):
"""Sync groups and students from Stud.IP to Gitlab and create student
projects
one-way sync!!!
"""
course = conf['course']
print(course.name)
course.group = course.sync_group(gl)
course.sync_base(gl)
with open(course.students, encoding='latin1') as csvfile:
for student in Student.from_csv(csvfile):
try:
student.user = student.sync_user(gl, conf['ldap'])
print("%s %s" % (student.user.username, student.user.name))
sync_project(gl, course, student)
except gitlab.exceptions.GitlabCreateError as e:
log.warn(e)
def list_projects(gl, conf, args):
groups = gl.groups.list(search=conf['course'].name)
print(groups)
if len(groups) == 0:
pass
for g in groups:
if (g.name == args.course):
for project in g.projects.list(all=True):
project = gl.projects.get(project.id)
print(project.ssh_url_to_repo)
def get_base_project(gl, conf, args):
return conf['course']['base']
def deadline(gl, conf, args):
"""Checks deadlines for course and triggers deadline if it is reached"""
deadline_name = args.tag_name
course = conf['course']
group = gl.groups.list(search=course.name)[0]
course.group = gl.groups.get(group.id)
for project in course.group.projects.list(all=True):
project = gl.projects.get(project.id)
print(project.name)
try:
create_tag(project, deadline_name, 'master')
except gitlab.exceptions.GitlabCreateError as e:
print(e)
def plagiates(gl, conf, args):
groups = gl.groups.list(search=conf['course'].name)
tag = args.tag_name
print(groups)
if len(groups) == 0:
pass
for g in groups:
if g.name == conf['course'].name:
try:
os.mkdir('repos')
except os.FileExistsError as e:
print(e)
os.chdir('repos')
for project in g.projects.list(all=True):
project = gl.projects.get(project.id)
try:
subprocess.run(
['git', 'clone', '--branch', tag, project.ssh_url_to_repo])
except subprocess.CalledProcessError as e:
print(e)
os.chdir('..')
subprocess.run(
['java', '-jar', '/app/jplag.jar', '-s', 'repos', '-p', 'java', '-r', 'results', '-bc', conf['course'].base, '-l', 'java17'])
def parseconf(conf):
"""Reads course from config file"""
with open(args.config[0], 'r') as conf:
return yaml.load(conf)
if __name__ == '__main__':
gl = gitlab.Gitlab.from_config()
gl.auth()
log.info('authenticated')
parser = argparse.ArgumentParser()
parser.add_argument(
'--config', type=str, nargs=1, help='path to config file',
default=['config.yml'])
subparsers = parser.add_subparsers(title='subcommands')
sync_parser = subparsers.add_parser(
'sync',
help='students and courses from Stud.IP and LDAP')
sync_parser.set_defaults(func=sync)
projects_parser = subparsers.add_parser(
'projects',
description='list projects for course')
projects_parser.set_defaults(func=list_projects)
projects_parser.add_argument('course')
deadline_parser = subparsers.add_parser(
'deadline',
description='set tags at deadline')
deadline_parser.set_defaults(func=deadline)
deadline_parser.add_argument('tag_name')
plagiates_parser = subparsers.add_parser(
'plagiates',
description='set tags at plagiates')
plagiates_parser.set_defaults(func=plagiates)
plagiates_parser.add_argument('tag_name')
args = parser.parse_args()
conf = parseconf(args.config)
log.basicConfig(filename='example.log', filemode='w', level=log.DEBUG)
if 'func' in args:
args.func(gl, conf, args)
else:
parser.print_help()