Read every page of pipeline jobs in the ensure-job checks
What does this MR do and why?
ensure-job-added and ensure-job-not-added get the pipeline's jobs with a single request to GET /projects/:id/pipelines/:pipeline_id/jobs. That endpoint is paginated: it answers 20 jobs unless per_page is given, 100 at most, newest first. So in a pipeline of more than 20 jobs both checks see only the 20 jobs created last:
ensure-job-addedfails for a job that is in the pipeline but was created before those 20.ensure-job-not-addedpasses for such a job, although it is in the pipeline.
This MR asks for 100 jobs a page and reads pages until one holds fewer than 100 jobs or is not a list, so the loop always ends. The count expression and the exit logic are unchanged. A pipeline of fewer than 100 jobs still takes a single request. A pipeline whose ensure-job-not-added check passed only because the job was past the first page now fails that check, which is the correction. An answer that is not a list of jobs (a 404 for a private project, an HTML error page) still fails the job as it does today, with a different jq message.
The commit carries a Changelog: Fixed trailer, so the release notes the generate_release_notes job builds from the changelog API list the fix.
!22 (merged) adds a JOB-TOKEN header to the same curl line, for private projects. This MR leaves authentication alone, and !22 (merged)'s header can be added to the new curl line unchanged. The two MRs conflict on that line only; if !22 (merged) is merged first, I'll rebase onto it.
References
- Related to !22 (merged)
How to validate
This project's own pipeline runs both templates, but its 10 jobs fit on one page, so it only shows that nothing else changes. To cross the page boundary, add a job in an earlier stage, 21 more jobs after it, and a check for the early job of each kind (the proof config below). I ran that on a throwaway GitLab CE 19.4.1 with GitLab Runner 19.4.1 (Docker executor, badouralix/curl-jq as in the templates), once on main and once on this branch.
Pipeline of 33 jobs, on main and on this branch
The first page of GET .../pipelines/:id/jobs (20 jobs, X-Total: 33) held generate_release_notes and filler 3/21 to filler 21/21, so every job the checks look for was on page 2.
| Job | main |
This branch |
|---|---|---|
ensure_job_added: early job |
failed: Job '^early job$' not found |
passed: 1 jobs found |
ensure_job_not_added: early job (allow_failure: true) |
passed: Job '^early job$' not found (as expected) |
failed: Job '^early job$' found |
ensure_job_added: check_readme |
failed | passed |
ensure_job_added: check_license |
failed | passed |
ensure_job_added: multi word |
failed | passed |
ensure_job_added: multi word job |
failed | passed |
ensure_job_not_added: excluded job |
passed | passed |
| every other job | passed | passed |
| Pipeline | failed | passed |
The ensure_job_not_added: early job row is the second half of the bug: early job is in the pipeline, so failing is the right answer, and main passes. generate_release_notes failed in both pipelines at first only because I had not pushed the project's tags to that instance; it passed in both once I pushed them and retried it. The project's unchanged .gitlab-ci.yml on this branch (9 jobs there; the GitLab.com pipeline also has dependency-scanning-profile-0) passed as well.
Proof config, on top of the project's .gitlab-ci.yml. I left it out of this MR because it adds 24 jobs to every pipeline of the project; if you want a regression test, I can add the ensure-job-added half (23 jobs), which passes without allow_failure:
@@ -13,8 +13,18 @@ include:
inputs:
job_name: "excluded job"
exact: true
+ # Proof only: a job created before more than 20 others.
+ - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/ensure-job-added@$CI_COMMIT_SHA
+ inputs:
+ job_name: "early job"
+ exact: true
+ - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/ensure-job-not-added@$CI_COMMIT_SHA
+ inputs:
+ job_name: "early job"
+ exact: true
stages:
+ - build
- test
- release
@@ -28,3 +38,18 @@ excluded job:
- exit 1
rules:
- when: never
+
+early job:
+ stage: build
+ script:
+ - echo "created before the filler jobs"
+
+filler:
+ parallel: 21
+ script:
+ - echo "filler $CI_NODE_INDEX of $CI_NODE_TOTAL"
+
+# "early job" is in the pipeline, so this check is right to fail; let the
+# pipeline status follow the other checks.
+"ensure_job_not_added: early job":
+ allow_failure: trueThe two scripts against a stand-in API, including page boundaries and error answers
I also ran each template's script, rendered with its inputs and preceded by the line GitLab Runner puts before every script (errexit, and pipefail where the shell has it), in badouralix/curl-jq against a small server that pages like this endpoint (per_page 20 by default and 100 at most, newest first, an empty array past the last page). The job looked for, early job, is the one created first.
| Check | Jobs in the pipeline | main |
This branch |
|---|---|---|---|
ensure-job-added, early job |
130 | fails: not found | passes, 2 requests |
ensure-job-not-added, early job |
130 | passes | fails: found, 2 requests |
ensure-job-not-added, absent job |
130 | passes | passes, 2 requests |
ensure-job-added, filler (not exact) |
130 | passes, 20 jobs found |
passes, 127 jobs found, 2 requests |
ensure-job-added, early job |
exactly 100 | fails: not found | passes, 2 requests (the second answers []) |
ensure-job-not-added, early job |
exactly 100 | passes | fails: found, 2 requests |
ensure-job-added, early job |
15 | passes | passes, 1 request |
| both checks | 404 {"message":"404 Project Not Found"} |
fail (jq error) |
fail (jq error), 1 request |
| both checks | 502 HTML page | fail (jq parse error) |
fail (jq parse error), 1 request |
ensure-job-added, early job |
200 with an empty body | passes ([ "" -eq 0 ] errors inside the if, then jobs found) |
fails: not found, 1 request |
With errexit off, a 404 or HTML answer still stops the loop after one request. The empty body is not something this endpoint sends; I list it because it is the one input I tried where the result changes other than through paging, from a pass with an empty count to a failure.