From ddd64c2bd3736404885aad151253fb59d6526219 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jun 13 2016 11:36:35 +0000 Subject: [PATCH 1/5] Add an ACL allowing to fork projects --- diff --git a/pagure/default_config.py b/pagure/default_config.py index 530644c..9a5b816 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -193,6 +193,7 @@ BLACKLISTED_GROUPS = ['forks'] ACLS = { 'create_project': 'Create a new project', + 'fork_project': 'Fork a project', 'issue_assign': 'Assign issue to someone', 'issue_create': 'Create a new ticket against this project', 'issue_change_status': 'Change the status of a ticket of this project', From dc21df88b5f2408f0a9cb4081ae6f6cc8007a1e9 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jun 13 2016 11:36:35 +0000 Subject: [PATCH 2/5] Add a new API endpoint to fork a project --- diff --git a/pagure/api/project.py b/pagure/api/project.py index 1fe9d02..bd5aa04 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -266,3 +266,83 @@ def api_new_project(): jsonout = flask.jsonify(output) return jsonout + + +@API.route('/fork/', methods=['POST']) +@API.route('/fork', methods=['POST']) +@api_login_required(acls=['fork_project']) +@api_method +def api_fork_project(): + """ + Fork a project + -------------------- + Fork a project on this pagure instance. + + :: + + POST /api/0//fork + + + Input + ^^^^^ + + +------------------+---------+--------------+---------------------------+ + | Key | Type | Optionality | Description | + +==================+=========+==============+===========================+ + | ``repo`` | string | Mandatory | | The name of the project | + | | | | to fork. | + +------------------+---------+--------------+---------------------------+ + | ``username`` | string | Optional | | The username of the user| + | | | | of the fork. | + +------------------+---------+--------------+---------------------------+ + + + Sample response + ^^^^^^^^^^^^^^^ + + :: + + { + "message": 'Repo "test" cloned to "pingou/test"' + } + + """ + output = {} + + form = pagure.forms.ForkRepoForm(csrf_enabled=False) + if form.validate_on_submit(): + repo = form.repo.data + username = form.username.data or None + + repo = pagure.lib.get_project(SESSION, repo, user=username) + if repo is None: + raise pagure.exceptions.APIError( + 404, error_code=APIERROR.ENOPROJECT) + + try: + message = pagure.lib.fork_project( + SESSION, + user=flask.g.fas_user.username, + repo=repo, + gitfolder=APP.config['GIT_FOLDER'], + docfolder=APP.config['DOCS_FOLDER'], + ticketfolder=APP.config['TICKETS_FOLDER'], + requestfolder=APP.config['REQUESTS_FOLDER'], + ) + SESSION.commit() + pagure.lib.git.generate_gitolite_acls() + output['message'] = message + except pagure.exceptions.PagureException as err: + raise pagure.exceptions.APIError( + 400, error_code=APIERROR.ENOCODE, error=str(err)) + except SQLAlchemyError as err: # pragma: no cover + APP.logger.exception(err) + SESSION.rollback() + raise pagure.exceptions.APIError( + 400, error_code=APIERROR.EDBERROR) + else: + raise pagure.exceptions.APIError( + 400, error_code=APIERROR.EINVALIDREQ) + + jsonout = flask.jsonify(output) + return jsonout diff --git a/pagure/forms.py b/pagure/forms.py index 4d8fb47..a9d1e0a 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -410,3 +410,14 @@ class EditCommentForm(wtf.Form): 'Comment*', [wtforms.validators.Required()] ) + + +class ForkRepoForm(wtf.Form): + ''' Form to fork a project in the API. ''' + repo = wtforms.TextField( + 'The project name *', + [wtforms.validators.Required()] + ) + username = wtforms.TextField( + 'User who forked the project', + [wtforms.validators.optional()]) From f0e08e24b5cd3092a661bb5cb818ed2455f73875 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jun 13 2016 11:36:35 +0000 Subject: [PATCH 3/5] Add unit-tests for the API endpoint to fork projects --- diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py index f85adf5..9963cda 100644 --- a/tests/test_pagure_flask_api_project.py +++ b/tests/test_pagure_flask_api_project.py @@ -318,6 +318,114 @@ class PagureFlaskApiProjecttests(tests.Modeltests): {'message': 'Project "test_42" created'} ) + @patch('pagure.lib.git.generate_gitolite_acls') + def test_api_fork_project(self, p_gga): + """ Test the api_fork_project method of the flask api. """ + p_gga.return_value = True + + tests.create_projects(self.session) + for folder in ['docs', 'tickets', 'requests', 'repos']: + tests.create_projects_git( + os.path.join(tests.HERE, folder), bare=True) + tests.create_tokens(self.session) + tests.create_tokens_acl(self.session) + + headers = {'Authorization': 'token foo_token'} + + # Invalid token + output = self.app.post('/api/0/fork', headers=headers) + self.assertEqual(output.status_code, 401) + data = json.loads(output.data) + self.assertDictEqual( + data, + { + "error": "Invalid or expired token. Please visit " \ + "https://pagure.org/ to get or renew your API token.", + "error_code": "EINVALIDTOK" + } + ) + + headers = {'Authorization': 'token aaabbbcccddd'} + + # No input + output = self.app.post('/api/0/fork', headers=headers) + self.assertEqual(output.status_code, 400) + data = json.loads(output.data) + self.assertDictEqual( + data, + { + "error": "Invalid or incomplete input submited", + "error_code": "EINVALIDREQ", + } + ) + + data = { + 'name': 'test', + } + + # Incomplete request + output = self.app.post( + '/api/0/fork', data=data, headers=headers) + self.assertEqual(output.status_code, 400) + data = json.loads(output.data) + self.assertDictEqual( + data, + { + "error": "Invalid or incomplete input submited", + "error_code": "EINVALIDREQ", + } + ) + + data = { + 'repo': 'test', + } + + # Valid request + output = self.app.post( + '/api/0/fork/', data=data, headers=headers) + self.assertEqual(output.status_code, 200) + data = json.loads(output.data) + self.assertDictEqual( + data, + { + "message": "Repo \"test\" cloned to \"pingou/test\"" + } + ) + + data = { + 'repo': 'test', + } + + # project already forked + output = self.app.post( + '/api/0/fork/', data=data, headers=headers) + self.assertEqual(output.status_code, 400) + data = json.loads(output.data) + self.assertDictEqual( + data, + { + "error": "Repo \"forks/pingou/test\" already exists", + "error_code": "ENOCODE" + } + ) + + data = { + 'repo': 'test', + 'username': 'pingou', + } + + # Fork already exists + output = self.app.post( + '/api/0/fork/', data=data, headers=headers) + self.assertEqual(output.status_code, 400) + data = json.loads(output.data) + self.assertDictEqual( + data, + { + "error": "Repo \"forks/pingou/test\" already exists", + "error_code": "ENOCODE" + } + ) if __name__ == '__main__': SUITE = unittest.TestLoader().loadTestsFromTestCase( From 68afd3cc22abbe242f7cf453c7c54eaaaaffd917 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jun 13 2016 11:36:35 +0000 Subject: [PATCH 4/5] Remove debugging code --- diff --git a/pagure/api/project.py b/pagure/api/project.py index bd5aa04..5bd1ae4 100644 --- a/pagure/api/project.py +++ b/pagure/api/project.py @@ -253,11 +253,9 @@ def api_new_project(): pagure.lib.git.generate_gitolite_acls() output['message'] = message except pagure.exceptions.PagureException as err: - print err, str(err) raise pagure.exceptions.APIError( 400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError as err: # pragma: no cover - print err APP.logger.exception(err) SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) From d605046e280eac666e436c054d61f487d11b8a65 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jun 13 2016 11:36:35 +0000 Subject: [PATCH 5/5] Fix existing tests for the new ACL added --- diff --git a/tests/test_pagure_flask_api_fork.py b/tests/test_pagure_flask_api_fork.py index a8e69c6..bce8e20 100644 --- a/tests/test_pagure_flask_api_fork.py +++ b/tests/test_pagure_flask_api_fork.py @@ -370,7 +370,7 @@ class PagureFlaskApiForktests(tests.Modeltests): # Allow the token to close PR item = pagure.lib.model.TokenAcl( token_id='foobar_token', - acl_id=6, + acl_id=7, ) self.session.add(item) self.session.commit() @@ -484,7 +484,7 @@ class PagureFlaskApiForktests(tests.Modeltests): # Allow the token to merge PR item = pagure.lib.model.TokenAcl( token_id='foobar_token', - acl_id=9, + acl_id=10, ) self.session.add(item) self.session.commit() diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index 0d8c9ee..e668cfe 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -1249,7 +1249,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): # is required item = pagure.lib.model.TokenAcl( token_id='pingou_foo', - acl_id=3, + acl_id=5, ) self.session.add(item) self.session.commit()