From 5ab53dc751919691415631e5381dcd6b317ff611 Mon Sep 17 00:00:00 2001
From: Slavek Kabrda <bkabrda@redhat.com>
Date: Aug 23 2018 15:30:05 +0000
Subject: [PATCH 1/2] Make it possible to only run gitolite post compilation stage when necessary


---

diff --git a/pagure/flask_app.py b/pagure/flask_app.py
index d619baf..58e6ea6 100644
--- a/pagure/flask_app.py
+++ b/pagure/flask_app.py
@@ -142,7 +142,9 @@ def generate_user_key_files():
         for user in users:
             pagure.lib.update_user_ssh(
                 flask.g.session, user, user.public_ssh_key,
-                pagure_config.get('GITOLITE_KEYDIR', None))
+                pagure_config.get('GITOLITE_KEYDIR', None),
+                update_only=True,
+            )
     pagure.lib.git.generate_gitolite_acls(project=None)
 
 
diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py
index 3b90f9c..2e2905e 100644
--- a/pagure/lib/__init__.py
+++ b/pagure/lib/__init__.py
@@ -3492,7 +3492,7 @@ def add_email_to_user(session, user, user_email):
             update_log_email_user(session, user_email, user)
 
 
-def update_user_ssh(session, user, ssh_key, keydir):
+def update_user_ssh(session, user, ssh_key, keydir, update_only=False):
     ''' Set up a new user into the database or update its information. '''
     if isinstance(user, six.string_types):
         user = get_user(session, user)
@@ -3500,7 +3500,10 @@ 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(project=None)
+        if update_only:
+            pagure.lib.tasks.gitolite_post_compile_only.delay()
+        else:
+            pagure.lib.git.generate_gitolite_acls(project=None)
     session.add(user)
     session.flush()
 
diff --git a/pagure/lib/git_auth.py b/pagure/lib/git_auth.py
index 64a520f..6b8d8a8 100644
--- a/pagure/lib/git_auth.py
+++ b/pagure/lib/git_auth.py
@@ -724,6 +724,19 @@ class Gitolite3Auth(Gitolite2Auth):
             _log.debug('Command: %s', cmd)
             return cmd
 
+    @classmethod
+    def post_compile_only(cls):
+        """ This method runs `gitolite trigger POST_COMPILE` without touching
+        any other gitolite configuration. Most importantly, this will process
+        SSH keys used by gitolite.
+        """
+        _log.info('Triggering gitolite POST_COMPILE')
+        gitolite_folder = pagure_config.get('GITOLITE_HOME', None)
+        if gitolite_folder:
+            cmd = 'HOME=%s gitolite trigger POST_COMPILE' % gitolite_folder
+            _log.debug('Command: %s', cmd)
+            cls._run_gitolite_cmd(cmd)
+
 
 class GitAuthTestHelper(GitAuthHelper):
     """ Simple test auth module to check the auth customization system. """
diff --git a/pagure/lib/tasks.py b/pagure/lib/tasks.py
index c3cea08..e4f0c44 100644
--- a/pagure/lib/tasks.py
+++ b/pagure/lib/tasks.py
@@ -162,6 +162,23 @@ def generate_gitolite_acls(
 
 @conn.task(queue=pagure_config.get('GITOLITE_CELERY_QUEUE', None), bind=True)
 @pagure_task
+def gitolite_post_compile_only(self, session):
+    """ Do gitolite post-processing only. Most importantly, this processes SSH
+    keys used by gitolite. This is an optimization task that's supposed to be
+    used if you only need to run `gitolite trigger POST_COMPILE` without
+    touching any other gitolite configuration
+    """
+    helper = pagure.lib.git_auth.get_git_auth_helper(
+        pagure_config['GITOLITE_BACKEND'])
+    _log.debug('Got helper: %s', helper)
+    if hasattr(helper, 'post_compile_only'):
+        helper.post_compile_only()
+    else:
+        helper.generate_acls(project=None)
+
+
+@conn.task(queue=pagure_config.get('GITOLITE_CELERY_QUEUE', None), bind=True)
+@pagure_task
 def delete_project(
         self, session, namespace=None, name=None, user=None, action_user=None):
     """ Delete a project in pagure.
diff --git a/pagure/ui/app.py b/pagure/ui/app.py
index a03a31d..1065027 100644
--- a/pagure/ui/app.py
+++ b/pagure/ui/app.py
@@ -1077,6 +1077,7 @@ def user_settings():
                     user=user,
                     ssh_key=ssh_key,
                     keydir=pagure_config.get('GITOLITE_KEYDIR', None),
+                    update_only=True,
                 )
                 flask.g.session.commit()
                 message = 'Public ssh key updated'
diff --git a/tests/test_pagure_lib.py b/tests/test_pagure_lib.py
index 14f1ed5..1b4af4b 100644
--- a/tests/test_pagure_lib.py
+++ b/tests/test_pagure_lib.py
@@ -2205,6 +2205,16 @@ class PagureLibtests(tests.Modeltests):
         user = pagure.lib.search_user(self.session, username='foo')
         self.assertEqual(user.public_ssh_key, None)
 
+    @patch('pagure.lib.tasks.gitolite_post_compile_only.delay')
+    def test_update_user_ssh_update_only(self, gitolite_post_compile_only):
+        """ Test that update_user_ssh method called with update_only=True
+        calls the gitolite_post_compile_only method of helper. """
+        user = pagure.lib.search_user(self.session, username='foo')
+        msg = pagure.lib.update_user_ssh(
+            self.session, user, 'blah', keydir='/tmp', update_only=True
+        )
+        gitolite_post_compile_only.assert_called_once()
+
     def avatar_url_from_email(self):
         """ Test the avatar_url_from_openid of pagure.lib. """
         output = pagure.lib.avatar_url_from_email('pingou@fedoraproject.org')
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
index 7aa5a64..ae8e375 100644
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -1,4 +1,4 @@
-from mock import patch, Mock
+from mock import patch, MagicMock, Mock
 from collections import namedtuple
 import os
 import unittest
@@ -114,3 +114,22 @@ class TestCommitsAuthorStats(unittest.TestCase):
         self.assertEqual(last_time, '2018-01-01 00:00')
         self.assertEqual(authors, [(2, [('Alice', None)]),
                                    (1, [('Bob', '')])])
+
+
+class TestGitolitePostCompileOnly(object):
+    @patch('pagure.lib.git_auth.get_git_auth_helper')
+    def test_backend_has_post_compile_only(self, get_helper):
+        helper = MagicMock()
+        get_helper.return_value = helper
+        helper.post_compile_only = MagicMock()
+        tasks.gitolite_post_compile_only()
+        helper.post_compile_only.assert_called_once()
+
+    @patch('pagure.lib.git_auth.get_git_auth_helper')
+    def test_backend_doesnt_have_post_compile_only(self, get_helper):
+        helper = MagicMock()
+        get_helper.return_value = helper
+        helper.generate_acls = MagicMock()
+        del helper.post_compile_only
+        tasks.gitolite_post_compile_only()
+        helper.generate_acls.assert_called_once_with(project=None)

From f68223f9f768b318a44193f0638fcfa0bb756fd2 Mon Sep 17 00:00:00 2001
From: Slavek Kabrda <bkabrda@redhat.com>
Date: Aug 23 2018 15:30:05 +0000
Subject: [PATCH 2/2] When manipulating deploy keys, run gitolite *after* keys are created, also only run gitolite post compilation


---

diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py
index 22a9089..b977204 100644
--- a/pagure/ui/repo.py
+++ b/pagure/ui/repo.py
@@ -1623,11 +1623,11 @@ def remove_deploykey(repo, keyid, username=None, namespace=None):
                 break
         try:
             flask.g.session.commit()
-            pagure.lib.git.generate_gitolite_acls(project=repo)
             pagure.lib.create_deploykeys_ssh_keys_on_disk(
                 repo,
                 pagure_config.get('GITOLITE_KEYDIR', None)
             )
+            pagure.lib.tasks.gitolite_post_compile_only.delay()
             flask.flash('Deploy key removed')
         except SQLAlchemyError as err:  # pragma: no cover
             flask.g.session.rollback()
@@ -1723,11 +1723,11 @@ def add_deploykey(repo, username=None, namespace=None):
                 user=flask.g.fas_user.username,
             )
             flask.g.session.commit()
-            pagure.lib.git.generate_gitolite_acls(project=repo)
             pagure.lib.create_deploykeys_ssh_keys_on_disk(
                 repo,
                 pagure_config.get('GITOLITE_KEYDIR', None)
             )
+            pagure.lib.tasks.gitolite_post_compile_only.delay()
             flask.flash(msg)
             return flask.redirect(flask.url_for(
                 'ui_ns.view_settings', repo=repo.name, username=username,

