diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1896c1dc5..c95f0e7c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,9 +30,8 @@ jobs: .github/bump_version ./ minor > atlassian/VERSION make docker-qa PYTHON_VERSION=${{matrix.python-version}} - name: Creating coverage report - if: ${{ ( matrix.python-version == '3.8' ) }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: ./coverage.xml - fail_ci_if_error: true + fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.readthedocs.yml b/.readthedocs.yml index 0e322d5dd..c29c150f8 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -5,6 +5,11 @@ # Required version: 2 +build: + os: ubuntu-lts-latest + tools: + python: "3.8" + # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py diff --git a/atlassian/VERSION b/atlassian/VERSION index f78008e28..d9cf50031 100644 --- a/atlassian/VERSION +++ b/atlassian/VERSION @@ -1 +1 @@ -3.41.12 +3.41.14 diff --git a/atlassian/bamboo.py b/atlassian/bamboo.py index f8c8461d3..08f897bcf 100755 --- a/atlassian/bamboo.py +++ b/atlassian/bamboo.py @@ -104,6 +104,14 @@ def projects( clover_enabled=False, max_results=25, ): + """ + Get all Projects + :param expand: + :param favourite: + :param clover_enabled: + :param max_results: + :return: + """ return self.base_list_call( "project", expand=expand, @@ -115,6 +123,14 @@ def projects( ) def project(self, project_key, expand=None, favourite=False, clover_enabled=False): + """ + Get a single project by the key + :param project_key: + :param expand: + :param favourite: + :param clover_enabled: + :return: + """ resource = "project/{}".format(project_key) return self.base_list_call( resource=resource, @@ -125,8 +141,21 @@ def project(self, project_key, expand=None, favourite=False, clover_enabled=Fals max_results=25, ) + def get_project(self, project_key): + """Method used to retrieve information for project specified as project key. + Possible expand parameters: plans, list of plans for project. plans.plan, list of plans with plan details + (only plans visible - READ permission for user)""" + resource = "project/{}?showEmpty".format(project_key) + return self.get(self.resource_url(resource)) + + def delete_project(self, project_key): + """Marks project for deletion. Project will be deleted by a batch job.""" + resource = "project/{}".format(project_key) + return self.delete(self.resource_url(resource)) + def project_plans(self, project_key, start_index=0, max_results=25): """ + Get all build plans in a project Returns a generator with the plans in a given project. :param project_key: project key :param start_index: @@ -153,6 +182,15 @@ def plans( start_index=0, max_results=25, ): + """ + Get all build plans + :param expand: + :param favourite: + :param clover_enabled: + :param start_index: + :param max_results: + :return: + """ return self.base_list_call( "plan", expand=expand, @@ -234,6 +272,14 @@ def enable_plan(self, plan_key): """ Branches """ def search_branches(self, plan_key, include_default_branch=True, max_results=25, start=0): + """ + Search Branches + :param plan_key: + :param include_default_branch: + :param max_results: + :param start: + :return: + """ params = { "max-result": max_results, "start-index": start, @@ -256,7 +302,16 @@ def plan_branches( clover_enabled=False, max_results=25, ): - """api/1.0/plan/{projectKey}-{buildKey}/branch""" + """ + Get all plan Branches + api/1.0/plan/{projectKey}-{buildKey}/branch + :param plan_key: + :param expand: + :param favourite: + :param clover_enabled: + :param max_results: + :return: + """ resource = "plan/{}/branch".format(plan_key) return self.base_list_call( resource, @@ -617,14 +672,30 @@ def comments( start_index=0, max_results=25, ): + """ + Get comments for a specific build + :param project_key: + :param plan_key: + :param build_number: + :param start_index: + :param max_results: + :return: + """ resource = "result/{}-{}-{}/comment".format(project_key, plan_key, build_number) params = {"start-index": start_index, "max-results": max_results} return self.get(self.resource_url(resource), params=params) - def create_comment(self, project_key, plan_key, build_number, comment, author=None): + def create_comment(self, project_key, plan_key, build_number, comment): + """ + Create a comment for a specific build + :param project_key: + :param plan_key: + :param build_number: + :param comment: + :return: + """ resource = "result/{}-{}-{}/comment".format(project_key, plan_key, build_number) comment_data = { - "author": author if author else self.username, "content": comment, } return self.post(self.resource_url(resource), data=comment_data) @@ -637,15 +708,40 @@ def labels( start_index=0, max_results=25, ): + """ + Get labels for a build + :param project_key: + :param plan_key: + :param build_number: + :param start_index: + :param max_results: + :return: + """ resource = "result/{}-{}-{}/label".format(project_key, plan_key, build_number) params = {"start-index": start_index, "max-results": max_results} return self.get(self.resource_url(resource), params=params) def create_label(self, project_key, plan_key, build_number, label): + """ + Create a label for a specific build + :param project_key: + :param plan_key: + :param build_number: + :param label: + :return: + """ resource = "result/{}-{}-{}/label".format(project_key, plan_key, build_number) return self.post(self.resource_url(resource), data={"name": label}) def delete_label(self, project_key, plan_key, build_number, label): + """ + Delete a label for a specific build + :param project_key: + :param plan_key: + :param build_number: + :param label: + :return: + """ resource = "result/{}-{}-{}/label/{}".format(project_key, plan_key, build_number, label) return self.delete(self.resource_url(resource)) @@ -671,34 +767,43 @@ def get_projects(self): for project in r["projects"]["project"]: yield project - def get_project(self, project_key): - """Method used to retrieve information for project specified as project key. - Possible expand parameters: plans, list of plans for project. plans.plan, list of plans with plan details - (only plans visible - READ permission for user)""" - resource = "project/{}?showEmpty".format(project_key) - return self.get(self.resource_url(resource)) - - def delete_project(self, project_key): - """Marks project for deletion. Project will be deleted by a batch job.""" - resource = "project/{}".format(project_key) - return self.delete(self.resource_url(resource)) - """ Deployments """ def deployment_projects(self): + """ + Returns all deployment projects. + :return: + """ resource = "deploy/project/all" for project in self.get(self.resource_url(resource)): yield project def deployment_project(self, project_id): + """ + Returns a deployment project. + :param project_id: + :return: + """ resource = "deploy/project/{}".format(project_id) return self.get(self.resource_url(resource)) def delete_deployment_project(self, project_id): + """ + Deletes a deployment project. + :param project_id: + :return: + """ resource = "deploy/project/{}".format(project_id) return self.delete(self.resource_url(resource)) def deployment_environment_results(self, env_id, expand=None, max_results=25): + """ + Get deployment environment results + :param env_id: + :param expand: + :param max_results: + :return: + """ resource = "deploy/environment/{environmentId}/results".format(environmentId=env_id) params = {"max-result": max_results, "start-index": 0} size = 1 @@ -844,23 +949,6 @@ def get_users_not_in_group(self, group_name, filter_users="", start=0, limit=25) url = "rest/api/latest/admin/groups/{}/more-non-members".format(group_name) return self.get(url, params=params) - def get_build_queue(self, expand="queuedBuilds"): - """ - Lists all the builds waiting in the build queue, adds or removes a build from the build queue. - May be used also to resume build on manual stage or rerun failed jobs. - :return: - """ - params = {"expand": expand} - return self.get("rest/api/latest/queue", params=params) - - def get_deployment_queue(self, expand="queuedDeployments"): - """ - Provide list of deployment results scheduled for execution and waiting in queue. - :return: - """ - params = {"expand": expand} - return self.get("rest/api/latest/queue/deployment", params=params) - def get_deployment_users(self, deployment_id, filter_name=None, start=0, limit=25): """ Retrieve a list of users with their explicit permissions to given resource. @@ -1022,6 +1110,23 @@ def grant_group_to_environment(self, environment_id, group, permissions): def server_info(self): return self.get(self.resource_url("info")) + def get_build_queue(self, expand="queuedBuilds"): + """ + Lists all the builds waiting in the build queue, adds or removes a build from the build queue. + May be used also to resume build on manual stage or rerun failed jobs. + :return: + """ + params = {"expand": expand} + return self.get("rest/api/latest/queue", params=params) + + def get_deployment_queue(self, expand="queuedDeployments"): + """ + Provide list of deployment results scheduled for execution and waiting in queue. + :return: + """ + params = {"expand": expand} + return self.get("rest/api/latest/queue/deployment", params=params) + def agent_status(self, online=False): """ Provides a list of all agents. @@ -1128,6 +1233,20 @@ def chart( start_index=9, max_results=25, ): + """ + Get chart data + :param report_key: + :param build_keys: + :param group_by_period: + :param date_filter: + :param date_from: + :param date_to: + :param width: + :param height: + :param start_index: + :param max_results: + :return: + """ params = { "reportKey": report_key, "buildKeys": build_keys, @@ -1173,6 +1292,8 @@ def health_check(self): response = self.get("rest/supportHealthCheck/1.0/check/") return response + """Elastic Bamboo""" + def get_elastic_instance_logs(self, instance_id): """ Get logs from an EC2 instance @@ -1201,7 +1322,7 @@ def create_elastic_configuration(self, json): def get_elastic_configuration(self, configuration_id): """ - Get informatin of an elastic configuration + Get information of an elastic configuration :param configuration_id: :return: """ @@ -1264,7 +1385,7 @@ def get_plugin_info(self, plugin_key): def get_plugin_license_info(self, plugin_key): """ - Provide plugin license info + Provide plugin license information :return a json specific License query """ url = "rest/plugins/1.0/{plugin_key}-key/license".format(plugin_key=plugin_key) @@ -1286,6 +1407,34 @@ def upload_plugin(self, plugin_path): url = "rest/plugins/1.0/?token={upm_token}".format(upm_token=upm_token) return self.post(url, files=files, headers=self.no_check_headers) + def disable_plugin(self, plugin_key): + """ + Disable a plugin + :param plugin_key: + :return: + """ + app_headers = { + "X-Atlassian-Token": "nocheck", + "Content-Type": "application/vnd.atl.plugins+json", + } + url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) + data = {"status": "disabled"} + return self.put(url, data=data, headers=app_headers) + + def enable_plugin(self, plugin_key): + """ + Enable a plugin + :param plugin_key: + :return: + """ + app_headers = { + "X-Atlassian-Token": "nocheck", + "Content-Type": "application/vnd.atl.plugins+json", + } + url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) + data = {"status": "enabled"} + return self.put(url, data=data, headers=app_headers) + def delete_plugin(self, plugin_key): """ Delete plugin @@ -1296,6 +1445,10 @@ def delete_plugin(self, plugin_key): return self.delete(url) def check_plugin_manager_status(self): + """ + Check plugin manager status + :return: + """ url = "rest/plugins/latest/safe-mode" return self.request(method="GET", path=url, headers=self.safe_mode_headers) diff --git a/atlassian/bitbucket/__init__.py b/atlassian/bitbucket/__init__.py index 722257a7c..e2ae5fd61 100644 --- a/atlassian/bitbucket/__init__.py +++ b/atlassian/bitbucket/__init__.py @@ -1,5 +1,7 @@ # coding=utf-8 import logging +from enum import Enum +from typing import Optional, Union from deprecated import deprecated from requests import HTTPError @@ -10,6 +12,16 @@ log = logging.getLogger(__name__) +class MergeStrategy(Enum): + """ + Merge strategies used by the merge_pull_request method. + """ + + MERGE_COMMIT = "merge_commit" + SQUASH = "squash" + FAST_FORWARD = "fast_forward" + + class Bitbucket(BitbucketBase): def __init__(self, url, *args, **kwargs): if "cloud" not in kwargs and ("bitbucket.org" in url): @@ -2194,7 +2206,16 @@ def is_pull_request_can_be_merged(self, project_key, repository_slug, pr_id): url = "{}/merge".format(self._url_pull_request(project_key, repository_slug, pr_id)) return self.get(url) - def merge_pull_request(self, project_key, repository_slug, pr_id, pr_version): + def merge_pull_request( + self, + project_key: str, + repository_slug: str, + pr_id: int, + merge_message: str, + close_source_branch: bool = False, + merge_strategy: Union[str, MergeStrategy] = MergeStrategy.MERGE_COMMIT, + pr_version: Optional[int] = None, + ): """ Merge pull request The authenticated user must have REPO_READ permission for the repository @@ -2203,14 +2224,23 @@ def merge_pull_request(self, project_key, repository_slug, pr_id, pr_version): :param project_key: PROJECT :param repository_slug: my_shiny_repo :param pr_id: 2341 + :param merge_message: "feat: add new file handler" :param pr_version: + :param close_source_branch: True + :param merge_strategy: "squash" :return: """ url = "{}/merge".format(self._url_pull_request(project_key, repository_slug, pr_id)) params = {} + data = { + "type": "pullrequest", + "message": merge_message, + "close_source_branch": close_source_branch, + "merge_strategy": MergeStrategy(merge_strategy).value, + } if not self.cloud: params["version"] = pr_version - return self.post(url, params=params) + return self.post(url, data=data, params=params) def reopen_pull_request(self, project_key, repository_slug, pr_id, pr_version): """ diff --git a/atlassian/confluence.py b/atlassian/confluence.py index f195ae2fd..7c5987392 100644 --- a/atlassian/confluence.py +++ b/atlassian/confluence.py @@ -506,27 +506,20 @@ def get_page_comments( return response - def get_draft_page_by_id(self, page_id, status="draft"): + def get_draft_page_by_id(self, page_id, status="draft", expand=None): """ - Provide content by id with status = draft - :param page_id: - :param status: + Gets content by id with status = draft + :param page_id: Content ID + :param status: (str) list of content statuses to filter results on. Default value: [draft] + :param expand: OPTIONAL: Default value: history,space,version + We can also specify some extensions such as extensions.inlineProperties + (for getting inline comment-specific properties) or extensions. Resolution + for the resolution status of each comment in the results :return: """ - url = "rest/api/content/{page_id}?status={status}".format(page_id=page_id, status=status) - - try: - response = self.get(url) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response + # Version not passed since draft versions don't match the page and + # operate differently between different collaborative modes + return self.get_page_by_id(page_id=page_id, expand=expand, status=status) def get_all_pages_by_label(self, label, start=0, limit=50): """ @@ -1322,17 +1315,20 @@ def attach_file( comment=comment, ) - def download_attachments_from_page(self, page_id, path=None): + def download_attachments_from_page(self, page_id, path=None, start=0, limit=50): """ Downloads all attachments from a page :param page_id: - :param path: path to directory where attachments will be saved. If None, current working directory will be used. + :param path: OPTIONAL: path to directory where attachments will be saved. If None, current working directory will be used. + :param start: OPTIONAL: The start point of the collection to return. Default: None (0). + :param limit: OPTIONAL: The limit of the number of attachments to return, this may be restricted by + fixed system limits. Default: 50 :return info message: number of saved attachments + path to directory where attachments were saved: """ if path is None: path = os.getcwd() try: - attachments = self.get_attachments_from_content(page_id=page_id)["results"] + attachments = self.get_attachments_from_content(page_id=page_id, start=start, limit=limit)["results"] if not attachments: return "No attachments found" for attachment in attachments: @@ -2727,6 +2723,34 @@ def upload_plugin(self, plugin_path): url = "rest/plugins/1.0/?token={upm_token}".format(upm_token=upm_token) return self.post(url, files=files, headers=self.no_check_headers) + def disable_plugin(self, plugin_key): + """ + Disable a plugin + :param plugin_key: + :return: + """ + app_headers = { + "X-Atlassian-Token": "nocheck", + "Content-Type": "application/vnd.atl.plugins+json", + } + url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) + data = {"status": "disabled"} + return self.put(url, data=data, headers=app_headers) + + def enable_plugin(self, plugin_key): + """ + Enable a plugin + :param plugin_key: + :return: + """ + app_headers = { + "X-Atlassian-Token": "nocheck", + "Content-Type": "application/vnd.atl.plugins+json", + } + url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) + data = {"status": "enabled"} + return self.put(url, data=data, headers=app_headers) + def delete_plugin(self, plugin_key): """ Delete plugin diff --git a/atlassian/crowd.py b/atlassian/crowd.py index f9548c841..933a483ae 100644 --- a/atlassian/crowd.py +++ b/atlassian/crowd.py @@ -2,6 +2,7 @@ import logging from jmespath import search +from bs4 import BeautifulSoup from .rest_client import AtlassianRestAPI @@ -264,3 +265,38 @@ def update_plugin_license(self, plugin_key, raw_license): url = "/plugins/1.0/{plugin_key}/license".format(plugin_key=plugin_key) data = {"rawLicense": raw_license} return self.put(url, data=data, headers=app_headers) + + @property + def memberships(self): + """ + Retrieves full details of all group memberships, with users and nested groups. + See: https://docs.atlassian.com/atlassian-crowd/5.3.1/REST/#usermanagement/1/group-getAllMemberships + :return: All membership mapping dict + """ + path = self._crowd_api_url("usermanagement", "group/membership") + headers = {"Accept": "application/xml"} + response = self.get(path, headers=headers) + soup = BeautifulSoup(response, "xml") + memberships = {} + for membership in soup.find_all("membership"): + group = membership["group"] + users = [user["name"] for user in membership.find_all("user")] + memberships[group] = users + return memberships + + def group_create(self, groupname, description=None, active=True): + """ + Create new group method + :param groupname: string: The name of new group + :param description: string: The description of new group, default is None + :param active: bool: Weather the group is active, default is True + :return: Create result + """ + group = { + "name": groupname, + "active": active, + "description": description, + "type": "GROUP", + } + + return self.post(self._crowd_api_url("usermanagement", "group"), data=group) diff --git a/atlassian/jira.py b/atlassian/jira.py index bd24798a6..6a283f3e7 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -284,11 +284,11 @@ def get_attachment_content(self, attachment_id): """ Returns the content for an attachment :param attachment_id: int - :return: json + :return: content as bytes """ base_url = self.resource_url("attachment") url = "{base_url}/content/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) - return self.get(url) + return self.get(url, not_json_response=True) def remove_attachment(self, attachment_id): """ @@ -1221,13 +1221,18 @@ def get_issue_changelog(self, issue_key, start=None, limit=None): :return: """ base_url = self.resource_url("issue") - url = "{base_url}/{issue_key}?expand=changelog".format(base_url=base_url, issue_key=issue_key) params = {} if start: params["startAt"] = start if limit: params["maxResults"] = limit - return (self.get(url) or {}).get("changelog", params) + + if self.cloud: + url = "{base_url}/{issue_key}/changelog".format(base_url=base_url, issue_key=issue_key) + return self.get(url, params=params) + else: + url = "{base_url}/{issue_key}?expand=changelog".format(base_url=base_url, issue_key=issue_key) + return (self.get(url) or {}).get("changelog", params) def issue_add_json_worklog(self, key, worklog): """ @@ -1320,7 +1325,7 @@ def update_issue_field(self, key, fields="*all", notify_users=True): def bulk_update_issue_field(self, key_list, fields="*all"): """ - :param key_list=list of issues with common filed to be updated + :param key_list: list of issues with common filed to be updated :param fields: common fields to be updated return Boolean True/False """ @@ -1336,6 +1341,33 @@ def bulk_update_issue_field(self, key_list, fields="*all"): return False return True + def issue_field_value_append(self, issue_id_or_key, field, value, notify_users=True): + """ + Add value to a multiple value field + + :param issue_id_or_key: str Issue id or issue key + :param field: str Field key ("customfield_10000") + :param value: str A value which need to append (use python value types) + :param notify_users: bool OPTIONAL if True, use project's default notification scheme to notify users via email. + if False, do not send any email notifications. (only works with admin privilege) + """ + base_url = self.resource_url("issue") + params = {"notifyUsers": True if notify_users else False} + current_value = self.issue_field_value(key=issue_id_or_key, field=field) + + if current_value: + new_value = current_value + [value] + else: + new_value = [value] + + fields = {"{}".format(field): new_value} + + return self.put( + "{base_url}/{key}".format(base_url=base_url, key=issue_id_or_key), + data={"fields": fields}, + params=params, + ) + def get_issue_labels(self, issue_key): """ Get issue labels. @@ -2407,7 +2439,6 @@ def projects(self, included_archived=None, expand=None): self._get_paged( self.resource_url("project/search"), params, - paging_workaround=True, ) ) else: @@ -3639,6 +3670,34 @@ def update_plugin_license(self, plugin_key, raw_license): data = {"rawLicense": raw_license} return self.put(url, data=data, headers=app_headers) + def disable_plugin(self, plugin_key): + """ + Disable a plugin + :param plugin_key: + :return: + """ + app_headers = { + "X-Atlassian-Token": "nocheck", + "Content-Type": "application/vnd.atl.plugins+json", + } + url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) + data = {"status": "disabled"} + return self.put(url, data=data, headers=app_headers) + + def enable_plugin(self, plugin_key): + """ + Enable a plugin + :param plugin_key: + :return: + """ + app_headers = { + "X-Atlassian-Token": "nocheck", + "Content-Type": "application/vnd.atl.plugins+json", + } + url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) + data = {"status": "enabled"} + return self.put(url, data=data, headers=app_headers) + def get_all_permissionschemes(self, expand=None): """ Returns a list of all permission schemes. @@ -5267,3 +5326,34 @@ def health_check(self): # check as support tools response = self.get("rest/supportHealthCheck/1.0/check/") return response + + def duplicated_account_checks_detail(self): + """ + Health check: Duplicate user accounts detail + https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html + :return: + """ + response = self.get("rest/api/2/user/duplicated/list") + return response + + def duplicated_account_checks_flush(self): + """ + Health check: Duplicate user accounts by flush + The responses returned by the count and list methods are stored in the duplicate users cache for 10 minutes. + The cache is flushed automatically every time a directory + is added, deleted, enabled, disabled, reordered, or synchronized. + https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html + :return: + """ + params = {"flush": "true"} + response = self.get("rest/api/2/user/duplicated/list", params=params) + return response + + def duplicated_account_checks_count(self): + """ + Health check: Duplicate user accounts count + https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html + :return: + """ + response = self.get("rest/api/2/user/duplicated/count") + return response diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index e92baa7f7..677a99313 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -13,6 +13,7 @@ from requests_oauthlib import OAuth1, OAuth2 from six.moves.urllib.parse import urlencode from urllib3.util import Retry +import urllib3 from atlassian.request_utils import get_default_logger @@ -117,7 +118,7 @@ def __init__( self._session = requests.Session() else: self._session = session - if backoff_and_retry: + if backoff_and_retry and int(urllib3.__version__.split(".")[0]) >= 2: # Note: we only retry on status and not on any of the # other supported reasons retries = Retry( @@ -537,7 +538,9 @@ def raise_for_status(self, response): else: error_msg_list = j.get("errorMessages", list()) errors = j.get("errors", dict()) - if isinstance(errors, dict): + if isinstance(errors, dict) and "message" not in errors: + error_msg_list.extend(errors.values()) + elif isinstance(errors, dict) and "message" in errors: error_msg_list.append(errors.get("message", "")) elif isinstance(errors, list): error_msg_list.extend([v.get("message", "") if isinstance(v, dict) else v for v in errors]) diff --git a/docs/bamboo.rst b/docs/bamboo.rst index b4e968165..c690683bc 100644 --- a/docs/bamboo.rst +++ b/docs/bamboo.rst @@ -19,6 +19,7 @@ Projects & Plans plans(expand=None, favourite=False, clover_enabled=False, start_index=0, max_results=25) # Get information about plan build directory + # Returns information about the directories where artifacts, build logs, and build results will be stored. plan_directory_info(plan_key) # Get plan information @@ -36,6 +37,12 @@ Projects & Plans # Enable plan enable_plan(plan_key) + # Retrieve information for project specified as project key. + get_project(project_key) + + # Delete project + delete_project(project_key) + Branches ------------- @@ -89,24 +96,27 @@ Build results # Execute build execute_build(plan_key, stage=None, execute_all_stages=True, custom_revision=None, **bamboo_variables) + # Stop Build + stop_build(plan_key) + Comments & Labels ----------------- .. code-block:: python - # Get comments for the build + # Get comments for a specific build comments(project_key, plan_key, build_number, start_index=0, max_results=25) - # Make a comment - create_comment(project_key, plan_key, build_number, comment, author=None) + # Create a comment for a specific build + create_comment(project_key, plan_key, build_number, comment) # Get labels for a build labels(project_key, plan_key, build_number, start_index=0, max_results=25) - # Create a label + # Create a label for a specific build create_label(project_key, plan_key, build_number, label) - # Delete a label + # Delete a label for a specific build delete_label(project_key, plan_key, build_number, label) Deployments @@ -114,7 +124,7 @@ Deployments .. code-block:: python - # Get deployment projects + # Get all deployment projects. deployment_projects() # Get deployments for a single project @@ -129,6 +139,12 @@ Deployments # Delete deployment project delete_deployment_project(project_id) + # Returns deployment projects associated with a build plan. + get_deployment_projects_for_plan(plan_key) + + # Triggers a deployment for a release version on the given environment. + trigger_deployment_for_version_on_environment(version_id, environment_id) + Users & Groups -------------- @@ -213,7 +229,7 @@ Agents agent_enable(agent_id=123456) # Disable agent - agent_enable(agent_id=123456) + agent_disable(agent_id) # Get agent details agent_details(agent_id=123456) @@ -247,9 +263,15 @@ Other actions reports(max_results=25) # Get charts - hart(report_key, build_keys, group_by_period, date_filter=None, date_from=None, date_to=None, + chart(report_key, build_keys, group_by_period, date_filter=None, date_from=None, date_to=None, width=None, height=None, start_index=9, max_results=25) + # Returns status of the current indexing operation. + reindex() + + # Kicks off a reindex. + stop_reindex() + # Health check health_check() @@ -288,3 +310,34 @@ Elastic Bamboo "elasticInstanceManagement": {"type": "Disabled"}, "uploadAwsAccountIdentifierToElasticInstances": False, "elasticAutoTermination": { "enabled": True, "shutdownDelay": 300}}) +Plugins information +------------------- + +.. code-block:: python + + # Get plugins information + get_plugins_info() + + # Get plugin information + get_plugin_info(plugin_key) + + # Provide plugin license information + get_plugin_license_info(plugin_key) + + # Provide plugin path for upload into Bamboo e.g. useful for auto deploy + upload_plugin(plugin_path) + + # Disable plugin + disable_plugin(plugin_key) + + # Enable plugin + enable_plugin(plugin_key) + + # Uninstall plugin + delete_plugin(plugin_key) + + # Check plugin manager status + get_plugin_module_info(plugin_key, module_key) + + # Update license for plugin (app) + update_plugin_license(plugin_key, raw_license) diff --git a/docs/crowd.rst b/docs/crowd.rst index 563d0e1d5..30bc12ddc 100644 --- a/docs/crowd.rst +++ b/docs/crowd.rst @@ -38,3 +38,16 @@ Manage groups # Get group's members crowd.group_members(group, kind='direct', max_results=99999) +Get memberships +---------------- + +.. code-block:: python + + # Retrieves full details of all group memberships. + # Return data structure: + # { + # GroupName1: [ Member1, Member2, ... ], + # GroupName2: [ MemberA, MemberB, ... ], + # ... + # } + crowd.memberships diff --git a/docs/jira.rst b/docs/jira.rst index 436993543..bfa484805 100644 --- a/docs/jira.rst +++ b/docs/jira.rst @@ -211,7 +211,7 @@ Manage projects # Returns a list of active users who have browse permission for a project that matches the search string for username. # Using " " string (space) for username gives All the active users who have browse permission for a project - jira.get_users_with_browse_permission_to_a_project(self, username, issue_key=None, project_key=None, start=0, limit=100) + jira.get_users_with_browse_permission_to_a_project(username, issue_key=None, project_key=None, start=0, limit=100) Manage issues ------------- @@ -228,8 +228,16 @@ Manage issues fields = {'summary': 'New summary'} jira.update_issue_field(key, fields, notify_users=True) + # Bulk update issue field + jira.bulk_update_issue_field(key_list, fields="*all") + + # Append value to issue field + field = "customfield_10000" + value = {"name": "username"} + jira.issue_field_value_append(issue_id_or_key, field, value, notify_users=True) + # Get existing custom fields or find by filter - jira.get_custom_fields(self, search=None, start=1, limit=50): + jira.get_custom_fields(search=None, start=1, limit=50): # Check issue exists jira.issue_exists(issue_key) @@ -286,7 +294,7 @@ Manage issues jira.issue_createmeta_issuetypes(project, start=None, limit=None) # Get create field metadata for a project and issue type id - jira.issue_createmeta_fieldtypes(self, project, issue_type_id, start=None, limit=None) + jira.issue_createmeta_fieldtypes(project, issue_type_id, start=None, limit=None) # Create Issue Link data = { @@ -467,10 +475,10 @@ Manage Sprints jira.get_all_issues_for_sprint_in_board(board_id, state=None, start=0, limit=50) # Get all versions for sprint in board - jira.get_all_versions_from_board(self, board_id, released="true", start=0, limit=50) + jira.get_all_versions_from_board(board_id, released="true", start=0, limit=50) # Create sprint - jira.jira.create_sprint(sprint_name, origin_board_id, start_datetime, end_datetime, goal) + jira.create_sprint(sprint_name, origin_board_id, start_datetime, end_datetime, goal) # Rename sprint jira.rename_sprint(sprint_id, name, start_date, end_date) @@ -580,6 +588,22 @@ Cluster methods (only for DC edition) # Request current index from node (the request is processed asynchronously). jira.request_current_index_from_node(node_id) +Health checks methods (only for on-prem edition) +------------------------------------------------ +.. code-block:: python + + # Get health status of Jira. + jira.health_check() + + # Health check: Duplicate user accounts detail + jira.duplicated_account_checks_detail() + + # Health check: Duplicate user accounts by flush + jira.duplicated_account_checks_flush() + + # Health check: Duplicate user accounts count + jira.duplicated_account_checks_count() + TEMPO ---------------------- .. code-block:: python diff --git a/pyproject.toml b/pyproject.toml index 50fd690f9..26ed17880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ [tool.black] target-version = ['py36', 'py37', 'py38', 'py39'] line-length = 120 -include_trailing_comma = false include = '(atlassian|examples|tests)\/.*(\.py|GET|POST)' [tool.pylint.format] @@ -617,4 +616,4 @@ min-public-methods = 2 # "BaseException, Exception". overgeneral-exceptions = '''BaseException, Exception -''' \ No newline at end of file +''' diff --git a/requirements-dev.txt b/requirements-dev.txt index ca98e39b1..9a22795a1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,9 +10,12 @@ coverage codecov # used for example confluence attach file python-magic +pylint +mypy +bandit +doc8 # On October 4, 2022 importlib-metadata released importlib-metadata 5.0.0 and in version 5.0.0 # They have Deprecated EntryPoints and that's why you are facing this error. importlib-metadata<=4.13.0 -# Add this package to search string in json -jmespath + diff --git a/requirements.txt b/requirements.txt index d6b0c6199..0092d3d55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -deprecated +Deprecated requests>=2.8.1 six oauthlib -requests_oauthlib +requests-oauthlib requests-kerberos==0.14.0 +# Add this package to search string in json jmespath beautifulsoup4 -lxml