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-added fails for a job that is in the pipeline but was created before those 20.
  • ensure-job-not-added passes 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

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: true
The 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.

Merge request reports

Loading
Loading