- Refactoring
- Use config
This commit is contained in:
parent
45c6330148
commit
3f82036c3d
3 changed files with 206 additions and 189 deletions
183
abgabesystem.py
Normal file
183
abgabesystem.py
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
#!/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 tag(self, project):
|
||||||
|
"""Create protected tag on ref for all deadlines that have been
|
||||||
|
reached"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
project.tags.create({
|
||||||
|
'tag_name': self.tag,
|
||||||
|
'ref': self.ref
|
||||||
|
})
|
||||||
|
except gitlab.exceptions.GitlabHttpError as e:
|
||||||
|
log.warn(e)
|
||||||
|
|
||||||
|
|
||||||
|
class Course(yaml.YAMLObject):
|
||||||
|
"""A course"""
|
||||||
|
|
||||||
|
yaml_tag = 'Course'
|
||||||
|
|
||||||
|
def setup_gitlab_group(self, groups):
|
||||||
|
try:
|
||||||
|
found = groups.list(search=self.name)
|
||||||
|
return found[0]
|
||||||
|
except gitlab.exceptions.GitlabHttpError as e:
|
||||||
|
log.info(e)
|
||||||
|
if e.response_code == 404:
|
||||||
|
gl.groups.create({})
|
||||||
|
path = self.name.replace(' ', '_').lower()
|
||||||
|
group = gl.groups.create({
|
||||||
|
'name': self.name,
|
||||||
|
'path': path
|
||||||
|
})
|
||||||
|
for repo in self.repos:
|
||||||
|
group.repos.create({
|
||||||
|
'name': repo,
|
||||||
|
'namespace_id': group.path,
|
||||||
|
'visibility': 'internal'
|
||||||
|
})
|
||||||
|
return group
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def __init__(self, name, deadlines, repos):
|
||||||
|
self.name = name
|
||||||
|
self.repos = repos
|
||||||
|
self.deadlines = deadlines
|
||||||
|
|
||||||
|
def projects(self, groups):
|
||||||
|
return groups.list(search=self.name)[0].projects.list()
|
||||||
|
|
||||||
|
|
||||||
|
class Student():
|
||||||
|
"""A student"""
|
||||||
|
|
||||||
|
def __init__(self, user, mail, name, group):
|
||||||
|
self.user = user
|
||||||
|
self.email = mail
|
||||||
|
self.name = name
|
||||||
|
self.group = group
|
||||||
|
|
||||||
|
def setup_project(self, base, course):
|
||||||
|
"""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.group.projects.list(name=self.user)
|
||||||
|
if len(fork) == 0:
|
||||||
|
fork = base.forks.create({
|
||||||
|
'name': self.user,
|
||||||
|
'namespace': base.namspace,
|
||||||
|
'visibility': 'private'
|
||||||
|
})
|
||||||
|
|
||||||
|
fork.members.create({
|
||||||
|
'user_id': self.user,
|
||||||
|
'access_level': gitlab.DEVELOPER_ACCESS
|
||||||
|
})
|
||||||
|
|
||||||
|
return fork
|
||||||
|
|
||||||
|
def setup_ldap_dummy(self, users, provider, basedn):
|
||||||
|
# TODO call from creation of student object()
|
||||||
|
|
||||||
|
"""Creates a dummy user for users that do not exist in gitlab
|
||||||
|
but in LDAP and have not logged in yet"""
|
||||||
|
|
||||||
|
users = users.list(search=self.user)
|
||||||
|
|
||||||
|
if len(users) == 0:
|
||||||
|
dummy = users.create({
|
||||||
|
'email': self.email,
|
||||||
|
'username': self.name,
|
||||||
|
'name': self.user,
|
||||||
|
'provider': provider,
|
||||||
|
'skip_confirmation': True,
|
||||||
|
'extern_uid': 'uid=%s,%s' % (self.user, basedn),
|
||||||
|
'password': secrets.token_urlsafe(nbytes=32)
|
||||||
|
})
|
||||||
|
|
||||||
|
return dummy
|
||||||
|
else:
|
||||||
|
return users[0]
|
||||||
|
|
||||||
|
def from_csv(csvfile):
|
||||||
|
reader = csv.DictReader(csvfile, delimiter=';', quotechar='"')
|
||||||
|
|
||||||
|
for line in reader:
|
||||||
|
student = Student(line['Nutzernamen'],
|
||||||
|
line['E-Mail'],
|
||||||
|
line['Vorname'] + ' ' + line['Nachname'],
|
||||||
|
line['Gruppe'])
|
||||||
|
yield student
|
||||||
|
|
||||||
|
|
||||||
|
def create_students(gl, conf, args):
|
||||||
|
with open(args.students[0], 'r') as csvfile:
|
||||||
|
for student in Student.from_csv(csvfile):
|
||||||
|
student.setup_ldap_dummy(gl.users, conf['ldap']['provider'], conf['ldap']['basedn'])
|
||||||
|
student.setup_project('solutions', args['course'][0]) #TODO
|
||||||
|
print(student)
|
||||||
|
|
||||||
|
|
||||||
|
def create_courses(gl, conf, args):
|
||||||
|
for course in conf['courses']:
|
||||||
|
course.setup_gitlab_group(gl.groups)
|
||||||
|
print(course)
|
||||||
|
|
||||||
|
|
||||||
|
def create_deadlines(gl, conf, args):
|
||||||
|
for course in conf['courses']:
|
||||||
|
for deadline in course.deadlines:
|
||||||
|
pass
|
||||||
|
#if deadline.time <= datetime.datetime.now():
|
||||||
|
# deadline has approached
|
||||||
|
# TODO setup cronjob?
|
||||||
|
# deadline.tag(project)
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
course_parser = parser.add_subparsers('courses')
|
||||||
|
course_parser.set_defaults(func=create_courses)
|
||||||
|
|
||||||
|
student_parser = parser.add_subparsers('students')
|
||||||
|
student_parser.add_argument('students', desc='Exported CSV file from Stud.IP', nargs=1, type=str)
|
||||||
|
student_parser.add_argument('course', desc='Course for CSV file', nargs=1, type=str)
|
||||||
|
student_parser.set_defaults(func=create_students)
|
||||||
|
|
||||||
|
deadline_parser = parser.add_subparsers('deadlines')
|
||||||
|
deadline_parser.set_defaults(func=create_deadlines)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
with open(args.config, 'r') as conf:
|
||||||
|
conf = yaml.safe_load(conf)
|
||||||
|
args.func(gl, conf, args)
|
23
config.yml
Normal file
23
config.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
ldap:
|
||||||
|
basedn: "ou=people,dc=tu-bs,dc=de"
|
||||||
|
dummy_user:
|
||||||
|
provider: main
|
||||||
|
skip_confirmation: true
|
||||||
|
|
||||||
|
courses:
|
||||||
|
-
|
||||||
|
--- !Course
|
||||||
|
name: Programmieren 1
|
||||||
|
repos: [solutions, exercises]
|
||||||
|
deadlines:
|
||||||
|
-
|
||||||
|
--- !Deadline
|
||||||
|
tag: Exercise 1
|
||||||
|
time: 2018-04-01t00:00.00
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
-
|
||||||
|
--- !Deadline
|
||||||
|
tag: Exercise 2
|
||||||
|
time: 2018-04-15t15:00.00
|
||||||
|
ref: master
|
189
groupimporter.py
189
groupimporter.py
|
@ -1,189 +0,0 @@
|
||||||
import csv
|
|
||||||
import argparse
|
|
||||||
import gitlab
|
|
||||||
|
|
||||||
from mimetypes import guess_type
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_REPOS = ['solutions', 'exercises']
|
|
||||||
BASEDN = 'ou=people,dc=tu-bs,dc=de'
|
|
||||||
|
|
||||||
gl = gitlab.Gitlab.from_config()
|
|
||||||
gl.auth()
|
|
||||||
|
|
||||||
|
|
||||||
def group_name(longname):
|
|
||||||
"""Use the group number (located at start of group name)"""
|
|
||||||
|
|
||||||
if longname in ['keine Teilnahme an den Übungen', 'keiner Funktion oder Gruppe zugeordnet', 'Gruppe']:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return longname.split(' ')[0]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_groups_csv(csvfile, encoding='utf-8'):
|
|
||||||
"""Reads the names of the groups from CSV file and yields the group names
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(csvfile, 'r', encoding=encoding) as lines:
|
|
||||||
""" Get distinct values from first column (groups)"""
|
|
||||||
|
|
||||||
reader = csv.reader(lines, delimiter=';', quotechar='"')
|
|
||||||
zipped = list(zip(*reader))
|
|
||||||
|
|
||||||
for groupname in set(zipped[0]):
|
|
||||||
short_name = group_name(groupname)
|
|
||||||
|
|
||||||
"""Discard groups that are None"""
|
|
||||||
if short_name:
|
|
||||||
yield(short_name)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_users_csv(csvfile, encoding='utf-8'):
|
|
||||||
"""Reads user information from a CSV file and yields id and group"""
|
|
||||||
|
|
||||||
with open(csvfile, 'r', encoding=encoding) as lines:
|
|
||||||
reader = csv.DictReader(lines, delimiter=';', quotechar='"')
|
|
||||||
|
|
||||||
for line in reader:
|
|
||||||
user = object()
|
|
||||||
user.id = line['Nutzernamen']
|
|
||||||
user.email = line['E-Mail']
|
|
||||||
user.name = line['Vorname'] + ' ' + line['Nachname']
|
|
||||||
user.group = group_name(line['Gruppe'])
|
|
||||||
|
|
||||||
|
|
||||||
def create_student(user_id, email, tutorial):
|
|
||||||
"""Create user and add to tutorial group and workgroup based on preferred Abgabepartner
|
|
||||||
User will later be updated with password from LDAP
|
|
||||||
|
|
||||||
https://gitlab.com/gitlab-org/gitlab-ee/issues/699
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def create_abgabegruppe(tutorial):
|
|
||||||
"""Create a Abgabegruppe"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def create_tutorial(course, group):
|
|
||||||
"""Creates a group via Gitlab API"""
|
|
||||||
|
|
||||||
# this is the short path, not the full path
|
|
||||||
path = group.replace(' ', '_').lower()
|
|
||||||
|
|
||||||
try:
|
|
||||||
subgroup = gl.groups.create({'name': group, 'path': path, 'parent_id': course.id})
|
|
||||||
return subgroup
|
|
||||||
except gitlab.exceptions.GitlabHttpError as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
def create_course(course_name):
|
|
||||||
|
|
||||||
courses = gl.groups.list(search=course_name)
|
|
||||||
course = None
|
|
||||||
|
|
||||||
if len(courses) == 0:
|
|
||||||
path = course_name.replace(' ', '_').lower()
|
|
||||||
course = gl.groups.create({'name': course_name, 'path': path})
|
|
||||||
else:
|
|
||||||
course = courses[0]
|
|
||||||
|
|
||||||
return course
|
|
||||||
|
|
||||||
|
|
||||||
def create_course_project(name, course):
|
|
||||||
|
|
||||||
try:
|
|
||||||
gl.projects.create({
|
|
||||||
'name': name,
|
|
||||||
'namespace_id': course.id,
|
|
||||||
'visibility': 'internal'
|
|
||||||
})
|
|
||||||
except gitlab.exceptions.GitlabHttpError as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_user_project(user, base, course):
|
|
||||||
"""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.projects.list(name=user)
|
|
||||||
if len(fork) == 0:
|
|
||||||
fork = base.forks.create({
|
|
||||||
'name': user.id,
|
|
||||||
'namespace': base.namspace,
|
|
||||||
'visibility': 'private'
|
|
||||||
})
|
|
||||||
|
|
||||||
fork.members.create({
|
|
||||||
'user_id': user.id,
|
|
||||||
'access_level': gitlab.DEVELOPER_ACCESS
|
|
||||||
})
|
|
||||||
|
|
||||||
return fork
|
|
||||||
|
|
||||||
|
|
||||||
def create_dummy_ldap_user(user):
|
|
||||||
"""Creates a dummy user for users that do not exist in gitlab
|
|
||||||
but in LDAP and have not logged in yet"""
|
|
||||||
|
|
||||||
users = gl.users.list(search=user)
|
|
||||||
|
|
||||||
if len(users) == 0:
|
|
||||||
dummy = gl.users.create({
|
|
||||||
'email': user.email,
|
|
||||||
'username': user.name,
|
|
||||||
'name': user.id,
|
|
||||||
'provider': 'main',
|
|
||||||
'skip_confirmation': true,
|
|
||||||
'extern_uid': 'uid=%s,%s' % (user.id, basedn),
|
|
||||||
'password': secrets.token_urlsafe(nbytes=32)
|
|
||||||
})
|
|
||||||
|
|
||||||
return dummy
|
|
||||||
else:
|
|
||||||
return users[0]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Import groups and users from data source')
|
|
||||||
parser.add_argument('course', type=str, nargs=1, help='name of the course')
|
|
||||||
parser.add_argument('source', type=str, nargs=1, help='data source')
|
|
||||||
parser.add_argument('--encoding', type=str, nargs=1, help='encoding of source')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
type, _ = guess_type(args.source[0])
|
|
||||||
course = args.course[0]
|
|
||||||
|
|
||||||
course = create_course(course)
|
|
||||||
|
|
||||||
for repo in DEFAULT_REPOS:
|
|
||||||
course_project = create_course_project(repo, course)
|
|
||||||
|
|
||||||
users = None
|
|
||||||
|
|
||||||
if type == 'text/csv':
|
|
||||||
# TODO get functions from API and add hiwis to admins
|
|
||||||
users = parse_users_csv(args.source[0], args.encoding[0])
|
|
||||||
|
|
||||||
elif type == None:
|
|
||||||
|
|
||||||
print('MIME type not recognized')
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
create_dummy_user_ldap(user)
|
|
||||||
setup_user_project(user, course)
|
|
||||||
|
|
||||||
"""TODO
|
|
||||||
- add users (LDAP) with custom attribute for group, matrikelnummer
|
|
||||||
+ if user exists set custom attributes
|
|
||||||
|
|
||||||
- create search for custom attribute (-> checkout repos for group)
|
|
||||||
"""
|
|
Loading…
Add table
Add a link
Reference in a new issue