From 4d0cf6c4e0aff9ff4cf09ef164f2960c1eb2dda3 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 4 Aug 2026 17:40:26 +0200 Subject: [PATCH 1/7] Add functionality to print commit_sha's --- README.md | 18 ++++++ app.cfg.example | 8 +++ eessi_bot_event_handler.py | 24 +++++++- tasks/build.py | 119 ++++++++++++++++++++++++++----------- tests/test_app.cfg | 3 + tests/test_task_build.py | 93 +++++++++++++++++++++++++++++ tools/config.py | 3 + 7 files changed, 229 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 07759d52..fc0a98a4 100644 --- a/README.md +++ b/README.md @@ -960,6 +960,24 @@ jobdir = Job dir: `{symlink}` `jobdir` is used as the fourth line in a comment to a PR when a new job has been created. +```ini +commit_sha = Commit SHA: `{commit_sha}` +``` + +`commit_sha` is used as the format string for the fifth line in a comment to a PR when a new job has been created. The `{commit_sha}` placeholder is replaced with the commit SHA from the cloned repository (i.e., the base branch HEAD at the time the job starts). + +```ini +repo_file = bot/commit_sha +``` + +`repo_file` specifies the path (relative to the repository root) of a file whose content should be printed as the sixth line in the PR comment. Leave empty to omit the sixth line and its column in the `bot:status` table. + +```ini +repo_file_header = software-layer-script SHA +``` + +`repo_file_header` is used as the label on the sixth line and as the header for the sixth column in the `bot:status` overview table. + ```ini with_accelerator =  and accelerator `{accelerator}` ``` diff --git a/app.cfg.example b/app.cfg.example index be8e2198..3e6f1297 100644 --- a/app.cfg.example +++ b/app.cfg.example @@ -415,6 +415,14 @@ new_job_instance_repo = New job on instance `{app_name}` for repository `{repo_i build_on_arch = Building on: `{on_arch}`{on_accelerator} build_for_arch = Building for: `{for_arch}`{for_accelerator} jobdir = Job dir: `{symlink}` +# commit_sha format for the 5th line in the initial PR comment; `{commit_sha}` is replaced with the commit SHA +# from the cloned repository (i.e., the base branch HEAD at the time the job starts). +commit_sha = Commit SHA: `{commit_sha}` +# repo_file path (relative to repo root) of a file whose content to print as the 6th line in the PR comment. +# Leave empty to omit the 6th line and its column in the bot:status table. +repo_file = bot/commit_sha +# repo_file_header is used as the label on the 6th line and as the header for the 6th column in bot:status. +repo_file_header = software-layer-script SHA with_accelerator =  and accelerator `{accelerator}` # initial_comment = New job on instance `{app_name}` for repository `{repo_id}`\nBuilding on: `{on_arch}`{on_accelerator}\nBuilding for: `{for_arch}`{for_accelerator}\nJob dir: `{symlink}` # no longer used diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index 41787e1b..704d862e 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -633,7 +633,8 @@ def handle_bot_command_status(self, event_info, bot_command): # Keep only the first entry for each 'for arch', as that is now the newest status_table_last = { - 'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': [] + 'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': [], + 'commit sha': [], 'repo file': [] } for x in range(0, len(sorted_table['date'])): # Check if the current 'for arch' AND 'for repo' are already in the status_table_last. If not, add it @@ -664,13 +665,30 @@ def handle_bot_command_status(self, event_info, bot_command): comment_status = '' comment_status += "\nThis is the status of all the `bot: build` commands:" - comment_status += "\n|on|for|repo|result|date|status|url|" - comment_status += "\n|----|----|----|------|----|------|---|" + + # Build header dynamically with optional repo_file column + submitted_job_comments_cfg = self.cfg[config.SECTION_SUBMITTED_JOB_COMMENTS] + repo_file = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE, '') + repo_file_header = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER, '') + + base_columns = ['on', 'for', 'repo', 'result'] + extra_columns = ['commit SHA'] + if repo_file and repo_file_header: + extra_columns.append(repo_file_header) + tail_columns = ['date', 'status', 'url'] + + all_columns = base_columns + extra_columns + tail_columns + comment_status += f"\n|{'|'.join(all_columns)}|" + comment_status += f"\n|{'|'.join(['----'] * len(all_columns))}|" + for x in range(0, len(status_table['date'])): comment_status += f"\n|{status_table['on arch'][x]}|" comment_status += f"{status_table['for arch'][x]}|" comment_status += f"{status_table['for repo'][x]}|" comment_status += f"{status_table['result'][x]}|" + comment_status += f"{status_table['commit sha'][x]}|" + if repo_file and repo_file_header: + comment_status += f"{status_table['repo file'][x]}|" comment_status += f"{status_table['date'][x]}|" comment_status += f"{status_table['status'][x]}|" comment_status += f"{status_table['url'][x]}|" diff --git a/tasks/build.py b/tasks/build.py index 6c191013..1440da2e 100644 --- a/tasks/build.py +++ b/tasks/build.py @@ -972,6 +972,23 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): # get current date and time dt = datetime.now(timezone.utc) + # Get commit SHA from cloned repo + commit_sha, _, _ = run_cmd('git rev-parse HEAD', 'Get commit SHA', job.working_dir, raise_on_error=False) + commit_sha = commit_sha.strip() if commit_sha else '' + + # Get file content if repo_file is configured + repo_file = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE, '') + repo_file_header = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER, '') + file_content = None + if repo_file: + file_path = os.path.join(job.working_dir, repo_file) + try: + with open(file_path, 'r') as f: + file_content = f.read().strip() + except Exception as err: + log(f"{fn}(): Failed to read {file_path}: {err}") + file_content = None + # construct initial job comment buildenv = config.read_config()[config.SECTION_BUILDENV] job_handover_protocol = buildenv.get(config.BUILDENV_SETTING_JOB_HANDOVER_PROTOCOL) @@ -979,6 +996,29 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): build_on_arch = submitted_job_comments_cfg[config.SUBMITTED_JOB_COMMENTS_SETTING_BUILD_ON_ARCH] build_for_arch = submitted_job_comments_cfg[config.SUBMITTED_JOB_COMMENTS_SETTING_BUILD_FOR_ARCH] jobdir = submitted_job_comments_cfg[config.SUBMITTED_JOB_COMMENTS_SETTING_JOBDIR] + commit_sha_fmt = submitted_job_comments_cfg.get( + config.SUBMITTED_JOB_COMMENTS_SETTING_COMMIT_SHA, 'Commit SHA: `{commit_sha}`') + + # Build header lines (1-4) + header_lines = (f"{new_job_instance_repo}\n" + f"{build_on_arch}\n" + f"{build_for_arch}\n" + f"{jobdir}\n").format( + app_name=app_name, + on_arch=on_arch, + for_arch=for_arch, + symlink=symlink, + repo_id=job.repo_id, + on_accelerator=on_accelerator_str, + for_accelerator=for_accelerator_str) + + # Build lines 5 and 6 + lines_5_6 = '' + if commit_sha_fmt: + lines_5_6 += f"{commit_sha_fmt.format(commit_sha=commit_sha)}\n" + if repo_file and file_content is not None: + lines_5_6 += f"{repo_file_header}: {file_content}\n" + if job_handover_protocol == config.JOB_HANDOVER_PROTOCOL_DELAYED_BEGIN: release_msg_string = config.SUBMITTED_JOB_COMMENTS_SETTING_AWAITS_RELEASE_DELAYED_BEGIN_MSG release_comment_template = submitted_job_comments_cfg[release_msg_string] @@ -987,44 +1027,24 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): poll_interval = int(job_manager_cfg.get(config.JOB_MANAGER_SETTING_POLL_INTERVAL)) delay_factor = float(buildenv.get(config.BUILDENV_SETTING_JOB_DELAY_BEGIN_FACTOR, 2)) eligible_in_seconds = int(poll_interval * delay_factor) - job_comment = (f"{new_job_instance_repo}\n" - f"{build_on_arch}\n" - f"{build_for_arch}\n" - f"{jobdir}\n" - f"|date|job status|comment|\n" - f"|----------|----------|------------------------|\n" - f"|{dt.strftime('%b %d %X %Z %Y')}|" - f"submitted|" - f"{release_comment_template}|").format( - app_name=app_name, - on_arch=on_arch, - for_arch=for_arch, - symlink=symlink, - repo_id=job.repo_id, - job_id=job_id, - delay_seconds=eligible_in_seconds, - on_accelerator=on_accelerator_str, - for_accelerator=for_accelerator_str) + table = (f"|date|job status|comment|\n" + f"|----------|----------|------------------------|\n" + f"|{dt.strftime('%b %d %X %Z %Y')}|" + f"submitted|" + f"{release_comment_template}|").format( + job_id=job_id, + delay_seconds=eligible_in_seconds) + job_comment = header_lines + lines_5_6 + table else: release_msg_string = config.SUBMITTED_JOB_COMMENTS_SETTING_AWAITS_RELEASE_HOLD_RELEASE_MSG release_comment_template = submitted_job_comments_cfg[release_msg_string] - job_comment = (f"{new_job_instance_repo}\n" - f"{build_on_arch}\n" - f"{build_for_arch}\n" - f"{jobdir}\n" - f"|date|job status|comment|\n" - f"|----------|----------|------------------------|\n" - f"|{dt.strftime('%b %d %X %Z %Y')}|" - f"submitted|" - f"{release_comment_template}|").format( - app_name=app_name, - on_arch=on_arch, - for_arch=for_arch, - symlink=symlink, - repo_id=job.repo_id, - job_id=job_id, - on_accelerator=on_accelerator_str, - for_accelerator=for_accelerator_str) + table = (f"|date|job status|comment|\n" + f"|----------|----------|------------------------|\n" + f"|{dt.strftime('%b %d %X %Z %Y')}|" + f"submitted|" + f"{release_comment_template}|").format( + job_id=job_id) + job_comment = header_lines + lines_5_6 + table # create comment to pull request repo_name = pr.base.repo.full_name @@ -1212,7 +1232,8 @@ def request_bot_build_issue_comments(repo_name, pr_number): """ fn = sys._getframe().f_code.co_name - status_table = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': []} + status_table = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], + 'result': [], 'commit sha': [], 'repo file': []} cfg = config.read_config() github_section = cfg[config.SECTION_GITHUB] api_timeout = int(github_section.get(config.GITHUB_SETTING_API_TIMEOUT, 10)) @@ -1342,6 +1363,30 @@ def request_bot_build_issue_comments(repo_name, pr_number): msg += f"{for_arch_re.pattern}\n" raise ValueError(msg) + # Extract commit SHA (line 5, index 4) + commit_sha = '' + commit_sha_fmt = submitted_job_comments_section.get( + config.SUBMITTED_JOB_COMMENTS_SETTING_COMMIT_SHA, 'Commit SHA: `{commit_sha}`') + if len(comment_body) >= 5: + commit_sha_re = template_to_regex(commit_sha_fmt) + commit_sha_match = re.match(commit_sha_re, comment_body[4]) + if commit_sha_match: + commit_sha = commit_sha_match.group('commit_sha') + else: + commit_sha = '' + + # Extract repo file content (line 6, index 5) if configured + repo_file = submitted_job_comments_section.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE, '') + repo_file_header = submitted_job_comments_section.get( + config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER, '') + repo_file_content = '' + if repo_file and len(comment_body) >= 6: + repo_file_content_fmt = f"{repo_file_header}: {{content}}" + repo_file_content_re = template_to_regex(repo_file_content_fmt) + repo_file_content_match = re.match(repo_file_content_re, comment_body[5]) + if repo_file_content_match: + repo_file_content = repo_file_content_match.group('content') + # get date, status, url and result from the markdown table comment_table = comment['body'][comment['body'].find('|'):comment['body'].rfind('|')+1] @@ -1397,6 +1442,8 @@ def request_bot_build_issue_comments(repo_name, pr_number): status_table['status'].append(status) status_table['url'].append(url) status_table['result'].append(result) + status_table['commit sha'].append(commit_sha) + status_table['repo file'].append(repo_file_content) return status_table diff --git a/tests/test_app.cfg b/tests/test_app.cfg index d0fff239..8f30c0ee 100644 --- a/tests/test_app.cfg +++ b/tests/test_app.cfg @@ -25,6 +25,9 @@ new_job_instance_repo = New job on instance `{app_name}` for repository `{repo_i build_on_arch = Building on: `{on_arch}`{on_accelerator} build_for_arch = Building for: `{for_arch}`{for_accelerator} jobdir = Job dir: `{symlink}` +commit_sha = Commit SHA: `{commit_sha}` +repo_file = +repo_file_header = with_accelerator =  and accelerator `{accelerator}` [new_job_comments] diff --git a/tests/test_task_build.py b/tests/test_task_build.py index cab91a35..d7f5d0b8 100644 --- a/tests/test_task_build.py +++ b/tests/test_task_build.py @@ -528,3 +528,96 @@ def test_create_read_metadata_file(mocked_github, tmp_path): job_id5 = "555" with pytest.raises(TypeError): create_metadata_file(job5, job_id5, pr_comment) + + +@pytest.mark.repo_name("EESSI/software-layer") +@pytest.mark.pr_number(1) +def test_create_pr_comment_with_commit_sha(monkeypatch, mocked_github, tmp_path): + """Tests that create_pr_comment includes commit SHA from cloned repo.""" + import subprocess + monkeypatch.setattr('tools.pr_comments.github', mocked_github) + + # Set up a git repo in tmp_path with a commit + subprocess.run(['git', 'init'], cwd=tmp_path, capture_output=True) + subprocess.run(['git', 'config', 'user.name', 'test'], cwd=tmp_path, capture_output=True) + subprocess.run(['git', 'config', 'user.email', 'test@test.com'], cwd=tmp_path, capture_output=True) + test_file = os.path.join(tmp_path, 'test.txt') + with open(test_file, 'w') as f: + f.write('test content') + subprocess.run(['git', 'add', '.'], cwd=tmp_path, capture_output=True) + subprocess.run(['git', 'commit', '-m', 'Initial commit'], cwd=tmp_path, capture_output=True) + + ym = datetime.today().strftime('%Y.%m') + pr_number = 1 + job = Job(tmp_path, "test/architecture", "EESSI", "--speed-up", ym, pr_number, "fpga/magic", "user01") + build_params = EESSIBotBuildParams("arch=amd/zen4,accel=nvidia/cc90") + + job_id = "123" + app_name = "pytest" + + repo_name = "EESSI/software-layer" + repo = mocked_github.get_repo(repo_name) + pr = repo.get_pull(pr_number) + symlink = "/symlink" + comment = create_pr_comment(job, job_id, app_name, pr, symlink, build_params) + + # Get the actual commit SHA + result = subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=tmp_path, capture_output=True, text=True) + expected_sha = result.stdout.strip() + + assert comment.id == 1 + assert f"Commit SHA: `{expected_sha}`" in comment.body + + +@pytest.mark.repo_name("EESSI/software-layer") +@pytest.mark.pr_number(1) +def test_create_pr_comment_with_repo_file(monkeypatch, mocked_github, tmp_path): + """Tests that create_pr_comment includes file content when repo_file is configured.""" + import subprocess + monkeypatch.setattr('tools.pr_comments.github', mocked_github) + + # Set up a git repo in tmp_path with a commit + subprocess.run(['git', 'init'], cwd=tmp_path, capture_output=True) + subprocess.run(['git', 'config', 'user.name', 'test'], cwd=tmp_path, capture_output=True) + subprocess.run(['git', 'config', 'user.email', 'test@test.com'], cwd=tmp_path, capture_output=True) + test_file = os.path.join(tmp_path, 'test.txt') + with open(test_file, 'w') as f: + f.write('test content') + subprocess.run(['git', 'add', '.'], cwd=tmp_path, capture_output=True) + subprocess.run(['git', 'commit', '-m', 'Initial commit'], cwd=tmp_path, capture_output=True) + + # Create the file to read + repo_file_path = os.path.join(tmp_path, 'bot') + os.makedirs(repo_file_path, exist_ok=True) + with open(os.path.join(repo_file_path, 'commit_sha'), 'w') as f: + f.write('def456789') + + # Monkeypatch config to set repo_file + from tools import config as build_config + original_read_config = build_config.read_config + + def mock_read_config(path='app.cfg'): + cfg = original_read_config(path) + if 'submitted_job_comments' in cfg: + cfg['submitted_job_comments']['repo_file'] = 'bot/commit_sha' + cfg['submitted_job_comments']['repo_file_header'] = 'software-layer-script SHA' + return cfg + + monkeypatch.setattr('tasks.build.config.read_config', mock_read_config) + + ym = datetime.today().strftime('%Y.%m') + pr_number = 1 + job = Job(tmp_path, "test/architecture", "EESSI", "--speed-up", ym, pr_number, "fpga/magic", "user01") + build_params = EESSIBotBuildParams("arch=amd/zen4,accel=nvidia/cc90") + + job_id = "123" + app_name = "pytest" + + repo_name = "EESSI/software-layer" + repo = mocked_github.get_repo(repo_name) + pr = repo.get_pull(pr_number) + symlink = "/symlink" + comment = create_pr_comment(job, job_id, app_name, pr, symlink, build_params) + + assert comment.id == 1 + assert "software-layer-script SHA: def456789" in comment.body diff --git a/tools/config.py b/tools/config.py index 10a7590d..5970f435 100644 --- a/tools/config.py +++ b/tools/config.py @@ -130,6 +130,9 @@ SUBMITTED_JOB_COMMENTS_SETTING_JOBDIR = 'jobdir' SUBMITTED_JOB_COMMENTS_SETTING_INITIAL_COMMENT = 'initial_comment' SUBMITTED_JOB_COMMENTS_SETTING_WITH_ACCELERATOR = 'with_accelerator' +SUBMITTED_JOB_COMMENTS_SETTING_COMMIT_SHA = 'commit_sha' +SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE = 'repo_file' +SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER = 'repo_file_header' SECTION_CLEAN_UP = 'clean_up' CLEAN_UP_SETTING_TRASH_BIN_ROOT_DIR = 'trash_bin_dir' From 1e1eb65484f5be3425321b6cdf82fa03dd0846c0 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 4 Aug 2026 19:35:37 +0200 Subject: [PATCH 2/7] Remove support for a repo_file configurable from which the output is printed. The commit SHA from the cloned repository already determines the full state - including the content of any file in that repository. There is no need to also print something else explicitely --- README.md | 8 ------ app.cfg.example | 5 ---- eessi_bot_event_handler.py | 16 +++-------- tasks/build.py | 42 +++++------------------------ tests/test_app.cfg | 3 +++ tests/test_task_build.py | 55 ++++++++++++++++++++++++++++++++++++-- tools/config.py | 2 -- 7 files changed, 66 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index fc0a98a4..54aa494e 100644 --- a/README.md +++ b/README.md @@ -970,14 +970,6 @@ commit_sha = Commit SHA: `{commit_sha}` repo_file = bot/commit_sha ``` -`repo_file` specifies the path (relative to the repository root) of a file whose content should be printed as the sixth line in the PR comment. Leave empty to omit the sixth line and its column in the `bot:status` table. - -```ini -repo_file_header = software-layer-script SHA -``` - -`repo_file_header` is used as the label on the sixth line and as the header for the sixth column in the `bot:status` overview table. - ```ini with_accelerator =  and accelerator `{accelerator}` ``` diff --git a/app.cfg.example b/app.cfg.example index 3e6f1297..15851ba1 100644 --- a/app.cfg.example +++ b/app.cfg.example @@ -418,11 +418,6 @@ jobdir = Job dir: `{symlink}` # commit_sha format for the 5th line in the initial PR comment; `{commit_sha}` is replaced with the commit SHA # from the cloned repository (i.e., the base branch HEAD at the time the job starts). commit_sha = Commit SHA: `{commit_sha}` -# repo_file path (relative to repo root) of a file whose content to print as the 6th line in the PR comment. -# Leave empty to omit the 6th line and its column in the bot:status table. -repo_file = bot/commit_sha -# repo_file_header is used as the label on the 6th line and as the header for the 6th column in bot:status. -repo_file_header = software-layer-script SHA with_accelerator =  and accelerator `{accelerator}` # initial_comment = New job on instance `{app_name}` for repository `{repo_id}`\nBuilding on: `{on_arch}`{on_accelerator}\nBuilding for: `{for_arch}`{for_accelerator}\nJob dir: `{symlink}` # no longer used diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index 704d862e..5e0fb3a3 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -634,7 +634,7 @@ def handle_bot_command_status(self, event_info, bot_command): # Keep only the first entry for each 'for arch', as that is now the newest status_table_last = { 'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': [], - 'commit sha': [], 'repo file': [] + 'commit sha': [] } for x in range(0, len(sorted_table['date'])): # Check if the current 'for arch' AND 'for repo' are already in the status_table_last. If not, add it @@ -666,18 +666,10 @@ def handle_bot_command_status(self, event_info, bot_command): comment_status = '' comment_status += "\nThis is the status of all the `bot: build` commands:" - # Build header dynamically with optional repo_file column + # Build header submitted_job_comments_cfg = self.cfg[config.SECTION_SUBMITTED_JOB_COMMENTS] - repo_file = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE, '') - repo_file_header = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER, '') - base_columns = ['on', 'for', 'repo', 'result'] - extra_columns = ['commit SHA'] - if repo_file and repo_file_header: - extra_columns.append(repo_file_header) - tail_columns = ['date', 'status', 'url'] - - all_columns = base_columns + extra_columns + tail_columns + all_columns = ['on', 'for', 'repo', 'result', 'commit SHA', 'date', 'status', 'url'] comment_status += f"\n|{'|'.join(all_columns)}|" comment_status += f"\n|{'|'.join(['----'] * len(all_columns))}|" @@ -687,8 +679,6 @@ def handle_bot_command_status(self, event_info, bot_command): comment_status += f"{status_table['for repo'][x]}|" comment_status += f"{status_table['result'][x]}|" comment_status += f"{status_table['commit sha'][x]}|" - if repo_file and repo_file_header: - comment_status += f"{status_table['repo file'][x]}|" comment_status += f"{status_table['date'][x]}|" comment_status += f"{status_table['status'][x]}|" comment_status += f"{status_table['url'][x]}|" diff --git a/tasks/build.py b/tasks/build.py index 1440da2e..b72ec93e 100644 --- a/tasks/build.py +++ b/tasks/build.py @@ -976,19 +976,6 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): commit_sha, _, _ = run_cmd('git rev-parse HEAD', 'Get commit SHA', job.working_dir, raise_on_error=False) commit_sha = commit_sha.strip() if commit_sha else '' - # Get file content if repo_file is configured - repo_file = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE, '') - repo_file_header = submitted_job_comments_cfg.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER, '') - file_content = None - if repo_file: - file_path = os.path.join(job.working_dir, repo_file) - try: - with open(file_path, 'r') as f: - file_content = f.read().strip() - except Exception as err: - log(f"{fn}(): Failed to read {file_path}: {err}") - file_content = None - # construct initial job comment buildenv = config.read_config()[config.SECTION_BUILDENV] job_handover_protocol = buildenv.get(config.BUILDENV_SETTING_JOB_HANDOVER_PROTOCOL) @@ -997,7 +984,7 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): build_for_arch = submitted_job_comments_cfg[config.SUBMITTED_JOB_COMMENTS_SETTING_BUILD_FOR_ARCH] jobdir = submitted_job_comments_cfg[config.SUBMITTED_JOB_COMMENTS_SETTING_JOBDIR] commit_sha_fmt = submitted_job_comments_cfg.get( - config.SUBMITTED_JOB_COMMENTS_SETTING_COMMIT_SHA, 'Commit SHA: `{commit_sha}`') + config.SUBMITTED_JOB_COMMENTS_SETTING_COMMIT_SHA, 'Commit SHA: {commit_sha}') # Build header lines (1-4) header_lines = (f"{new_job_instance_repo}\n" @@ -1012,12 +999,10 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): on_accelerator=on_accelerator_str, for_accelerator=for_accelerator_str) - # Build lines 5 and 6 - lines_5_6 = '' + # Build line 5 + line_5 = '' if commit_sha_fmt: - lines_5_6 += f"{commit_sha_fmt.format(commit_sha=commit_sha)}\n" - if repo_file and file_content is not None: - lines_5_6 += f"{repo_file_header}: {file_content}\n" + line_5 += f"{commit_sha_fmt.format(commit_sha=commit_sha)}\n" if job_handover_protocol == config.JOB_HANDOVER_PROTOCOL_DELAYED_BEGIN: release_msg_string = config.SUBMITTED_JOB_COMMENTS_SETTING_AWAITS_RELEASE_DELAYED_BEGIN_MSG @@ -1034,7 +1019,7 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): f"{release_comment_template}|").format( job_id=job_id, delay_seconds=eligible_in_seconds) - job_comment = header_lines + lines_5_6 + table + job_comment = header_lines + line_5 + table else: release_msg_string = config.SUBMITTED_JOB_COMMENTS_SETTING_AWAITS_RELEASE_HOLD_RELEASE_MSG release_comment_template = submitted_job_comments_cfg[release_msg_string] @@ -1044,7 +1029,7 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): f"submitted|" f"{release_comment_template}|").format( job_id=job_id) - job_comment = header_lines + lines_5_6 + table + job_comment = header_lines + line_5 + table # create comment to pull request repo_name = pr.base.repo.full_name @@ -1233,7 +1218,7 @@ def request_bot_build_issue_comments(repo_name, pr_number): fn = sys._getframe().f_code.co_name status_table = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], - 'result': [], 'commit sha': [], 'repo file': []} + 'result': [], 'commit sha': []} cfg = config.read_config() github_section = cfg[config.SECTION_GITHUB] api_timeout = int(github_section.get(config.GITHUB_SETTING_API_TIMEOUT, 10)) @@ -1375,18 +1360,6 @@ def request_bot_build_issue_comments(repo_name, pr_number): else: commit_sha = '' - # Extract repo file content (line 6, index 5) if configured - repo_file = submitted_job_comments_section.get(config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE, '') - repo_file_header = submitted_job_comments_section.get( - config.SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER, '') - repo_file_content = '' - if repo_file and len(comment_body) >= 6: - repo_file_content_fmt = f"{repo_file_header}: {{content}}" - repo_file_content_re = template_to_regex(repo_file_content_fmt) - repo_file_content_match = re.match(repo_file_content_re, comment_body[5]) - if repo_file_content_match: - repo_file_content = repo_file_content_match.group('content') - # get date, status, url and result from the markdown table comment_table = comment['body'][comment['body'].find('|'):comment['body'].rfind('|')+1] @@ -1443,7 +1416,6 @@ def request_bot_build_issue_comments(repo_name, pr_number): status_table['url'].append(url) status_table['result'].append(result) status_table['commit sha'].append(commit_sha) - status_table['repo file'].append(repo_file_content) return status_table diff --git a/tests/test_app.cfg b/tests/test_app.cfg index 8f30c0ee..86e59038 100644 --- a/tests/test_app.cfg +++ b/tests/test_app.cfg @@ -40,3 +40,6 @@ running_job = job `{job_id}` is running [bot_control] command_permission = user01 second_user + +[github] +api_timeout = 10 diff --git a/tests/test_task_build.py b/tests/test_task_build.py index d7f5d0b8..0296440d 100644 --- a/tests/test_task_build.py +++ b/tests/test_task_build.py @@ -18,7 +18,7 @@ import filecmp import os import re -from unittest.mock import patch +from unittest.mock import Mock, patch # Third party imports (anything installed into the local Python environment) from collections import namedtuple @@ -26,7 +26,7 @@ import pytest # Local application imports (anything from EESSI/eessi-bot-software-layer) -from tasks.build import Job, create_pr_comment +from tasks.build import Job, create_pr_comment, request_bot_build_issue_comments from tools import run_cmd, run_subprocess from tools.build_params import EESSIBotBuildParams from tools.job_metadata import create_metadata_file, read_metadata_file @@ -621,3 +621,54 @@ def mock_read_config(path='app.cfg'): assert comment.id == 1 assert "software-layer-script SHA: def456789" in comment.body + + +@pytest.mark.repo_name("EESSI/software-layer") +@pytest.mark.pr_number(1) +def test_request_bot_build_issue_comments(monkeypatch): + """Tests that request_bot_build_issue_comments extracts commit SHA and repo file content.""" + from tools import config as build_config + original_read_config = build_config.read_config + + def mock_read_config(path='app.cfg'): + cfg = original_read_config(path) + cfg['submitted_job_comments']['repo_file'] = 'bot/commit_sha' + cfg['submitted_job_comments']['repo_file_header'] = 'software-layer-script SHA' + return cfg + + monkeypatch.setattr('tasks.build.config.read_config', mock_read_config) + + # Mock the GitHub token + token_mock = Mock() + token_mock.token = 'mock-token' + monkeypatch.setattr('tasks.build.github.token', lambda: token_mock) + monkeypatch.setattr('tasks.build.github.get_instance', lambda: Mock()) + + # Mock the response from the GitHub API + comment_body = "\n".join([ + "New job on instance `pytest` for repository `EESSI/software-layer`", + "Building on: `x86_64/generic`", + "Building for: `x86_64/generic`", + "Job dir: `symlink`", + "Commit SHA: `abc123`", + "software-layer-script SHA: def456", + "|date|job status|comment|", + "|----------|----------|------------------------|", + "|Jan 01 00:00:00 UTC 2025|finished|SUCCESS|", + ]) + response_mock = Mock() + response_mock.json.return_value = [{'body': comment_body, 'html_url': 'https://example.com'}] + response_mock.links = {} + response_mock.headers = { + 'X-RateLimit-Reset': '0', + 'X-RateLimit-Limit': '5000', + 'X-RateLimit-Remaining': '4999', + } + monkeypatch.setattr('tasks.build.requests.get', lambda *args, **kwargs: response_mock) + + status_table = request_bot_build_issue_comments('EESSI/software-layer', 1) + + assert status_table['commit sha'] == ['abc123'] + assert status_table['repo file'] == ['def456'] + assert status_table['for repo'] == ['EESSI/software-layer'] + assert status_table['result'] == [':grin: SUCCESS'] diff --git a/tools/config.py b/tools/config.py index 5970f435..2a7dced1 100644 --- a/tools/config.py +++ b/tools/config.py @@ -131,8 +131,6 @@ SUBMITTED_JOB_COMMENTS_SETTING_INITIAL_COMMENT = 'initial_comment' SUBMITTED_JOB_COMMENTS_SETTING_WITH_ACCELERATOR = 'with_accelerator' SUBMITTED_JOB_COMMENTS_SETTING_COMMIT_SHA = 'commit_sha' -SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE = 'repo_file' -SUBMITTED_JOB_COMMENTS_SETTING_REPO_FILE_HEADER = 'repo_file_header' SECTION_CLEAN_UP = 'clean_up' CLEAN_UP_SETTING_TRASH_BIN_ROOT_DIR = 'trash_bin_dir' From 4493de12279724482530f1f9cc6d6d0383b5e312 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 4 Aug 2026 19:55:17 +0200 Subject: [PATCH 3/7] Update the tests to also remove the repo file logic there --- tests/test_app.cfg | 2 -- tests/test_task_build.py | 61 +--------------------------------------- 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/tests/test_app.cfg b/tests/test_app.cfg index 86e59038..e35586ef 100644 --- a/tests/test_app.cfg +++ b/tests/test_app.cfg @@ -26,8 +26,6 @@ build_on_arch = Building on: `{on_arch}`{on_accelerator} build_for_arch = Building for: `{for_arch}`{for_accelerator} jobdir = Job dir: `{symlink}` commit_sha = Commit SHA: `{commit_sha}` -repo_file = -repo_file_header = with_accelerator =  and accelerator `{accelerator}` [new_job_comments] diff --git a/tests/test_task_build.py b/tests/test_task_build.py index 0296440d..26e26a82 100644 --- a/tests/test_task_build.py +++ b/tests/test_task_build.py @@ -569,71 +569,15 @@ def test_create_pr_comment_with_commit_sha(monkeypatch, mocked_github, tmp_path) assert f"Commit SHA: `{expected_sha}`" in comment.body -@pytest.mark.repo_name("EESSI/software-layer") -@pytest.mark.pr_number(1) -def test_create_pr_comment_with_repo_file(monkeypatch, mocked_github, tmp_path): - """Tests that create_pr_comment includes file content when repo_file is configured.""" - import subprocess - monkeypatch.setattr('tools.pr_comments.github', mocked_github) - - # Set up a git repo in tmp_path with a commit - subprocess.run(['git', 'init'], cwd=tmp_path, capture_output=True) - subprocess.run(['git', 'config', 'user.name', 'test'], cwd=tmp_path, capture_output=True) - subprocess.run(['git', 'config', 'user.email', 'test@test.com'], cwd=tmp_path, capture_output=True) - test_file = os.path.join(tmp_path, 'test.txt') - with open(test_file, 'w') as f: - f.write('test content') - subprocess.run(['git', 'add', '.'], cwd=tmp_path, capture_output=True) - subprocess.run(['git', 'commit', '-m', 'Initial commit'], cwd=tmp_path, capture_output=True) - - # Create the file to read - repo_file_path = os.path.join(tmp_path, 'bot') - os.makedirs(repo_file_path, exist_ok=True) - with open(os.path.join(repo_file_path, 'commit_sha'), 'w') as f: - f.write('def456789') - - # Monkeypatch config to set repo_file - from tools import config as build_config - original_read_config = build_config.read_config - - def mock_read_config(path='app.cfg'): - cfg = original_read_config(path) - if 'submitted_job_comments' in cfg: - cfg['submitted_job_comments']['repo_file'] = 'bot/commit_sha' - cfg['submitted_job_comments']['repo_file_header'] = 'software-layer-script SHA' - return cfg - - monkeypatch.setattr('tasks.build.config.read_config', mock_read_config) - - ym = datetime.today().strftime('%Y.%m') - pr_number = 1 - job = Job(tmp_path, "test/architecture", "EESSI", "--speed-up", ym, pr_number, "fpga/magic", "user01") - build_params = EESSIBotBuildParams("arch=amd/zen4,accel=nvidia/cc90") - - job_id = "123" - app_name = "pytest" - - repo_name = "EESSI/software-layer" - repo = mocked_github.get_repo(repo_name) - pr = repo.get_pull(pr_number) - symlink = "/symlink" - comment = create_pr_comment(job, job_id, app_name, pr, symlink, build_params) - - assert comment.id == 1 - assert "software-layer-script SHA: def456789" in comment.body - - @pytest.mark.repo_name("EESSI/software-layer") @pytest.mark.pr_number(1) def test_request_bot_build_issue_comments(monkeypatch): - """Tests that request_bot_build_issue_comments extracts commit SHA and repo file content.""" + """Tests that request_bot_build_issue_comments extracts commit SHA.""" from tools import config as build_config original_read_config = build_config.read_config def mock_read_config(path='app.cfg'): cfg = original_read_config(path) - cfg['submitted_job_comments']['repo_file'] = 'bot/commit_sha' - cfg['submitted_job_comments']['repo_file_header'] = 'software-layer-script SHA' return cfg monkeypatch.setattr('tasks.build.config.read_config', mock_read_config) @@ -651,7 +595,6 @@ def mock_read_config(path='app.cfg'): "Building for: `x86_64/generic`", "Job dir: `symlink`", "Commit SHA: `abc123`", - "software-layer-script SHA: def456", "|date|job status|comment|", "|----------|----------|------------------------|", "|Jan 01 00:00:00 UTC 2025|finished|SUCCESS|", @@ -669,6 +612,4 @@ def mock_read_config(path='app.cfg'): status_table = request_bot_build_issue_comments('EESSI/software-layer', 1) assert status_table['commit sha'] == ['abc123'] - assert status_table['repo file'] == ['def456'] - assert status_table['for repo'] == ['EESSI/software-layer'] assert status_table['result'] == [':grin: SUCCESS'] From e334ac6b508f12967b7fb81ec3bc51b1130fd74e Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 4 Aug 2026 19:57:41 +0200 Subject: [PATCH 4/7] Remove line that is now no longer needed after removal of the repo file logic --- eessi_bot_event_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index 5e0fb3a3..91e26914 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -667,8 +667,6 @@ def handle_bot_command_status(self, event_info, bot_command): comment_status += "\nThis is the status of all the `bot: build` commands:" # Build header - submitted_job_comments_cfg = self.cfg[config.SECTION_SUBMITTED_JOB_COMMENTS] - all_columns = ['on', 'for', 'repo', 'result', 'commit SHA', 'date', 'status', 'url'] comment_status += f"\n|{'|'.join(all_columns)}|" comment_status += f"\n|{'|'.join(['----'] * len(all_columns))}|" From 3f292e2f9468302a12e534e51796e2cacbd101c0 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 4 Aug 2026 20:02:17 +0200 Subject: [PATCH 5/7] Remove the backticks, so that the commit sha becomes a link --- app.cfg.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.cfg.example b/app.cfg.example index 15851ba1..f473cff1 100644 --- a/app.cfg.example +++ b/app.cfg.example @@ -417,7 +417,7 @@ build_for_arch = Building for: `{for_arch}`{for_accelerator} jobdir = Job dir: `{symlink}` # commit_sha format for the 5th line in the initial PR comment; `{commit_sha}` is replaced with the commit SHA # from the cloned repository (i.e., the base branch HEAD at the time the job starts). -commit_sha = Commit SHA: `{commit_sha}` +commit_sha = Commit SHA: {commit_sha} with_accelerator =  and accelerator `{accelerator}` # initial_comment = New job on instance `{app_name}` for repository `{repo_id}`\nBuilding on: `{on_arch}`{on_accelerator}\nBuilding for: `{for_arch}`{for_accelerator}\nJob dir: `{symlink}` # no longer used From 4689cf8adf58ce790e4a2fd779f9210b7d3af194 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Mon, 10 Aug 2026 13:41:32 +0200 Subject: [PATCH 6/7] Remove this, as we decided that having the commit_sha is sufficient --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 54aa494e..6cfc2628 100644 --- a/README.md +++ b/README.md @@ -966,10 +966,6 @@ commit_sha = Commit SHA: `{commit_sha}` `commit_sha` is used as the format string for the fifth line in a comment to a PR when a new job has been created. The `{commit_sha}` placeholder is replaced with the commit SHA from the cloned repository (i.e., the base branch HEAD at the time the job starts). -```ini -repo_file = bot/commit_sha -``` - ```ini with_accelerator =  and accelerator `{accelerator}` ``` From 6f5f9dd5b1bf5bc916a02c3d8fbe182d27ac2455 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Mon, 10 Aug 2026 13:42:20 +0200 Subject: [PATCH 7/7] Replaced with more descriptive name --- tasks/build.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks/build.py b/tasks/build.py index b72ec93e..83be0e23 100644 --- a/tasks/build.py +++ b/tasks/build.py @@ -1000,9 +1000,9 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): for_accelerator=for_accelerator_str) # Build line 5 - line_5 = '' + commit_sha_line = '' if commit_sha_fmt: - line_5 += f"{commit_sha_fmt.format(commit_sha=commit_sha)}\n" + commit_sha_line += f"{commit_sha_fmt.format(commit_sha=commit_sha)}\n" if job_handover_protocol == config.JOB_HANDOVER_PROTOCOL_DELAYED_BEGIN: release_msg_string = config.SUBMITTED_JOB_COMMENTS_SETTING_AWAITS_RELEASE_DELAYED_BEGIN_MSG @@ -1019,7 +1019,7 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): f"{release_comment_template}|").format( job_id=job_id, delay_seconds=eligible_in_seconds) - job_comment = header_lines + line_5 + table + job_comment = header_lines + commit_sha_line + table else: release_msg_string = config.SUBMITTED_JOB_COMMENTS_SETTING_AWAITS_RELEASE_HOLD_RELEASE_MSG release_comment_template = submitted_job_comments_cfg[release_msg_string] @@ -1029,7 +1029,7 @@ def create_pr_comment(job, job_id, app_name, pr, symlink, build_params): f"submitted|" f"{release_comment_template}|").format( job_id=job_id) - job_comment = header_lines + line_5 + table + job_comment = header_lines + commit_sha_line + table # create comment to pull request repo_name = pr.base.repo.full_name