From: Simon Glass <simon.glass@canonical.com> Add PickmanMr pipeline_status/pipeline_id fields (extracted from the head_pipeline attribute), PipelineInfo and FailedJob named tuples, and get_failed_jobs() which fetches the trace logs of failed jobs from a GitLab pipeline. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/pickman/gitlab_api.py | 77 ++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/tools/pickman/gitlab_api.py b/tools/pickman/gitlab_api.py index 9262ac15a0d..d9635d392ee 100644 --- a/tools/pickman/gitlab_api.py +++ b/tools/pickman/gitlab_api.py @@ -42,14 +42,21 @@ class MrCreateError(Exception): # Use defaults for new fields so existing code doesn't break PickmanMr = namedtuple('PickmanMr', [ 'iid', 'title', 'web_url', 'source_branch', 'description', - 'has_conflicts', 'needs_rebase' -], defaults=[False, False]) + 'has_conflicts', 'needs_rebase', 'pipeline_status', 'pipeline_id' +], defaults=[False, False, None, None]) # Comment info returned by get_mr_comments() MrComment = namedtuple('MrComment', [ 'id', 'author', 'body', 'created_at', 'resolvable', 'resolved' ]) +# Pipeline info +PipelineInfo = namedtuple('PipelineInfo', ['id', 'status', 'web_url']) + +# Failed job info from a pipeline +FailedJob = namedtuple('FailedJob', + ['id', 'name', 'stage', 'web_url', 'log_tail']) + def check_available(): """Check if the python-gitlab module is available @@ -320,6 +327,9 @@ def get_pickman_mrs(remote, state='opened'): # For open MRs, fetch full details since list() doesn't # include accurate merge status fields + pipeline_status = None + pipeline_id = None + if state == 'opened': full_mr = project.mergerequests.get(merge_req.iid) has_conflicts = getattr(full_mr, 'has_conflicts', False) @@ -333,6 +343,12 @@ def get_pickman_mrs(remote, state='opened'): diverged = getattr(full_mr, 'diverged_commits_count', 0) needs_rebase = diverged and diverged > 0 + # Extract pipeline info from head_pipeline + head_pipeline = getattr(full_mr, 'head_pipeline', None) + if head_pipeline: + pipeline_status = head_pipeline.get('status') + pipeline_id = head_pipeline.get('id') + pickman_mrs.append(PickmanMr( iid=merge_req.iid, title=merge_req.title, @@ -341,6 +357,8 @@ def get_pickman_mrs(remote, state='opened'): description=merge_req.description or '', has_conflicts=has_conflicts, needs_rebase=needs_rebase, + pipeline_status=pipeline_status, + pipeline_id=pipeline_id, )) return pickman_mrs except gitlab.exceptions.GitlabError as exc: @@ -423,6 +441,61 @@ def get_mr_comments(remote, mr_iid): return None +def get_failed_jobs(remote, pipeline_id, max_log_lines=200): + """Get failed jobs from a pipeline + + Args: + remote (str): Remote name + pipeline_id (int): Pipeline ID + max_log_lines (int): Maximum log lines to fetch per job + + Returns: + list: List of FailedJob tuples, or None on failure + """ + if not check_available(): + return None + + token = get_token() + if not token: + tout.error('GITLAB_TOKEN environment variable not set') + return None + + remote_url = get_remote_url(remote) + host, proj_path = parse_url(remote_url) + + if not host or not proj_path: + return None + + try: + glab = gitlab.Gitlab(f'https://{host}', private_token=token) + project = glab.projects.get(proj_path) + pipeline = project.pipelines.get(pipeline_id) + jobs = pipeline.jobs.list(scope='failed', get_all=True) + + failed_jobs = [] + for job in jobs: + # Fetch full job to get trace + full_job = project.jobs.get(job.id) + try: + trace = full_job.trace().decode('utf-8', errors='replace') + lines = trace.splitlines() + log_tail = '\n'.join(lines[-max_log_lines:]) + except (AttributeError, gitlab.exceptions.GitlabError): + log_tail = '' + + failed_jobs.append(FailedJob( + id=job.id, + name=job.name, + stage=job.stage, + web_url=job.web_url, + log_tail=log_tail, + )) + return failed_jobs + except gitlab.exceptions.GitlabError as exc: + tout.error(f'GitLab API error: {exc}') + return None + + def reply_to_mr(remote, mr_iid, message): """Post a reply to a merge request -- 2.43.0