From ccb160ee3d69b017e44849c0f27c74dde2cad1ac Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 07 2017 10:14:37 +0000 Subject: Optimize generating the gitolite configuration for group change This allows updating just the section about groups or even a single group without re-generating the entire gitolite configuration file. Fixes https://pagure.io/pagure/issue/2399 Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/lib/git.py b/pagure/lib/git.py index 8b2f4e9..8675109 100644 --- a/pagure/lib/git.py +++ b/pagure/lib/git.py @@ -88,7 +88,7 @@ Subject: {subject} return patch -def generate_gitolite_acls(project=None): +def generate_gitolite_acls(project=None, group=None): """ Generate the gitolite configuration file. :arg project: the project of which to update the ACLs. This argument @@ -100,16 +100,19 @@ def generate_gitolite_acls(project=None): If project is a pagure.lib.model.Project, the configuration of this project should be updated. :type project: None, int or pagure.lib.model.Project + :kwarg group: the group to refresh the members of + :type group: None or str """ if project != -1: tasks.generate_gitolite_acls.delay( namespace=project.namespace if project else None, name=project.name if project else None, - user=project.user.user if project and project.is_fork else None + user=project.user.user if project and project.is_fork else None, + group=group ) else: - tasks.generate_gitolite_acls.delay(name=-1) + tasks.generate_gitolite_acls.delay(name=-1, group=group) def update_git(obj, repo, repofolder): diff --git a/pagure/lib/git_auth.py b/pagure/lib/git_auth.py index 6a84b33..aaa3f83 100644 --- a/pagure/lib/git_auth.py +++ b/pagure/lib/git_auth.py @@ -54,7 +54,7 @@ class GitAuthHelper(object): @classmethod @abc.abstractmethod - def generate_acls(self, project): + def generate_acls(self, project, group=None): """ This is the method that is called by pagure to generate the configuration file. @@ -63,10 +63,13 @@ class GitAuthHelper(object): If project is ``-1``, the configuration should be refreshed for *all* projects. If project is ``None``, there no specific project to refresh - but the ssh key of an user was added and updated. + but the ssh key of an user was added and updated or a group + was removed. If project is a pagure.lib.model.Project, the configuration of this project should be updated. :type project: None, int or pagure.lib.model.Project + :kwarg group: the group to refresh the members of + :type group: None or pagure.lib.model.PagureGroup (This behaviour is based on the workflow of gitolite, if you are implementing a different auth backend and need more granularity, @@ -91,7 +94,7 @@ class Gitolite2Auth(GitAuthHelper): """ A gitolite 2 authentication module. """ @classmethod - def _process_project(cls, project, config, groups, global_pr_only): + def _process_project(cls, project, config, global_pr_only): """ Generate the gitolite configuration for the specified project. :arg project: the project to generate the configuration for @@ -105,15 +108,11 @@ class Gitolite2Auth(GitAuthHelper): :arg global_pr_only: boolean on whether the pagure instance enforces the PR workflow only or not :type global_pr_only: bool - :return: a tuple containing the updated config and groups variables - :return type: tuple(list, dict(str: list)) + :return: the updated config + :return type: list """ _log.debug(' Processing project: %s', project.fullname) - for group in project.committer_groups: - if group.group_name not in groups: - groups[group.group_name] = [ - user.username for user in group.users] # Check if the project or the pagure instance enforce the PR only # development model. @@ -162,11 +161,11 @@ class Gitolite2Auth(GitAuthHelper): deploykey.id)) config.append('') - return (groups, config) + return config @classmethod def _clean_current_config( - cls, current_config, project, preconfig=None, postconfig=None): + cls, current_config, project): """ Remove the specified project from the current configuration file :arg current_config: the content of the current/actual gitolite @@ -174,71 +173,145 @@ class Gitolite2Auth(GitAuthHelper): :type current_config: list :arg project: the project to update in the configuration file :type project: pagure.lib.model.Project - :kwarg preconfig: the content of the file to place at the top of - gitolite configuration file read from the disk - :type preconfig: list - :kwarg postconfig: the content of the file to place at the bottom - of gitolite configuration file read from the disk - :type postconfig: list """ keys = [ 'repo %s%s' % (repos, project.fullname) for repos in ['', 'docs/', 'tickets/', 'requests/'] ] - prestart = preend = None - if preconfig: - preconfig = preconfig.strip().split('\n') - prestart = preconfig[0].strip() - preend = preconfig[-1].strip() - poststart = postend = None - if postconfig: - postconfig = postconfig.strip().split('\n') - poststart = postconfig[0].strip() - postend = postconfig[-1].strip() keep = True - header = footer = False config = [] for line in current_config: line = line.rstrip() - if line == prestart: - header = True - continue - - if line == poststart: - footer = True - continue if line in keys: keep = False continue - if line == preend: - header = False - continue - - if line == postend: - footer = False - continue - if keep is False and line == '': keep = True - # Avoid too many empty lines - if line == '' and ( - config and config[-1] == '' - or not config): - continue - - if keep and not header and not footer: + if keep: config.append(line) return config @classmethod + def _clean_groups(cls, config, group=None): + """ Removes the groups in the given configuration file. + + :arg config: the current configuration + :type config: list + :kwarg group: the group to refresh the members of + :type group: None or pagure.lib.model.PagureGroup + :return: the configuration without the groups + :return type: list + + """ + + if group is None: + output = [ + row.rstrip() + for row in config + if not row.startswith('@') + and row.strip() != '# end of groups'] + else: + end_grp = None + seen = False + output = [] + for idx, row in enumerate(config): + if end_grp is None and row.startswith('repo '): + end_grp = idx + + if row.startswith('@%s ' % group.group_name): + seen = True + row = '@%s = %s' % ( + group.group_name, + ' '.join(sorted( + [user.username for user in group.users]) + ) + ) + output.append(row) + + if not seen: + row = '@%s = %s' % ( + group.group_name, + ' '.join(sorted([user.username for user in group.users])) + ) + output.insert(end_grp, '') + output.insert(end_grp, row) + + return output + + @classmethod + def _generate_groups_config(cls, session): + """ Generate the gitolite configuration for all of the groups. + + :arg session: the session with which to connect to the database + :return: the gitolite configuration for the groups + :return type: list + + """ + query = session.query( + model.PagureGroup + ).order_by( + model.PagureGroup.group_name + ) + + groups = {} + for grp in query.all(): + groups[grp.group_name] = [user.username for user in grp.users] + + return groups + + @classmethod + def _get_current_config(cls, configfile, preconfig=None, postconfig=None): + """ Load the current gitolite configuration file from the disk. + + :arg configfile: the name of the configuration file to load + :type configfile: str + :kwarg preconf: the content of the file to include at the top of the + gitolite configuration file, used here to determine that a part of + the configuration file should be cleaned at the top. + :type preconf: None or str + :kwarg postconf: the content of the file to include at the bottom of the + gitolite configuration file, used here to determine that a part of + the configuration file should be cleaned at the bottom. + :type postconf: None or str + + """ + _log.info('Reading in the current configuration: %s', configfile) + with open(configfile) as stream: + current_config = stream.readlines() + + if preconfig: + idx = None + for idx, row in enumerate(current_config): + if row.strip() == '# end of header': + break + if idx is not None: + idx = idx + 1 + _log.info('Removing the first %s lines', idx) + current_config = current_config[idx:] + + if postconfig: + idx = None + for idx, row in enumerate(current_config): + if row.strip() == '# end of body': + break + if idx is not None: + _log.info( + 'Keeping the first %s lines out of %s', + idx, len(current_config)) + current_config = current_config[:idx] + + return current_config + + @classmethod def write_gitolite_acls( - cls, session, configfile, project, preconf=None, postconf=None): + cls, session, configfile, project, preconf=None, postconf=None, + group=None): """ Generate the configuration file for gitolite for all projects on the forge. @@ -251,8 +324,9 @@ class Gitolite2Auth(GitAuthHelper): file. It can be of three types/values. If it is ``-1`` or if the file does not exist on disk, the entire gitolite configuration will be re-generated. - If it is ``None``, the gitolite configuration will not be - changed but will be re-compiled. + If it is ``None``, the gitolite configuration will have its + groups information updated but not the projects and will be + re-compiled. If it is a ``pagure.lib.model.Project``, the gitolite configuration will be updated for just this project. :type project: None, int or spagure.lib.model.Project @@ -262,6 +336,8 @@ class Gitolite2Auth(GitAuthHelper): :kwarg postconf: a file to include at the bottom of the configuration file :type postconf: None or str + :kwarg group: the group to refresh the members of + :type group: None or pagure.lib.model.PagureGroup """ _log.info('Write down the gitolite configuration file') @@ -281,41 +357,63 @@ class Gitolite2Auth(GitAuthHelper): global_pr_only = pagure.APP.config.get('PR_ONLY', False) config = [] groups = {} + if group is None: + groups = cls._generate_groups_config(session) + if project == -1 or not os.path.exists(configfile): _log.info('Refreshing the configuration for all projects') query = session.query(model.Project).order_by(model.Project.id) for project in query.all(): - groups, config = cls._process_project( - project, config, groups, global_pr_only) + config = cls._process_project( + project, config, global_pr_only) elif project: _log.info('Refreshing the configuration for one project') - groups, config = cls._process_project( - project, config, groups, global_pr_only) + config = cls._process_project(project, config, global_pr_only) + + current_config = cls._get_current_config( + configfile, preconfig, postconfig) - _log.info('Reading in the current configuration') - with open(configfile) as stream: - current_config = stream.readlines() current_config = cls._clean_current_config( - current_config, - project, - preconfig, - postconfig) + current_config, project) config = current_config + config + if config: + _log.info('Cleaning the group %s from the loaded config', group) + config = cls._clean_groups(config, group=group) + + else: + current_config = cls._get_current_config( + configfile, preconfig, postconfig) + + _log.info( + 'Cleaning the group %s from the config on disk', group) + config = cls._clean_groups(current_config, group=group) + if not config: return + _log.info('Writing the configuration to: %s', configfile) with open(configfile, 'w') as stream: if preconfig: stream.write(preconfig + '\n') + stream.write('# end of header\n') - for key, users in groups.iteritems(): - stream.write('@%s = %s\n' % (key, ' '.join(users))) - stream.write('\n') + if groups: + for key, users in groups.iteritems(): + stream.write('@%s = %s\n' % (key, ' '.join(users))) + stream.write('# end of groups\n\n') + prev = None for row in config: + if prev is None: + prev = row + if prev == row == '': + continue stream.write(row + '\n') + prev = row + + stream.write('# end of body\n') if postconfig: stream.write(postconfig + '\n') @@ -336,7 +434,7 @@ class Gitolite2Auth(GitAuthHelper): return cmd @classmethod - def generate_acls(cls, project): + def generate_acls(cls, project, group=None): """ Generate the gitolite configuration file for all repos :arg project: the project to update in the gitolite configuration @@ -347,18 +445,21 @@ class Gitolite2Auth(GitAuthHelper): changed but will be re-compiled. If it is a ``pagure.lib.model.Project``, the gitolite configuration will be updated for just this project. - :type project: None, int or spagure.lib.model.Project + :type project: None, int or pagure.lib.model.Project + :kwarg group: the group to refresh the members of + :type group: None or pagure.lib.model.PagureGroup """ _log.info('Refresh gitolite configuration') - if project is not None: + if project is not None or group is not None: cls.write_gitolite_acls( pagure.SESSION, project=project, configfile=pagure.APP.config['GITOLITE_CONFIG'], preconf=pagure.APP.config.get('GITOLITE_PRE_CONFIG') or None, - postconf=pagure.APP.config.get('GITOLITE_POST_CONFIG') or None + postconf=pagure.APP.config.get('GITOLITE_POST_CONFIG') or None, + group=group, ) cmd = cls._get_gitolite_command() @@ -400,15 +501,17 @@ class GitAuthTestHelper(GitAuthHelper): """ Simple test auth module to check the auth customization system. """ @classmethod - def generate_acls(cls, project): + def generate_acls(cls, project, group=None): """ Print a statement when called, useful for debugging, only. :arg project: this variable is just printed out but not used in any real place. :type project: None, int or spagure.lib.model.Project + :kwarg group: the group to refresh the members of + :type group: None or pagure.lib.model.PagureGroup """ out = 'Called GitAuthTestHelper.generate_acls() ' \ - 'with arg %s' % project + 'with args: project=%s, group=%s' % (project, group) print(out) return out diff --git a/pagure/lib/tasks.py b/pagure/lib/tasks.py index fe9e1c3..38cd7f3 100644 --- a/pagure/lib/tasks.py +++ b/pagure/lib/tasks.py @@ -68,16 +68,18 @@ def gc_clean(): @conn.task -def generate_gitolite_acls(namespace=None, name=None, user=None): +def generate_gitolite_acls(namespace=None, name=None, user=None, group=None): """ Generate the gitolite configuration file either entirely or for a specific project. :kwarg namespace: the namespace of the project - :type namespace: str + :type namespace: None or str :kwarg name: the name of the project - :type name: str + :type name: None or str :kwarg user: the user of the project, only set if the project is a fork - :type user: str + :type user: None or str + :kwarg group: the group to refresh the members of + :type group: None or str """ session = pagure.lib.create_session() @@ -90,7 +92,12 @@ def generate_gitolite_acls(namespace=None, name=None, user=None): helper = pagure.lib.git_auth.get_git_auth_helper( APP.config['GITOLITE_BACKEND']) _log.debug('Got helper: %s', helper) - helper.generate_acls(project=project) + + group_obj = pagure.lib.search_groups(session, group_name=group) or None + _log.debug( + 'Calling helper: %s with arg: project=%s, group=%s', + helper, project, group_obj) + helper.generate_acls(project=project, group=group_obj) session.remove() gc_clean() diff --git a/pagure/ui/groups.py b/pagure/ui/groups.py index 40e6515..0691ce4 100644 --- a/pagure/ui/groups.py +++ b/pagure/ui/groups.py @@ -84,7 +84,7 @@ def view_group(group): is_admin=pagure.is_admin(), ) pagure.SESSION.commit() - pagure.lib.git.generate_gitolite_acls(project=-1) + pagure.lib.git.generate_gitolite_acls(project=None, group=group) flask.flash(msg) except pagure.exceptions.PagureException as err: pagure.SESSION.rollback() @@ -195,7 +195,7 @@ def group_user_delete(user, group): is_admin=pagure.is_admin() ) pagure.SESSION.commit() - pagure.lib.git.generate_gitolite_acls(project=-1) + pagure.lib.git.generate_gitolite_acls(project=None, group=group) flask.flash( 'User `%s` removed from the group `%s`' % (user, group)) except pagure.exceptions.PagureException as err: @@ -249,7 +249,7 @@ def group_delete(group): pagure.SESSION.delete(group_obj) pagure.SESSION.commit() - pagure.lib.git.generate_gitolite_acls(project=-1) + pagure.lib.git.generate_gitolite_acls(project=None) flask.flash( 'Group `%s` has been deleted' % (group)) diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py index bf63316..8300527 100644 --- a/tests/test_pagure_lib_git.py +++ b/tests/test_pagure_lib_git.py @@ -81,8 +81,7 @@ class PagureLibGittests(tests.Modeltests): with open(outputconf) as stream: data = stream.read() - exp = """ -repo test + exp = """repo test R = @all RW+ = pingou RW+ = foo @@ -142,6 +141,7 @@ repo tickets/forks/pingou/test3 repo requests/forks/pingou/test3 RW+ = pingou +# end of body """ #print data self.assertEqual(data, exp) @@ -172,7 +172,7 @@ repo requests/forks/pingou/test3 data = stream.read() exp = """# this is a header that is manually added - +# end of header repo test R = @all RW+ = pingou @@ -215,9 +215,10 @@ repo tickets/somenamespace/test3 repo requests/somenamespace/test3 RW+ = pingou +# end of body """ #print data - self.assertEqual(data, exp) + self.assertEqual(data.split('\n'), exp.split('\n')) os.unlink(outputconf) self.assertFalse(os.path.exists(outputconf)) @@ -251,7 +252,7 @@ repo requests/somenamespace/test3 data = stream.read() exp = """# this is a header that is manually added - +# end of header repo test R = @all RW+ = pingou @@ -294,6 +295,7 @@ repo tickets/somenamespace/test3 repo requests/somenamespace/test3 RW+ = pingou +# end of body # end of generated configuration """ #print data @@ -324,8 +326,7 @@ repo requests/somenamespace/test3 with open(outputconf) as stream: data = stream.read() - exp = """ -repo test + exp = """repo test R = @all RW+ = pingou @@ -367,6 +368,7 @@ repo tickets/somenamespace/test3 repo requests/somenamespace/test3 RW+ = pingou +# end of body # end of generated configuration """ #print data @@ -420,8 +422,7 @@ repo requests/somenamespace/test3 with open(outputconf) as stream: data = stream.read() - exp = """ -repo test + exp = """repo test R = @all RW+ = pingou R = deploykey_test_1 @@ -485,6 +486,7 @@ repo tickets/forks/pingou/test3 repo requests/forks/pingou/test3 RW+ = pingou +# end of body """ #print data self.assertEqual(data, exp) @@ -531,8 +533,7 @@ repo requests/forks/pingou/test3 with open(outputconf) as stream: data = stream.read() - exp = """ -repo test + exp = """repo test R = @all RW+ = pingou @@ -588,6 +589,7 @@ repo tickets/forks/pingou/test3 repo requests/forks/pingou/test3 RW+ = pingou +# end of body """ #print data self.assertEqual(data, exp) @@ -634,8 +636,7 @@ repo requests/forks/pingou/test3 with open(outputconf) as stream: data = stream.read() - exp = """ -repo test + exp = """repo test R = @all RW+ = pingou RW+ = foo @@ -695,6 +696,7 @@ repo tickets/forks/pingou/test3 repo requests/forks/pingou/test3 RW+ = pingou +# end of body """ #print data self.assertEqual(data, exp) @@ -786,8 +788,9 @@ repo requests/forks/pingou/test3 with open(outputconf) as stream: data = stream.read() - exp = """@devs = pingou -@sysadmin = pingou + exp = """@devs = pingou +@sysadmin = pingou +# end of groups repo test R = @all @@ -853,6 +856,7 @@ repo tickets/forks/pingou/test2 repo requests/forks/pingou/test2 RW+ = pingou +# end of body """ #print data self.assertEqual(data.split('\n'), exp.split('\n')) @@ -946,7 +950,10 @@ repo requests/forks/pingou/test2 with open(outputconf) as stream: data = stream.read() - exp = """ + exp = """@devs = pingou +@sysadmin = pingou +# end of groups + repo test R = @all RW+ = pingou @@ -1007,6 +1014,7 @@ repo tickets/forks/pingou/test2 repo requests/forks/pingou/test2 RW+ = pingou +# end of body """ #print data self.assertEqual(data.split('\n'), exp.split('\n')) @@ -1100,8 +1108,9 @@ repo requests/forks/pingou/test2 with open(outputconf) as stream: data = stream.read() - exp = """@devs = pingou -@sysadmin = pingou + exp = """@devs = pingou +@sysadmin = pingou +# end of groups repo test R = @all @@ -1167,6 +1176,7 @@ repo tickets/forks/pingou/test2 repo requests/forks/pingou/test2 RW+ = pingou +# end of body """ #print data self.assertEqual(data.split('\n'), exp.split('\n')) @@ -1220,8 +1230,7 @@ repo requests/forks/pingou/test2 with open(outputconf) as stream: data = stream.read() - exp = """ -repo docs/test + exp = """repo docs/test R = @all RW+ = pingou RW+ = foo @@ -1276,6 +1285,7 @@ repo tickets/forks/pingou/test3 repo requests/forks/pingou/test3 RW+ = pingou +# end of body """ #print data self.assertEqual(data, exp) @@ -1325,8 +1335,7 @@ repo requests/forks/pingou/test3 with open(outputconf) as stream: data = stream.read() - exp = """ -repo docs/test + exp = """repo docs/test R = @all RW+ = pingou RW+ = foo @@ -1373,6 +1382,7 @@ repo tickets/forks/pingou/test3 repo requests/forks/pingou/test3 RW+ = pingou +# end of body """ #print data self.assertEqual(data, exp) diff --git a/tests/test_pagure_lib_gitolite_config.py b/tests/test_pagure_lib_gitolite_config.py index 6105790..7219200 100644 --- a/tests/test_pagure_lib_gitolite_config.py +++ b/tests/test_pagure_lib_gitolite_config.py @@ -78,6 +78,8 @@ repo requests/somenamespace/test3 class PagureLibGitoliteConfigtests(tests.Modeltests): """ Tests for pagure.lib.git """ + maxDiff = None + def setUp(self): """ Set up the environnment, ran before every tests. """ super(PagureLibGitoliteConfigtests, self).setUp() @@ -123,16 +125,17 @@ class PagureLibGitoliteConfigtests(tests.Modeltests): self.assertTrue(os.path.exists(self.outputconf)) with open(self.outputconf) as stream: - data = stream.read() + data = stream.read().decode('utf-8') - exp = """# this is a header that is manually added + exp = u"""# this is a header that is manually added @group1 = foo bar baz @group2 = threebean puiterwijk kevin pingou - +# end of header %s +# end of body # end of generated configuration # \ó/ # end of footer @@ -180,16 +183,17 @@ class PagureLibGitoliteConfigtests(tests.Modeltests): self.assertTrue(os.path.exists(self.outputconf)) with open(self.outputconf) as stream: - data = stream.read() + data = stream.read().decode('utf-8') - exp = """# this is a header that is manually added + exp = u"""# this is a header that is manually added @group1 = foo bar baz @group2 = threebean puiterwijk kevin pingou - +# end of header %s +# end of body # end of generated configuration # \ó/ # end of footer @@ -219,14 +223,14 @@ class PagureLibGitoliteConfigtests(tests.Modeltests): self.assertTrue(os.path.exists(self.outputconf)) with open(self.outputconf) as stream: - data = stream.read() + data = stream.read().decode('utf-8') - exp = """# this is a header that is manually added + exp = u"""# this is a header that is manually added @group1 = foo bar baz @group2 = threebean puiterwijk kevin pingou - +# end of header repo test R = @all RW+ = pingou @@ -241,6 +245,7 @@ repo tickets/test repo requests/test RW+ = pingou +# end of body # end of generated configuration # \ó/ # end of footer @@ -285,14 +290,14 @@ repo requests/test self.assertTrue(os.path.exists(self.outputconf)) with open(self.outputconf) as stream: - data = stream.read() + data = stream.read().decode('utf-8') - exp = """# this is a header that is manually added + exp = u"""# this is a header that is manually added @group1 = foo bar baz @group2 = threebean puiterwijk kevin pingou - +# end of header repo test2 R = @all RW+ = pingou @@ -339,6 +344,326 @@ repo requests/test RW+ = foo RW+ = pingou +# end of body +# end of generated configuration +# \ó/ +# end of footer + +""" + #print data + self.assertEqual(data, exp) + + +class PagureLibGitoliteGroupConfigtests(tests.Modeltests): + """ Tests for generating the gitolite configuration file for a group + change + + """ + + maxDiff = None + + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureLibGitoliteGroupConfigtests, self).setUp() + + pagure.lib.git.SESSION = self.session + tests.create_projects(self.session) + + pagure.lib.add_group( + self.session, + group_name='grp', + display_name='grp group', + description=None, + group_type='user', + user='pingou', + is_admin=False, + blacklist=[], + ) + pagure.lib.add_group( + self.session, + group_name='grp2', + display_name='grp2 group', + description=None, + group_type='user', + user='foo', + is_admin=False, + blacklist=[], + ) + self.session.commit() + + self.outputconf = os.path.join(self.path, 'test_gitolite.conf') + + self.preconf = os.path.join(self.path, 'header_gitolite') + with open(self.preconf, 'w') as stream: + stream.write('# this is a header that is manually added\n') + stream.write('\n') + stream.write('@group1 = foo bar baz\n') + stream.write('@group2 = threebean puiterwijk kevin pingou\n') + + self.postconf = os.path.join(self.path, 'footer_gitolite') + with open(self.postconf, 'w') as stream: + stream.write('# end of generated configuration\n') + stream.write('# \ó/\n') + stream.write('# end of footer\n') + + def tearDown(self): + """ Tearn down the environnment, ran before every tests. """ + super(PagureLibGitoliteGroupConfigtests, self).tearDown() + + if os.path.exists(self.outputconf): + os.unlink(self.outputconf) + self.assertFalse(os.path.exists(self.outputconf)) + + def test_write_gitolite_project_test_group(self): + """ Test the write_gitolite_acls when updating a single group. """ + + with open(self.outputconf, 'w') as stream: + pass + + project = pagure.lib._get_project(self.session, 'test') + group = pagure.lib.search_groups(self.session, group_name='grp') + + helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') + helper.write_gitolite_acls( + self.session, + self.outputconf, + project=project, + preconf=self.preconf, + postconf=self.postconf, + group=group, + ) + self.assertTrue(os.path.exists(self.outputconf)) + + with open(self.outputconf) as stream: + data = stream.read().decode('utf-8') + + exp = u"""# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + +# end of header +@grp = pingou + +repo test + R = @all + RW+ = pingou + +repo docs/test + R = @all + RW+ = pingou + +repo tickets/test + RW+ = pingou + +repo requests/test + RW+ = pingou + +# end of body +# end of generated configuration +# \ó/ +# end of footer + +""" + #print data + self.assertEqual(data, exp) + + def test_write_gitolite_project_test_all_groups(self): + """ Test the write_gitolite_acls when updating all groups. """ + + with open(self.outputconf, 'w') as stream: + pass + + project = pagure.lib._get_project(self.session, 'test') + + helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') + helper.write_gitolite_acls( + self.session, + self.outputconf, + project=project, + preconf=self.preconf, + postconf=self.postconf, + ) + self.assertTrue(os.path.exists(self.outputconf)) + + with open(self.outputconf) as stream: + data = stream.read().decode('utf-8') + + exp = u"""# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + +# end of header +@grp2 = foo +@grp = pingou +# end of groups + +repo test + R = @all + RW+ = pingou + +repo docs/test + R = @all + RW+ = pingou + +repo tickets/test + RW+ = pingou + +repo requests/test + RW+ = pingou + +# end of body +# end of generated configuration +# \ó/ +# end of footer + +""" + #print data + self.assertEqual(data, exp) + + def test_write_gitolite_project_all_projects_groups(self): + """ Test the generating the entire gitolite config. """ + + with open(self.outputconf, 'w') as stream: + pass + + helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') + helper.write_gitolite_acls( + self.session, + self.outputconf, + project=-1, + preconf=self.preconf, + postconf=self.postconf, + ) + self.assertTrue(os.path.exists(self.outputconf)) + + with open(self.outputconf) as stream: + data = stream.read().decode('utf-8') + + exp = u"""# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + +# end of header +@grp2 = foo +@grp = pingou +# end of groups + +%s + +# end of body +# end of generated configuration +# \ó/ +# end of footer + +""" % CORE_CONFIG + #print data + self.assertEqual(data, exp) + + def test_write_gitolite_project_all_projects_one_group(self): + """ Test the generating the entire gitolite config. """ + + # Generate the full gitolite config that we will update + self.test_write_gitolite_project_all_projects_groups() + + project = pagure.lib._get_project(self.session, 'test') + group = pagure.lib.search_groups(self.session, group_name='grp') + + # Let's add `foo` to `grp` so something changes + msg = pagure.lib.add_user_to_group( + self.session, + username='foo', + group=group, + user='pingou', + is_admin=False, + ) + self.session.commit() + self.assertEqual(msg, 'User `foo` added to the group `grp`.') + + # Let's add `foo` to `test` so the project changes as well + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='foo', + user='pingou', + access='commit' + ) + self.assertEqual(msg, 'User added') + self.session.commit() + + helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') + helper.write_gitolite_acls( + self.session, + self.outputconf, + project=project, + group=group, + preconf=self.preconf, + postconf=self.postconf, + ) + self.assertTrue(os.path.exists(self.outputconf)) + + with open(self.outputconf) as stream: + data = stream.read().decode('utf-8') + + exp = u"""# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + +# end of header +@grp2 = foo +@grp = foo pingou +# end of groups + +repo test2 + R = @all + RW+ = pingou + +repo docs/test2 + R = @all + RW+ = pingou + +repo tickets/test2 + RW+ = pingou + +repo requests/test2 + RW+ = pingou + +repo somenamespace/test3 + R = @all + RW+ = pingou + +repo docs/somenamespace/test3 + R = @all + RW+ = pingou + +repo tickets/somenamespace/test3 + RW+ = pingou + +repo requests/somenamespace/test3 + RW+ = pingou + +repo test + R = @all + RW+ = pingou + RW+ = foo + +repo docs/test + R = @all + RW+ = pingou + RW+ = foo + +repo tickets/test + RW+ = pingou + RW+ = foo + +repo requests/test + RW+ = pingou + RW+ = foo + +# end of body # end of generated configuration # \ó/ # end of footer @@ -347,6 +672,54 @@ repo requests/test #print data self.assertEqual(data, exp) + def test_write_gitolite_delete_group(self): + """ Test the updating the gitolite config after having + deleted a group. + """ + + # Generate the full gitolite config that we will update + self.test_write_gitolite_project_all_projects_groups() + + # Delete the group `grp` + self.assertEqual(len(pagure.lib.search_groups(self.session)), 2) + group = pagure.lib.search_groups(self.session, group_name='grp') + self.session.delete(group) + self.session.commit() + self.assertEqual(len(pagure.lib.search_groups(self.session)), 1) + + helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') + helper.write_gitolite_acls( + self.session, + self.outputconf, + project=None, + preconf=self.preconf, + postconf=self.postconf, + ) + self.assertTrue(os.path.exists(self.outputconf)) + + with open(self.outputconf) as stream: + data = stream.read().decode('utf-8') + + exp = u"""# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + +# end of header +@grp2 = foo +# end of groups + +%s + +# end of body +# end of generated configuration +# \ó/ +# end of footer + +""" % CORE_CONFIG + #print data + self.assertEqual(data, exp) + if __name__ == '__main__': unittest.main(verbosity=2)