From f725860b6dfec047766577601ceb8803372919bf Mon Sep 17 00:00:00 2001 From: Dmitrij Djachkov Date: Wed, 27 Nov 2019 14:55:46 +0300 Subject: [PATCH] Bitbucket: add branch-permissions methods, add some docs, add examples --- atlassian/bitbucket.py | 188 ++++++++++++++++++++++ docs/bitbucket.rst | 47 +++++- examples/bitbucket-branch-restrictions.py | 80 +++++++++ examples/bitbucket-manage-pull-request.py | 29 ++++ 4 files changed, 336 insertions(+), 8 deletions(-) create mode 100644 examples/bitbucket-branch-restrictions.py create mode 100644 examples/bitbucket-manage-pull-request.py diff --git a/atlassian/bitbucket.py b/atlassian/bitbucket.py index 740da636d..01e4e622d 100644 --- a/atlassian/bitbucket.py +++ b/atlassian/bitbucket.py @@ -8,6 +8,8 @@ class Bitbucket(AtlassianRestAPI): + bulk_headers = {"Content-Type": "application/vnd.atl.bitbucket.bulk+json"} + def project_list(self, limit=None): """ Provide the project list @@ -748,6 +750,89 @@ def create_pull_request(self, project_key, repository, data): repository=repository) return self.post(url, data=data) + def decline_pull_request(self, project_key, repository, pr_id, pr_version): + """ + Decline a pull request. + The authenticated user must have REPO_READ permission for the repository + that this pull request targets to call this resource. + + :param project_key: PROJECT + :param repository: my_shiny_repo + :param pr_id: 2341 + :param pr_version: 12 + :return: + """ + url = 'rest/api/1.0/projects/{project_key}/repos/{repository}/pull-requests/{pr_id}/decline'.format( + project_key=project_key, + repository=repository, + pr_id=pr_id + ) + params = {'version': pr_version} + + return self.post(url, params=params) + + def is_pull_request_can_be_merged(self, project_key, repository, pr_id): + """ + Test whether a pull request can be merged. + A pull request may not be merged if: + - there are conflicts that need to be manually resolved before merging; and/or + - one or more merge checks have vetoed the merge. + The authenticated user must have REPO_READ permission for the repository + that this pull request targets to call this resource. + + :param project_key: PROJECT + :param repository: my_shiny_repo + :param pr_id: 2341 + :return: + """ + url = 'rest/api/1.0/projects/{project_key}/repos/{repository}/pull-requests/{pr_id}/merge'.format( + project_key=project_key, + repository=repository, + pr_id=pr_id + ) + return self.get(url) + + def merge_pull_request(self, project_key, repository, pr_id, pr_version): + """ + Merge pull request + The authenticated user must have REPO_READ permission for the repository + that this pull request targets to call this resource. + + :param project_key: PROJECT + :param repository: my_shiny_repo + :param pr_id: 2341 + :return: + """ + url = 'rest/api/1.0/projects/{project_key}/repos/{repository}/pull-requests/{pr_id}/merge'.format( + project_key=project_key, + repository=repository, + pr_id=pr_id + ) + params = {'version': pr_version} + + return self.post(url, params=params) + + def reopen_pull_request(self, project_key, repository, pr_id, pr_version): + """ + Re-open a declined pull request. + The authenticated user must have REPO_READ permission for the repository + that this pull request targets to call this resource. + + :param project_key: PROJECT + :param repository: my_shiny_repo + :param pr_id: 2341 + :param pr_version: 12 + :return: + """ + url = 'rest/api/1.0/projects/{project_key}/repos/{repository}/pull-requests/{pr_id}/reopen'.format( + project_key=project_key, + repository=repository, + pr_id=pr_id + ) + params = {'version': pr_version} + + return self.post(url, params=params) + def check_inbox_pull_requests_count(self): return self.get('rest/api/1.0/inbox/pull-requests/count') @@ -1100,6 +1185,109 @@ def get_branches_permissions(self, project, repository=None, start=0, limit=25): params['start'] = start return self.get(url, params=params) + def set_branches_permissions( + self, project_key, + multiple_permissions=False, + matcher_type=None, + matcher_value=None, + permission_type=None, + repository=None, + except_users=[], + except_groups=[], + except_access_keys=[], + start=0, + limit=25 + ): + """ + Create a restriction for the supplied branch or set of branches to be applied to the given repository. + Allows creating multiple restrictions at once. + To use multiple restrictions you should format payload manually - see the bitbucket-branch-restrictions.py example. + Reference: https://docs.atlassian.com/bitbucket-server/rest/6.8.0/bitbucket-ref-restriction-rest.html + + :param project_key: + :param repository: + :return: + """ + headers = self.default_headers + if repository: + url = "/rest/branch-permissions/2.0/projects/{project_key}/repos/{repository}/restrictions".format( + project_key=project_key, + repository=repository + ) + else: + url = "/rest/branch-permissions/2.0/projects/{project_key}/restrictions".format( + project_key=project_key + ) + if multiple_permissions: + headers = self.bulk_headers + restriction = multiple_permissions + else: + restriction = { + "type": permission_type, + "matcher": { + "id": matcher_value, + "displayId": matcher_value, + "type": { + "id": matcher_type.upper(), + "name": matcher_type.capitalize() + }, + "active": True, + }, + "users": except_users, + "groups": except_groups, + "accessKeys": except_access_keys, + } + params = {"start": start, "limit": limit} + return self.post(url, data=restriction, params=params, headers=headers) + + def delete_branch_permission(self, project_key, permission_id, repository=None): + """ + Deletes a restriction as specified by a restriction id. + The authenticated user must have REPO_ADMIN permission or higher to call this resource. + + :param project_key: + :param repository: + :param permission_id: + :return: + """ + + if repository: + url = "/rest/branch-permissions/2.0/projects/{project_key}/repos/{repository}/restrictions/{id}".format( + project_key=project_key, + repository=repository, + id=permission_id + ) + else: + url = "/rest/branch-permissions/2.0/projects/{project_key}/restrictions/{id}".format( + project_key=project_key, + id=permission_id + ) + return self.delete(url) + + def get_branch_permission(self, project_key, permission_id, repository=None): + """ + Returns a restriction as specified by a restriction id. + The authenticated user must have REPO_ADMIN permission or higher to call this resource. + + :param project_key: + :param repository: + :param permission_id: + :return: + """ + + if repository: + url = "/rest/branch-permissions/2.0/projects/{project_key}/repos/{repository}/restrictions/{id}".format( + project_key=project_key, + repository=repository, + id=permission_id + ) + else: + url = "/rest/branch-permissions/2.0/projects/{project_key}/restrictions/{id}".format( + project_key=project_key, + id=permission_id + ) + return self.get(url) + def all_branches_permissions(self, project, repository=None): """ Get branches permissions from a given repo diff --git a/docs/bitbucket.rst b/docs/bitbucket.rst index cf844d845..9f9aa9f60 100644 --- a/docs/bitbucket.rst +++ b/docs/bitbucket.rst @@ -76,9 +76,9 @@ Manage repositories 'prefix': 'release/'}]} bitbucket.set_branching_model(project_key, repo_key, data) - bitbucket.repo_users(self, project_key, repo_key, limit=99999, filter_str=None) + bitbucket.repo_users(project_key, repo_key, limit=99999, filter_str=None) - bitbucket.repo_groups(self, project_key, repo_key, limit=99999, filter_str=None) + bitbucket.repo_groups(project_key, repo_key, limit=99999, filter_str=None) # Grant repository permission to an specific user bitbucket.repo_grant_user_permissions(project_key, repo_key, username, permission) @@ -112,7 +112,7 @@ Manage code # Requires an existing project in which this repository will be created. The only parameters which will be used # are name and scmId. # The authenticated user must have PROJECT_ADMIN permission for the context project to call this resource. - bitbucket.create_repo(project_key, repository, forkable=False, is_private=True): + bitbucket.create_repo(project_key, repository, forkable=False, is_private=True) # Get branches from repo bitbucket.get_branches(project, repository, filter='', limit=99999, details=True) @@ -128,16 +128,16 @@ Manage code bitbucket.get_pull_requests(project, repository, state='OPEN', order='newest', limit=100, start=0) # Get pull request activities - bitbucket.get_pull_requests_activities(self, project, repository, pull_request_id) + bitbucket.get_pull_requests_activities(project, repository, pull_request_id) # Get pull request changes - bitbucket.get_pull_requests_changes(self, project, repository, pull_request_id) + bitbucket.get_pull_requests_changes(project, repository, pull_request_id) # Get pull request commits - bitbucket.get_pull_requests_commits(self, project, repository, pull_request_id) + bitbucket.get_pull_requests_commits(project, repository, pull_request_id) # Add comment into pull request - bitbucket.add_pull_request_comment(self, project, repository, pull_request_id, text) + bitbucket.add_pull_request_comment(project, repository, pull_request_id, text) # Get tags for related repo bitbucket.get_tags(project, repository, filter='', limit=99999) @@ -168,4 +168,35 @@ Manage code """ Retrieve the raw content for a file path at a specified revision. The authenticated user must have REPO_READ permission for the specified repository to call this resource. - """ \ No newline at end of file + """ + +Branch permissions +------------------ + +.. code-block:: python + + # Set branches permissions + bitbucket.set_branches_permissions(project_key, multiple_permissions=False, matcher_type=None, matcher_value=None, permission_type=None, repository=None, except_users=[], except_groups=[], except_access_keys=[], start=0, limit=25) + + # Delete a single branch permission by premission id + bitbucket.delete_branch_permission(project_key, permission_id, repository=None) + + # Get a single branch permission by permission id + bitbucket.get_branch_permission(project_key, permission_id, repository=None) + +Pull Request management +----------------------- + +.. code-block:: python + + # Decline pull request + bitbucket.decline_pull_request(project_key, repository, pr_id, pr_version) + + # Check if pull request can be merged + bitbucket.is_pull_request_can_be_merged(project_key, repository, pr_id) + + # Merge pull request + bitbucket.merge_pull_request(project_key, repository, pr_id, pr_version) + + # Reopen pull request + bitbucket.reopen_pull_request(project_key, repository, pr_id, pr_version) diff --git a/examples/bitbucket-branch-restrictions.py b/examples/bitbucket-branch-restrictions.py new file mode 100644 index 000000000..24845aa18 --- /dev/null +++ b/examples/bitbucket-branch-restrictions.py @@ -0,0 +1,80 @@ +# coding=utf-8 +from atlassian import Bitbucket + +""" +For all possible arguments and values please visit: +https://docs.atlassian.com/bitbucket-server/rest/latest/bitbucket-ref-restriction-rest.html +""" +bitbucket = Bitbucket( + url='http://localhost:7990', + username='admin', + password='admin', + advanced_mode=True) # For more simple response handling + +single_permission = bitbucket.set_branches_permissions( + "PROJECT_KEY", + matcher_type="branch", # lowercase matcher type + matcher_value="master", + permission_type="no-deletes", + repository="repository_name", + except_users=["user1", "user2"] +) +print(single_permission) +pid = single_permission.json().get("id") + +single_permission = bitbucket.get_branch_permission("PROJECT_KEY", pid) +print(single_permission) + +deleted_permission = bitbucket.delete_branch_permission("PROJECT_KEY", pid) +print(deleted_permission) + +multiplpe_permissions_payload = [ + { + "type": "read-only", + "matcher": { + "id": "master", + "displayId": "master", + "type": { + "id": "BRANCH", + "name": "Branch" + }, + "active": True + }, + "users": [ + "user1", + ], + "groups": [ + + ], + "accessKeys": [] + }, + { + "type": "pull-request-only", + "matcher": { + "id": "refs/tags/**", + "displayId": "refs/tags/**", + "type": { + "id": "PATTERN", + "name": "Pattern" + }, + "active": True + }, + "users": [ + "user2" + ], + "groups": [ + + ], + "accessKeys": [] + } +] +multiple_permissions = bitbucket.set_branches_permissions( + "PROJECT_KEY", + multiple_permissions=multiplpe_permissions_payload, + matcher_type="branch", + matcher_value="master", + permission_type="no-deletes", + repository="repository_name", + users=["user1", "user2"] +) +print(multiple_permissions) diff --git a/examples/bitbucket-manage-pull-request.py b/examples/bitbucket-manage-pull-request.py new file mode 100644 index 000000000..eea5eaf0f --- /dev/null +++ b/examples/bitbucket-manage-pull-request.py @@ -0,0 +1,29 @@ +# coding=utf-8 +from atlassian import Bitbucket + +bitbucket = Bitbucket( + url='http://localhost:7990', + username='admin', + password='admin') +pr_id = 12345 + +pr = bitbucket.get_pullrequest("project_name", "repository_name", pr_id) +ver = pr.json().get("version") +print(f"PR version: {ver}") + +response = bitbucket.decline_pull_request("project_name", "repository_name", pr_id, ver) +print(f"Declined: {response}") +ver = response.json().get("version") +print(f"PR version: {ver}") + +response = bitbucket.reopen_pull_request("project_name", "repository_name", pr_id, ver) +print(f"Reopen: {response}") +ver = response.json().get("version") +print(f"PR version: {ver}") + +response = bitbucket.is_pull_request_can_be_merged("project_name", "repository_name", pr_id) +print(f"Reopen: {response}") +print(f"PR version: {ver}") + +response = bitbucket.merge_pull_request("project_name", "repository_name", pr_id, ver) +print(f"Merged: {response}")