fix: grant workflows:write so release-major-tag can move the v1 tag @osterman (#2962)
What
Adds workflows: write to the permissions: block in .github/workflows/release-major-tag.yml.
Why
The release-major-tag job force-pushes the moving v1 tag (so external consumers can reference cloudposse/atmos/actions/cache@v1) using only contents: write. GitHub rejects any push — including a tag force-push — whose resulting tree differs from the target ref in .github/workflows/**, unless the token also has workflows: write.
Confirmed via gh run view --log on both a passing run (v1.225.0) and failing runs (v1.223.0, v1.226.0):
refs/tags/v1:refs/tags/v1 [remote rejected] (refusing to allow a GitHub App
to create or update workflow `.github/workflows/codeql.yml` without
`workflows` permission)
This made the job fail intermittently — specifically whenever a .github/workflows/* file changed since v1 was last successfully moved — forcing consumers to pin exact release tags (e.g. @v1.226.0) instead of the moving @v1 tag. The tagger action authenticates purely via github.token, so widening this workflow's own permissions: block is sufficient; no PAT or other change is required.
References
.github/workflows/release-major-tag.yml- Failing runs: v1.223.0 (
29426305603), v1.226.0 (32307634756)
Summary by CodeRabbit
- Chores
- Improved release automation to support more reliable creation and maintenance of major-version tags.
- Updated the release process to use dedicated authorization for tag updates.
- No user-facing product features, functionality, or interface changes are included in this update.
🚀 Enhancements
fix: name the offending component/stack in backend_type mismatch errors @osterman (#2965)
what
checkTerraformBackendTypeMatch/checkRemoteStateBackendTypeMatchnow name the offending component and stack directly in the error's hint text, e.g.:component "bootstrap" in stack "sandbox": remote_state_backend_type is "local" but remote_state_backend: only configures s3...
- Added a Go unit test (
TestProcessTerraformRemoteStateBackend_InheritedLocalTypeMismatch) reproducing the exact real-world shape that surfaced this, plus hint-text assertions on the existing mismatch tests. - Added a CLI-level fixture/test-case (
tests/fixtures/scenarios/remote-state-backend-type-mismatch/,tests/test-cases/remote-state-backend-type-mismatch.yaml) proving the fix end-to-end through the real binary.
why
- These two checks (added in #2953) run during whole-repo stack processing, so a mismatch in one component blocks
describe stacks/terraform planfor every other stack too, including ones totally unrelated to the misconfigured component. - The component/stack causing the failure were previously attached only via
WithContext, which the CLI's default (non--verbose) error renderer drops entirely. The printed error gave zero indication of which of potentially hundreds of components across a repo was actually at fault. - Found live in cloudposse/infra-live:
atmos describe stacks --stack core-gbl-marketplacefailed with only "remote_state_backend_type is local but remote_state_backend: only configures s3" — no component, no stack. It took manually instrumenting the binary with debug prints to discover the real cause was an unrelatedplat/sandboxcomponent (vpc-no-provider) that overridesbackend_type: localwithout a matchingremote_state_backend_type. With this fix, that same failure now reads:component "vpc-no-provider" in stack "orgs/cplive/plat/sandbox/us-east-2": remote_state_backend_type is "local" but remote_state_backend: only configures s3...
which is immediately actionable.
references
None — found and reported internally, no existing issue.
Summary by CodeRabbit
- Bug Fixes
- Improved Terraform backend and remote-state mismatch errors with clearer component and stack context.
- Enhanced error hints to identify conflicting backend types, including inherited configuration issues.
- Tests
- Added regression coverage for backend mismatches across regular and remote-state configurations.
- Added scenario coverage for mismatches involving unrelated stacks.
- Improved parallel HTTP redirect test isolation to prevent connection cleanup races.
fix(terraform): support Terraform 1.15+ module source interpolation @osterman (#2915)
what
- Atmos no longer fails to parse a Terraform component whose
moduleblock uses variable interpolation insource(e.g.source = "./mods/${var.org}") when the variable is declaredconst = true— valid syntax under Terraform 1.15+, not just OpenTofu 1.8+. - Generalized the existing "Variables not allowed" diagnostic skip in
internal/exec/utils.go/internal/exec/terraform_detection.goso it no longer depends on detecting OpenTofu — renamedisKnownOpenTofuFeature→isKnownModuleSourceInterpolationDiagnostic, and thecomponent_infoflagvalidation_skipped_opentofu→validation_skipped_module_source_interpolation. - Hardened that skip so it can never silently swallow a genuine, unrelated HCL error that happens to co-occur in the same module: diagnostics are now inspected individually and grouped by source position (
allDiagnosticsAreModuleSourceInterpolation), instead of pattern-matching the collapsedDiagnostics.Error()string, which only renders the first diagnostic's text. - Added a new regression fixture/test reproducing the exact issue on plain
terraform(nocommand:override), plus a fixture/test proving a real unrelated error is still surfaced when it co-occurs with the known-safe diagnostic. - Investigated whether Atmos's SBOM generation is affected by dynamic module sources; confirmed it isn't (it reads already-resolved sources from
terraform modules -json, never the static parser), and added a permanent guard test (pkg/sbom/terraform_test.go) for that invariant. - Bumped the
nanoidpnpm override inwebsite/package.jsonto resolve the transitivewebsite/pnpm-lock.yamldependency tonanoid@3.3.18, fixing an open Dependabot alert (infinite loop on zero-size input). The two openimage-sizealerts have no upstream patch yet and are not auto-fixable. - Fixed pre-existing EditorConfig violations (tabs instead of the required 2-space indent) in
docs/prd/opentofu-module-source-interpolation.md, surfaced once that file entered the branch's diff.
why
- Atmos pre-parses every Terraform component with
terraform-config-inspectbefore running any Terraform/OpenTofu command. That library decodes a module'ssourceattribute with anilhcl.EvalContext, so any variable reference there always produces the "Variables not allowed" diagnostic — regardless of whether the configured tool/version actually supports it. - Atmos already tolerated this diagnostic for OpenTofu 1.8+ (PR #1756), but Terraform 1.15 (April 2026) added the equivalent capability via
const = truevariables, so plain-Terraform users hit the same diagnostic as a hard failure even though their syntax is valid. - The diagnostic text can't distinguish "valid under a modern tool" from "genuinely invalid" — Atmos already accepted that ambiguity unconditionally for OpenTofu, so extending the same leniency to Terraform is consistent, provided a real unrelated error can never be silently discarded alongside it (the second commit's fix).
references
- closes #2913
docs/prd/opentofu-module-source-interpolation.md(updated with a 2026-08-10 addendum)docs/fixes/2026-08-10-terraform-module-source-interpolation.md
Summary by CodeRabbit
-
Bug Fixes
- Added support for Terraform 1.15+ interpolation in module source paths.
- Prevented known parser diagnostics from suppressing unrelated configuration errors.
- Preserved resolved dynamic module sources in component and SBOM metadata.
-
Documentation
- Documented supported Terraform behavior, validation handling, and expected component results.
-
Tests
- Added regression coverage for valid interpolation, mixed diagnostics, component metadata, and SBOM output.
fix(container): build.load works without bake; portable bake vars @osterman (#2963)
what
- Adds a standalone
load: truefield to the plain (non-bake)engine: buildxcontainerbuild step, sodocker buildx build --loadworks without adoptingbake:. - Wires the new field through schema, build-config, arg-building, and validation (
load: truenow requiresengine: buildx, mirroring the existingdriver/cachecheck). - Switches
bake.varsfrom the--varCLI flag to environment-variable injection (NAME=value), sincedocker buildx bakehas always resolved HCLvariable {}blocks from the environment, while--varis a newer flag missing from older buildx builds (e.g. Debian Trixie's 0.13.1). - Documents both changes in
website/docs/workflows/workflows/workflow/steps/type/container.mdxand records the fixes indocs/fixes/.
why
- With a non-default Buildx driver (e.g.
docker-container), a build's output lands in BuildKit's own cache rather than the local Docker image store, so a followingpushstep can't find the image unless--loadis passed. Previouslyloadonly existed underbake:, forcing anyone who needed--loadto also adopt an externaldocker-bake.hclfile just to flip one boolean. docker buildx bake --varisn't implemented on every buildx release (e.g. Debian Trixie ships 0.13.1, which predatesdocker/buildx#3610), so builds usingbake.varsfailed withunknown flag: --varon those hosts. Environment-variable injection is the pre-existing, universally-supported mechanismdocker buildx bakeuses to resolve HCLvariable "NAME" {}blocks, so it fixes portability with no change in what a bake file can express.
references
docs/fixes/2026-08-20-container-build-load-without-bake.mddocs/fixes/2026-08-19-container-bake-vars-env-injection.md
Summary by CodeRabbit
-
New Features
- Added
loadsupport for plain Buildx builds, allowing images to be added to the local Docker image store. - Improved Bake variable handling through environment-based resolution and broader Buildx compatibility.
- Added
-
Bug Fixes
- Added validation for incompatible image-loading configurations.
- Improved reliability for slower environments during container session startup.
-
Documentation
- Documented image-loading requirements and Bake variable behavior.
- Added fix notes covering container build and Bake variable updates.
fix(ci): recover per-run assertion detail in test summary fallback @osterman (#2959)
what
- The CI job-summary fallback for
terraform testoutput (used when per-runrun "name"... pass/failstatus lines weren't captured) now recovers the failing assertion's file, line, and message from terraform'sError:diagnostic block when it survived in the captured output. - Previously the fallback always synthesized a bare aggregate row like
test summary (per-run detail unavailable): N passed, M failedwith no location or message, even when that detail was still present in the raw text. - Added
errorLocationReto parse theon <file> line <N>:locator out of a terraform error block, and reuse the existingExtractErrorBlockshelper to populate the synthesized row'sError/File/Linefields. No template changes were needed —templates/test.mdalready renders those fields forfail/errorrows. - Added a fix-log record at
docs/fixes/2026-08-19-ci-test-summary-fallback-recovers-error-detail.md.
why
- A prior fix (
docs/fixes/2026-08-14-ci-summary-test-table-fallback-dropped.md) stopped the results table from disappearing entirely on this fallback path, but the synthesized row still carried no per-test detail — CI test summaries showed only aggregate pass counts and a reproduction command, with no individual test-run/assertion detail, even when that detail was actually recoverable from the captured output. - A reproduction test (
TestTestTemplate_SummaryFallback_LosesRunDetail) confirmed the gap before this fix; it's retained to document the remaining, genuinely irreducible case where noError:block survives at all.
references
docs/fixes/2026-08-14-ci-summary-test-table-fallback-dropped.md— the prior fix this builds on.docs/fixes/2026-08-19-ci-test-summary-fallback-recovers-error-detail.md— this fix's record.
Summary by CodeRabbit
-
Bug Fixes
- Improved Terraform test failure summaries by recovering file and line details when a single error is available.
- Preserved complete error messages without corrupting report tables or duplicating content.
- Avoided assigning potentially incorrect locations when multiple errors are present.
- Increased the Terraform registry cache CI timeout from 20 to 30 minutes.
-
Documentation
- Added fix documentation covering CI summary recovery and registry cache timeout handling.
fix: type: store step misrouted to container decoder in custom commands and hooks @osterman (#2961)
what
- Fix
decodeStepWith(pkg/schema/workflow.go) so a step is only routed to the containerwith:decoder whentype: container, instead of wheneveraction:is non-empty. - Fix the
kind: stephook bridge (pkg/hooks/step_engine.go) to backfillWorkflowStep.Withfrom the hook'swith:payload when the normal decode leaves it nil. - Add regression tests covering both the workflow-file and custom-command/Viper decode paths for
type: store, and both the static and runtime hook decode paths.
why
- A documented custom-command/workflow step shaped like:
failed before ever reaching execution with
- type: store action: write with: store: image-metadata key: image-dev value: "..." stack: dev component: app
containeraction: writedoes not accept awith:block.decodeStepWithtreated any step with a non-emptyaction:as a container step regardless oftype:, sotype: store(and any other non-container type that setsaction:) was misrouted into the container decoder. - Investigating the same class of bug surfaced a second, independent issue: the documented
kind: step/type: storecomponent-hook pattern (see/workflows/steps/type/store) also silently dropped itsstore/key/valueconfig, because the hook bridge round-trips the hook'swith:payload directly intoWorkflowStep's top-level fields — which works for step types with flat fields (archive,say) but not for step types likestore/tflintwhose config lives only in the genericWithmap.StoreHandler.Validatethen failed with a generic "store is required" error that never showed the store the user actually configured.
references
- N/A
Summary by CodeRabbit
-
Bug Fixes
- Preserved
withvalues for store hooks when decoding workflow steps. - Kept step parameters consistent across workflow, runtime, YAML, and map-based decoding.
- Limited container-specific processing to container steps.
- Prevented valid parameters on non-container steps from being lost or misinterpreted.
- Kept store step parameters available without populating container-only fields.
- Increased the Terraform registry cache timeout for Windows jobs to improve reliability.
- Preserved
-
Documentation
- Documented the step-parameter decoding and Windows timeout fixes.
fix(emulator): join Atmos's container to the shared network when reuse fails @osterman (#2960)
what
- Atmos's own container now joins the dedicated per-stack Docker/Podman network when it can't reuse its existing one, via a new
NetworkConnectorruntime capability (docker/podman network connect). - Hardens the last-resort emulator endpoint guess to prefer
host.docker.internal(only when it actually resolves) before falling back to the default-gateway IP guess. - Adds a real, unmocked regression test that runs entirely inside a nested container (no
--network, host socket mounted) to prove the self-detection/join mechanism works against a real daemon, not just a mocked runtime.
why
atmos terraform test --cirun inside a CI job container (talking to Docker only through a mounted socket) started the AWS emulator successfully but reported an endpoint (http://172.17.0.1:<port>) unreachable from that same job container --connection refusedagainst the AWS provider'sGetCallerIdentitycall.- Root cause: a job container started with a plain
docker run(no--network) sits on Docker's default bridge, whichCurrentContainerNetworkcorrectly excludes from reuse (no embedded DNS/aliases). Reuse failing meant the endpoint fell back to a guessed default-gateway IP, which isn't where Docker Desktop's port-forwarding actually listens for sibling containers. - Instead of only checking whether the existing network happens to be reusable,
AttachSharedNetworknow actively makes it reusable by connecting Atmos's own container to the dedicated network too -- so the existing DNS-alias endpoint logic just works, for every built-in emulator driver, not just AWS. - Verified live against the reported reproduction (disposable copy of the affected application repo,
docker:clijob container, no--network, mounted host socket): the emulator now reports a DNS alias instead of an IP, andatmos terraform test app -s fixtures --cicompletes fully (Success! 1 passed, 0 failed, 0 skipped.) where it previously failed withconnection refused. - New
pkg/container/sibling_network_test.go+sibling_network_docker_test.go(opt-in viaATMOS_TEST_SIBLING_CONTAINER=1) reproduces the bug end-to-end inside a real nested container -- confirmed it fails withno such hostwhen the join logic is reverted, and passes with it in place.
references
- Closes the job-container endpoint-reachability gap left open by #2942 ("Shared per-stack networking for containers, emulators & run steps").
Summary by CodeRabbit
- New Features
- Running containers can join dedicated Docker or Podman networks with optional DNS aliases.
- Stack containers automatically connect to shared networks when supported.
- Improved access to published services through
host.docker.internal.
- Bug Fixes
- Network connections safely handle already-connected containers.
- Network attachment failures no longer block container creation.
- Host gateway detection avoids hanging during unavailable DNS resolution.
- Tests
- Added coverage for aliases, network connectivity, runtime behavior, and container communication.
- Documentation
- Documented emulator endpoint and container networking improvements.