Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2832c57
ci: guard Windows DNS against harden-runner's post-step race; allowli…
osterman Sep 3, 2026
97e87d1
ci: allow the whole NTP pool (*.pool.ntp.org), not just 0.pool.ntp.org
osterman Sep 3, 2026
aeadac9
ci: address review on the Windows DNS guard
osterman Sep 3, 2026
7fa797d
ci: Windows Defender exclusions and restore-only toolchain cache on s…
osterman Sep 3, 2026
951158d
ci: install the toolchain once per OS and ship it in a build artifact
osterman Sep 3, 2026
69da94f
docs: fix indentation in the CI toolchain fix log
osterman Sep 3, 2026
b343149
fix(ci): drop the go.exe Defender process exclusion; it's spoofable
osterman Sep 3, 2026
55e78cb
docs(fixes): correct the mock job's tool list in the summary
osterman Sep 3, 2026
b0bbb9a
Merge remote-tracking branch 'origin/main' into osterman/ci-toolchain…
osterman Sep 4, 2026
620e588
Merge remote-tracking branch 'origin/main' into osterman/ci-windows-d…
osterman Sep 4, 2026
145a8c4
temp: diagnostic - dump live Windows Defender state (to be reverted)
osterman Sep 4, 2026
bc97d8c
Revert "temp: diagnostic - dump live Windows Defender state (to be re…
osterman Sep 4, 2026
faceee0
ci: drop Windows Defender exclusions, they're a no-op
osterman Sep 4, 2026
36cf92d
Merge origin/main into osterman/ci-toolchain-in-build-artifact
osterman Sep 4, 2026
f8c0f44
Merge remote-tracking branch 'origin/osterman/ci-windows-defender-res…
osterman Sep 4, 2026
64dbfc5
ci: the CI toolchain is one step - atmos toolchain install from .tool…
osterman Sep 4, 2026
69b8337
ci(actions/cache): a mode input instead of a restore-only boolean
osterman Sep 4, 2026
afc2104
ci(ci-toolchain): pass the cache mode straight through to the Atmos C…
osterman Sep 4, 2026
1497964
fix(actions/cache): make the post-step save work when nested in a com…
osterman Sep 4, 2026
933f440
Merge remote-tracking branch 'origin/osterman/ci-windows-defender-res…
osterman Sep 4, 2026
3dba100
docs(fixes): nested post-step save and the mode input
osterman Sep 4, 2026
cd82b93
Merge remote-tracking branch 'origin/osterman/ci-windows-defender-res…
osterman Sep 4, 2026
9349603
docs: fix CodeRabbit-flagged doc issues on the cache/Defender fix notes
osterman Sep 4, 2026
ad0e273
ci(mock): allow the toolchain download hosts, like the test job
osterman Sep 4, 2026
62045e9
Merge branch 'main' into osterman/ci-windows-defender-restore-only-cache
osterman Sep 4, 2026
6e2cf28
test(testhelpers): keep one record per fake-runtime invocation with m…
osterman Sep 4, 2026
7a661c2
Merge remote-tracking branch 'origin/osterman/ci-windows-defender-res…
osterman Sep 4, 2026
8b3e8a2
docs(fixes): reconcile toolchain-cache size figures with the stated t…
osterman Sep 4, 2026
3537721
Merge branch 'main' into osterman/ci-toolchain-in-build-artifact
osterman Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/actions/ci-toolchain/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: 'Set up CI toolchain'
description: >
Puts the tools `.tool-versions` pins (Terraform, OpenTofu, Packer, Helm,
Helmfile, ...) on PATH the way a developer does: `atmos toolchain install`,
which reads `.tool-versions` and skips every tool already on disk, then
`atmos toolchain env` to export their directories to PATH. Versions live in
`.tool-versions` only.

Persistence across jobs and runs is atmos's own `ci.cache` (the Atmos Cache
action, ./actions/cache): the one producer job per OS saves the toolchain
directory at job end (`cache: restore-and-save`); every consumer restores it
and never saves (`cache: restore-only`), so ten shards do not race to write one key.
On a miss, `atmos toolchain install` installs from the network as it always
did.

Requires the `atmos` binary to be on PATH (install it before this step).

inputs:
github-token:
description: 'GitHub token for the toolchain registry and release-asset lookups (raises the unauthenticated API rate limit).'
required: true
cache:
description: >
Passed through to the Atmos Cache action's `mode`: `restore-and-save`
(the one producer job per OS - restore, and save at job end),
`restore-only` (consumers, the default), or `none` to skip the cache.
required: false
default: 'restore-only'

runs:
using: composite
steps:
- name: Restore the Atmos toolchain cache
if: inputs.cache != 'none'
continue-on-error: true
uses: ./actions/cache
with:
mode: ${{ inputs.cache }}

- name: Install the tools in .tool-versions that are not already on disk
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
run: atmos toolchain install

- name: Export the toolchain directories to PATH
shell: bash
run: atmos toolchain env --format=github
90 changes: 46 additions & 44 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ jobs:
# other Windows/macOS legs in this file.
allowed-endpoints: >
api.github.com:443
get.helm.sh:443
github.com:443
raw.githubusercontent.com:443
rekor.sigstore.dev:443
Expand Down Expand Up @@ -252,6 +253,24 @@ jobs:
shell: bash
run: go tool mage acceptance:verify "${{ matrix.target }}" "$TEST_SHARD_COUNT"

# Install the toolchain once per OS with the atmos just built (so the
# on-disk layout is the one the consumers' binary expects) and save it
# to atmos's ci.cache; the acceptance shards, terraform-registry-cache
# and mock jobs restore it (never save) and run the same
# `atmos toolchain install`, which then skips every tool. macos-intel is
# skipped: its only consumer (the k3s macOS job) installs no toolchain.
- name: Put the freshly built atmos on PATH for the toolchain install
if: ${{ matrix.target != 'macos-intel' && ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
shell: bash
run: echo "${GITHUB_WORKSPACE}/build" >> "$GITHUB_PATH"

- name: Install the CI toolchain (atmos toolchain install from .tool-versions; saves the cache)
if: ${{ matrix.target != 'macos-intel' && ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
uses: ./.github/actions/ci-toolchain
with:
cache: restore-and-save
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
Expand Down Expand Up @@ -425,22 +444,17 @@ jobs:
path: ${{ github.workspace }}
add-to-path: 'true'

- name: Cache Atmos toolchain
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
continue-on-error: true
uses: ./actions/cache

- name: Install Terraform, OpenTofu, Packer, Helm, and Helmfile
# `atmos toolchain install` from .tool-versions, with the toolchain
# directory restored from atmos's own ci.cache (saved by the build job
# for this OS; consumers never save, so shards do not race for the key).
# Do not export ATMOS_XDG_CACHE_HOME or TF_PLUGIN_CACHE_DIR here: many
# tests assert XDG defaults and Terraform's plugin cache is not safe for
# shared concurrent use.
- name: Set up the CI toolchain
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install hashicorp/terraform
atmos toolchain install opentofu/opentofu
atmos toolchain install hashicorp/packer
atmos toolchain install helm/helm
atmos toolchain install helmfile/helmfile
atmos toolchain env --format=github
uses: ./.github/actions/ci-toolchain
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Verify Terraform, OpenTofu, Packer, Helm, and Helmfile
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
Expand Down Expand Up @@ -662,12 +676,12 @@ jobs:
path: ${{ github.workspace }}
add-to-path: 'true'

# Dogfood `atmos ci cache`: cache the configured Atmos cache root via the
# recommended composite action. Do not export ATMOS_XDG_CACHE_HOME or
# TF_PLUGIN_CACHE_DIR for the full acceptance job: many tests assert XDG
# defaults and Terraform's plugin cache is not safe for shared concurrent
# use. This cache step restores/saves toolchain bits only; it must stay a
# pure accelerator.
# `atmos toolchain install` from .tool-versions, with the toolchain
# directory restored from atmos's own ci.cache (saved by the build job
# for this OS; consumers never save, so shards do not race for the key).
# Do not export ATMOS_XDG_CACHE_HOME or TF_PLUGIN_CACHE_DIR here: many
# tests assert XDG defaults and Terraform's plugin cache is not safe for
# shared concurrent use.
#
# Restore-only on the shards. The repo's Actions cache holds 18.9 GB in
# 17 entries against a 10 GB LRU quota, every entry scoped to a
Expand All @@ -679,24 +693,11 @@ jobs:
# costing 43 s avg / 3 min max per shard for nothing. The
# terraform-registry-cache job keeps the plain (restore+save) step and is
# the single writer of this key per OS.
- name: Cache Atmos toolchain
- name: Set up the CI toolchain
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
continue-on-error: true
uses: ./actions/cache
uses: ./.github/actions/ci-toolchain
with:
mode: restore-only

- name: Install Terraform, OpenTofu, Packer, Helm, and Helmfile
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install hashicorp/terraform
atmos toolchain install opentofu/opentofu
atmos toolchain install hashicorp/packer
atmos toolchain install helm/helm
atmos toolchain install helmfile/helmfile
atmos toolchain env --format=github
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Verify Terraform, OpenTofu, Packer, Helm, and Helmfile
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
Expand Down Expand Up @@ -1762,6 +1763,10 @@ jobs:
ocsp.usertrust.com:80
ocsp.digicert.com:80
*.pool.ntp.org:123
get.helm.sh:443
proxy.golang.org:443
sum.golang.org:443
google.golang.org:443

- name: Check out code into the Go module directory
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
Expand All @@ -1787,14 +1792,11 @@ jobs:
path: ${{ github.workspace }}
add-to-path: 'true'

- name: Install Terraform and OpenTofu with Atmos toolchain
- name: Set up the CI toolchain
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install hashicorp/terraform
atmos toolchain install opentofu/opentofu
atmos toolchain env --format=github
uses: ./.github/actions/ci-toolchain
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Verify OpenTofu
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
Expand Down
56 changes: 56 additions & 0 deletions docs/fixes/2026-09-04-ci-toolchain-one-step.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Fix: the CI toolchain is one step - `atmos toolchain install` from `.tool-versions`

**Date:** 2026-09-04 (supersedes the 2026-09-03 "ship the toolchain as a build artifact" design)

## Summary

Every acceptance shard, the `terraform-registry-cache` legs and the `mock` jobs need the external
tools the repository pins in `.tool-versions` (Terraform, OpenTofu, Packer, Helm, Helmfile, ...).
Two things had grown around that over time:

1. **Versions duplicated in the workflow.** `test.yml` carried `OPEN_TOFU_VERSION`,
`PACKER_VERSION`, `HELM_VERSION`, `HELMFILE_VERSION` next to the pins in `.tool-versions`, and the
two drifted (a stale `OPEN_TOFU_VERSION` of 1.12.2 behind the file's 1.12.5 went undetected;
#3022 removed the env vars). The first version of this PR still fed those variables into its
action, so after merging main it would have written empty pins.
2. **Hand-rolled caching.** Because the "Cache Atmos toolchain" step never hit (the ~5 GB of Go caches
written per run churned the 10 GB Actions cache before a toolchain entry survived), the first
version of this PR built its own mechanism: the build job tarred the installed tree with
`cygpath`-aware shell, uploaded it as a `toolchain-<os>` artifact, and every consumer downloaded and
unpacked it before installing. ~150 lines of shell doing what atmos already does.

## Fix

`.github/actions/ci-toolchain` is now exactly what a developer runs, plus the cache atmos already
knows how to describe:

1. `./actions/cache` (the Atmos Cache action, driven by `ci.cache` in `atmos.yaml`): `cache: save` on
the one producer job per OS (the `build` job, with the atmos it just built), `restore-only` (the
default) on every consumer, so ten shards restore one key and none of them races to write it
(`restore-only` comes from #3038, merged into this branch).
2. `atmos toolchain install`: reads `.tool-versions`, installs what is not on disk, skips the rest.
This is the from-file form, the only one that takes the "already installed" path; the per-tool
`--default owner/repo@version` form always re-resolves and re-verifies.
3. `atmos toolchain env --format=github`: exports the tool directories to `PATH`.

Every job that needs the toolchain is one step: `uses: ./.github/actions/ci-toolchain` with the
token (and `cache: save` on the build job). Versions are pinned in `.tool-versions` only. No tar, no
artifact, no `cygpath`, no per-job tool list.

Why atmos's cache is enough now: the repository's Actions cache limit was raised from 10 GB to 50 GB
(org setting, 2026-09-04) after measuring that main's entries never survived a run at 10 GB, and
`restore-only` on consumers removes the ten-way save race. A toolchain entry per OS is ~400-600 MB.

## Validation

- `actionlint .github/workflows/test.yml`, pre-commit hooks clean.
- The PR's own `Tests` run: the build job saves `atmos-toolchain-<os>-<arch>-v2`; each shard's
`Set up the CI toolchain` step restores it and `atmos toolchain install` logs every tool as already
installed.

## Follow-ups

- `HELM_DIFF_VERSION` (a Helm plugin, not a toolchain tool) is still a workflow env var; the
`helm plugin install` step stays as is.
- Jobs that install a single tool inline (`atmos toolchain install opentofu/opentofu` in the floci,
kubernetes-e2e and container-step jobs) could use the same action; left as they are here.
Loading