Skip to content

feat: automate collection releases with semantic-release - #1

Merged
Mearman merged 13 commits into
mainfrom
feat/semantic-release
Sep 25, 2026
Merged

Mearman merged 13 commits into
mainfrom
feat/semantic-release

Conversation

@Mearman

@Mearman Mearman commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Adds semantic-release, so a push to main that warrants a release cuts a version, builds the collection tarball, and builds and pushes all three container images tagged with that version.

  • package.json/package-lock.json: semantic-release with the changelog, exec, git and github plugins, and commitlint with the conventional-commits config. Private, devDependencies only, and excluded from the built collection by galaxy.yml's build_ignore.
  • .releaserc.json: a breaking change is major, feat is minor, and every other conventional type is a patch, so almost every push releases. The exec plugin runs scripts/release/stamp_version.py, which writes the version into galaxy.yml and into the ARC role's heartbeat and autoscaler image defaults (matched by variable name), then builds the tarball. The git plugin commits the changelog and both stamped files as chore(release): <version> [skip ci]; the github plugin attaches the tarball to the release; Ansible Galaxy publishing runs only if GALAXY_API_KEY is set.
  • .github/workflows/release.yml: runs semantic-release on push to main and, when a release is published, builds the runner, heartbeat and autoscaler images multi-arch on GitHub-hosted ubuntu-latest (QEMU for arm64), tagged with the exact version, the floating major and latest. It replaces build-runner-image.yml, which only built the runner image. The release commit and tag are pushed over SSH with a write deploy key (RELEASE_DEPLOY_KEY); the GitHub API calls use the job's own token.
  • .github/workflows/ci.yml: commitlint on every commit in a PR and on the PR title (run from the repo's own devDependencies), and a collection build check that builds the tarball, installs it and lists what shipped. Both join required-checks.
  • README: a Releases section, and the image-build passages now describe the release workflow.

The deploy key and RELEASE_DEPLOY_KEY secret are set on this repository. No tag exists yet, so the first release will be computed from the commit history.

@Mearman
Mearman marked this pull request as ready for review September 25, 2026 20:14
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-09-25T20:19:20.849832Z 8c2ee74 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Root-level tooling for automating collection releases: semantic-release
plus the changelog, exec, git, and github plugins, and commitlint with
the conventional-commits config. Not published; the package itself is
private and excluded from the built collection tarball.
Replaces build-runner-image.yml, which only ever built the runner image
and could not push a version-matched tag for a release. release.yml now
builds and pushes the runner, heartbeat, and autoscaler images together,
once per published release, each tagged with the exact version, the
floating major version, and latest on the same manifest, using
docker/metadata-action's semver patterns against the version semantic-
release just decided.

The image build stays on the fleet's own dedicated exadev-image-builder
profile rather than routing through the ordinary CI fallback: it is a
privileged Docker-in-Docker workload with no GitHub-hosted fallback that
reliably schedules for this org (see closed issue #10), the same
reasoning build-runner-image.yml already documented.
.releaserc.json drives the release from Conventional Commits: a
breaking change is major, feat is minor, every other conventional type
(fix, perf, refactor, docs, and so on) is patch, matching
commitlint.config.js's own accepted types one for one.

Its exec step runs scripts/release/stamp_version.py, which writes the
new version into galaxy.yml's version field and into the arc role's
two published-image tag defaults (github_runner_arc_heartbeat_image,
github_runner_arc_autoscaler_image), matched by variable name rather
than line number so it stays correct against concurrent edits to that
file, then builds the collection tarball. The git step commits the
changelog and both stamped files back to main with a skip-ci release
commit; the github step attaches the tarball to the GitHub Release;
the exec publish step pushes to Ansible Galaxy only when a
GALAXY_API_KEY is actually configured.
galaxy.yml's build_ignore already excluded everything that isn't
collection content; node_modules, the two package manifests, the
semantic-release config, and the commitlint config belong in that same
list, not in the tarball ansible-galaxy publishes. .gitignore gets the
matching entries for node_modules and the tarball itself, plus .ansible
(ansible-lint's own local collection cache, created as a side effect
of running it against this checkout).
commitlint runs against every commit in a pull request (wagoid/
commitlint-github-action) and against the pull request's own title
(amannn/action-semantic-pull-request) separately, since either one can
carry a conventional-commit violation the other wouldn't catch; both
are pull-request-only, since there's nothing to validate on an
ordinary push to main.

ansible-lint runs at its min profile, not the default: the default
profile already flags real, pre-existing findings across roles/ (task
naming casing, Jinja templates placed mid-name) that are a separate,
already-owned concern for whoever next touches that role, not
something this change should turn into a blocking failure for every
unrelated pull request. min still catches genuine errors.

The collection build check builds the tarball, installs it into a
temp path, and lists what actually shipped (roles, filter plugins) -
catching a build_ignore mistake that silently drops or includes the
wrong content before it reaches a release.

All four are added to required-checks' own needs list alongside the
existing lint jobs.
…laim

Adds a Releases section covering what triggers a release, what each
release step does, the [skip ci] release commit's own reasoning, the
version this repo will start from with no prior tags, and the exact
secrets the release push and the optional Galaxy publish need.

Also corrects two passages that still described
build-runner-image.yml as running on GitHub-hosted ubuntu-latest and
failing outright (true when written, per closed issue #10): that
workflow is gone, and the image build it used to attempt now happens
from release.yml on the fleet's own exadev-image-builder profile.
v6 is the action's current release; there's no existing pin to match
here since this action is new to the workflow, unlike the other
docker/* actions in the same job, which intentionally keep the exact
versions build-runner-image.yml already ran in production.
Three comments (actionlint's own self-hosted-runner label config, and
two fleet configs describing why the image-builder scale-set profile
is separately labelled) still named the workflow file this change
deletes. All three now point at release.yml's build-images job, which
does the same job on the same profile.
The release commit and tag go over SSH with a write-enabled deploy key
scoped to this repository; the GitHub API calls keep using GITHUB_TOKEN.
The Docker-based commitlint action could not pull its image on the
self-hosted runners, and the PR-title action lacked permission to read
the pull request, so both now run the pinned commitlint with npx. pip
installed ansible-lint outside PATH, which failed the lint job with
exit 127.
Comments and the README cited issue numbers and a values file from the
private repository this history came from, which don't exist here.
They now state the constraint itself: GitHub-hosted runners can't be
relied on for this org, and image builds run on the fleet's builder
profile.
ansible-lint's production profile applies yamllint's comment spacing to
every YAML file, workflows included, and failed on these three.
The release job and the three multi-arch image builds move to
ubuntu-latest like the rest of the repository's CI, with QEMU building
the arm64 half, so a release never depends on the fleet it ships images
for. actionlint no longer needs to know the builder profile's label.
@Mearman
Mearman force-pushed the feat/semantic-release branch from 8c2ee74 to daa49e4 Compare September 25, 2026 20:48
@Mearman
Mearman merged commit cccdfc6 into main Sep 25, 2026
10 checks passed
@Mearman
Mearman deleted the feat/semantic-release branch September 25, 2026 21: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.

1 participant