diff --git a/.cspell.json b/.cspell.json index 8999649f094..b775aee7ae4 100644 --- a/.cspell.json +++ b/.cspell.json @@ -358,6 +358,7 @@ "PSHOME", "PSSA", "PULLREQUEST", + "pullrequests", "PULLREQUESTID", "PWSH", "PYLINT", @@ -367,6 +368,7 @@ "PYTHONPATH", "PYTHONPYCACHEPREFIX", "PYTYPE", + "pagelen", "Pansino", "Parallelly", "Philipp", diff --git a/CHANGELOG.md b/CHANGELOG.md index 16398b3879e..32edebdefb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Note: Can be used with `oxsecurity/megalinter@beta` in your GitHub Action mega-linter.yml file, or with `oxsecurity/megalinter:beta` docker image -- New reporter **MARKDOWN_SUMMARY_REPORTER**, allows saving MegaLinter results summary as a markdown file. This file can be further used to add comments on the pull request (PR) from Jenkins and other continuous integration (CI) tools. +- New reporter **MARKDOWN_SUMMARY_REPORTER**, allows saving MegaLinter results summary as a markdown file. This file can be further utilised to add comments on the pull request (PR) from Jenkins and other continuous integration (CI) tools. + +- New reporter **BITBUCKET_COMMENT_REPORTER** allowing to post MegaLinter results as comments on Bitbucket pull requests. - Core - mega-linter-runner: Remove container by default, except of `no-remove-container` option is sent diff --git a/README.md b/README.md index 09fde9f488b..51417bb5179 100644 --- a/README.md +++ b/README.md @@ -1287,6 +1287,7 @@ MegaLinter can generate various reports that you can activate / deactivate and c | [GitHub Pull Request comments](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/GitHubCommentReporter.md) | MegaLinter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if GitHub Action | | [Gitlab Merge Request comments](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/GitlabCommentReporter.md) | Mega-Linter posts a comment on the MR with a summary of lint results, and links to detailed logs | Active if in Gitlab CI | | [Azure Pipelines Pull Request comments](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/AzureCommentReporter.md) | Mega-Linter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if in Azure Pipelines | +| [Bitbucket Pull Request comments](docs/reporters/BitbucketCommentReporter.md) | Mega-Linter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if in Bitbucket CI | | [Updated sources](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/UpdatedSourcesReporter.md) | Zip containing **all formatted and autofixed sources** so you can extract them in your repository | Active | | [IDE Configuration](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/ConfigReporter.md) | Apply MegaLinter configuration in your local IDE with linter config files and IDE extensions | Active | | [GitHub Status](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/GitHubStatusReporter.md) | One GitHub status by linter on the PR, with links to detailed logs | Active if GitHub Action | @@ -1721,6 +1722,10 @@ MegaLinter can be run locally thanks to [mega-linter-runner](https://megalinter. ![Screenshot](https://github.com/oxsecurity/megalinter/blob/main/docs/assets/images/GitlabCommentReporter.jpg?raw=true) +- [Bitbucket Pull Request comments](docs/reporters/BitbucketCommentReporter.md) + +![Screenshot](docs/assets/images/BitbucketCommentReporter.png) + - [Azure Pull Request comments](https://github.com/oxsecurity/megalinter/tree/main/docs/reporters/AzureCommentReporter.md) ![Screenshot](https://github.com/oxsecurity/megalinter/blob/main/docs/assets/images/AzureCommentReporter.jpg?raw=true) diff --git a/docs/assets/images/BitbucketCommentReporter.png b/docs/assets/images/BitbucketCommentReporter.png new file mode 100644 index 00000000000..a2d716b5937 Binary files /dev/null and b/docs/assets/images/BitbucketCommentReporter.png differ diff --git a/docs/mega-linter-vs-super-linter.md b/docs/mega-linter-vs-super-linter.md index 2510300ae77..1056c4e623a 100644 --- a/docs/mega-linter-vs-super-linter.md +++ b/docs/mega-linter-vs-super-linter.md @@ -65,6 +65,10 @@ MegaLinter can be run locally thanks to [mega-linter-runner](https://megalinter. ![Screenshot](https://github.com/oxsecurity/megalinter/blob/main/docs/assets/images/AzureCommentReporter.jpg?raw=true) +- [Bitbucket Pull Request comments](./reporters/BitbucketCommentReporter.md) + +![Screenshot](./assets/images/BitbucketCommentReporter.png) + - [Markdown Summary](docs/reporters/MarkdownSummaryReporter.md) ![Screenshot](docs/assets/images/MarkdownSummaryReporter_2.png) diff --git a/docs/reporters.md b/docs/reporters.md index 3fcd00c38c1..4c5df8098a0 100644 --- a/docs/reporters.md +++ b/docs/reporters.md @@ -10,21 +10,21 @@ description: "List of all available reporters: Text, SARIF, TAP, GitHub, Gitlab, MegaLinter can generate various reports that you can activate / deactivate and customize -| Reporter | Description | Default | -|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|------------------------------| -| [Text files](reporters/TextReporter.md) | Generates **One log file by linter** + suggestions for fixes that can not be automated | Active | -| [SARIF (beta)](reporters/SarifReporter.md) | Generates an aggregated SARIF output file | Inactive | -| [GitHub Pull Request comments](reporters/GitHubCommentReporter.md) | MegaLinter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if GitHub Action | -| [Gitlab Merge Request comments](reporters/GitlabCommentReporter.md) | Mega-Linter posts a comment on the MR with a summary of lint results, and links to detailed logs | Active if in Gitlab CI | -| [Azure Pipelines Pull Request comments](reporters/AzureCommentReporter.md) | Mega-Linter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if in Azure Pipelines | -| [Updated sources](reporters/UpdatedSourcesReporter.md) | Zip containing **all formatted and autofixed sources** so you can extract them in your repository | Active | -| [IDE Configuration](reporters/ConfigReporter.md) | Apply MegaLinter configuration in your local IDE with linter config files and IDE extensions | Active | -| [GitHub Status](reporters/GitHubStatusReporter.md) | One GitHub status by linter on the PR, with links to detailed logs | Active if GitHub Action | -| [File.io](reporters/FileIoReporter.md) | **Send reports on file.io** so you can access them with a simple hyperlink provided at the end of console log | Inactive | -| [JSON](reporters/JsonReporter.md) | Generates a JSON output report file | Inactive | -| [Email](reporters/EmailReporter.md) | Receive **all reports on your e-mail**, if you can not use artifacts | Active | -| [TAP files](reporters/TapReporter.md) | One file by linter following [**Test Anything Protocol**](https://testanything.org/) format | Active | -| [Console](reporters/ConsoleReporter.md) | **Execution logs** visible in **console** with **summary table** and **links to other reports** at the end | Active | -| [Markdown Summary](docs/reporters/MarkdownSummaryReporter.md) | Generates a Markdown summary report file | Inactive | - +| Reporter | Description | Default | +|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|----------------------------------| +| [Text files](reporters/TextReporter.md) | Generates **One log file by linter** + suggestions for fixes that can not be automated | Active | +| [SARIF (beta)](reporters/SarifReporter.md) | Generates an aggregated SARIF output file | Inactive | +| [GitHub Pull Request comments](reporters/GitHubCommentReporter.md) | MegaLinter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if GitHub Action | +| [Gitlab Merge Request comments](reporters/GitlabCommentReporter.md) | Mega-Linter posts a comment on the MR with a summary of lint results, and links to detailed logs | Active if in Gitlab CI | +| [Azure Pipelines Pull Request comments](reporters/AzureCommentReporter.md) | Mega-Linter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if in Azure Pipelines | +| [Bitbucket Pull Request comments](./reporters/BitbucketCommentReporter.md) | Mega-Linter posts a comment on the PR with a summary of lint results, and links to detailed logs | Active if in Bitbucket Pipelines | +| [Updated sources](reporters/UpdatedSourcesReporter.md) | Zip containing **all formatted and autofixed sources** so you can extract them in your repository | Active | +| [IDE Configuration](reporters/ConfigReporter.md) | Apply MegaLinter configuration in your local IDE with linter config files and IDE extensions | Active | +| [GitHub Status](reporters/GitHubStatusReporter.md) | One GitHub status by linter on the PR, with links to detailed logs | Active if GitHub Action | +| [File.io](reporters/FileIoReporter.md) | **Send reports on file.io** so you can access them with a simple hyperlink provided at the end of console log | Inactive | +| [JSON](reporters/JsonReporter.md) | Generates a JSON output report file | Inactive | +| [Email](reporters/EmailReporter.md) | Receive **all reports on your e-mail**, if you can not use artifacts | Active | +| [TAP files](reporters/TapReporter.md) | One file by linter following [**Test Anything Protocol**](https://testanything.org/) format | Active | +| [Console](reporters/ConsoleReporter.md) | **Execution logs** visible in **console** with **summary table** and **links to other reports** at the end | Active | +| [Markdown Summary](./reporters/MarkdownSummaryReporter.md) | Generates a Markdown summary report file | Inactive | diff --git a/docs/reporters/BitbucketCommentReporter.md b/docs/reporters/BitbucketCommentReporter.md new file mode 100644 index 00000000000..78d80dfef4c --- /dev/null +++ b/docs/reporters/BitbucketCommentReporter.md @@ -0,0 +1,29 @@ +--- +title: Bitbucket Pull Request Comments Reporter for MegaLinter +description: Posts MegaLinter SAST results summary in the comments of the related Bitbucket Pull Request (if existing) +--- + +# Bitbucket Comment Reporter + +Posts MegaLinter results summary in the comments of the related Bitbucket pull request (if existing) + +## Usage + +Click on hyperlinks to access detailed logs (click on **Download** in **Artifacts section** at the left of a CI job page) + +![Screenshot](../assets/images/BitbucketCommentReporter.png) + +After a first MegaLinter run, a comment is posted on the PR. To avoid multiplicating MegaLinter PR comments, future MegaLinter runs will update the existing PR comment instead of posting a new one. + +If you really want a new PR comment for each MegaLinter run, define variable `BITBUCKET_COMMENT_REPORTER_OVERWRITE_COMMENT` to `false`. + +## Configuration + +- [Create a Repository Access Token](https://support.atlassian.com/bitbucket-cloud/docs/create-a-repository-access-token/) with scope **Pull-requests: Write** +- Paste the access token in a [masked CI/CD variable](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/) named **BITBUCKET_REPO_ACCESS_TOKEN** in your project (repository) + +| Variable | Description | Default value | +|----------------------------------------------|----------------------------------------------------------------------------------------------|---------------| +| BITBUCKET_COMMENT_REPORTER | Activates/deactivates reporter | `true` | +| BITBUCKET_REPO_ACCESS_TOKEN | Must contain a Bitbucket repository access token defined with api access | | +| BITBUCKET_COMMENT_REPORTER_OVERWRITE_COMMENT | Set to false to not overwrite existing comments in case of new runs on the same Pull Request | `true` | diff --git a/megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json b/megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json index 2403f3ebe92..9d0cf16be3d 100644 --- a/megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json +++ b/megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json @@ -1416,6 +1416,20 @@ "title": "Including regex filter for BICEP descriptor", "type": "string" }, + "BITBUCKET_COMMENT_REPORTER": { + "$id": "#/properties/BITBUCKET_COMMENT_REPORTER", + "default": true, + "description": "Posts Mega-Linter results summary in the comments of the related pull request (if existing)", + "title": "Activate Bitbucket PR Comments reporter", + "type": "boolean" + }, + "BITBUCKET_COMMENT_REPORTER_OVERWRITE_COMMENT": { + "$id": "#/properties/BITBUCKET_COMMENT_REPORTER_OVERWRITE_COMMENT", + "default": true, + "description": "Set to false to disable the overwrite of existing MegaLinter Pull Request comment in case of new run", + "title": "Overwrite Bitbucket Pull Request Comment", + "type": "boolean" + }, "CLEAR_REPORT_FOLDER": { "$id": "#/properties/CLEAR_REPORT_FOLDER", "default": false, diff --git a/megalinter/reporters/BitbucketCommentReporter.py b/megalinter/reporters/BitbucketCommentReporter.py new file mode 100644 index 00000000000..a94e9241770 --- /dev/null +++ b/megalinter/reporters/BitbucketCommentReporter.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Bitbucket Comment reporter +Post a comment on Bitbucket Merge Requests +""" +import logging +import urllib.parse + +import requests +from megalinter import Reporter, config +from megalinter.utils_reporter import build_markdown_summary + + +class BitbucketCommentReporter(Reporter): + name = "BITBUCKET_COMMENT" + scope = "mega-linter" + + BITBUCKET_API = "https://api.bitbucket.org/2.0" + + def manage_activation(self): + if ( + config.get(self.master.request_id, + "BITBUCKET_COMMENT_REPORTER", "true") + == "true" + ): + self.is_active = True + else: + self.is_active = False + + def produce_report(self): + # Post comment on Bitbucket pull request + + BITBUCKET_REPO_ACCESS_TOKEN = config.get( + self.master.request_id, "BITBUCKET_REPO_ACCESS_TOKEN", "" + ) + bitbucket_repo_fullname = config.get( + self.master.request_id, "BITBUCKET_REPO_FULL_NAME", "" + ) + bitbucket_project_url = config.get( + self.master.request_id, "BITBUCKET_GIT_HTTP_ORIGIN", "" + ) + bitbucket_pipeline_job_number = config.get( + self.master.request_id, "BITBUCKET_BUILD_NUMBER", "" + ) + bitbucket_pr_id = config.get( + self.master.request_id, "BITBUCKET_PR_ID", "" + ) + pipeline_step_run_uuid = config.get( + self.master.request_id, "BITBUCKET_STEP_UUID", "" + ) + + if ( + BITBUCKET_REPO_ACCESS_TOKEN == "" + or bitbucket_repo_fullname == "" + or bitbucket_project_url == "" + or bitbucket_pipeline_job_number == "" + or bitbucket_pr_id == "" + or pipeline_step_run_uuid == "" + ): + logging.info( + "[Bitbucket Comment Reporter] Required Bitbucket CI CD variables not found, so skipped post of PR " + "comment" + ) + return + + pipeline_step_run_uuid = urllib.parse.quote(pipeline_step_run_uuid) + pipeline_step_run_url = ( + f"{bitbucket_project_url}/pipelines/results/" + f"{bitbucket_pipeline_job_number}/steps/{pipeline_step_run_uuid}" + ) + + p_r_msg = build_markdown_summary(self, pipeline_step_run_url) + bitbucket_auth_header = { + "Authorization": f"Bearer {BITBUCKET_REPO_ACCESS_TOKEN}" + } + + # To-Do: Ignore if PR is already merged + try: + pr = requests.get( + f"{self.BITBUCKET_API}/repositories/{bitbucket_repo_fullname}/pullrequests/{bitbucket_pr_id}", + headers=bitbucket_auth_header + ) + if pr.status_code != 200: + pr.raise_for_status() + pr_state = pr.json().get('state', '') + + if pr_state.lower() != 'open': + logging.info( + "[Bitbucket Comment Reporter] PR is not in OPEN state, skipped posting comment" + ) + return + except Exception as e: + logging.warning( + "[Bitbucket Comment Reporter] Unable to get PR details" + ) + self.display_auth_error(e) + return + + # List comments on pull request + comment_id = None + if ( + config.get( + self.master.request_id, + "BITBUCKET_COMMENT_REPORTER_OVERWRITE_COMMENT", + "true", + ) + == "true" + ): + try: + comments = requests.get( + f"{self.BITBUCKET_API}/repositories/{bitbucket_repo_fullname}/" + f"pullrequests/{bitbucket_pr_id}/comments?pagelen=100", + headers=bitbucket_auth_header + ) + if comments.status_code != 200: + pr.raise_for_status() + existing_comments = comments.json().get('values', []) + except Exception as e: + logging.warning( + "[Bitbucket Comment Reporter] Unable to fetch existing comments on PR" + str(e) + ) + return + # Check if there is already a MegaLinter comment + for comment in existing_comments: + if "MegaLinter is graciously provided by" in comment.get('content', {}).get('raw', ''): + comment_id = comment.get('id', None) + break + + # Process comment + try: + data = { + "content": { + "raw": p_r_msg + } + } + if comment_id is not None: + # Existing comment + logging.debug(f"Updated Bitbucket comment: {p_r_msg}") + logging.info( + f"[Bitbucket Comment Reporter] Updated existing comment summary " + f"on {bitbucket_repo_fullname} #PR {bitbucket_pr_id}" + ) + requests.put( + f"{self.BITBUCKET_API}/repositories/{bitbucket_repo_fullname}/pullrequests/" + f"{bitbucket_pr_id}/comments/{comment_id}", + headers=bitbucket_auth_header, + json=data, + ) + else: + # New comment + requests.post( + f"{self.BITBUCKET_API}/repositories/{bitbucket_repo_fullname}/pullrequests/" + f"{bitbucket_pr_id}/comments", + headers=bitbucket_auth_header, + json=data, + ) + logging.info( + f"[Bitbucket Comment Reporter] PR comment summary added on {bitbucket_repo_fullname} " + f"#PR {bitbucket_pr_id}" + ) + + except Exception as e: + logging.warning( + "[Bitbucket Comment Reporter] Error while posting comment" + ) + self.display_auth_error(e) + + def display_auth_error(self, e): + logging.error( + "[Bitbucket Comment Reporter] You may need to define a masked " + "Bitbucket CI/CD variable BITBUCKET_REPO_ACCESS_TOKEN containing " + "a access token with scope 'Pull-requests: write' " + "(if already defined, your access token is probably invalid): " + str(e) + ) diff --git a/mkdocs.yml b/mkdocs.yml index 73c79e91d89..58880fbab9e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -345,6 +345,7 @@ nav: - "GitHub Pull Request comments": "reporters/GitHubCommentReporter.md" - "Gitlab Merge Request comments": "reporters/GitlabCommentReporter.md" - "Azure Pull Request comments": "reporters/AzureCommentReporter.md" + - "Bitbucket Pull Request comments": "reporters/BitbucketCommentReporter.md" - "GitHub Status": "reporters/GitHubStatusReporter.md" - "SARIF Reporter": "reporters/SarifReporter.md" - "Updated sources": "reporters/UpdatedSourcesReporter.md" @@ -354,6 +355,7 @@ nav: - "TAP files": "reporters/TapReporter.md" - "Console": "reporters/ConsoleReporter.md" - "JSON": "reporters/JsonReporter.md" + - "Markdown Summary": "reporters/MarkdownSummaryReporter.md" - "Flavors": - "All flavors": "flavors.md" # flavors-start