#!/usr/bin/env python3 import argparse import yaml import gitlab import datetime import logging as log import csv import secrets class Deadline(yaml.YAMLObject): """A deadline""" yaml_tag = 'Deadline' def __init__(self, tag, time, ref): self.tag = tag self.time = time self.ref = ref def trigger(self, project): """Create protected tag on ref""" try: project.tags.create({ 'tag_name': self.tag, 'ref': self.ref }) except gitlab.exceptions.GitlabHttpError as e: log.warn(e) def test(self): return self.time <= datetime.datetime.now() class Course(yaml.YAMLObject): """A course""" yaml_tag = 'Course' def __init__(self, name, base, ldap, deadlines): self.name = name self.base = base self.ldap = ldap self.deadlines = deadlines def create_group(self, gl): path = self.name.replace(' ', '_').lower() self.group = gl.groups.create({ 'name': self.name, 'path': path }) return self.group 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 create_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""" return gl.users.create({ 'email': self.email, 'username': self.name, 'name': self.user, 'provider': ldap['provider'], 'skip_confirmation': True, 'extern_uid': 'uid=%s,%s' % (self.user, ldap['basedn']), 'password': secrets.token_urlsafe(nbytes=32) }) def fork_project(gl, group, base, user): """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""" # fork course base project (e.g. solutions) fork = base.forks.create({ 'name': user['name'], 'namespace': group.namspace, 'visibility': 'private' }) # add student as member of project fork.members.create({ 'user_id': user, 'access_level': gitlab.DEVELOPER_ACCESS }) return fork def create_project(group, name): return group.projects.create({ 'name': name, 'namespace_id': group.path, 'visibility': 'internal' }) def deadlines(gl, course, args): """Checks deadlines for course and triggers deadline if it is reached""" for deadline in course.deadlines: if deadline.test(): deadline.trigger(course) def sync(gl, courses, args): """Sync groups and students from Stud.IP to Gitlab and create student projects one-way sync!!! """ for course in courses: course.create_group(gl) create_project(course.group) with open(args.students[0]) as csvfile: for student in Student.from_csv(csvfile): student.create_user(gl, course.ldap) def parseconf(conf): """Reads courses from config file""" with open(args.config, 'r') as conf: return yaml.safe_load(conf) if __name__ == '__main__': gl = gitlab.Gitlab.from_config() gl.auth() 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', description='students and courses from Stud.IP and LDAP') sync_parser.set_defaults(func=sync) sync_parser.add_argument('--students', nargs=1, description='Students CSV file') deadline_parser = subparsers.add_parser('deadlines', description='trigger deadlines') deadline_parser.set_defaults(func=deadlines) args = parser.parse_args() conf = parseconf(args.conf) args.func(gl, conf, args)