Add task-runner dependencies, freshness checks, and preconditions to custom commands and workflows @osterman (#2882)
## what- Adds
dependencies.commands/dependencies.workflowsto custom commands and workflows: named, parameterized, concurrent-by-default dependency ordering across units, with automatic dedup of identical invocations. - Adds
inputs/artifactsstep fields: skip a step when its declared sources haven't changed since the last successful run (implicitwhen: checksum.changed), exposingchecksum.changed/timestamp.changed/sources/artifactsaswhen:CEL facts. - Adds
preconditionsstep field: skip a step when a required tool is already onPATH(implicitwhen: "!preconditions.success"), resolved viaexec.LookPath— no shell involved. Pluralized (precondition→preconditions) to match the block-of-checks convention already used byinputs/artifacts/dependencies. - Adds
continue: alwaysstep field, mirroring GitHub Actions'continue-on-error: a step's own failure is forgiven, later steps still run, overall exit status unaffected. - Fixes
type: parallel/type: matrixsteps silently failing in custom commands (only workflows supported them before). - Adds
platformsviawhen:CEL facts (os/arch/platform), native per-commandaliases:/internal:, and avalues:constraint on flags/arguments with an interactive picker. - Fixes Windows-specific bugs in the new step types: shell child-process argument quoting for
parallel/matrix, and verbatimCmdLineconstruction forcmd.exe /C. - Fixes a cluster of concurrency and correctness bugs surfaced during implementation and review: dependency-scheduling and freshness-check race conditions, hashfile collisions, non-atomic multi-line step output, diamond-dependency de-duplication, wrong-binary resolution in workflow command dependencies, and a
UnitDependenciesstring-shorthand schema gap. - Remediates 3 Dependabot security alerts surfaced while this branch was open: js-yaml, mermaid, and a nanoid infinite-loop DoS (GHSA-2v37-7h3g-55p8 / CVE-2026-67213).
- Relocates
cmd/custom_command_dependency_adapter.goandcmd/custom_command_values.gointopkg/taskgraph/adaptersandpkg/flagsrespectively, so this logic is unit-testable in isolation instead of coupled tocmd's live command registry. - Adds Docusaurus docs for every new field/fact and updates the JSON Schema (
atmos/manifest,config/global,stacks/stack-config) accordingly.
why
Atmos workflows and custom commands already covered most of what a task runner needs, but a handful of real gaps kept teams running go-task alongside Atmos: no dependency ordering between named commands/workflows, no up-to-date checking, no continue-on-error, no precondition shortcut, and custom commands couldn't even use parallel/matrix steps — the exact recipe the project's own go-task migration guide recommends for concurrent dependents. This closes those gaps using the existing when:/CEL condition engine and scheduler rather than inventing a second mechanism.
references
- Blog post:
website/blog/2026-08-05-taskfile-convergence.mdx
Summary by CodeRabbit
-
New Features
- Added command and workflow dependencies with parallel execution, deduplication, parameterized inputs, and configurable failure handling.
- Added freshness checks for sources and artifacts, tool preconditions, platform-aware conditions, and optional failure continuation.
- Added parallel and matrix control steps for custom commands.
- Added command aliases, hidden internal commands, and constrained argument and flag values.
-
Bug Fixes
- Improved Windows shell quoting, dependency error handling, freshness evaluation, and workflow validation.
-
Documentation
- Added configuration guides, examples, and release documentation for the new task-runner capabilities.
feat(provisioner): Azure (azurerm) backend auto-provisioning @aknysh (#2911)
## whatAdds automatic provisioning for the azurerm Terraform state backend — the Azure counterpart to the existing S3 backend provisioner. When provision.backend.enabled: true on a component using an azurerm backend, Atmos creates the resource group (if missing), storage account, and blob container before terraform init, with opinionated secure defaults.
Previously, atmos terraform backend create returned create not implemented for backend type: azurerm and provision.backend.enabled silently skipped for Azure.
What gets created (hardcoded secure defaults)
- Resource group in the identity's location (or reuses an existing group's location)
- Storage account:
StorageV2/Standard_LRS, TLS 1.2 minimum, HTTPS-only, public blob access blocked - Entra ID hardening: shared-key access disabled when the backend sets
use_azuread_auth: true - Blob versioning + 30-day soft delete (the S3-versioning analog)
- Private container;
Name+ManagedBy=Atmostags
No lock resource is created — the azurerm backend serializes concurrent writes with native Azure Blob Storage blob leases (the DynamoDB / native-S3-locking analog, built into Blob Storage).
Design
- Self-registers
create/delete/exists/nameinto the shared backend registry viainit(), so thebefore.terraform.inithook andatmos terraform backend create/deletepick upazurermwith no wiring changes to the hook or CLI. - A narrow
azureBackendAPIinterface hides the ARM SDK pollers behind synchronous methods for testability, mirroring the S3 client factory and the existingazurermstate-reader wrapper. A test-injectable client factory (SetAzureBackendClientFactory/ResetAzureBackendClientFactory) mirrorsSetS3ClientFactory. - Location is sourced from the active Azure identity (or an existing resource group), never from the
backendblock — it is not a validazurermbackend argument and Terraform would reject it inbackend.tf.json. - Adds
armresources+armstorageSDK deps (azcore/azidentity/azblobwere already present).
Also fixes: azurerm backend init under Atmos-managed CLI auth
Field-testing the provisioner end-to-end surfaced a pre-existing bug that blocked azurerm backends whenever an Atmos profile was active. For CLI / device-code / interactive auth, Atmos exported both ARM_SUBSCRIPTION_ID and ARM_TENANT_ID to the Terraform subprocess. OpenTofu's azurerm backend authenticates via the Azure CLI and runs az account get-access-token --subscription <id> --tenant <id>, which the CLI rejects with Please specify only one of subscription and tenant, not both. It fails at argument validation — before the CLI even checks the session — so terraform init cannot list existing workspaces or read state at all:
Error: Failed to get existing workspaces: error listing blobs:
AzureCLICredential: ERROR: Please specify only one of subscription and tenant, not both
The tenant is now exported only for OIDC (service-principal / federated) auth, which needs it and does not shell out to the Azure CLI. On the CLI path only ARM_SUBSCRIPTION_ID is exported; the tenant is already fixed by the MSAL session Atmos seeds (and the active subscription), and the azurerm/azapi/azuread providers auto-detect it. oidc.go re-adds the tenant in its OIDC override, so service-principal auth is unchanged.
pkg/auth/cloud/azure/env.go:PrepareEnvironmentgates the tenant export onUseOIDC.pkg/auth/providers/azure/oidc.go: OIDC override re-addsARM_TENANT_ID/AZURE_TENANT_ID.- Test updates assert the tenant is omitted on the CLI/device-code/interactive path and present for OIDC.
why
Brings Azure to feature parity with AWS for backend bootstrapping and eliminates the chicken-and-egg problem of needing remote state before Terraform can run — replacing the bespoke cold-start storage-account component teams currently hand-roll on Azure.
The CLI-auth fix is what makes the provisioned backend actually usable under Atmos-managed Azure auth: without it, terraform init against any azurerm backend fails while a profile is active.
references
- New PRD:
docs/prd/azurerm-backend-provisioner.md - Fix doc:
docs/fixes/2026-08-11-azurerm-backend-cli-subscription-tenant-conflict.md - Mirrors:
docs/prd/s3-backend-provisioner.md
test
- Unit tests (
pkg/provisioner/backend/azurerm_test.go,azurerm_wrappers_test.go): table-driven, mockedazureBackendAPIand Azure SDK fake servers — config extraction/precedence,use_azuread_authparsing, full-create, resource-group reuse, existing-account warning, location-required error, every error path, existence checks, delete safety, registry wiring, and the ARM passthrough wrappers (404→exists mapping, poller completion, error propagation). 95.1% package coverage. - Auth fix tests (
pkg/auth/cloud/azure/{env,setup}_test.go,pkg/auth/providers/azure/{cli,device_code,oidc}_test.go): both branches of the tenant gate verified — omitted for CLI/device-code/interactive, present for OIDC.env.goandoidc.goPrepareEnvironmentare both 100% covered. go build ./...,go vet,gofmt, and all pre-commit hooks (go-fumpt, golangci-lint, go.mod tidy) pass.
[!NOTE]
The auto-provisioned account uses secure-but-simple defaults (Standard_LRS, Microsoft-managed keys, public network access with Entra ID/RBAC gating) intended for dev/test/bootstrap — not production. For production, import the resources into a managed module (e.g.Azure/avm-res-storage-storageaccount); the provisioner is idempotent, soprovision.backend.enabled: truecan be left in place.
Summary by CodeRabbit
-
New Features
- Added automatic Azure
azurermbackend provisioning with resource groups, storage accounts, and private blob containers. - Applies secure defaults, blob versioning, soft-delete retention, native lease locking, and idempotent operations.
- Added force-protected deletion while preserving resource groups.
- Integrated Azure backends with discovery and lifecycle commands.
- Added automatic Azure
-
Bug Fixes
- Improved Azure CLI, device-code, and OIDC environment handling to prevent authentication conflicts.
-
Documentation
- Added configuration, migration, usage, FAQ, and production limitation guidance.
- Updated backend provisioning documentation and roadmap.
🚀 Enhancements
fix(terraform): prevent concurrent output corruption @zack-is-cool (#2898)
## WhatPrevent concurrent Terraform runs from interleaving provisioner and lifecycle UI with component-prefixed output.
Why
JIT provisioning, backend provisioning, post-init provider locking, and clear or spin step hooks could bypass the scheduler's concurrent-output suppression. Terminal control sequences could corrupt output from other components.
Validation
go build ./...atmos lint --changed- Focused scheduler, hooks, runner-step, provisioner, source, workdir, and Terraform-init tests.
- Full
internal/execsuite completed in a clean worktree. - Full CLI suite completed with an extended timeout. Remaining failures require external GitHub access for
tenv, a non-linked Git worktree for one sandbox test, and an environment without inherited GitLab tokens.
Summary by CodeRabbit
- New Features
- Improved concurrent Terraform runs by suppressing transient spinners, hook output, and terminal-line updates.
- Standardized output routing across initialization, provisioning, hooks, and post-initialization steps.
- Preserved provisioning status messages and warnings through configured output streams.
- Bug Fixes
- Prevented transient progress indicators from interfering with concurrent execution output.
- Improved handling of missing execution contexts.
- Documentation
- Added guidance for managing transient Terraform output during concurrent runs.
fix: preserve trailing newlines in text-based 3-way merges @jorrite (#2891)
## what- Fix
atmos scaffold generate --updateunconditionally stripping the trailing newline from every file it 3-way-merges, whether or not the file actually changed. pkg/generator/merge/text_merger.go:TextMerger.Merge()now appends onenewlineSeparator("\n") to each ofours/base/theirsbefore handing them todiff3.Merge, sodiff3's guaranteed loss of exactly one trailing newline cancels out and the original count survives.pkg/generator/merge/text_merger_test.go: consolidated the trailing-newline regression coverage into a single table-driven test,TestTextMerger_TrailingNewlinePreservation, asserting exact byte-for-byte output across a no-op merge (0/1/2/3 trailing newlines, plus an internal blank line) and a genuine template change (theirs with 0/1/2 trailing newlines).- No change to conflict detection, threshold behavior, or
ConflictStrategyhandling — out of scope, and unaffected since the appended newline is identical across all three inputs.
why
TextMerger.Merge()delegates the actual 3-way merge toepiclabs-io/diff3, which reads each ofbase/ours/theirsline-by-line viabufio.Scanner(ScanLines, Go's standard-library default split function) and rejoins the merged lines withstrings.Join(lines, "\n").ScanLinesstrips every line's terminator — including the last — and gives no way to tell afterward whether the original input ended with a trailing newline or not. Concretely: for content ending in N trailing newlines, the round-trip throughGetLines+Joinalways reconstructs exactly N-1 (it loses exactly one, regardless of how many there were; for N = 0 there was nothing to lose in the first place). Verified directly: generating a file with 3 trailing newlines and running--updatewith nothing changed on the template side reproducibly comes back with 2.- Appending one newline to each input before the merge bumps every input's count to at least 1, so that guaranteed loss of exactly one cancels out and the original count is preserved — for both the no-op case and genuine changes, since whichever side's content ends up dominating a given region carries its own (now-restored) newline count through, independent of the others.
- This must be applied to all three inputs, not just
theirs: appending it only totheirsmakes an otherwise-identicalours/theirspair (a very common no-op shape) differ by one trailing newline as far asdiff3is concerned, which turns a no-op into a spurious detected change/conflict instead of fixing anything.
references
- Closes #2887.
Summary by CodeRabbit
-
Bug Fixes
- Improved text merging to preserve the exact number of trailing newlines.
- Prevented unintended changes to blank lines and end-of-file newline states during conflict-free merges.
- Ensured merged content remains byte-for-byte consistent when no substantive changes are made.
-
Tests
- Added coverage for varying trailing-newline counts, blank lines, and template-only changes.
-
Documentation
- Documented the trailing-newline preservation fix and its impact on text-based scaffold merges.
fix(scaffold): preserve source in scaffold config @jorrite (#2869)
## what- Fix
atmos scaffold generate/atmos initrecording a dangling, already-deleted temp-directory path inspec.sourceof.atmos/scaffold.yamlwhenever the template source is remote (git::...or a barehttps://...URL). - In the file
pkg/generator/source/resolver.go: inresolveRemote(), setconf.Source = src(the original source string the caller passed in) after loading the template configuration from the temporary download directory, instead of leavingConfiguration.Sourceas whateverLoadConfigurationFromDirwas given (the temp dir itself). pkg/generator/source/resolver_test.go: added/extended tests assertingConfiguration.Sourceholds the original source for both local and remote paths, plus a dedicated regression test (TestResolve_RemoteRecordsOriginalSource) that fails on the pre-fix code and passes after.- No change to local source handling (
resolveLocal), which already recorded the correct value.
why
- For a remote scaffold source,
resolveRemote()downloads the template intoos.MkdirTemp("", "atmos-scaffold-"), then loaded the config with that temp dir passed in as the "source" — soConfiguration.Source, and
therefore the persistedspec.source, ended up holding something like/var/folders/xx/.../atmos-scaffold-1234567890. That directory is removed bycleanup()immediately after the command finishes, so the recorded provenance is a dangling reference to nothing as soon as generation completes — useless for anything that might want to read it back later (e.g. a future--update/re-resolve flow), and directly contradictsSaveProjectRecord's own doc comment: "spec.source and spec.baseRef record provenance for future updates." - Local sources (a relative/absolute path, or
file://...) were correct only by accident of not having a temp-dir indirection step inresolveLocal, not because anything special-cased provenance for them. - Reproduced directly:
atmos scaffold generate "git::https://.../scaffold-template.git" ./out --defaults, thencat ./out/.atmos/scaffold.yamlshows a/var/folders/...//tmp/...path forspec.source, and that path no longer exists on disk.
references
- No upstream GitHub issue — checked issue search and the web for
cloudposse/atmos+spec.source/scaffold, nothing matched as of 2026-08.
Summary by CodeRabbit
-
Bug Fixes
- Remote scaffolds now retain their original source URI after resolution instead of displaying a temporary download location.
- Local scaffold operations consistently preserve the original source path.
- Improved source tracking for downloaded scaffold files, making configuration provenance clearer and more reliable.
-
Documentation
- Added details about the source-tracking fix, including expected behavior and validation coverage.
fix(version): exclude draft GitHub releases from Version Tracker resolution @osterman (#2900)
## whatpkg/github.GetReleasesnow excludes draft GitHub releases unconditionally, alongside the existing prerelease filter. This fixes the Version Tracker'sgithub-releasesdatasource resolver,atmos version list, andGetReleaseVersions, which all share this function.- Removes the deprecated
atmos version track rendersubcommand. It was markedDeprecated/Hiddenin the same commit that introduced it and has never had a non-deprecated existence in any release, so no migration path is needed. The sharedrenderTemplatehelper moves intoapply.go, its only remaining consumer. - Fixes pre-existing EditorConfig indentation drift (3-space list/fence indents instead of the required 2-space multiple) in
docs/prd/atmos-version-management.md, surfaced once the file was touched by this branch's--affectedvalidation. - Expands the
version.filesand!versionfunction docs with worked before/after examples for themarkerandgithub-actionsfile managers (including SHA pinning) and adds Helm/Container-component examples alongside the existing Terraform one.
why
atmos version trackwithdatasource: github-releasesanddesired: latestcould resolve to an unpublished draft release instead of the actual latest published release, whenever the GitHub token had repo write access (e.g.secrets.GITHUB_TOKENin a repo's own CI). Reproduced againstcloudposse/atmositself:atmos version track lockresolved tov1.226.0, whichgh release view v1.226.0 --repo cloudposse/atmos --json isDraftconfirmed was a draft, when the real latest published release wasv1.225.0. Unlike prerelease, there's no legitimate case for ever resolving to a draft, so it's excluded unconditionally rather than gated behind a new opt-in policy field.atmos version track renderwas superseded byapply/the file-managers architecture within its own introducing PR and has been carried as dead weight across multiple releases; removing it avoids maintaining a command with no live users.- The docs updates make the Version Tracker's file-manager and
!versionbehavior easier to learn from concrete examples rather than a single terse case.
references
- N/A
Summary by CodeRabbit
-
New Features
- Version tracking now excludes draft releases when resolving available versions.
- Expanded documentation for version markers, Dockerfiles, GitHub Actions, Terraform, Helm, and container images.
-
Bug Fixes
- Prevented draft GitHub releases from being selected as version candidates.
- Improved reliability of version-related documentation and examples.
-
Removed
- Removed the deprecated
version track rendercommand and associated rendering behavior.
- Removed the deprecated
🤖 Automatic Updates
fix(deps): update github.com/epiclabs-io/diff3 digest to 3b16698 @[renovate[bot]](https://github.com/apps/renovate) (#2917)
This PR contains the following updates:| Package | Type | Update | Change |
|---|---|---|---|
| github.com/epiclabs-io/diff3 | require | digest | 280ec18 → 3b16698
|
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.
fix(deps): update kubernetes monorepo to v0.36.3 @[renovate[bot]](https://github.com/apps/renovate) (#2918)
This PR contains the following updates:| Package | Change | Age | Confidence |
|---|---|---|---|
| k8s.io/apimachinery | v0.36.2 → v0.36.3
| ||
| k8s.io/client-go | v0.36.2 → v0.36.3
|
Release Notes
Configuration
📅 Schedule: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about these updates again.
- If you want to rebase/retry this PR, check this box
This PR was generated by Mend Renovate. View the repository job log.