STAC-25142 Move the agent CI to GitHub Actions and retire the GitLab pipelines - #444
Conversation
3cd1a54 to
7308812
Compare
76349d9 to
c2e9da2
Compare
…ration phase 1) Ports filename_linting + unbranded/branded unit_tests from .gitlab-ci-agent.yml to GitHub Actions, tracking the active stackstate-7.78.2 pipeline. - filename-linting on `docker` (cheap gate; only needs `git ls-files`). - unbranded/branded unit tests on `xlarge` inside the datadog_build image (tag 7af9194f), pulled via the read-only quay proxy. - 7.78.2 deltas vs 7.71.2: `--major-version` dropped from agent.build/inv test (removed upstream in DD 7.78), and branded runs scripts/verify_branding_literals.sh after fix_branding.sh. Test-execution policy (architect directive — minimize redundant runs): each suite runs once. filename-lint on every PR; the two heavy ~2h suites on pull_request, deduped via concurrency cancel-in-progress. They move to merge_group (run once at land) at phase 6; master/version-branch pushes then build+publish without re-running tests. Security: minimal contents: read, SHA-pinned checkout, persist-credentials false, no PATs, registry-proxy secret only via container.credentials, fork PRs skipped. Zizmor clean; actionlint clean apart from self-hosted labels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…; conda needs bash)
…ds the 3.x version tag
…on phase 2) Port build_binaries and build_cluster_agent from .gitlab-ci-agent.yml to .github/workflows/build-binaries.yml: build the branded (StackState) production agent (+ rtloader) and cluster-agent in the datadog_build container on public xlarge runners, once per change (pull_request), deduped via concurrency. Carry over the GitLab pipeline speedups: self-contained jobs with no shared deps prerequisite (the Go module cache is untransportable between jobs and shared vendoring breaks the go.mod<->vendor reconcile), version.txt generated in-job from git, and a runner-local persistent-cache follow-up noted for the real warm-cache win. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e image Speeds up the migrated stackstate-agent GitHub Actions build/test jobs by replacing each job's cold `go clean -modcache` + `inv deps` (go mod download + tidy) reconcile with a warm GOMODCACHE delivered as a prebuilt CI image, keyed by a content hash of the module graph. Mirrors the StackGraph ci-metadata / build-ci-image pattern. - .github/scripts/agent-godeps-cache-metadata.sh: content-addressed image tag from every go.mod/go.sum + go.work + modules.yml plus the Dockerfile/script themselves, so any graph or mechanism change rotates the tag. - .github/docker/godeps-cache/Dockerfile: FROM datadog_build, bakes external modules into GOMODCACHE via a per-module `go mod download` loop. - .github/workflows/godeps-cache.yml: reusable workflow (tag lookup + build only when missing); pushes to quay.io/stackstate (private), pulled by consumers through the registry.tooling quay proxy with the existing REGISTRY_* creds. - build-binaries.yml / lint-and-unit-tests.yml: add the godeps-cache job, repoint the 4 heavy jobs to the cache image, drop the per-job modcache reset + inv deps (keep `go work sync` / `go work vendor` against the warm cache). Zizmor clean (narrow unpinned-images ignore on the content-addressed container refs, which are effectively digest-pinned but computed per run). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c2e9da2 to
c07d0c8
Compare
RUN ran under dash so the read -d '' loop never iterated; -mod=mod is rejected in workspace mode.
STAC-25429 Warm the Go module cache with a prebuilt, content-addressed CI image
STAC-25459 Wire inv check-mod-tidy into CI, gated on the godeps cache hash
… STAC-25142-agent-omnibus-deb
STAC-25142 Add omnibus DEB build and container image workflows
d7e06f4 to
ca28691
Compare
STAC-25500 Port the DEB signing and pre-release publishing lane to GitHub Actions
STAC-25457 Publish and sign the agent and cluster-agent images from GitHub Actions
|
Do not merge yet — the DEB signing credentials are only half-provisioned. pulumi-infra #283 merged and its apply partially succeeded:
Safe to merge once the permission is granted and the pulumi apply is re-run green. |
|
Hold cleared — credentials are provisioned. `agent-pre-release` environment on this repo now has all four `SIGNING_*` secrets, with a protected-branches-only deployment policy, and `AGENT_PRERELEASE_ROLE_ARN` is set. pulumi-infra #283 is merged and applied (run 31400655652). Remaining blocker on this PR is review only. |
Branch protection required 'Build agent binary (branded / StackState)' and the cluster-agent equivalent, but the build lanes are an arch matrix, so the emitted contexts carry a ', amd64' / ', arm64' suffix and those two required contexts could never report. Every pull request to stackstate-7.78.2 was BLOCKED. Enumerating the suffixed names would work until the next matrix or rename change breaks it again, silently. Instead adopt the aggregate already used by stackstate-agent-integrations: one job per workflow that evaluates its needs and reports a single stable context. The aggregate fails on a skipped upstream job rather than passing. Fork pull requests skip every build and test job, so treating skipped as success would let a fork merge on a pipeline that ran nothing. mod-tidy is the one documented exception, skipped when the cache image already covers the module graph. Publish, signing and manifest jobs are excluded: they are push-only and can never report on a pull request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Unblocked — this now needs only a review. pulumi-infra#286 merged and applied, so the release-branch protection reads: All three report |
GitLab pipeline schedule 20682 runs the agent pipeline on the release branch every weekday evening. Archiving the GitLab project without an equivalent here would end nightly builds silently, so this adds one. Those scheduled runs are now the only pipelines still executing over there. That schedule is 22:00 in Europe/Amsterdam. GitHub cron is UTC with no timezone support, so this uses 20:00 UTC to match during CEST and accepts an hour's drift in winter, a few minutes off the hour because scheduled runs are delayed at popular times. The schedule covers the build, DEB and image workflows but not the unit suites. Those are two ~2h jobs on self-hosted xlarge runners and they already run on the pull request and again on the push, so a third nightly run buys no signal. Publication had to be widened as well. Every publishing job tested for a push event, and a scheduled run is not one, so the nightly would have built the packages and images and then discarded them. The Cerberus conditions carried the same test, which would have left a nightly failure reported to nobody. Image tags are the commit SHA, so a nightly on an unchanged branch republishes an identical tag. The point is drift in the base images and fetched dependencies, not fresh artifacts. Keying concurrency on the event name stops a push from cancelling a nightly that is already running. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2a3ce7e to
05d578f
Compare
The GitHub workflows cover the pipeline, so the GitLab definitions are dead weight. Only the four StackState-owned files go: the parent pipeline, the agent child pipeline, the manifest-merge pipeline, and the runner-image pipeline whose trigger has been commented out for a long time. Deliberately left in place: - `.gitlab/`, 129 upstream files that STS pipelines never included but that around fifteen upstream `tasks/*.py` modules read. Deleting them would break upstream tooling and guarantee conflicts on the next merge. - `.gitlab-scripts/setup_artifact_registry.sh`, which the GitHub DEB build, `tasks/omnibus.py` and an omnibus software definition all call. The name is now misleading but the file is live. - `.ci-builders/`, no longer built by anything but not pipeline definitions. `tasks/buildimages.py` and two Renovate regex managers read `.gitlab-ci.yml` for `CI_IMAGE_*` variables. The STS file never defined any, so both were already no-ops and are left untouched rather than diverging further from upstream. Documentation is repointed at the workflows. Two claims went stale rather than moving: the `[cluster-agent]` commit-message optimisation, which neither pipeline ever implemented, and `AGENT_HELM_CHART_VERSION`, an override the GitHub lane does not carry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review catch (LouisLotter): a scheduled run reports github.sha as the current default-branch commit, so every publish job would re-derive the same <sha8> tag the preceding push already published. push-single-arch refuses to overwrite an existing final tag and exits 1, so the first weekday schedule after any push would have failed in publish-agent-image and publish-cluster-agent-image. Publishing on a schedule was never the point. The nightly exists to catch drift in base images, dependencies and CVE gates, and it still builds the binaries, the DEB and both container images, and scans them. Republishing a byte-identical artifact under a tag that already resolves adds nothing, and the images the tag points at are already there from the push. Reverts the seven publish gates to push-only and leaves the two Cerberus gates on push || schedule, so a failed nightly still raises a Slack notification. This also restores the "they only run on push" comments in both ci-success jobs, which the earlier change had silently made untrue. Neither ci-success requires a publish job, so skipping them cannot redden a scheduled run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review catch (LouisLotter): skipping mod-tidy when the godeps cache image already exists let an untidy module graph go green. The cache image is published before the check runs, and the DEB and binary workflows publish it too, so a failing check that is simply re-run sees image_exists == true, skips, and ci-success accepted "skipped". The premise was also wrong independently of the ordering. The cache tag hashes only the module manifests, while tidiness is a function of those manifests and the imports in the Go sources. Dropping the last import of a dependency leaves go.mod untouched, so the tag does not rotate, the image still exists, and exactly the drift this check exists to catch is skipped. Measured before changing it: across the last 40 runs of this workflow the check executed once, on 2026-07-31, and was skipped the other 39 times. It has been effectively off for two weeks. That single run took 16 minutes against unit suites that take about an hour on the same trigger, so running it every time costs no wall-clock - it finishes well inside the critical path. Drops the skip condition, the now-unused force_mod_tidy dispatch input, and the allow_skipped helper in ci-success, which existed only for this job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Reviewed the ported workflows against the four deleted GitLab pipeline files and the pinned image-pipeline action source. Overall this is a careful port — the fork-PR guards, proxy-defaulted Dockerfile ARGs, full-severity scan gates on locally built images, and the checksum-verified deb-s3 install all land correctly, and the single-arch image publish is actually stricter than GitLab was.
Findings are inline. Two I would treat as blocking:
- The nightly cannot succeed (
build-deb.yml:9). Bothpush-single-archandmerge-multiarchexit 1on an existing tag at the pinned SHA, and a scheduled run recomputes the same<sha8>tag the last push published. The PR body states the opposite, so this reads as a genuine misreading of the action rather than an accepted tradeoff. Confirms and extends @LouisLotter's comment. mod-tidycan skip its way to green (lint-and-unit-tests.yml:73) — also @LouisLotter's; I have added why it bites harder given this PR makes that check a gate.
Then three structural ones worth your judgement: the cluster-agent manifest has lost its cross-workflow gates on the DEB build and branding verification (build-binaries.yml:331), the agent manifest has no edge to DEB signing within its own workflow (build-deb.yml:379), and tag-triggered builds are dropped with no mention in the description (build-deb.yml:10).
Three minor: the nightly amd64 cache stagger degenerates to a 15-minute wait, Cerberus loses the commit title on scheduled runs, and one residual unpinned apt-get in the signing job.
On the manifest-gating ones especially — those are a consequence of splitting one GitLab pipeline into three workflows, so there is a real design call to make about whether the gate is worth reconstructing, and you have more context than I do on how the release branch is actually consumed. Happy to be told the risk is acceptable.
Two things I checked that did not turn out to be problems, in case they come up: the dropped floating :stackstate-7.78.2-<arch> tag has no consumers (helm-charts-internal values, its updatecli policy, and beest/helpers/resolve-agent-hashes.sh all resolve sha8 tags), and excluding unit tests from the nightly is faithful — they were when: never on the release branch in GitLab too. All five action tag comments resolve correctly, and the image-pipeline pin is the current merged main tip.
One request: no Zizmor result in the description, and the repo has no zizmor.yml. Also the body cites validation on 3e2a23fe while head is ba25dc9f — the three aggregates are green on ba25dc9f as well, so it is only the body that is stale.
Splitting the GitLab child pipeline into three workflows dropped the stage barrier that gated publication on every earlier verification job. `needs:` cannot cross workflow boundaries, so a push could publish the agent DEB and both images while unit tests were failing. await-checks.yml rebuilds the barrier by polling the commit's check runs until the named aggregates conclude, and fails closed on failure, on a non-success conclusion and on timeout. filter=latest is used so a re-run that fixed a failure is what gets read. Publishing in build-deb waits for lint and unit tests; publishing in build-binaries additionally waits for the DEB build, which is what GitLab's `strategy: depend` on the child pipeline enforced. The agent manifest now also waits for DEB signing, so the image cannot advertise a package that never reached the apt repository. The gate deliberately stays out of ci-success and cerberus-notify: it is push-only, so requiring it would fail every pull request on `skipped`, and notifying on it would fire three Slack alerts for one unit test failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The staggered wait needs exactly one caller on delay 0 to build the cache image while the others wait it out. amd64's leader was lint-and-unit-tests, which has no schedule trigger, so on a nightly with a cold cache both remaining callers waited the full 900s before building anything. Move the amd64 leader to build-deb, which does run on the schedule and is the longest job on the critical path. arm64 already had its leader in build-binaries, which is also scheduled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
schedule payloads carry no head_commit, so the Slack message lost its commit title in exactly the case the nightly notification exists for. Fall back to the commits API, and to a placeholder if that fails, so a missing title can never fail the notify job itself. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The workflows were being audited with Zizmor by hand but nothing enforced it in CI, so a future workflow change could regress unnoticed. Run it as a required job on ubuntu-24.04, pinned and Renovate-tracked, and fold it into the lint and unit test aggregate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ithub The runbook still instructed a blanket `git rm -r .github` after every upstream merge, which predates the agent pipeline living there and would now delete the entire CI. Narrow it to pruning Datadog's own files, name the StackState-owned tree explicitly, and record that the GitLab mirror that motivated the blanket delete is retired. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cut the header and hash-input comments back to the constraints that are not visible in the code: why the cache has its own quay repo, and why the base image name is hashed as well as its tag. Drops the narration of which repo the approach came from and the restatement of the file globs. The script hashes itself, so this rotates the cache tag once by design. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Review feedback addressed — six commits, all threads answered inline.
The two blocking bot findings were already fixed in Two left open, both answered inline rather than changed here:
Description refreshed: it no longer claims the nightly publishes, and it covers the publish gate and the tag decision. |
What
Ports the
stackstate-agentGitLab pipeline to GitHub Actions, for amd64 and arm64.inv check-mod-tidy, Zizmor workflow audit, branded + unbranded unit suitesEach top-level workflow ends in a
CI success (...)aggregate, and those three are the required checks on the base branch. A matrix job cannot be required directly by name, so the aggregate is what branch protection can pin.Publication is push-only, and gated on the other workflows
Every publishing job is gated on
github.event_name == 'push'. A PR builds, smoke-tests and scans the images and the DEB, then throws them away. Jobs needing registry credentials skip on fork PRs.GitLab ran this as a single pipeline, so publication sat behind every earlier verification job. Three workflows cannot express that with
needs:, soawait-checks.ymlrebuilds the barrier by polling the commit's check runs, failing closed on failure and on timeout. The DEB lane waits for lint and unit tests; the binaries lane waits for those plus the DEB build, which is whatstrategy: dependon the child pipeline used to enforce. The agent manifest additionally waits for DEB signing, so an image cannot advertise a package that never reached the apt repository.Nightly
GitLab schedule 20682 builds the release branch every weekday evening (22:00 Europe/Amsterdam; GitHub cron is UTC-only, so the trigger uses 20:00 UTC and drifts an hour in winter). The build and DEB workflows carry an equivalent
schedule:trigger so archiving GitLab does not end nightly builds silently. The unit suites are excluded — two ~2h jobs that already run on the PR and on the push.The nightly does not publish. Image tags are the commit SHA, so a scheduled run on an unchanged branch would re-derive a tag the preceding push already pushed, and the publish action refuses to overwrite. Its value is catching drift in base images and dependencies; Cerberus still reports scheduled failures.
Tag triggers are not carried over
The GitLab rules fired on tags as well as commits. Tags on this repo exist only to satisfy an upstream-merge requirement — nothing builds from them and no release depends on one — so the workflows trigger on
pushto the release branch,pull_request,scheduleandworkflow_dispatchonly.GitLab pipelines are removed
The four StackState-owned GitLab pipeline files are deleted, since these workflows replace them. Scheduled runs were the only pipelines GitLab had executed since 27 July, so the nightly above is what was keeping it alive.
Left in place deliberately:
.gitlab/(129 upstream files that ~15 upstreamtasks/*.pymodules read),.gitlab-scripts/setup_artifact_registry.sh(called by the DEB build here,tasks/omnibus.pyand an omnibus software definition), and.ci-builders/. Docs are repointed at the workflows, andUPSTREAM_MERGE.mdno longer tells the next merge torm -rf .github.The 256 go.mod / go.sum files
One commit, STAC-25466.
inv check-mod-tidyis now a gate and the workspace manifests did not survive it. No version was picked by hand; this is whatgo mod tidyproduces.Validation
All three required aggregates green on
90014e4c, the review-feedback head, including the full arm64 run. Zizmor is clean over the whole tree in both offline and online modes, and actionlint reports nothing beyond the known self-hostedrunner-labelfalse positives.The publish gate was exercised against a stubbed API before it shipped: both checks passing exits zero, a failing check exits non-zero without waiting out the poll, and a check that never appears fails on timeout rather than falling through.
Jira: https://stackstate.atlassian.net/browse/STAC-25142
Sub-tasks merged into this branch: STAC-24889, 25429, 25455, 25457, 25459, 25461, 25464, 25466, 25494, 25498, 25500, 25536, 25575, 25583