Skip to content

STAC-25142 Move the agent CI to GitHub Actions and retire the GitLab pipelines - #444

Merged
LouisParkin merged 61 commits into
stackstate-7.78.2from
STAC-25142-agent-lint-unit
Aug 13, 2026
Merged

STAC-25142 Move the agent CI to GitHub Actions and retire the GitLab pipelines#444
LouisParkin merged 61 commits into
stackstate-7.78.2from
STAC-25142-agent-lint-unit

Conversation

@LouisParkin

@LouisParkin LouisParkin commented Jul 10, 2026

Copy link
Copy Markdown

What

Ports the stackstate-agent GitLab pipeline to GitHub Actions, for amd64 and arm64.

Workflow Covers
Lint and unit tests filename linting, inv check-mod-tidy, Zizmor workflow audit, branded + unbranded unit suites
Binary builds agent and cluster-agent binaries, cluster-agent image build/publish/sign, multi-arch manifest
DEB package build omnibus DEB, branding verification, agent image build/publish/sign, GPG signing, pre-release apt repository, install script
Go dependency cache image reusable — bakes the module cache so the heavy jobs start warm instead of re-downloading
Await checks reusable — holds publication until the other workflows have passed
Cerberus notify reusable — reports release-branch failures to Slack

Each 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:, so await-checks.yml rebuilds 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 what strategy: depend on 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 push to the release branch, pull_request, schedule and workflow_dispatch only.

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 upstream tasks/*.py modules read), .gitlab-scripts/setup_artifact_registry.sh (called by the DEB build here, tasks/omnibus.py and an omnibus software definition), and .ci-builders/. Docs are repointed at the workflows, and UPSTREAM_MERGE.md no longer tells the next merge to rm -rf .github.

The 256 go.mod / go.sum files

One commit, STAC-25466. inv check-mod-tidy is now a gate and the workspace manifests did not survive it. No version was picked by hand; this is what go mod tidy produces.

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-hosted runner-label false 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

@LouisParkin
LouisParkin changed the base branch from stackstate-7.71.2 to stackstate-7.78.2 July 10, 2026 15:20
@LouisParkin
LouisParkin force-pushed the STAC-25142-agent-lint-unit branch from 3cd1a54 to 7308812 Compare July 10, 2026 15:22
@LouisParkin
LouisParkin force-pushed the STAC-25142-agent-lint-unit branch from 76349d9 to c2e9da2 Compare July 27, 2026 09:25
LouisParkin and others added 8 commits July 28, 2026 11:39
…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>
…ic/xlarge-public)

Public repos can't use the shared docker/xlarge runners; use the isolated agent-public pool (pulumi-infra #227 + argocd-apps #115).
…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>
@LouisParkin
LouisParkin force-pushed the STAC-25142-agent-lint-unit branch from c2e9da2 to c07d0c8 Compare July 28, 2026 09:40
@LouisParkin
LouisParkin force-pushed the STAC-25142-agent-lint-unit branch from d7e06f4 to ca28691 Compare August 10, 2026 08:22
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
@LouisParkin

Copy link
Copy Markdown
Author

Do not merge yet — the DEB signing credentials are only half-provisioned.

pulumi-infra #283 merged and its apply partially succeeded: stackstate-agent now has the agent-pre-release environment (correctly restricted to protected branches) and the AGENT_PRERELEASE_ROLE_ARN variable, but zero secrets in the environment. The pulumi-infrastructure App lacks the environments permission, so it created the environment and then got 403 writing secrets into it (run 31400655652). Tracked as STAC-25571, approved to fix.

sign-and-publish-deb and publish-install-script are gated if: github.event_name == 'push', so nothing here exercises them and this PR stays green — they would fail on the first push to stackstate-7.78.2 after merge.

Safe to merge once the permission is granted and the pulumi apply is re-run green.

@LouisParkin

Copy link
Copy Markdown
Author

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>
@LouisParkin

Copy link
Copy Markdown
Author

Unblocked — this now needs only a review.

pulumi-infra#286 merged and applied, so the release-branch protection reads:

CI success (lint and unit tests)
CI success (DEB package build)
CI success (binary builds)

All three report success on head 3e2a23fe. The two contexts that could never report (the Build … binary names, stale since the build jobs became an arch matrix) are gone, so BLOCKED is now just the one required approving review — enforce_admins is on, so it cannot be bypassed.

@LouisParkin LouisParkin changed the title STAC-25142 Add GitHub Actions lint + unit-test workflow (agent CI migration phase 1) STAC-25142 Port the agent build, package, and publish pipeline to GitHub Actions Aug 12, 2026
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>
@LouisParkin
LouisParkin force-pushed the STAC-25142-agent-lint-unit branch from 2a3ce7e to 05d578f Compare August 12, 2026 12:35
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>
@LouisParkin LouisParkin changed the title STAC-25142 Port the agent build, package, and publish pipeline to GitHub Actions STAC-25142 Move the agent CI to GitHub Actions and retire the GitLab pipelines Aug 12, 2026
Comment thread .github/workflows/build-binaries.yml Outdated
Comment thread .github/workflows/lint-and-unit-tests.yml Outdated
LouisParkin and others added 3 commits August 13, 2026 09:33
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>
Comment thread exceptions/stackstate-k8s-agent/CVE-2025-15366.yaml
Comment thread .github/workflows/lint-and-unit-tests.yml
Comment thread .github/scripts/agent-godeps-cache-metadata.sh Outdated
Comment thread UPSTREAM_MERGE.md
Comment thread .github/workflows/build-deb.yml
Comment thread .github/workflows/build-deb.yml

@ai-collaboration-app ai-collaboration-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. The nightly cannot succeed (build-deb.yml:9). Both push-single-arch and merge-multiarch exit 1 on 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.
  2. mod-tidy can 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.

Comment thread .github/workflows/build-deb.yml
Comment thread .github/workflows/lint-and-unit-tests.yml
Comment thread .github/workflows/build-binaries.yml
Comment thread .github/workflows/build-deb.yml Outdated
Comment thread .github/workflows/build-deb.yml
Comment thread .github/workflows/build-deb.yml Outdated
Comment thread .github/workflows/cerberus-notify.yml
Comment thread .github/workflows/build-deb.yml
LouisParkin and others added 6 commits August 13, 2026 12:21
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>
@LouisParkin

Copy link
Copy Markdown
Author

Review feedback addressed — six commits, all threads answered inline.

Commit Fixes
86ecee68 publication gated on the other workflows' verification (@rb3ckers) — new reusable await-checks.yml, fails closed
59fe9383 Zizmor now a required CI job (@rb3ckers)
f3bb79e1 UPSTREAM_MERGE.md no longer tells the next merge to delete .github (@rb3ckers)
90014e4c trimmed the cache-metadata commentary (@rb3ckers)
bfe1ba6d amd64 dependency cache had no leader on scheduled runs
36633e44 Cerberus lost the commit title on scheduled runs

The two blocking bot findings were already fixed in 15681e34 and 2a43e940 before the review landed.

Two left open, both answered inline rather than changed here:

  • build-agent-image builds a different image than publish-agent-image publishes, so the scan gate is not running against the shipped artifact. Real, but fixing it properly means changing push-single-arch in the shared image-pipeline, so I would rather follow up than widen this PR. The job itself still has to stay — it is the only image build and scan on a pull request.
  • Tag triggers are deliberately not carried over; now stated in the description.

Description refreshed: it no longer claims the nightly publishes, and it covers the publish gate and the tag decision.

@LouisParkin
LouisParkin requested a review from rb3ckers August 13, 2026 11:43
@LouisParkin
LouisParkin merged commit a416eb0 into stackstate-7.78.2 Aug 13, 2026
55 of 57 checks passed
@LouisParkin
LouisParkin deleted the STAC-25142-agent-lint-unit branch August 13, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants