diff --git a/pagure/__init__.py b/pagure/__init__.py index 5d9d7f5..f07f7bf 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -401,9 +401,10 @@ def generate_user_key_files(): if gitolite_home: users = pagure.lib.search_user(SESSION) for user in users: - pagure.lib.update_user_ssh(SESSION, user, user.public_ssh_key, - APP.config.get('GITOLITE_KEYDIR', None)) - pagure.lib.git.generate_gitolite_acls() + pagure.lib.update_user_ssh( + SESSION, user, user.public_ssh_key, + APP.config.get('GITOLITE_KEYDIR', None)) + pagure.lib.git.generate_gitolite_acls(project=None) def login_required(function): diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 307119a..3821f39 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2733,7 +2733,7 @@ def update_user_ssh(session, user, ssh_key, keydir): user.public_ssh_key = ssh_key if keydir and user.public_ssh_key: create_user_ssh_keys_on_disk(user, keydir) - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=None) session.add(user) session.flush() diff --git a/pagure/lib/git.py b/pagure/lib/git.py index 8ab5afc..8b2f4e9 100644 --- a/pagure/lib/git.py +++ b/pagure/lib/git.py @@ -88,8 +88,28 @@ Subject: {subject} return patch -def generate_gitolite_acls(): - tasks.generate_gitolite_acls.delay() +def generate_gitolite_acls(project=None): + """ Generate the gitolite configuration file. + + :arg project: the project of which to update the ACLs. This argument + can take three values: ``-1``, ``None`` and a project. + 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. + 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 + + """ + 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 + ) + else: + tasks.generate_gitolite_acls.delay(name=-1) def update_git(obj, repo, repofolder): diff --git a/pagure/lib/git_auth.py b/pagure/lib/git_auth.py index 1924962..c2c46b1 100644 --- a/pagure/lib/git_auth.py +++ b/pagure/lib/git_auth.py @@ -53,9 +53,24 @@ class GitAuthHelper(object): @classmethod @abc.abstractmethod - def generate_acls(self): + def generate_acls(self, project): """ This is the method that is called by pagure to generate the configuration file. + + :arg project: the project of which to update the ACLs. This argument + can take three values: ``-1``, ``None`` and a project. + 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. + 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 + + (This behaviour is based on the workflow of gitolite, if you are + implementing a different auth backend and need more granularity, + feel free to let us know.) + """ pass @@ -75,9 +90,155 @@ class Gitolite2Auth(GitAuthHelper): """ A gitolite 2 authentication module. """ @classmethod + def _process_project(cls, project, config, groups, global_pr_only): + """ Generate the gitolite configuration for the specified project. + + :arg project: the project to generate the configuration for + :type project: pagure.lib.model.Project + :arg config: a list containing the different lines of the + configuration file + :type config: list + :arg groups: a dictionary containing the group name as key and the + users member of the group as values + :type groups: dict(str: list) + :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)) + + """ + _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. + pr_only = project.settings.get('pull_request_access_only', False) + + for repos in ['repos', 'docs/', 'tickets/', 'requests/']: + if repos == 'repos': + # Do not grant access to project enforcing the PR model + if pr_only or (global_pr_only and not project.is_fork): + continue + repos = '' + + config.append('repo %s%s' % (repos, project.fullname)) + if repos not in ['tickets/', 'requests/']: + config.append(' R = @all') + if project.committer_groups: + config.append(' RW+ = @%s' % ' @'.join( + [ + group.group_name + for group in project.committer_groups + ] + )) + config.append(' RW+ = %s' % project.user.user) + for user in project.committers: + # This should never be the case (that the project.user + # is in the committers) but better safe than sorry + if user.user != project.user.user: + config.append(' RW+ = %s' % user.user) + for deploykey in project.deploykeys: + access = 'R' + if deploykey.pushaccess: + access = 'RW+' + # Note: the replace of / with _ is because gitolite + # users can't contain a /. At first, this might look + # like deploy keys in a project called + # $namespace_$project would give access to the repos of + # a project $namespace/$project or vica versa, however + # this is NOT the case because we add the deploykey.id + # to the end of the deploykey name, which means it is + # unique. The project name is solely there to make it + # easier to determine what project created the deploykey + # for admins. + config.append(' %s = deploykey_%s_%s' % + (access, + werkzeug.secure_filename(project.fullname), + deploykey.id)) + config.append('') + + return (groups, config) + + @classmethod + def _clean_current_config( + cls, current_config, project, preconfig=None, postconfig=None): + """ Remove the specified project from the current configuration file + + :arg current_config: the content of the current/actual gitolite + configuration file read from the disk + :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: + config.append(line) + + return config + + @classmethod def write_gitolite_acls( - cls, session, configfile, preconf=None, postconf=None): - ''' Generate the configuration file for gitolite for all projects + cls, session, configfile, project, preconf=None, postconf=None): + """ Generate the configuration file for gitolite for all projects on the forge. :arg cls: the current class @@ -85,6 +246,15 @@ class Gitolite2Auth(GitAuthHelper): :arg session: a session to connect to the database with :arg configfile: the name of the configuration file to generate/write :type configfile: str + :arg project: the project to update in the gitolite configuration + 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 a ``pagure.lib.model.Project``, the gitolite + configuration will be updated for just this project. + :type project: None, int or spagure.lib.model.Project :kwarg preconf: a file to include at the top of the configuration file :type preconf: None or str @@ -92,7 +262,7 @@ class Gitolite2Auth(GitAuthHelper): configuration file :type postconf: None or str - ''' + """ _log.info('Write down the gitolite configuration file') preconfig = None @@ -110,64 +280,30 @@ class Gitolite2Auth(GitAuthHelper): global_pr_only = pagure.APP.config.get('PR_ONLY', False) config = [] groups = {} - query = session.query( - model.Project - ).order_by( - model.Project.id - ) - for project in query.all(): - _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. - pr_only = project.settings.get('pull_request_access_only', False) - - for repos in ['repos', 'docs/', 'tickets/', 'requests/']: - if repos == 'repos': - # Do not grant access to project enforcing the PR model - if pr_only or (global_pr_only and not project.is_fork): - continue - repos = '' - - config.append('repo %s%s' % (repos, project.fullname)) - if repos not in ['tickets/', 'requests/']: - config.append(' R = @all') - if project.committer_groups: - config.append(' RW+ = @%s' % ' @'.join( - [ - group.group_name - for group in project.committer_groups - ] - )) - config.append(' RW+ = %s' % project.user.user) - for user in project.committers: - # This should never be the case (that the project.user - # is in the committers) but better safe than sorry - if user.user != project.user.user: - config.append(' RW+ = %s' % user.user) - for deploykey in project.deploykeys: - access = 'R' - if deploykey.pushaccess: - access = 'RW+' - # Note: the replace of / with _ is because gitolite - # users can't contain a /. At first, this might look - # like deploy keys in a project called - # $namespace_$project would give access to the repos of - # a project $namespace/$project or vica versa, however - # this is NOT the case because we add the deploykey.id - # to the end of the deploykey name, which means it is - # unique. The project name is solely there to make it - # easier to determine what project created the deploykey - # for admins. - config.append(' %s = deploykey_%s_%s' % - (access, - werkzeug.secure_filename(project.fullname), - deploykey.id)) - config.append('') + 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) + elif project: + _log.info('Refreshing the configuration for one project') + groups, config = cls._process_project( + project, config, groups, global_pr_only) + + _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) + + config = current_config + config + + if not config: + return with open(configfile, 'w') as stream: if preconfig: @@ -199,16 +335,30 @@ class Gitolite2Auth(GitAuthHelper): return cmd @classmethod - def generate_acls(cls): + def generate_acls(cls, project): """ Generate the gitolite configuration file for all repos + + :arg project: the project to update in the gitolite configuration + 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 a ``pagure.lib.model.Project``, the gitolite + configuration will be updated for just this project. + :type project: None, int or spagure.lib.model.Project + """ _log.info('Refresh gitolite configuration') - cls.write_gitolite_acls( - pagure.SESSION, - 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 - ) + + if project 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 + ) cmd = cls._get_gitolite_command() if cmd: @@ -249,8 +399,15 @@ class GitAuthTestHelper(GitAuthHelper): """ Simple test auth module to check the auth customization system. """ @classmethod - def generate_acls(cls): - """ Print a statement when called, useful for debugging, only. """ - out = 'Called GitAuthTestHelper.generate_acls()' + def generate_acls(cls, project): + """ 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 + + """ + out = 'Called GitAuthTestHelper.generate_acls() ' \ + 'with arg %s' % project print(out) return out diff --git a/pagure/lib/tasks.py b/pagure/lib/tasks.py index 2f7d314..a63b3e0 100644 --- a/pagure/lib/tasks.py +++ b/pagure/lib/tasks.py @@ -45,6 +45,13 @@ conn.conf.update(APP.config['CELERY_CONFIG']) def get_result(uuid): + """ Returns the AsyncResult object for a given task. + + :arg uuid: the unique identifier of the task to retrieve. + :type uuid: str + :return: celery.result.AsyncResult + + """ return AsyncResult(uuid, conn.backend) @@ -55,21 +62,57 @@ def ret(endpoint, **kwargs): def gc_clean(): + """ Force a run of the garbage collector. """ # https://pagure.io/pagure/issue/2302 gc.collect() @conn.task -def generate_gitolite_acls(): +def generate_gitolite_acls(namespace=None, name=None, user=None): + """ Generate the gitolite configuration file either entirely or for a + specific project. + + :kwarg namespace: the namespace of the project + :type namespace: str + :kwarg name: the name of the project + :type name: str + :kwarg user: the user of the project, only set if the project is a fork + :type user: str + + """ + session = pagure.lib.create_session() + project = None + if name and name != -1: + project = pagure.lib._get_project( + session, namespace=namespace, name=name, user=user) + elif name == -1: + project = name helper = pagure.lib.git_auth.get_git_auth_helper( APP.config['GITOLITE_BACKEND']) - helper.generate_acls() + helper.generate_acls(project=project) + session.remove() gc_clean() @conn.task def create_project(username, namespace, name, add_readme, ignore_existing_repo): + """ Create a project. + + :kwarg username: the user creating the project + :type user: str + :kwarg namespace: the namespace of the project + :type namespace: str + :kwarg name: the name of the project + :type name: str + :kwarg add_readme: a boolean specifying if the project should be + created with a README file or not + :type add_readme: bool + :kwarg ignore_existing_repo: a boolean specifying whether the creation + of the project should fail if the repo exists on disk or not + :type ignore_existing_repo: bool + + """ session = pagure.lib.create_session() project = pagure.lib._get_project(session, namespace=namespace, @@ -157,10 +200,14 @@ def create_project(username, namespace, name, add_readme, plugin.install(project, dbobj) session.commit() + generate_gitolite_acls.delay( + namespace=project.namespace, + name=project.name, + user=project.user.user if project.is_fork else None) + session.remove() gc_clean() - generate_gitolite_acls.delay() return ret('view_repo', repo=name, namespace=namespace) @@ -248,12 +295,30 @@ def delete_branch(name, namespace, user, branchname): @conn.task def fork(name, namespace, user_owner, user_forker, editbranch, editfile): + """ Forks the specified project for the specified user. + + :arg namespace: the namespace of the project + :type namespace: str + :arg name: the name of the project + :type name: str + :arg user_owner: the user of which the project is forked, only set + if the project is already a fork + :type user_owner: str + :arg user_forker: the user forking the project + :type user_forker: str + :kwarg editbranch: the name of the branch in which the user asked to + edit a file + :type editbranch: str + :kwarg editfile: the file the user asked to edit + :type editfile: str + + """ session = pagure.lib.create_session() - repo_from = pagure.lib._get_project(session, namespace=namespace, - name=name, user=user_owner) - repo_to = pagure.lib._get_project(session, namespace=namespace, name=name, - user=user_forker) + repo_from = pagure.lib._get_project( + session, namespace=namespace, name=name, user=user_owner) + repo_to = pagure.lib._get_project( + session, namespace=namespace, name=name, user=user_forker) with repo_to.lock('WORKER'): reponame = os.path.join(APP.config['GIT_FOLDER'], repo_from.path) @@ -315,9 +380,13 @@ def fork(name, namespace, user_owner, user_forker, editbranch, editfile): ), ) - session.remove() del frepo - generate_gitolite_acls() + session.remove() + + generate_gitolite_acls( + namespace=repo_to.namespace, + name=repo_to.name, + user=repo_to.user.user if repo_to.is_fork else None) gc_clean() if editfile is None: diff --git a/pagure/ui/groups.py b/pagure/ui/groups.py index df4b3f5..40e6515 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() + pagure.lib.git.generate_gitolite_acls(project=-1) 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() + pagure.lib.git.generate_gitolite_acls(project=-1) 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() + pagure.lib.git.generate_gitolite_acls(project=-1) flask.flash( 'Group `%s` has been deleted' % (group)) diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index e5415d4..6252161 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -1526,7 +1526,7 @@ def remove_deploykey(repo, keyid, username=None, namespace=None): break try: SESSION.commit() - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=None) pagure.lib.create_deploykeys_ssh_keys_on_disk( repo, APP.config.get('GITOLITE_KEYDIR', None) @@ -1588,7 +1588,7 @@ def remove_user(repo, userid, username=None, namespace=None): break try: SESSION.commit() - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=repo) flask.flash('User removed') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() @@ -1641,7 +1641,7 @@ def add_deploykey(repo, username=None, namespace=None): user=flask.g.fas_user.username, ) SESSION.commit() - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=None) pagure.lib.create_deploykeys_ssh_keys_on_disk( repo, APP.config.get('GITOLITE_KEYDIR', None) @@ -1724,7 +1724,7 @@ def add_user(repo, username=None, namespace=None): required_groups=APP.config.get('REQUIRED_GROUPS') ) SESSION.commit() - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=repo) flask.flash(msg) return flask.redirect(flask.url_for( '.view_settings', repo=repo.name, username=username, @@ -1797,7 +1797,7 @@ def remove_group_project(repo, groupid, username=None, namespace=None): break try: SESSION.commit() - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=repo) flask.flash('Group removed') except SQLAlchemyError as err: # pragma: no cover SESSION.rollback() @@ -1868,7 +1868,7 @@ def add_group_project(repo, username=None, namespace=None): is_admin=pagure.is_admin(), ) SESSION.commit() - pagure.lib.git.generate_gitolite_acls() + pagure.lib.git.generate_gitolite_acls(project=repo) flask.flash(msg) return flask.redirect(flask.url_for( '.view_settings', repo=repo.name, username=username, diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py index 84a6f53..bf63316 100644 --- a/tests/test_pagure_lib_git.py +++ b/tests/test_pagure_lib_git.py @@ -73,7 +73,8 @@ class PagureLibGittests(tests.Modeltests): outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls( + self.session, configfile=outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -162,6 +163,7 @@ repo requests/forks/pingou/test3 helper.write_gitolite_acls( self.session, outputconf, + project=-1, preconf=preconf ) self.assertTrue(os.path.exists(outputconf)) @@ -239,6 +241,7 @@ repo requests/somenamespace/test3 helper.write_gitolite_acls( self.session, outputconf, + project=-1, preconf=preconf, postconf=postconf ) @@ -313,6 +316,7 @@ repo requests/somenamespace/test3 helper.write_gitolite_acls( self.session, outputconf, + project=-1, postconf=postconf ) self.assertTrue(os.path.exists(outputconf)) @@ -409,7 +413,7 @@ repo requests/somenamespace/test3 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -520,7 +524,7 @@ repo requests/forks/pingou/test3 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -623,7 +627,7 @@ repo requests/forks/pingou/test3 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -775,7 +779,7 @@ repo requests/forks/pingou/test3 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -935,7 +939,7 @@ repo requests/forks/pingou/test2 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -1089,7 +1093,7 @@ repo requests/forks/pingou/test2 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -1209,7 +1213,7 @@ repo requests/forks/pingou/test2 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -1314,7 +1318,7 @@ repo requests/forks/pingou/test3 outputconf = os.path.join(self.path, 'test_gitolite.conf') helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.write_gitolite_acls(self.session, outputconf) + helper.write_gitolite_acls(self.session, outputconf, project=-1) self.assertTrue(os.path.exists(outputconf)) @@ -2991,7 +2995,7 @@ index 0000000..60f7480 proc.returncode = 0 popen.return_value = proc helper = pagure.lib.git_auth.get_git_auth_helper('gitolite3') - helper.generate_acls() + helper.generate_acls(project=None) popen.assert_called_with( 'HOME=/tmp gitolite compile && ' 'HOME=/tmp gitolite trigger POST_COMPILE', diff --git a/tests/test_pagure_lib_gitolite_config.py b/tests/test_pagure_lib_gitolite_config.py new file mode 100644 index 0000000..6105790 --- /dev/null +++ b/tests/test_pagure_lib_gitolite_config.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2017 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +__requires__ = ['SQLAlchemy >= 0.8'] + +import pkg_resources + +import datetime +import os +import shutil +import sys +import tempfile +import time +import unittest + +import pygit2 +from mock import patch, MagicMock + +sys.path.insert(0, os.path.join(os.path.dirname( + os.path.abspath(__file__)), '..')) + +import pagure +import pagure.lib.git +import tests +from pagure.lib.repo import PagureRepo + + +CORE_CONFIG = """repo test + R = @all + RW+ = pingou + +repo docs/test + R = @all + RW+ = pingou + +repo tickets/test + RW+ = pingou + +repo requests/test + RW+ = pingou + +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""" + + +class PagureLibGitoliteConfigtests(tests.Modeltests): + """ Tests for pagure.lib.git """ + + def setUp(self): + """ Set up the environnment, ran before every tests. """ + super(PagureLibGitoliteConfigtests, self).setUp() + + pagure.lib.git.SESSION = self.session + tests.create_projects(self.session) + + 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(PagureLibGitoliteConfigtests, self).tearDown() + + if os.path.exists(self.outputconf): + os.unlink(self.outputconf) + self.assertFalse(os.path.exists(self.outputconf)) + + def test_write_gitolite_pre_post_projectNone(self): + """ Test the write_gitolite_acls function of pagure.lib.git with + a postconf set """ + + 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() + + exp = """# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + + +%s + +# end of generated configuration +# \ó/ +# end of footer + +""" % CORE_CONFIG + #print data + self.assertEqual(data, exp) + + def test_write_gitolite_pre_post_projectNone(self): + """ Test the write_gitolite_acls function of pagure.lib.git with + a postconf set """ + + 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=None, + preconf=self.preconf, + postconf=self.postconf + ) + self.assertTrue(os.path.exists(self.outputconf)) + + with open(self.outputconf) as stream: + data = stream.read() + self.assertEqual(data, '') + + def test_write_gitolite_pre_post_project_1(self): + """ Test the write_gitolite_acls function of pagure.lib.git with + a postconf set """ + + 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() + + exp = """# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + + +%s + +# end of generated configuration +# \ó/ +# end of footer + +""" % CORE_CONFIG + + #print data + self.assertEqual(data, exp) + + def test_write_gitolite_pre_post_project_test(self): + """ Test the write_gitolite_acls function of pagure.lib.git with + a postconf set """ + + 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() + + exp = """# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin 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 generated configuration +# \ó/ +# end of footer + +""" + #print data + self.assertEqual(data, exp) + + def test_write_gitolite_pre_post_project_test_full_file(self): + """ Test the write_gitolite_acls function of pagure.lib.git with + a postconf set """ + + # Re-generate the gitolite config for all the projects + self.test_write_gitolite_pre_post_project_1() + self.assertTrue(os.path.exists(self.outputconf)) + + project = pagure.lib._get_project(self.session, 'test') + project.user_id = 2 + self.session.add(project) + self.session.commit() + + project = pagure.lib._get_project(self.session, 'test') + msg = pagure.lib.add_user_to_project( + self.session, + project=project, + new_user='pingou', + user='foo', + access='commit' + ) + self.assertEqual(msg, 'User added') + self.session.commit() + + 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() + + exp = """# this is a header that is manually added + +@group1 = foo bar baz +@group2 = threebean puiterwijk kevin pingou + + +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+ = foo + RW+ = pingou + +repo docs/test + R = @all + RW+ = foo + RW+ = pingou + +repo tickets/test + RW+ = foo + RW+ = pingou + +repo requests/test + RW+ = foo + RW+ = pingou + +# end of generated configuration +# \ó/ +# end of footer + +""" + #print data + self.assertEqual(data, exp) + + +if __name__ == '__main__': + unittest.main(verbosity=2)