Skip to content

fix(scaffold): preserve source in scaffold config #26787

fix(scaffold): preserve source in scaffold config

fix(scaffold): preserve source in scaffold config #26787

Workflow file for this run

name: Tests
on:
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
- "README.md"
# Required checks must also run against the synthetic commit created for a
# GitHub merge queue entry.
merge_group:
types: [checks_requested]
push:
branches:
- main
- release/v*
paths-ignore:
- ".github/**"
- "docs/**"
- "examples/**"
- "test/**"
workflow_dispatch:
# Grant `packages: read` so jobs that pull OCI images from ghcr.io
# (e.g. vendor pulls in mock/acceptance tests) can authenticate with
# the auto-generated GITHUB_TOKEN. The default PR-event scope is
# `contents: read` only, which causes ghcr.io to reject the token with
# `DENIED: denied` whenever credentials are sent (as happens on Windows
# runners that have no usable Docker keychain entry for ghcr.io).
permissions:
checks: read
contents: read
packages: read
pull-requests: read
env:
ATMOS_BOOTSTRAP_VERSION: "1.223.0"
OPEN_TOFU_VERSION: "1.12.2"
HELM_VERSION: "v3.19.2"
HELM_DIFF_VERSION: "v3.15.10"
HELMFILE_VERSION: "v1.1.0"
PACKER_VERSION: "1.14.2"
# Use pipe fallback so transient proxy.golang.org errors (5xx, HTTP/2 stream
# resets) during `go mod download` fall back to direct module fetches. The
# default comma-separated GOPROXY list only falls through on "not found"
# responses, not network errors. See .github/workflows/native-ci.yml.
GOPROXY: "https://proxy.golang.org|direct"
jobs:
# ensure the code builds...
build:
name: Build (${{ matrix.target }})
# windows-latest is the slowest target: it compiles this large dependency
# tree far slower than the Linux (self-hosted, s3-cache) and macOS runners,
# and on top of that the post-job Go cache save (tar + zstd of
# GOMODCACHE/GOCACHE) takes several minutes. A cold cache after dependency
# changes plus that save pushes the job past a tight limit and the run gets
# cancelled mid-save (see #2713's windows build being killed at 30m10s).
# Give the slowest target real headroom for build + cache save.
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- os: "runs-on=${{github.run_id}}/runner=terraform/tag=atmos/extras=s3-cache/private=false"
target: linux
- os: "windows-latest"
target: windows
- os: "macos-15"
target: macos
# Docker-backed macOS CI uses Colima. GitHub's M-series macOS runners
# do not support the virtualization path Colima needs, so build a
# separate Intel artifact for the k3s macOS jobs only.
- os: "macos-15-intel"
target: macos-intel
runs-on: ${{ matrix.os }}
steps:
- uses: runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60 # v2.1.2
if: matrix.target == 'linux'
- name: Announce build target
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
run: echo "Building on ${{ matrix.os }}"
- name: Add GNU tar to PATH (significantly faster than windows tar)
if: matrix.target == 'windows' && ! github.event.pull_request.draft
shell: pwsh
run: echo "C:\Program Files\Git\usr\bin" >> $Env:GITHUB_PATH
- name: Check out code into the Go module directory
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Go
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
# setup-go v6 requires runner v2.327.1+ and can affect toolchain handling.
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version-file: "go.mod"
# Disable setup-go's module cache to avoid restoring or saving mutable
# cache entries from PR-controlled keys; make deps repopulates modules
# from checksummed sources instead.
cache: false
id: go
- name: Set up Atmos bootstrap
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
uses: ./.github/actions/setup-atmos-bootstrap
with:
atmos-version: ${{ env.ATMOS_BOOTSTRAP_VERSION }}
- name: Get dependencies
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
shell: bash
run: |
# go mod download hits the network (proxy.golang.org, falling back to
# a direct github.com clone for modules it doesn't index) and has no
# built-in retry. A transient DNS resolution failure on the hosted
# runner otherwise fails this whole build matrix leg and cascades into
# skipped downstream jobs (e.g. k3s-required's "Check k3s matrix
# result" gate). `atmos build deps` (.atmos.d/build.yaml) wraps it
# with a retry policy instead of hand-rolling a shell loop here.
atmos build deps
- name: Build
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
shell: bash
run: |
atmos build binary --target=${{ matrix.target }} --version=test
- name: Version
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
shell: bash
run: |
atmos build version --target=${{ matrix.target }}
- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ ! ( matrix.target == 'windows' && github.event.pull_request.draft ) }}
with:
name: build-artifacts-${{ matrix.target }}
path: |
./build/
validate-affected:
name: Validation (affected)
needs: [build]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out source with validation base history
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# `atmos validate --affected` compares HEAD to the PR merge-base.
fetch-depth: 0
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
- name: Validate affected project inputs
# editorconfig-checker is unaware of Go raw string literals, so it flags the
# space-indented YAML/text embedded in *_test.go fixtures as tab violations;
# exclude *.go since gofumpt/golangci-lint already own Go source formatting.
run: atmos validate --affected --exclude 'tests/fixtures/**' --exclude '**/*.go' --format rich
# run acceptance tests
test:
name: Acceptance Tests (${{ matrix.flavor.target }})
needs: [build]
strategy:
fail-fast: false
matrix:
flavor:
- { os: "ubuntu-latest", target: linux }
- { os: "windows-latest", target: windows }
- { os: "macos-15", target: macos }
timeout-minutes: 75
runs-on: ${{ matrix.flavor.os }}
steps:
- name: Check out code into the Go module directory
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Add GNU tar to PATH (significantly faster than windows tar)
if: matrix.flavor.target == 'windows' && ! github.event.pull_request.draft
shell: pwsh
run: echo "C:\Program Files\Git\usr\bin" >> $Env:GITHUB_PATH
- name: Set up Atmos (install build artifact for ${{ matrix.flavor.target }})
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
uses: ./.github/actions/setup-atmos-install
with:
target: ${{ matrix.flavor.target }}
path: ${{ github.workspace }}
add-to-path: 'true'
- name: Install Packer
uses: ./.github/actions/setup-packer-retry
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
with:
version: ${{ env.PACKER_VERSION }}
# 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.
- 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, Helm, and Helmfile
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install --default hashicorp/terraform
atmos toolchain install --default opentofu/opentofu@${{ env.OPEN_TOFU_VERSION }}
atmos toolchain install --default helm/helm@${{ env.HELM_VERSION }}
atmos toolchain install --default helmfile/helmfile@${{ env.HELMFILE_VERSION }}
atmos toolchain env --format=github
- name: Verify Terraform, OpenTofu, Helm, and Helmfile
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
run: |
terraform version
tofu version
helm version --short
helmfile version
- name: Install helm-diff plugin
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
run: helm plugin install https://github.com/databus23/helm-diff --version ${{ env.HELM_DIFF_VERSION }}
- name: Check atmos.exe integrity
if: matrix.flavor.target == 'windows' && ! github.event.pull_request.draft
shell: pwsh
run: |
Write-Output "PATH=$Env:PATH"
Write-Output "PATHEXT=$Env:PATHEXT"
Get-ChildItem "${{ github.workspace }}"
Get-Command "${{ github.workspace }}\atmos.exe"
atmos version
- name: Set up Go
# setup-go v6 requires runner v2.327.1+ and can affect toolchain handling.
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
with:
go-version-file: "go.mod"
# Disable setup-go's module cache to avoid restoring or saving mutable
# cache entries from PR-controlled keys; make deps repopulates modules
# from checksummed sources instead.
cache: false
id: go
- name: Get dependencies
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
run: |
atmos build deps
- name: Verify function parser coverage
if: matrix.flavor.target == 'linux'
run: scripts/check-function-parser-coverage.sh
- name: Verify Terraform example syntax
if: matrix.flavor.target == 'linux'
run: |
bash scripts/test-check-terraform-example-syntax.sh
scripts/check-terraform-example-syntax.sh
- name: Terraform registry cache acceptance test
timeout-minutes: 25
if: matrix.flavor.target == 'linux' || matrix.flavor.target == 'macos' || (matrix.flavor.target == 'windows' && ! github.event.pull_request.draft)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ATMOS_TEST_SKIP_PRECONDITION_CHECKS: true
ATMOS_TEST_TERRAFORM_REGISTRY_CACHE: 1
CGO_ENABLED: 0
run: go test ./tests -run '^TestTerraformRegistryCache$' -count=1 -timeout 10m -v
# NOTE: The test-case YAML files (tests/test-cases/*.yaml) are validated against
# tests/test-cases/schema.json by the Go test `TestTestCaseSchemaValidation`, which
# dogfoods Atmos's own schema validator (the engine behind `atmos validate schema`).
# That runs in the unit/acceptance test jobs, so no separate CI step is needed here.
# TestTerraformRegistryCache runs once above on every active OS. Skip it
# here so Linux coverage does not execute the same trust/cache test twice.
- name: Acceptance tests with coverage
timeout-minutes: 60
if: matrix.flavor.target == 'linux'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Use the GitHub token for downloading the Packer plugins
PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Skip precondition checks in CI to ensure tests run and maintain coverage
ATMOS_TEST_SKIP_PRECONDITION_CHECKS: true
run: TESTARGS="-skip=^TestTerraformRegistryCache$" atmos test acceptance --cover
- name: Acceptance tests
# windows-latest is the slowest target for this suite too (see the
# Build job's timeout comment above): runs of this step have been
# observed between 37m43s and 45m06s (the latter timed out at the
# old 45m budget) — 7-8 minutes of CI-to-CI variance on a budget with
# almost no headroom, force-cancelling the step with no real test
# failure in the logs. 60m matches Linux's coverage-enabled step
# budget and gives real slack; macOS finishes this step in ~35m
# today so the extra headroom is a no-op for it.
timeout-minutes: 60
if: matrix.flavor.target == 'macos' || (matrix.flavor.target == 'windows' && ! github.event.pull_request.draft)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Use the GitHub token for downloading the Packer plugins
PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Skip precondition checks in CI to ensure tests run and maintain coverage
ATMOS_TEST_SKIP_PRECONDITION_CHECKS: true
run: atmos test acceptance
- name: Upload coverage report artifact
if: matrix.flavor.target == 'linux' && hashFiles('coverage.out') != ''
timeout-minutes: 5
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: acceptance-coverage-linux
path: coverage.out
if-no-files-found: error
retention-days: 1
compression-level: 0
coverage:
name: Upload acceptance coverage to Codecov
needs: test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download linux coverage report artifact
uses: ./.github/actions/download-artifact-retry
with:
name: acceptance-coverage-linux
path: ${{ github.workspace }}
- name: Upload coverage report to Codecov
timeout-minutes: 10
continue-on-error: true
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: ${{ github.repository }}
files: coverage.out
disable_search: true
# Codecov's uploader downloads its CLI and verifies the binary's GPG
# signature. Transient Codecov-side issues (e.g. an empty public-key
# import: "gpg: no valid OpenPGP data found" / "Can't check signature:
# No public key") make verification exit 1, which would hard-fail this
# job even when every test passed. Don't let a coverage-upload/infra
# flake fail a green test run — the 85% coverage gate is still enforced
# by Codecov's own PR status check, independent of this step's exit code.
fail_ci_if_error: false
flags: unittests
verbose: true
# Install the Codecov CLI from PyPI instead of cli.codecov.io. The
# default path fetches Codecov's GPG public key from Keybase
# (https://keybase.io/codecovsecurity/pgp_keys.asc) to verify the CLI
# binary; that URL is currently returning 404 ("SELF-SIGNED PUBLIC KEY
# NOT FOUND"), so the import yields "no valid OpenPGP data found" and
# the run fails with "Could not verify signature". The PyPI path avoids
# the broken keyserver fetch. See codecov/codecov-action#1955. Safe to
# remove once Codecov restores the Keybase key.
use_pypi: true
docker:
name: "[lint] Dockerfile"
needs: build
runs-on: ubuntu-latest
# The workflow-level `permissions:` block above (added in #2487 for
# `packages: read` on ghcr.io pulls) REPLACES — not extends — the default
# token scope for every job. That stripped the implicit
# `security-events: write` this job relied on for SARIF upload, breaking
# the post-merge run on main. Job-level `permissions:` also fully
# overrides the workflow-level set, so `contents: read` is re-listed here
# for actions/checkout.
permissions:
contents: read
security-events: write
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
id: hadolint
with:
dockerfile: Dockerfile
failure-threshold: warning
format: sarif
output-file: hadolint.sarif
# https://github.com/hadolint/hadolint?tab=readme-ov-file#rules
# DL3008 Pin versions in apt-get install
ignore: DL3008
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
if: always()
with:
# Path to SARIF file relative to the root of the repository
sarif_file: hadolint.sarif
# Optional category for the results (used to differentiate multiple results for one commit)
category: hadolint
wait-for-processing: true
# Run the AWS-emulator demo tests against Floci (a free, LocalStack-compatible emulator;
# LocalStack Community Edition was EOL'd in March 2026).
# NOTE: the "[floci] emulator-aws" status check must be kept in sync with the required
# status checks configured in branch protection on main.
floci:
name: "[floci] ${{ matrix.demo-folder }}"
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
demo-folder:
- emulator-aws
- terraform-tests
- backend-provisioning
timeout-minutes: 30
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
- name: Install Terraform and OpenTofu with Atmos toolchain
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install hashicorp/terraform
atmos toolchain install --default opentofu/opentofu@${{ env.OPEN_TOFU_VERSION }}
atmos toolchain env --format=github
- name: Verify OpenTofu
run: tofu version
- name: Run tests for ${{ matrix.demo-folder }}
run: |
cd examples/${{ matrix.demo-folder }}
timeout 25m atmos test || {
status=$?
echo "atmos test failed or timed out with status ${status}"
exit "${status}"
}
# Run Go E2E tests against Floci. These cover store/secrets workflows that are
# easier to assert directly from Go than through the demo `atmos test` runner.
floci-go:
name: "[floci] go e2e"
needs: build
runs-on: ubuntu-latest
services:
floci:
# Pin by immutable digest for reproducibility/supply-chain safety (matches our SHA-pinned actions). Comment tracks the human-readable tag.
image: floci/floci@sha256:c88ec20bf221630dd195d38a14eeb0ac52ddfa72c37ebb3c8aa17f63ae27c5f2 # 1.5.23
ports:
- 4566:4566
floci-gcp:
# Pin by immutable digest for reproducibility/supply-chain safety. Comment tracks the human-readable tag.
image: floci/floci-gcp@sha256:a6420f308ad721fa4a203b70658563eab9c8fbc8d091feca2d95016239f5854a # latest
ports:
- 4588:4588
floci-az:
# Pin by immutable digest for reproducibility/supply-chain safety. Comment tracks the human-readable tag.
image: floci/floci-az@sha256:1e514c57db14dc41938f7925bbc1aca0293aa4da272c7014d98f1fba378cedb2 # latest
env:
FLOCI_AZ_TLS_ENABLED: "true"
ports:
- 4577:4577
timeout-minutes: 30
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
- name: Set up Go
# setup-go v6 requires runner v2.327.1+ and can affect toolchain handling.
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version-file: "go.mod"
cache: false
- name: Install OpenTofu with Atmos toolchain
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install --default opentofu/opentofu@${{ env.OPEN_TOFU_VERSION }}
atmos toolchain env --format=github
- name: Verify OpenTofu
run: tofu version
- name: Install Linux build dependencies
run: |
# The Azure mirror used by GitHub's Ubuntu runners can stall long
# enough to exhaust this job's timeout. Use Ubuntu's primary archive
# for this small, deterministic dependency install instead.
sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu|http://archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 update
sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 install -y --no-install-recommends libudev-dev pkg-config
- name: Run Floci Go E2E tests
env:
ATMOS_TEST_FLOCI: "true"
FLOCI_ENDPOINT_URL: "http://localhost:4566"
FLOCI_GCP_ENDPOINT: "http://localhost:4588"
FLOCI_AZURE_ENDPOINT: "http://localhost:4577"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go test ./tests -run 'Test((AWS(StoreHooks|Secrets)|GCPSecrets|AzureSecrets)FlociE2E|LocalGitOpsPushE2E|Scaffold(AWSLandingZone|GCPLandingZone|AzureLandingZone|AWSApp)FlociE2E|InitFromTemplateRepoGiteaE2E|TerraformFlociTfmigrateS3History)' -count=1 -timeout 25m -v
# run k3s demo tests
k3s:
name: "[k3s-${{ matrix.flavor.target }}] ${{ matrix.demo-folder }}"
needs: build
runs-on: ${{ matrix.flavor.os }}
env:
ATMOS_LOGS_LEVEL: Debug
HOMEBREW_NO_AUTO_UPDATE: "1"
HOMEBREW_NO_INSTALL_CLEANUP: "1"
strategy:
fail-fast: false
matrix:
flavor:
- { os: ubuntu-latest, target: linux, artifact: linux }
- { os: "macos-15-intel", target: macos, artifact: macos-intel }
demo-folder:
- demo-helmfile
- helm
timeout-minutes: 60
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# The local k3s cluster is no longer started via docker-compose: `atmos test`
# brings it up through the native Kubernetes emulator (`atmos emulator up
# kubernetes`), which runs the privileged k3s container itself on the runner's
# Docker, and the helmfile component harvests its kubeconfig via `!emulator`.
# Download the build artifact before starting Colima. Colima's VM bring-up
# attaches new virtual network interfaces on the macOS host, which can
# transiently disrupt host DNS resolution right as it happens; this is the
# only step in the job that needs public DNS from the host, so keep it away
# from that window.
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: ${{ matrix.flavor.artifact }}
- name: Start Docker-compatible runtime on macOS
if: matrix.flavor.target == 'macos'
timeout-minutes: 45
uses: ./.github/actions/setup-colima
- name: Configure Docker Hub mirror on Linux
if: matrix.flavor.target == 'linux'
shell: bash
run: |
set -euo pipefail
sudo install -d -m 0755 /etc/docker
if sudo test -s /etc/docker/daemon.json; then
sudo jq '."registry-mirrors" = ((."registry-mirrors" // []) as $mirrors | if ($mirrors | index("https://mirror.gcr.io")) == null then $mirrors + ["https://mirror.gcr.io"] else $mirrors end)' \
/etc/docker/daemon.json | sudo tee /etc/docker/daemon.json.tmp >/dev/null
sudo mv /etc/docker/daemon.json.tmp /etc/docker/daemon.json
else
printf '%s\n' '{"registry-mirrors":["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json >/dev/null
fi
sudo systemctl restart docker
docker info --format '{{json .RegistryConfig.Mirrors}}' | grep -F 'https://mirror.gcr.io'
- name: Write a default AWS profile to the AWS config file
run: |
mkdir -p ~/.aws
echo '[default]' > ~/.aws/config
- name: Run tests for ${{ matrix.demo-folder }}
run: |
cd examples/${{ matrix.demo-folder }}
run_with_timeout() {
python3 - "$@" <<'PY'
import os
import signal
import subprocess
import sys
timeout = int(sys.argv[1])
cmd = sys.argv[2:]
proc = subprocess.Popen(cmd, start_new_session=True)
try:
raise SystemExit(proc.wait(timeout=timeout))
except subprocess.TimeoutExpired:
print(f"{' '.join(cmd)} timed out after {timeout}s", file=sys.stderr)
os.killpg(proc.pid, signal.SIGTERM)
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
os.killpg(proc.pid, signal.SIGKILL)
proc.wait()
raise SystemExit(124)
PY
}
cleanup_k3s() {
atmos emulator down kubernetes -s dev || true
docker rm -f atmos-dev-emulator-kubernetes >/dev/null 2>&1 || true
}
# macOS k3s jobs can occasionally hang in the Docker/Colima stack.
# Bound each attempt so the matrix can retry instead of consuming the
# whole job timeout and cancelling the required aggregate check.
attempt_timeout=1500
if [ "${{ matrix.flavor.target }}" = "macos" ]; then
attempt_timeout=900
fi
trap cleanup_k3s EXIT
for attempt in 1 2; do
run_with_timeout "${attempt_timeout}" atmos test && exit 0
status=$?
echo "atmos test failed (attempt ${attempt}/2, status ${status})" >&2
cleanup_k3s
[ "${attempt}" -lt 2 ] && sleep 15
done
exit "${status}"
k3s-required:
name: "[k3s] demo-helmfile"
needs: k3s
runs-on: ubuntu-latest
if: ${{ always() }}
steps:
- name: Check k3s matrix result
run: |
if [ "${{ needs.k3s.result }}" != "success" ]; then
echo "k3s matrix result was '${{ needs.k3s.result }}'"
exit 1
fi
# Exercise the Kubernetes native component against a real kube-apiserver+etcd
# control plane (started by controller-runtime envtest): server-side apply,
# dry-run diff, delete, server-side validate, and a CRD round-trip. The
# control-plane binaries are provisioned on demand by the tests themselves via
# the Atmos toolchain (an inline registry in envtest_test.go) — there is no
# install step here. Windows is intentionally excluded: there is no official
# kube-apiserver build for it, so the tests are gated off that platform.
#
# The binaries land in the toolchain cache under the user cache dir; CI does
# not yet persist that across runs, so each run re-downloads the (small)
# tarball. Adding an actions/cache step keyed on the envtest version is an
# easy follow-up.
kubernetes-e2e:
name: "[kubernetes] e2e tests (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-15
timeout-minutes: 20
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Go
# setup-go v6 requires runner v2.327.1+ and can affect toolchain handling.
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
cache: false
go-version-file: "go.mod"
- name: Download Go dependencies
shell: bash
run: |
# This job compiles directly from a cold module cache instead of using
# the build job's dependency step. Retry transient hosted-runner DNS
# and network failures here so they do not appear as test failures.
delay=5
for attempt in {1..5}; do
if go mod download; then
exit 0
fi
if [ "$attempt" -eq 5 ]; then
exit 1
fi
echo "go mod download failed (attempt $attempt/5); retrying in ${delay}s"
sleep "$delay"
delay=$((delay * 2))
done
- name: Run Kubernetes end-to-end tests
env:
GOCACHE: ${{ runner.temp }}/go-build
GOTMPDIR: ${{ runner.temp }}/go-tmp
TMPDIR: ${{ runner.temp }}/tmp
# Atmos builds with CGO disabled. Match that here so the
# toolchain's transitive CGO dependencies (e.g. bearsh/hid → libudev)
# are not compiled — they are unused and libudev is absent on runners.
CGO_ENABLED: "0"
# Fail loudly if the control plane can't be provisioned/started, rather
# than silently skipping all coverage.
ATMOS_ENVTEST_REQUIRED: "true"
run: |
mkdir -p "$GOCACHE" "$GOTMPDIR" "$TMPDIR"
go test -tags envtest -count=1 ./pkg/component/kubernetes/... -run TestEnvtest
# run other demo tests
mock:
name: "[mock-${{ matrix.flavor.target}}] ${{ matrix.demo-folder }}"
needs: build
runs-on: ${{ matrix.flavor.os }}
strategy:
fail-fast: false
matrix:
flavor:
- { os: ubuntu-latest, target: linux }
- { os: windows-latest, target: windows }
- { os: "macos-15", target: macos }
demo-folder:
- examples/demo-atlantis
# - examples/demo-component-manifest
- examples/demo-component-versions
- examples/demo-context
# - examples/demo-custom-command
# - examples/demo-json-validation
# - examples/demo-opa-validation
# - examples/demo-opentofu
# - examples/demo-project
# - examples/demo-stacks
# - examples/demo-terraform
# - examples/demo-terraform-overrides
# - examples/demo-workflows
# - examples/demo-yaml-anchors
# - examples/demo-mock-architecture
# - examples/demo-stack-templating
# - examples/demo-multi-cloud
- examples/demo-vendoring
- examples/config-profiles
- tests/fixtures/scenarios/complete
timeout-minutes: 20
steps:
- name: Check out code into the Go module directory
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Add GNU tar to flavor.target (significantly faster than windows tar)
if: matrix.flavor.target == 'windows' && ! github.event.pull_request.draft
shell: pwsh
run: echo "C:\Program Files\Git\usr\bin" >> $Env:GITHUB_PATH
- name: Set up Atmos (install build artifact for ${{ matrix.flavor.target }})
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
uses: ./.github/actions/setup-atmos-install
with:
target: ${{ matrix.flavor.target }}
path: ${{ github.workspace }}
add-to-path: 'true'
- name: Install Terraform and OpenTofu with Atmos 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 --default opentofu/opentofu@${{ env.OPEN_TOFU_VERSION }}
atmos toolchain env --format=github
- name: Verify OpenTofu
if: ${{ ! ( matrix.flavor.target == 'windows' && github.event.pull_request.draft ) }}
run: tofu version
- name: Run tests in ${{ matrix.demo-folder }} for ${{ matrix.flavor.target }}
working-directory: ${{ matrix.demo-folder }}
if: matrix.flavor.target == 'linux' || matrix.flavor.target == 'macos'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Retry to ride out transient registry/network failures — e.g. a 502
# from github.com while terraform fetches a provider signature. A
# genuine test failure still fails on the final attempt.
for attempt in 1 2 3; do
atmos test && exit 0
status=$?
echo "atmos test failed (attempt ${attempt}/3, status ${status})" >&2
[ "${attempt}" -lt 3 ] && sleep 15
done
exit "${status}"
- name: Check atmos.exe integrity
if: matrix.flavor.target == 'windows' && ! github.event.pull_request.draft
shell: pwsh
run: |
Write-Output "PATH=$Env:PATH"
Write-Output "PATHEXT=$Env:PATHEXT"
Get-ChildItem "${{ github.workspace }}"
Get-Command "${{ github.workspace }}\atmos.exe"
atmos version
- name: Run tests in ${{ matrix.demo-folder }} for ${{ matrix.flavor.target }}
working-directory: ${{ matrix.demo-folder }}
if: matrix.flavor.target == 'windows' && ! github.event.pull_request.draft
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Retry to ride out transient registry/network failures (e.g. a 502
# from github.com during terraform provider download). A genuine test
# failure still fails on the final attempt.
for ($attempt = 1; $attempt -le 3; $attempt++) {
atmos test
if ($LASTEXITCODE -eq 0) { exit 0 }
Write-Output "atmos test failed (attempt $attempt/3, exit $LASTEXITCODE)"
if ($attempt -lt 3) { Start-Sleep -Seconds 15 }
}
exit $LASTEXITCODE
# run other demo tests
lint:
name: "[lint] ${{ matrix.demo-folder }}"
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- demo-folder: quick-start-advanced
component: kms-key
stack: plat-ue2-dev
- demo-folder: quick-start-advanced
component: s3-bucket
stack: plat-ue2-dev
- demo-folder: quick-start-advanced
component: dynamodb-table
stack: plat-ue2-dev
- demo-folder: quick-start-advanced
component: sns-topic
stack: plat-ue2-dev
- demo-folder: quick-start-advanced
component: sqs-queue
stack: plat-ue2-dev
- demo-folder: quick-start-advanced
component: app-config
stack: plat-ue2-dev
timeout-minutes: 20
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
- name: Install OpenTofu and TFLint with Atmos toolchain
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install --default opentofu/opentofu@${{ env.OPEN_TOFU_VERSION }}
atmos toolchain install --default terraform-linters/tflint@0.59.1
atmos toolchain env --format=github
- name: Verify OpenTofu and TFLint
run: |
tofu version
tflint --version
- name: Lint ${{ matrix.demo-folder }}/${{ matrix.component }}
working-directory: examples/${{ matrix.demo-folder }}
# Lint reads static HCL only. Disable the quick-start stack's emulator
# identity because this CI job intentionally does not start the emulator.
run: atmos terraform lint ${{ matrix.component }} -s ${{ matrix.stack }} --identity=false
hooks-tflint:
name: "[hooks-tflint] example"
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
- name: Install OpenTofu and TFLint with Atmos toolchain
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
atmos toolchain install --default opentofu/opentofu@${{ env.OPEN_TOFU_VERSION }}
atmos toolchain install --default terraform-linters/tflint@0.59.1
atmos toolchain env --format=github
- name: Run the TFLint hook example
working-directory: examples/hooks-tflint
run: atmos terraform plan example -s test
# run other demo tests
validate:
name: "[validate] ${{ matrix.demo-folder }}"
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
demo-folder:
- demo-context
- emulator-aws
- terraform-tests
- backend-provisioning
- demo-stacks
- quick-start-simple
# Examples below use runtime Atmos YAML functions (`!terraform.state`, `!store`,
# `!secret`, …). They were previously excluded because the static yaml-language-server
# hardcodes an empty customTags list and fails them with "Unresolved tag". Atmos
# itself understands its own tags natively, so `atmos validate stacks` validates them.
- custom-components
- sops-secrets
- onepassword-secrets
- packer-docker
# quick-start-advanced and native-terraform are intentionally NOT validated here:
# - quick-start-advanced: its `stacks/workflows/*.yaml` manifests use newer workflow
# step types (`type: stage`/`spin`, `title:`) that the atmos-manifest JSON Schema
# does not describe yet, so schema validation fails. (Functions now parse fine.)
# - native-terraform: intentionally configures no `stacks.name_pattern`/`name_template`,
# which `atmos validate stacks` requires, so the command cannot resolve stacks.
# Both are still exercised end-to-end by the floci and acceptance jobs.
timeout-minutes: 20
steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
# Dogfood Atmos to validate the example stacks instead of a third-party action.
# `atmos validate stacks` checks YAML syntax, validates every manifest against the
# Atmos manifest JSON Schema, resolves imports, and detects duplicate components —
# and it understands Atmos YAML tags natively (`!terraform.state`, `!store`, `!secret`).
#
# No `--schemas-atmos-manifest` override is needed: the `atmos` binary under test is built
# from this PR's source, so when an example doesn't set `schemas.atmos.manifest` (the
# common case — see examples/*/atmos.yaml), `atmos validate stacks` falls back to the
# schema embedded in that same binary (pkg/datafetcher/schema/atmos/manifest/1.0.json),
# which already reflects this PR's schema changes. Atmos ignores the `# yaml-language-server:
# $schema` modeline, so the committed files keep their public URL for users who copy them.
- name: Validate stacks
working-directory: examples/${{ matrix.demo-folder }}
run: atmos validate stacks
# run container step tests against a local registry (build -> push -> run)
# Docker is available directly on the runner (see the k3s job), and a
# registry:2 service container provides a hermetic push/pull target on
# localhost:5000 so the full build -> push -> run cycle is exercised without
# GitHub Container Registry, leftover packages, or fork-PR token limits.
container-step:
name: "[container-step] example"
needs: build
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
timeout-minutes: 15
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Atmos (install build artifact)
uses: ./.github/actions/setup-atmos-install
with:
target: linux
- name: Set up Go for the Buildx cache integration test
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version-file: "go.mod"
- name: Verify remote Buildx registry cache
env:
ATMOS_TEST_REGISTRY_CACHE: "1"
run: go test ./pkg/container -run '^TestDockerRuntime_RemoteRegistryCache_Integration$' -count=1
- name: Run container steps (run, build, bake, push, workspace, env)
working-directory: examples/container-step
run: |
set -euo pipefail
atmos workflow hello -f container-step
atmos workflow build-run -f container-step
atmos workflow bake-build-run -f container-step
atmos workflow push-local-registry -f container-step
atmos workflow workspace -f container-step
atmos workflow env -f container-step
grep -q "## .*atmos-container-step:local" "$GITHUB_STEP_SUMMARY"
grep -q "## .*localhost:5000/atmos-container-step:local" "$GITHUB_STEP_SUMMARY"
grep -q '| Digest | `sha256:' "$GITHUB_STEP_SUMMARY"
grep -q "<summary>.*Layers (" "$GITHUB_STEP_SUMMARY"
grep -q "<summary>.*Raw JSON</summary>" "$GITHUB_STEP_SUMMARY"
- name: Verify a failing container step propagates a non-zero exit code
working-directory: examples/container-step
run: |
if atmos workflow failing-check -f container-step; then
echo "expected the 'failing-check' workflow to fail, but it succeeded"
exit 1
fi
echo "failing-check correctly returned a non-zero exit code"
release:
needs: [test, kubernetes-e2e, lint, mock, k3s, floci, floci-go, docker, validate, container-step]
if: github.event_name == 'push'
uses: cloudposse/.github/.github/workflows/shared-go-auto-release.yml@8244c7c9142e92281e7841f655fa48e9ceb9b454 # main
with:
publish: false
format: binary
runs-on: '["runs-on=${{github.run_id}}", "runner=large", "tag=atmos","extras=s3-cache", "private=false"]'
secrets: inherit