Add ECR Public authentication: `aws/ecr-public` integration and `atmos aws ecr login --public` @osterman (#2231)
## whatAdd ECR Public authentication to Atmos for authenticated access to public.ecr.aws, solving Docker rate limiting on public ECR images. Two entry points:
atmos aws ecr login --public— direct, zero-config login using ambient AWS credentials (the AWS SDK default chain: env, shared config/profile, SSO, IMDS/IRSA/ECS), or--public --identity <name>to use a specific identity. Ideal for CI.aws/ecr-publicintegration kind — for automatic login onatmos auth loginand identity linking.
Key changes:
- Command (
cmd/aws/ecr/login.go): new--publicflag onatmos aws ecr login; ambient-credential and identity-based ECR Public login paths; mutually exclusive with a positional integration argument and--registry. - Cloud layer (
pkg/auth/cloud/aws/ecr_public.go):GetPublicAuthorizationToken()callsecrpublic:GetAuthorizationToken, always in us-east-1. - Integration layer (
pkg/auth/integrations/aws/ecr_public.go):ECRPublicIntegrationfactory registering theaws/ecr-publickind, with region validation at config time. Implements the fullIntegrationinterface includingCleanup()(docker logout) andEnvironment()(DOCKER_CONFIG). - Region validation: rejects unsupported regions (only us-east-1 and us-west-2 have service endpoints; auth is us-east-1 only).
- Tests: cloud-layer and integration-layer unit tests (token retrieval, region validation, cleanup, error handling) with a generated mock ECR Public client; command tests for the
--publicflag and mode validation. - Documentation:
atmos aws ecr logincommand reference (added--publicflag), ECR authentication tutorial, and a PRD (docs/prd/ecr-public-authentication.md). - Blog post + roadmap: announcement and a shipped milestone linking to the changelog.
Note: this branch has been merged up to
main. Following #2144 (atmos auth ecr-login→atmos aws ecr login), ECR login lives under theawsnamespace, and the integration was adapted tomain's evolvedIntegrationinterface (exportedBuildAWSConfigFromCreds, newCleanup/Environmentmethods).
why
Docker pulls from public.ecr.aws hit rate limits when unauthenticated. This blocks CI workflows, especially those using cloudposse/github-action-docker-build-push which pulls BuildKit/binfmt images on every run. Authenticated pulls have significantly higher (or no) rate limits.
Because public.ecr.aws is global, any valid AWS credentials unlock authenticated pulls — so --public with ambient credentials "just works" in CI with zero configuration. ECR Public otherwise differs from private ECR: it uses the ecrpublic SDK service, a bearer token instead of SigV4, a hardcoded us-east-1 auth region, and a fixed public.ecr.aws registry URL. It requires ecr-public:GetAuthorizationToken and sts:GetServiceBearerToken IAM permissions.
references
- ECR Public Authentication Tutorial — configuration examples, multi-environment setup.
atmos aws ecr loginCommand Reference — command usage,--publicflag, integration configuration.- ECR Public Blog Post — announcement and use cases.
- PRD:
docs/prd/ecr-public-authentication.md. - AWS Docs: ECR Public APIs.
Summary by CodeRabbit
-
New Features
- ECR Public authentication (aws/ecr-public) with atmos aws ecr login --public, identity-driven auto-provisioning, and enforced us-east-1 auth.
-
Documentation
- Tutorials, blog post, and roadmap updated with ECR Public examples, permissions, CI guidance, and troubleshooting.
-
Bug Fixes
- Improved identity selection UX (confirmation message) and safer CLI behavior for non‑TTY identity selection.
-
Tests
- Extensive unit and integration tests covering ECR Public flows and CLI routing.
-
Chores
- NOTICE/dependencies updated and minor .gitignore tweak.
feat(auth): Atmos Pro STS — JIT GitHub token broker for CI @osterman (#2546)
## what- Add a new auth provider
kind: atmos/prothat authenticates the Atmos CLI to Atmos Pro by federating the GitHub Actions runner's OIDC token into an Atmos Pro session JWT (v1 is OIDC-only). - Add a new auth integration
kind: github/sts— a just-in-time GitHub token broker for CI. On login it mints short-lived, scoped GitHub App installation tokens viaPOST /api/v1/sts, materializes them as per-ownerGIT_CONFIG_*URL rewrites (envorfilemode), and revokes them at command-end (viaatmos auth execin CI) and onatmos auth logout. - Add a passthrough
kind: atmos/proidentity, a keyring-registeredProCredentialstype, realm scoping for integration state, andvia.providerbinding for integrations (in addition tovia.identity). - Add
ATMOS_PRO_GITHUB_TOKEN, preferred by Atmos-native git operations (vendoring,source:provisioning, go-getter) ahead ofATMOS_GITHUB_TOKEN/GITHUB_TOKEN. - Add the PRD (
docs/prd/atmos-pro-sts.md), a changelog blog post, a shipped roadmap milestone, and configuration docs; full unit-test coverage for the provider, identity, integration, keyring round-trip,via.providermatching, revoke gating, and token precedence.
why
- Fetching private Terraform modules, Atmos
source:components, and vendored artifacts in CI today requires a long-lived, over-privileged GitHub credential (PAT, machine user, or deploy key) sitting in a CI secret — a standing breach risk that can't be scoped per-run. - Atmos Pro STS replaces that with least-privilege, deny-by-default, short-lived tokens minted at the start of a run and revoked at the end — with zero
.tfchanges (the injectedGIT_CONFIG_*rewrites are honored by both go-getter and Terraform's native git), and multi-org support because tokens are minted per(installation, permission-set). - Built into Atmos CLI core (CI-native, OIDC-aware) rather than as a GitHub Action, modeled on the existing
aws/ecr/aws/eksintegrations; the workflow only needspermissions: id-token: write.
references
- PRD:
docs/prd/atmos-pro-sts.md(includes deferred Future Work: moving Pro connection config underauth, unifyingpkg/proonto auth-issued sessions, and broadening command-end revoke beyondatmos auth exec) - Changelog:
website/blog/2026-05-29-atmos-pro-github-sts.mdx
Summary by CodeRabbit
-
New Features
- Atmos Pro GitHub token broker: new atmos/pro provider + github/sts integration for just-in-time GitHub tokens (env or git-config modes) with realm-scoped state and optional token export.
- ATMOS_PRO_GITHUB_TOKEN added as preferred GitHub token source.
- CI-gated automatic token revocation on command exit/logout.
- Ambient credential broker registry to auto-provision env vars for remote reads.
-
Documentation
- PRD, docs, and blog post for Atmos Pro STS and usage guidance.
docs: re-date custom commands step types blog post to 2026-05-30 @osterman (#2550)
## what- Re-dated the "25+ Interactive Step Types" blog post from
2026-01-03to2026-05-30. - Renamed the file prefix (
git mv, history preserved) and added a matchingdate: 2026-05-30frontmatter field.
why
- Aligns the post's publish date with its actual release timing so it surfaces correctly in the changelog feed.
- Adds the explicit
date:field to match the repo convention (e.g.2026-05-28-git-yaml-functions.mdx). - The
slugis unchanged, so the published URL stays the same.
references
- N/A — docs-only date adjustment, no user-facing code change.
Summary by CodeRabbit
- Documentation
- Published comprehensive guide to custom commands and workflow step types, featuring 25+ interactive step types with usage examples, including input collection, output formatting, and variable passing conventions for enhanced automation capabilities.
fix(website): use consistent brand-blue announcement bar @osterman (#2551)
## what- Removed the per-announcement
backgroundColor/textColoroverrides fromwebsite/src/data/announcements.jsso every announcement bar entry inherits the brand-blue (#3578e5) / white-text defaults from the--announcement-bar-*CSS variables. - Documented the convention in the file header so future announcements don't reintroduce per-entry colors.
why
- The announcement bar cycled through a rainbow of saturated Tailwind-600 colors (emerald green, violet, cyan, amber, red, indigo, teal...) that looked like "crayola" against the site's dark, near-black theme.
- A single restrained, on-brand color reads as sophisticated and consistent with the rest of the dark site, and matches the bar's original styling.
references
- N/A (website cosmetic change)
Summary by CodeRabbit
- Refactor
- Standardized announcement bar styling configuration to use shared CSS variables instead of per-announcement color settings, improving consistency across announcements.
feat: Implement workflow step types with registry pattern (DEV-263, DEV-2969) @osterman (#1899)
## what- Add 20+ step types across 4 categories (Interactive, Output, UI, Command) with extensible registry pattern
- Support Go template variable passing between steps (e.g.,
{{ .steps.step1.value }}) - Implement per-step output modes: viewport (pager), raw (passthrough), log (grouped), none (silent)
- Interactive handlers with TTY detection and clear error messages in CI environments
why
Addresses DEV-263 (add input type to workflows) and DEV-2969 (add viewport support). Enables users to build complex multi-step workflows with user interaction, conditional execution, and flexible result display.
references
Summary by CodeRabbit
Release Notes
-
New Features
- Added 25+ interactive step types for workflows and custom commands (input, confirm, choose, filter, file, write, markdown, spin, table, style, and more).
- Support for configurable output modes (viewport, raw, log, none) and step-level display options.
- Workflow progress rendering and status indicators.
-
Documentation
- Comprehensive guides for interactive workflows and custom commands with step type reference.
- New examples demonstrating interactive deployments, credentials collection, and multi-step flows.
-
Bug Fixes
- Improved error messaging for workflow step validation and execution failures.
Add process and I/O execution foundation @shirkevich (#2464)
## SummaryThis is PR 1 for the DAG concurrent execution rollout. It introduces the reusable process and stream-isolation foundation without enabling scheduler behavior or changing Terraform bulk routing.
Changes:
- Add
pkg/processwithRunner,TaskSpec,Streams,Result, defaultos/execrunner, context-aware execution, cancellation reporting, and exit-code preservation. - Extend
pkg/iowith prefixed per-node stream composition for terminal, file, and capture sinks. - Refactor
internal/exec.ExecuteShellCommand()into a backward-compatible wrapper overpkg/processwhile preserving CI stdout/stderr capture options. - Replace the
runTerraformShow()globalos.Stdoutswap with injected stdout capture.
Scope
No scheduler, CLI routing consolidation, concurrency flags, or Terraform adapter behavior is enabled in this PR.
Stacking
This PR is the bottom of the DAG rollout stack and targets main.
Supersedes the earlier fork-headed draft #2459 now that the stack branches exist in cloudposse/atmos.
Validation
rtk env GOCACHE=/private/tmp/atmos-gocache GOMODCACHE=/private/tmp/atmos-gomodcache go test ./pkg/process ./pkg/io ./internal/exec ./cmd/terraformNext PR
PR 2 branches from codex/dag-process-io-foundation and adds the generic pkg/scheduler core with ready-queue scheduling, bounded workers, deterministic aggregate results, and isolated unit tests only.
Summary by CodeRabbit
Release Notes
-
New Features
- Configurable subprocess execution with optional contexts and injectable streams
- Composable, scope-scoped output writers with per-line prefixing and masking
-
Bug Fixes
- More accurate subprocess exit/error reporting and improved stream-redirection behavior
-
Tests
- Expanded unit tests for subprocess execution, stream injection/capture, and output utilities
-
Documentation
- Updated concurrent execution docs to reflect stream-based output handling
🚀 Enhancements
fix(auth): honor keyring.type config and send DPoP proof on AWS webflow @osterman (#2545)
## what- Honor
auth.keyring.typefromatmos.yamlacross all auth-manager entrypoints by threadingauthConfigintocredentials.NewCredentialStoreWithConfig(...)(was silently dropped via the no-argNewCredentialStore()), and inject the manager's config-aware store into AWSuseridentities via a new optionalSetCredentialStoreinterface. - Add an RFC 9449 DPoP proof (EC P-256 / ES256, stdlib-only) to the AWS browser webflow token requests; generate the key per session, persist it in the refresh-token cache, and reuse it on refresh (a cache without a key falls back to the browser flow).
- Add
AuthManager.CredentialStoreType()for observability/testability, mark the no-argNewCredentialStore()constructorDeprecated, and add unit tests for both fixes (keyring backend selection, DPoP proof structure/signature, key round-trip, header presence).
why
- #2544: with
auth.keyring.type: memoryset, Atmos still selected the defaultsystemkeyring and hung indefinitely on hosts where the keyring service is present but unusable (e.g. a lockedgnome-keyring-daemon). The config value was read and then thrown away before backend selection — onlyATMOS_KEYRING_TYPEworked. Now the configured backend is honored everywhere an auth manager is built. - #2542: AWS sign-in's
/v1/tokenendpoint now rejects requests without a DPoP proof (HTTP 400 INVALID_REQUEST), so browser-based authentication foraws/useridentities failed at the code-exchange step. Sending the proof restores the flow; because the public-client refresh token is bound to the DPoP key, the key is persisted and reused on refresh.
references
- closes #2542
- closes #2544
- RFC 9449 (DPoP): https://datatracker.ietf.org/doc/html/rfc9449
Summary by CodeRabbit
-
New Features
- Added RFC 9449 DPoP support for AWS OAuth token exchanges to strengthen token binding.
- Auth now respects configured keyring backend across authentication flows.
-
Bug Fixes
- Fixed AWS token parsing to match real-world snake_case responses.
-
Improvements
- Auth manager exposes credential store backend type for easier diagnostics.
fix(yaml-functions): honor init.pass_vars when resolving !terraform.output (#1412) @thejrose1984 (#2548)
## whatWhen components.terraform.init.pass_vars: true is set, forward the component's vars to the internal terraform init that runs while resolving !terraform.output, via TF_VAR_* environment variables.
ComponentConfiggainsPassVars+Vars, populated inExtractComponentConfig.SetupEnvironmentinjectsTF_VAR_*for each var whenPassVarsis true (strings verbatim, other types JSON-encoded).- Regression tests cover the enabled path (string/number/bool/list), the disabled default, and env-section precedence.
why
Closes #1412.
The main terraform path honors pass_vars by passing -var-file to init (terraform_execute_helpers.go), so modules with init-time variable dependencies (e.g. a module version/source bound to var.aks_version) can initialize. But the init that runs while resolving !terraform.output goes through pkg/terraform/output, which uses the terraform-exec library and never honored pass_vars:
runInitonly setUpgrade(false)+ optionalReconfigure(true).ComponentConfighad noPassVars/vars plumbing.terraform-exec'sinitConfighas no var-file field — it structurally cannot pass-var-filetoinit.
So atmos tofu init/plan -s <stack> failed with Unable to compute static value / module.aks.version depends on var.aks_version which is not available whenever an init-time var came from a component resolved via !terraform.output.
Why TF_VAR_* rather than a var-file
terraform-exec can't attach a var-file to init, and an auto-loaded *.auto.tfvars.json on disk would risk cross-stack contamination when components are resolved concurrently. TF_VAR_* is process/runner-scoped, reaches init transparently through the existing SetEnv call, and Terraform/OpenTofu accept these values for the matching variable types (JSON encodings of lists/maps are valid HCL2). Gated behind pass_vars (default false), so it's a no-op unless opted in; an explicit TF_VAR_* in the component env section still wins.
references
- Closes #1412
test plan
go test ./pkg/terraform/output/...
New tests:
TestDefaultEnvironmentSetup_PassVars— vars exported asTF_VAR_*with correct encoding.TestDefaultEnvironmentSetup_PassVarsDisabled— noTF_VAR_*whenpass_varsis off.TestDefaultEnvironmentSetup_PassVarsEnvSectionWins— explicit env-sectionTF_VAR_*wins.
Validation note: verified at the unit level (init env now carries the component vars when
pass_varsis set; previously the init invocation was unchanged whetherpass_varswas on or off). I don't have terraform/tofu in this environment to re-run the reporter's fulltofu init/planend-to-end, so a maintainer check against a real init-time-dependent module would be worthwhile before release.
🤖 Generated with Claude Code
Summary by CodeRabbit
-
New Features
- Added an option to forward component variables as TF_VAR_* environment entries during Terraform/OpenTofu init; existing TF_VAR_* values are preserved and non-string values are JSON-encoded.
-
Tests
- Added tests for enabled/disabled forwarding, JSON encoding of non-strings, precedence of explicit env values, and end-to-end propagation to the runner env.
-
Documentation
- Docs updated to note init.pass_vars also applies to implicit init runs and how forwarded vars are presented as TF_VAR_*.
test(yaml-functions): regression test for mixed state/output circular dependency (#2005) @thejrose1984 (#2547)
## what- Add a regression test and fixture for a cross-component circular dependency that mixes
!terraform.stateand!terraform.output(component-a →!terraform.statecomponent-b; component-b →!terraform.outputcomponent-a). - New fixture:
tests/fixtures/scenarios/yaml-functions-circular-deps-mixed. - New test:
TestYAMLFunctionsCrossComponentCycleMixed.
why
This is the exact scenario from #2005. It was the same root cause as #2457 and was fixed by #2533 (making ProcessCustomYamlTags reuse the goroutine-local ResolutionContext so the Visited map survives nested walks). That fix covers both state↔state and the mixed state↔output path, but only the state↔state case had a regression test — so #2005 could silently regress while the existing test stayed green.
Verified the mixed cycle hangs (infinite recursion / goroutine stack overflow) on the commit before #2533, and returns a clean ErrCircularDependency on current main.
references
test plan
go test ./tests -run TestYAMLFunctionsCrossComponentCycle -v
Both TestYAMLFunctionsCrossComponentCycle (state↔state) and TestYAMLFunctionsCrossComponentCycleMixed (state↔output) pass. The mixed test asserts ErrCircularDependency is returned and that the MaxResolutionDepth safety net is not what fired (which would indicate the primary cycle detector regressed).
🤖 Generated with Claude Code
fix: defer custom-command/built-in collision warning to invocation time @thejrose1984 (#2549)
## whatScope is intentionally narrow: change only when the existing collision warning fires — defer it from command-registration time to the moment the conflicting command is actually invoked.
- No change to collision behavior: the built-in still wins and custom
stepsare still ignored. - No
override:/invoke:work — that opt-in design is tracked separately in thecustom-command-builtin-overridePRD. - Implemented by wrapping the conflicting built-in command's
PreRunEinprocessCustomCommands(preserving any existingPreRunE/PreRunand honoring Cobra's precedence ofPreRunEoverPreRun). - Adds a regression test asserting the warning is absent at registration and present (exactly once) on invocation.
why
Today the warning (introduced in #2191) is emitted from processCustomCommands, which runs during root init on every Atmos invocation. So a single colliding custom command makes every command — atmos list stacks, atmos terraform ..., etc. — print a warning about, say, a plan collision it never touched. The result is worse than noisy:
- It's misleading — the warning points at a command the user didn't run.
- It breaks scripting/CI that reads stderr, since every command (except
version) emits it.
Deferring the warning to invocation makes it accurate and actionable: it appears exactly once, only when you run the command the warning is actually about, and stderr stays clean for every other command. Same information, delivered at the moment it's relevant instead of on every unrelated call.
Behavior
| Invocation | Before | After |
|---|---|---|
atmos list stacks (with a colliding custom plan)
| ⚠ warning printed | no warning |
atmos <colliding command>
| ⚠ warning printed (and also for every other command) | ⚠ warning printed once, here only |
references
test
go test ./cmd/ -run 'TestCustomCommand_.*Collision|TestCustomCommand_StepsConflictWarning|TestCustomCommand_NamespaceMerge|TestCustomCommand_DeepNesting'
Verified the new test fails against the previous (emit-at-registration) behavior and passes with the fix.
🤖 Generated with Claude Code
Summary by CodeRabbit
-
Bug Fixes
- Collision warnings for custom commands that overlap built-in leaf commands are now deferred until the conflicting command is invoked, reducing startup noise and preserving existing pre-run error behavior.
-
Tests
- Added regression tests to verify deferred warnings are emitted exactly once on invocation and that existing pre-run behavior and error propagation remain intact; tests skip on Windows where stderr capture is unreliable.
fix(flags): register --settings-list-merge-strategy as a global flag (#2398) @thejrose1984 (#2540)
## what- Register
--settings-list-merge-strategyas a global persistent flag onRootCmd, with env binding toATMOS_SETTINGS_LIST_MERGE_STRATEGY. - Add a Cobra-direct fallback in
ProcessCommandLineArgsso the value reachesConfigAndStacksInfoeven when Cobra strips the flag fromRunE's args. - In
setSettingsConfig, scanos.Args(mirroringsetLogConfig'sparseFlags()pattern) so command paths that callInitCliConfigdirectly with a zero-valueConfigAndStacksInfo(e.g.describe config) still honor the flag. - Unit test the registration, inheritance, defaults, CLI value, and env-var path.
why
The flag is advertised in two places:
atmos.yaml:344— "Can also be set using 'ATMOS_SETTINGS_LIST_MERGE_STRATEGY' environment variable, or '--settings-list-merge-strategy' command-line argument"website/docs/cli/configuration/settings/settings.mdx:54
And Atmos's internal arg/flag layer already expects it:
pkg/config/const.go:147—SettingsListMergeStrategyFlag = \"--settings-list-merge-strategy\"internal/exec/cli_utils.go:72— listed incommonFlagsinternal/exec/cli_utils.go:495— string-flag handler that writesinfo.SettingsListMergeStrategypkg/config/utils.go:726— applies it ontoatmosConfig.Settings.ListMergeStrategy
But it was never registered with Cobra at the global level. Subcommands that don't whitelist unknown flags (e.g. terraform plan, which has no FParseErrWhitelist) rejected the flag before the legacy commonFlags post-processing ever ran:
$ atmos --settings-list-merge-strategy=append terraform plan vpc -s test
Error: unknown flag --settings-list-merge-strategy for command atmos terraform plan
references
- Closes #2398
test plan
Unit tests added in pkg/flags/global_registry_test.go:
flag is registered on RootCmd as persistentdefaults to empty stringCLI flag value flows through ViperATMOS_SETTINGS_LIST_MERGE_STRATEGY env var flows through Vipersubcommand inherits the persistent flag
End-to-end verification on a minimal project (atmos.yaml has settings.list_merge_strategy: replace):
| Invocation | list_merge_strategy
|
|---|---|
atmos describe config
| replace (baseline from atmos.yaml)
|
atmos --settings-list-merge-strategy=append describe config
| append
|
atmos describe config --settings-list-merge-strategy=merge
| merge
|
ATMOS_SETTINGS_LIST_MERGE_STRATEGY=append atmos describe config
| append
|
atmos --help now lists --settings-list-merge-strategy.
Full test suites pass for the touched packages:
ok github.com/cloudposse/atmos/pkg/flags
ok github.com/cloudposse/atmos/pkg/flags/global
ok github.com/cloudposse/atmos/pkg/config
ok github.com/cloudposse/atmos/internal/exec
Summary by CodeRabbit
- New Features
- Added --settings-list-merge-strategy CLI flag (replace, append, merge) and ATMOS_SETTINGS_LIST_MERGE_STRATEGY env var to override list-merge behavior for an invocation
- Documentation
- Documented the new flag and environment variable with usage and defaults
- Tests
- Updated CLI help snapshots to include the new flag and refreshed help text formatting across commands