From a6b84b5ccc04c35c7c8e4160e8cd833b9c431ccf Mon Sep 17 00:00:00 2001 From: Aleksei Apaseev Date: Tue, 2 Jul 2024 17:38:41 +0800 Subject: [PATCH] feat(ci): add retry job functionality to dynamic pipeline report Introduced changes: - add a manual ci job to retry failed jobs. - refactor js scripts in report template - extract the CI ENV vars related to the report generation script to the predefined constants.py module - introduce a new action "retry_failed_jobs" in helper script "gitlab_api.py" --- .gitlab-ci.yml | 1 + .gitlab/ci/common.yml | 1 + .gitlab/ci/retry_failed_jobs.yml | 14 + tools/ci/dynamic_pipelines/constants.py | 10 + tools/ci/dynamic_pipelines/models.py | 2 +- tools/ci/dynamic_pipelines/report.py | 27 +- .../templates/report.template.html | 214 ++++++++------ .../templates/retry-jobs.png | Bin 0 -> 17552 bytes .../expected_job_report.html | 216 ++++++++------ .../expected_target_test_report.html | 264 ++++++++++-------- .../test_report_generator.py | 2 +- tools/ci/dynamic_pipelines/utils.py | 37 ++- tools/ci/python_packages/gitlab_api.py | 31 +- 13 files changed, 512 insertions(+), 307 deletions(-) create mode 100644 .gitlab/ci/retry_failed_jobs.yml create mode 100644 tools/ci/dynamic_pipelines/templates/retry-jobs.png diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fc0403e0c4..0e449ec7e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,4 +31,5 @@ include: - '.gitlab/ci/host-test.yml' - '.gitlab/ci/deploy.yml' - '.gitlab/ci/post_deploy.yml' + - '.gitlab/ci/retry_failed_jobs.yml' - '.gitlab/ci/test-win.yml' diff --git a/.gitlab/ci/common.yml b/.gitlab/ci/common.yml index b43e1d3d57..223a1f9dd9 100644 --- a/.gitlab/ci/common.yml +++ b/.gitlab/ci/common.yml @@ -12,6 +12,7 @@ stages: - test_deploy - deploy - post_deploy + - retry_failed_jobs variables: # System environment diff --git a/.gitlab/ci/retry_failed_jobs.yml b/.gitlab/ci/retry_failed_jobs.yml new file mode 100644 index 0000000000..28a2c1e06f --- /dev/null +++ b/.gitlab/ci/retry_failed_jobs.yml @@ -0,0 +1,14 @@ +retry_failed_jobs: + stage: retry_failed_jobs + tags: [shiny, fast_run] + image: $ESP_ENV_IMAGE + dependencies: null + before_script: [] + cache: [] + extends: [] + script: + - echo "Retrieving and retrying all failed jobs for the pipeline..." + - python tools/ci/python_packages/gitlab_api.py retry_failed_jobs $CI_MERGE_REQUEST_PROJECT_ID --pipeline_id $CI_PIPELINE_ID + when: manual + needs: + - generate_failed_jobs_report diff --git a/tools/ci/dynamic_pipelines/constants.py b/tools/ci/dynamic_pipelines/constants.py index 21f903338d..bbeefcfc4d 100644 --- a/tools/ci/dynamic_pipelines/constants.py +++ b/tools/ci/dynamic_pipelines/constants.py @@ -29,8 +29,18 @@ REPORT_TEMPLATE_FILEPATH = os.path.join( IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'report.template.html' ) +RETRY_JOB_PICTURE_PATH = 'tools/ci/dynamic_pipelines/templates/retry-jobs.png' +RETRY_JOB_TITLE = '\n\nRetry failed jobs with with help of "retry_failed_jobs" stage of the pipeline:' +RETRY_JOB_PICTURE_LINK = '![Retry Jobs Image]({pic_url})' + BUILD_ONLY_LABEL = 'For Maintainers: Only Build Tests' KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH = os.path.join( IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'known_generate_test_child_pipeline_warnings.yml' ) + +CI_JOB_TOKEN = os.getenv('CI_JOB_TOKEN', '') +CI_DASHBOARD_API = os.getenv('CI_DASHBOARD_API', '') +CI_PAGES_URL = os.getenv('CI_PAGES_URL', '') +CI_PROJECT_URL = os.getenv('CI_PROJECT_URL', '') +CI_MERGE_REQUEST_SOURCE_BRANCH_SHA = os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', '') diff --git a/tools/ci/dynamic_pipelines/models.py b/tools/ci/dynamic_pipelines/models.py index b40d827448..5565917940 100644 --- a/tools/ci/dynamic_pipelines/models.py +++ b/tools/ci/dynamic_pipelines/models.py @@ -164,7 +164,7 @@ class TestCase: 'name': node.attrib['name'], 'file': node.attrib.get('file'), 'time': float(node.attrib.get('time') or 0), - 'ci_job_url': node.attrib.get('ci_job_url') or '', + 'ci_job_url': node.attrib.get('ci_job_url') or 'Not found', 'ci_dashboard_url': f'{grafana_base_url}?{encoded_params}', 'dut_log_url': node.attrib.get('dut_log_url') or 'Not found', } diff --git a/tools/ci/dynamic_pipelines/report.py b/tools/ci/dynamic_pipelines/report.py index be8fce0af3..b36c540978 100644 --- a/tools/ci/dynamic_pipelines/report.py +++ b/tools/ci/dynamic_pipelines/report.py @@ -19,12 +19,16 @@ from prettytable import PrettyTable from .constants import COMMENT_START_MARKER from .constants import REPORT_TEMPLATE_FILEPATH +from .constants import RETRY_JOB_PICTURE_LINK +from .constants import RETRY_JOB_PICTURE_PATH +from .constants import RETRY_JOB_TITLE from .constants import TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME from .models import GitlabJob from .models import TestCase from .utils import fetch_failed_testcases_failure_ratio from .utils import format_permalink -from .utils import get_report_url +from .utils import get_artifacts_url +from .utils import get_repository_file_url from .utils import is_url from .utils import load_known_failure_cases @@ -69,13 +73,14 @@ class ReportGenerator: # for example, {URL}/-/esp-idf/-/jobs/{id}/artifacts/list_job_84.txt # CI_PAGES_URL is {URL}/esp-idf, which missed one `-` - report_url: str = get_report_url(job_id, output_filepath) + report_url: str = get_artifacts_url(job_id, output_filepath) return report_url def generate_html_report(self, table_str: str) -> str: # we're using bootstrap table table_str = table_str.replace( - '', '
' + '
', + '
', ) with open(REPORT_TEMPLATE_FILEPATH) as fr: template = fr.read() @@ -245,20 +250,23 @@ class ReportGenerator: if self.mr is None: print('No MR found, skip posting comment') return - + retry_job_picture_comment = (f'{RETRY_JOB_TITLE}\n\n' + f'{RETRY_JOB_PICTURE_LINK}').format(pic_url=get_repository_file_url(RETRY_JOB_PICTURE_PATH)) + del_retry_job_pic_pattern = re.escape(RETRY_JOB_TITLE) + r'.*?' + re.escape(f'{RETRY_JOB_PICTURE_PATH})') for note in self.mr.notes.list(iterator=True): if note.body.startswith(COMMENT_START_MARKER): updated_str = re.sub(self.REGEX_PATTERN.format(self.title), comment, note.body) if updated_str == note.body: # not updated updated_str = f'{note.body.strip()}\n\n{comment}' - note.body = updated_str + updated_str = re.sub(del_retry_job_pic_pattern, '', updated_str, flags=re.DOTALL) + note.body = updated_str + retry_job_picture_comment note.save() break else: new_comment = f"""{COMMENT_START_MARKER} -{comment}""" +{comment}{retry_job_picture_comment}""" self.mr.notes.create({'body': new_comment}) @@ -526,7 +534,7 @@ class TargetTestReportGenerator(ReportGenerator): 'Test Case', 'Test Script File Path', 'Failure Reason', - 'Failures across all other branches (40 latest testcases)', + 'Cases that failed in other branches as well (40 latest testcases)', 'Dut Log URL', 'Job URL', 'Grafana URL', @@ -534,7 +542,7 @@ class TargetTestReportGenerator(ReportGenerator): row_attrs=['name', 'file', 'failure', 'dut_log_url', 'ci_job_url', 'ci_dashboard_url'], value_functions=[ ( - 'Failures across all other branches (40 latest testcases)', + 'Cases that failed in other branches as well (40 latest testcases)', lambda item: f"{getattr(item, 'latest_failed_count', '')} / {getattr(item, 'latest_total_count', '')}", ) ], @@ -696,11 +704,10 @@ class JobReportGenerator(ReportGenerator): ) ], ) - relevant_failed_jobs_report_url = get_report_url(self.job_id, self.failed_jobs_report_file) + relevant_failed_jobs_report_url = get_artifacts_url(self.job_id, self.failed_jobs_report_file) self.additional_info += self.generate_additional_info_section( self.report_titles_map['failed_jobs'], len(relevant_failed_jobs), relevant_failed_jobs_report_url ) report_str = self.generate_html_report(''.join(report_sections)) - return report_str diff --git a/tools/ci/dynamic_pipelines/templates/report.template.html b/tools/ci/dynamic_pipelines/templates/report.template.html index bb35367f94..cefa904b46 100644 --- a/tools/ci/dynamic_pipelines/templates/report.template.html +++ b/tools/ci/dynamic_pipelines/templates/report.template.html @@ -1,88 +1,132 @@ - + - - - {{title}} - - - - - - - -
{{table}}
- - - - - - - - + th:nth-child(1), + td:nth-child(1) { + width: 5%; + } + th:nth-child(2), + td:nth-child(2), + th:nth-child(3), + td:nth-child(3) { + width: 30%; + } + th, + td { + overflow: hidden; + text-overflow: ellipsis; + } + h2 { + margin-top: 10px; + } + .copy-link-icon { + font-size: 20px; + margin-left: 10px; + color: #8f8f97; + cursor: pointer; + } + .copy-link-icon:hover { + color: #282b2c; + } + + + +
{{table}}
+ + + + + + + diff --git a/tools/ci/dynamic_pipelines/templates/retry-jobs.png b/tools/ci/dynamic_pipelines/templates/retry-jobs.png new file mode 100644 index 0000000000000000000000000000000000000000..a8a60112c01546ea8c8ff06fc5b8d30f83ed4bcd GIT binary patch literal 17552 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 literal 0 HcmV?d00001 diff --git a/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_job_report.html b/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_job_report.html index 3c75ce390f..3c35a534fd 100644 --- a/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_job_report.html +++ b/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_job_report.html @@ -1,38 +1,61 @@ - + - - - Job Report - - - - - - - -

Failed Jobs (Excludes "integration_test" and "target_test" jobs)

+ + + Job Report + + + + + + + +

Failed Jobs (Excludes "integration_test" and "target_test" jobs)

@@ -70,57 +93,78 @@
Job Name
- - - - - + + + + + - - - + + function scrollToHashLocation() { + const hash = window.location.hash; + if (hash) { + setTimeout(() => { + $("html, body").animate( + { scrollTop: $(hash).offset().top }, + 100 + ); + }, 100); + } + } + + function copyPermalink(anchorId) { + const fullUrl = `${window.location.origin}${window.location.pathname}${anchorId}`; + history.pushState(null, null, anchorId); + navigator.clipboard.writeText(fullUrl); + scrollToHashLocation(); + } + + function toggleText(e) { + e.preventDefault(); + const link = $(this), + textSpan = link.siblings(".full-text"), + toggleSpan = link.siblings(".text-toggle"); + const visible = textSpan.is(":visible"); + link.text(visible ? "Show More" : "Show Less"); + textSpan.toggle(); + toggleSpan.toggle(); + } + + function setupTextToggles() { + $("table.table td").each(function () { + var cell = $(this); + if (cell.text().length > 100) { + var originalText = cell.text(); + var displayText = + originalText.substring(0, 100) + "..."; + cell.html( + `${displayText}Show More` + ); + } + }); + } + + diff --git a/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_target_test_report.html b/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_target_test_report.html index c2d0b95c97..dc63e26152 100644 --- a/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_target_test_report.html +++ b/tools/ci/dynamic_pipelines/tests/test_report_generator/reports_sample_data/expected_target_test_report.html @@ -1,44 +1,67 @@ - + - - - Test Report - - - - - - - -

Failed Test Cases on Other branches (Excludes Known Failure Cases)

+ + + Test Report + + + + + + + +

Failed Test Cases on Other branches (Excludes Known Failure Cases)

- + @@ -51,7 +74,7 @@ - + @@ -60,7 +83,7 @@ - + @@ -69,7 +92,7 @@ - + @@ -78,7 +101,7 @@ - + @@ -87,7 +110,7 @@ - + @@ -96,7 +119,7 @@ - + @@ -105,12 +128,12 @@ - +
Test Case Test Script File Path Failure ReasonFailures across all other branches (40 latest testcases)Cases that failed in other branches as well (40 latest testcases) Dut Log URL Job URL Grafana URLfailed on setup with "EOFError" 0 / 40 linkNot found link
pexpect.exceptions.TIMEOUT: Not found "Press ENTER to see the list of tests" Bytes in current buffer (color code eliminated): ce710,len:0x2afc entry 0x403cc710 Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.release.test_esp_timer/dut.txt 0 / 40 linkNot found link
pexpect.exceptions.TIMEOUT: Not found "Press ENTER to see the list of tests" Bytes in current buffer (color code eliminated): 0 d4 000 00x0000 x0000x00 000000 0 Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.default.test_wpa_supplicant_ut/dut.txt 0 / 40 linkNot found link
failed on setup with "EOFError" 3 / 40 linkNot found link
AssertionError: Unity test failed 3 / 40 linkNot found link
pexpect.exceptions.TIMEOUT: Not found "re.compile(b'^[-]+\\s*(\\d+) Tests (\\d+) Failures (\\d+) Ignored\\s*(?POK|FAIL)', re.MULTILINE)" Bytes in current buffer (color code eliminated): Serial port /dev/ttyUSB16 Connecting.... Connecting.... esptool.py v4.7.0 Found 1 serial ports Chip is ESP32-C3 (QFN32) (revision v0.3) Features: WiFi, BLE, Embedded Flash 4MB... (total 6673 bytes) Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.512safe.test_wear_levelling/dut.txt 3 / 40 linkNot found link
pexpect.exceptions.TIMEOUT: Not found "re.compile(b'^[-]+\\s*(\\d+) Tests (\\d+) Failures (\\d+) Ignored\\s*(?POK|FAIL)', re.MULTILINE)" Bytes in current buffer (color code eliminated): Serial port /dev/ttyUSB16 Connecting.... Connecting.... esptool.py v4.7.0 Found 1 serial ports Chip is ESP32-C3 (QFN32) (revision v0.3) Features: WiFi, BLE, Embedded Flash 4MB... (total 24528 bytes) Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.release.test_wear_levelling/dut.txt 3 / 40 linkNot found link

Known Failure Cases

+ onclick="copyPermalink('#known-failure-cases')">
@@ -125,33 +148,33 @@ - + - + - + - +
Test Caseesp32c2.default.test_wpa_supplicant_ut components/wpa_supplicant/test_apps/pytest_wpa_supplicant_ut.py AssertionError: Unity test failedNot found link
esp32c3.release.test_esp_timer components/esp_timer/test_apps/pytest_esp_timer_ut.py pexpect.exceptions.TIMEOUT: Not found "Press ENTER to see the list of tests" Bytes in current buffer (color code eliminated): ce710,len:0x2afc entry 0x403cc710 Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.release.test_esp_timer/dut.txtNot found link
esp32c3.512safe.test_wear_levelling components/wear_levelling/test_apps/pytest_wear_levelling.py pexpect.exceptions.TIMEOUT: Not found "re.compile(b'^[-]+\\s*(\\d+) Tests (\\d+) Failures (\\d+) Ignored\\s*(?POK|FAIL)', re.MULTILINE)" Bytes in current buffer (color code eliminated): Serial port /dev/ttyUSB16 Connecting.... Connecting.... esptool.py v4.7.0 Found 1 serial ports Chip is ESP32-C3 (QFN32) (revision v0.3) Features: WiFi, BLE, Embedded Flash 4MB... (total 6673 bytes) Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.512safe.test_wear_levelling/dut.txtNot found link
esp32c3.default.test_wpa_supplicant_ut components/wpa_supplicant/test_apps/pytest_wpa_supplicant_ut.py pexpect.exceptions.TIMEOUT: Not found "Press ENTER to see the list of tests" Bytes in current buffer (color code eliminated): 0 d4 000 00x0000 x0000x00 000000 0 Please check the full log here: /builds/espressif/esp-idf/pytest_embedded/2024-05-17_17-50-04/esp32c3.default.test_wpa_supplicant_ut/dut.txtNot found link

Skipped Test Cases

+ onclick="copyPermalink('#skipped-test-cases')">
@@ -169,7 +192,7 @@
Test Case

Succeeded Test Cases

+ onclick="copyPermalink('#succeeded-test-cases')">
@@ -182,110 +205,131 @@ - + - + - + - + - + - + - + - + - +
Test Case
esp32c2.default.test_vfs_default components/vfs/test_apps/pytest_vfs.pyNot found link
esp32c2.iram.test_vfs_default components/vfs/test_apps/pytest_vfs.pyNot found link
test_python_interpreter_unix test_common.pyNot found link
test_invoke_confserver test_common.pyNot found link
test_ccache_used_to_build test_common.pyNot found link
test_toolchain_prefix_in_description_file test_common.pyNot found link
test_subcommands_with_options test_common.pyNot found link
test_fallback_to_build_system_target test_common.pyNot found link
test_create_component_project test_common.pyNot found link
- - - - - + + + + + - - - + + function scrollToHashLocation() { + const hash = window.location.hash; + if (hash) { + setTimeout(() => { + $("html, body").animate( + { scrollTop: $(hash).offset().top }, + 100 + ); + }, 100); + } + } + + function copyPermalink(anchorId) { + const fullUrl = `${window.location.origin}${window.location.pathname}${anchorId}`; + history.pushState(null, null, anchorId); + navigator.clipboard.writeText(fullUrl); + scrollToHashLocation(); + } + + function toggleText(e) { + e.preventDefault(); + const link = $(this), + textSpan = link.siblings(".full-text"), + toggleSpan = link.siblings(".text-toggle"); + const visible = textSpan.is(":visible"); + link.text(visible ? "Show More" : "Show Less"); + textSpan.toggle(); + toggleSpan.toggle(); + } + + function setupTextToggles() { + $("table.table td").each(function () { + var cell = $(this); + if (cell.text().length > 100) { + var originalText = cell.text(); + var displayText = + originalText.substring(0, 100) + "..."; + cell.html( + `${displayText}Show More` + ); + } + }); + } + + diff --git a/tools/ci/dynamic_pipelines/tests/test_report_generator/test_report_generator.py b/tools/ci/dynamic_pipelines/tests/test_report_generator/test_report_generator.py index abdd8a7b64..81bae786ce 100644 --- a/tools/ci/dynamic_pipelines/tests/test_report_generator/test_report_generator.py +++ b/tools/ci/dynamic_pipelines/tests/test_report_generator/test_report_generator.py @@ -44,8 +44,8 @@ class TestReportGeneration(unittest.TestCase): self.mock_project.mergerequests.get.return_value = self.mock_mr self.addCleanup(self.gitlab_patcher.stop) - self.addCleanup(self.env_patcher.stop) self.addCleanup(self.failure_rate_patcher.stop) + self.addCleanup(self.env_patcher.stop) self.addCleanup(self.cleanup_files) def cleanup_files(self) -> None: diff --git a/tools/ci/dynamic_pipelines/utils.py b/tools/ci/dynamic_pipelines/utils.py index 79cd3f44d7..08eceb1f9a 100644 --- a/tools/ci/dynamic_pipelines/utils.py +++ b/tools/ci/dynamic_pipelines/utils.py @@ -10,6 +10,11 @@ from urllib.parse import urlparse import requests import yaml +from .constants import CI_DASHBOARD_API +from .constants import CI_JOB_TOKEN +from .constants import CI_MERGE_REQUEST_SOURCE_BRANCH_SHA +from .constants import CI_PAGES_URL +from .constants import CI_PROJECT_URL from .models import GitlabJob from .models import Job from .models import TestCase @@ -95,11 +100,9 @@ def fetch_failed_jobs(commit_id: str) -> t.List[GitlabJob]: :param commit_id: The commit ID for which to fetch jobs. :return: A list of jobs if the request is successful, otherwise an empty list. """ - token = os.getenv('ESPCI_TOKEN', '') - ci_dash_api_backend_host = os.getenv('CI_DASHBOARD_API', '') response = requests.get( - f'{ci_dash_api_backend_host}/commits/{commit_id}/jobs', - headers={'Authorization': f'Bearer {token}'} + f'{CI_DASHBOARD_API}/commits/{commit_id}/jobs', + headers={'Authorization': f'Bearer {CI_JOB_TOKEN}'} ) if response.status_code != 200: print(f'Failed to fetch jobs data: {response.status_code} with error: {response.text}') @@ -113,8 +116,8 @@ def fetch_failed_jobs(commit_id: str) -> t.List[GitlabJob]: failed_job_names = [job['name'] for job in jobs if job['status'] == 'failed'] response = requests.post( - f'{ci_dash_api_backend_host}/jobs/failure_ratio', - headers={'Authorization': f'Bearer {token}'}, + f'{CI_DASHBOARD_API}/jobs/failure_ratio', + headers={'Authorization': f'Bearer {CI_JOB_TOKEN}'}, json={'job_names': failed_job_names, 'exclude_branches': [os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', '')]}, ) if response.status_code != 200: @@ -139,12 +142,10 @@ def fetch_failed_testcases_failure_ratio(failed_testcases: t.List[TestCase], bra :param branches_filter: The filter to filter testcases by branch names. :return: A list of testcases with enriched with failure rates data. """ - token = os.getenv('ESPCI_TOKEN', '') - ci_dash_api_backend_host = os.getenv('CI_DASHBOARD_API', '') req_json = {'testcase_names': list(set([testcase.name for testcase in failed_testcases])), **branches_filter} response = requests.post( - f'{ci_dash_api_backend_host}/testcases/failure_ratio', - headers={'Authorization': f'Bearer {token}'}, + f'{CI_DASHBOARD_API}/testcases/failure_ratio', + headers={'Authorization': f'Bearer {CI_JOB_TOKEN}'}, json=req_json, ) if response.status_code != 200: @@ -191,13 +192,23 @@ def format_permalink(s: str) -> str: return formatted_string -def get_report_url(job_id: int, output_filepath: str) -> str: +def get_artifacts_url(job_id: int, output_filepath: str) -> str: """ - Generates the url of the path where the report will be stored in the job's artifacts . + Generates the url of the path where the artifact will be stored in the job's artifacts . :param job_id: The job identifier used to construct the URL. :param output_filepath: The path to the output file. :return: The modified URL pointing to the job's artifacts. """ - url = os.getenv('CI_PAGES_URL', '').replace('esp-idf', '-/esp-idf') + url = CI_PAGES_URL.replace('esp-idf', '-/esp-idf') return f'{url}/-/jobs/{job_id}/artifacts/{output_filepath}' + + +def get_repository_file_url(file_path: str) -> str: + """ + Generates the url of the file path inside the repository. + + :param file_path: The file path where the file is stored. + :return: The modified URL pointing to the file's path in the repository. + """ + return f'{CI_PROJECT_URL}/-/raw/{CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}/{file_path}' diff --git a/tools/ci/python_packages/gitlab_api.py b/tools/ci/python_packages/gitlab_api.py index 1bffbef372..7c83d6f205 100644 --- a/tools/ci/python_packages/gitlab_api.py +++ b/tools/ci/python_packages/gitlab_api.py @@ -254,7 +254,7 @@ class Gitlab(object): @staticmethod def decompress_archive(path: str, destination: str) -> str: full_destination = os.path.abspath(destination) - # By default max path lenght is set to 260 characters + # By default max path length is set to 260 characters # Prefix `\\?\` extends it to 32,767 characters if sys.platform == 'win32': full_destination = '\\\\?\\' + full_destination @@ -279,6 +279,29 @@ class Gitlab(object): job = self.project.jobs.get(job_id) return ','.join(job.tag_list) + def retry_failed_jobs(self, pipeline_id: int, retry_allowed_failures: bool = False) -> List[int]: + """ + Retry failed jobs for a specific pipeline. Optionally include jobs marked as 'allowed failures'. + + :param pipeline_id: ID of the pipeline whose failed jobs are to be retried. + :param retry_allowed_failures: Whether to retry jobs that are marked as allowed failures. + """ + pipeline = self.project.pipelines.get(pipeline_id) + jobs_to_retry = [ + job + for job in pipeline.jobs.list(scope='failed') + if retry_allowed_failures or not job.attributes.get('allow_failure', False) + ] + jobs_succeeded_retry = [] + for job in jobs_to_retry: + try: + res = self.project.jobs.get(job.id).retry() + jobs_succeeded_retry.append(job.id) + logging.info(f'Retried job {job.id} with result {res}') + except Exception as e: + logging.error(f'Failed to retry job {job.id}: {str(e)}') + return jobs_succeeded_retry + def main() -> None: parser = argparse.ArgumentParser() @@ -291,6 +314,9 @@ def main() -> None: parser.add_argument('--project_name', '-m', default=None) parser.add_argument('--destination', '-d', default=None) parser.add_argument('--artifact_path', '-a', nargs='*', default=None) + parser.add_argument( + '--retry-allowed-failures', action='store_true', help='Flag to retry jobs marked as allowed failures' + ) args = parser.parse_args() gitlab_inst = Gitlab(args.project_id) @@ -306,6 +332,9 @@ def main() -> None: elif args.action == 'get_project_id': ret = gitlab_inst.get_project_id(args.project_name) print('project id: {}'.format(ret)) + elif args.action == 'retry_failed_jobs': + res = gitlab_inst.retry_failed_jobs(args.pipeline_id, args.retry_allowed_failures) + print('job retried successfully: {}'.format(res)) elif args.action == 'get_job_tags': ret = gitlab_inst.get_job_tags(args.job_id) print(ret)