Skip to content

feat(cloudformation): add changesets, drift detection, fmt/list/source verbs [EXPERIMENTAL] - #3000

Draft
Erik Osterman (Cloud Posse) (osterman) wants to merge 10 commits into
osterman/cfn-phase1-core-lifecyclefrom
osterman/cfn-phase2-changesets-drift-outputs
Draft

feat(cloudformation): add changesets, drift detection, fmt/list/source verbs [EXPERIMENTAL]#3000
Erik Osterman (Cloud Posse) (osterman) wants to merge 10 commits into
osterman/cfn-phase1-core-lifecyclefrom
osterman/cfn-phase2-changesets-drift-outputs

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Aug 26, 2026

Copy link
Copy Markdown
Member

what

  • Phase 2 of the native aws/cloudformation component type: changeset management
    (changeset create/execute/list/delete), drift detection (drift detect/describe), template/policy
    fetch (get template/policy), plus fmt, list, and source (JIT vendoring) inspection verbs.
  • Raises coverage to 93.5%/100%/93.7% across the added packages.
  • Documents the Phase 2 verbs and the interop bridge with existing Atmos tooling.

why

  • Extends Phase 1's core lifecycle (apply/diff/delete) with the changeset-driven review workflow
    and drift visibility that CloudFormation users expect, matching the ergonomics the archived Rain
    CLI used to provide.

references

  • Builds on cfn-phase1-core-lifecycle in this stack.
  • Part of a 6-PR stack; see cfn-phase4-migration-graduation for the final layer and blog post.

@atmos-pro

atmos-pro Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@github-actions github-actions Bot added the size/xl Extra large size PR label Aug 26, 2026
@mergify mergify Bot added the triage Needs triage label Aug 26, 2026
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@osterman Erik Osterman (Cloud Posse) (osterman) added the no-release Do not create a new release (wait for additional code changes) label Aug 26, 2026
@osterman Erik Osterman (Cloud Posse) (osterman) changed the title osterman/cfn phase2 changesets drift outputs feat(cloudformation): add changesets, drift detection, fmt/list/source verbs [EXPERIMENTAL] Aug 26, 2026
@osterman
Erik Osterman (Cloud Posse) (osterman) force-pushed the osterman/cfn-phase2-changesets-drift-outputs branch from e43e0f4 to 845cff7 Compare August 27, 2026 11:28
@osterman
Erik Osterman (Cloud Posse) (osterman) force-pushed the osterman/cfn-phase2-changesets-drift-outputs branch from 845cff7 to 8fad5cf Compare August 27, 2026 11:39
@osterman
Erik Osterman (Cloud Posse) (osterman) force-pushed the osterman/cfn-phase2-changesets-drift-outputs branch from 8fad5cf to dbf1a63 Compare August 27, 2026 11:44
@osterman
Erik Osterman (Cloud Posse) (osterman) force-pushed the osterman/cfn-phase2-changesets-drift-outputs branch from 825ba90 to 9b85623 Compare August 27, 2026 15:43
@osterman
Erik Osterman (Cloud Posse) (osterman) force-pushed the osterman/cfn-phase2-changesets-drift-outputs branch from 9b85623 to d579baf Compare August 27, 2026 16:00
…t verbs

Explicit changeset control (create/execute/list/delete), complementing the
implicit changeset flow apply/deploy/diff already use:
- atmos aws cloudformation changeset create/execute/list/delete
- atmos aws cloudformation drift detect/describe (DetectStackDrift/
  DescribeStackDriftDetectionStatus/DescribeStackResourceDrifts)
- atmos aws cloudformation get template/policy (GetTemplate/GetStackPolicy)

Also the Terraform<->CloudFormation interop bridge:
- `!aws.cloudformation.output <component> [stack] <output>` YAML function,
  sibling to !terraform.output (same cycle-detection/nested-auth machinery).
- atmos.Component(...).outputs now resolves for aws/cloudformation targets,
  not just terraform.

The CFN client interface grew ListChangeSets/DeleteChangeSet/GetTemplate/
GetStackPolicy/ListStacks/DetectStackDrift/DescribeStackDriftDetectionStatus/
DescribeStackResourceDrifts. runOperation and ComponentProvider.Execute were
refactored from long switches to map-based dispatch (cyclomatic complexity
had crossed the lint threshold with the new operations).

The Outputs-fetching logic behind the YAML/template functions couldn't live in
internal/exec directly (the provider-agnostic-auth depguard rule forbids AWS
SDK imports there) or reuse pkg/component/aws/cloudformation's own client (it
already imports internal/exec, so the reverse import would cycle) — added a
small leaf package, pkg/aws/cloudformation, mirroring pkg/aws/identity's shape.

Verified live against the Floci AWS emulator: changeset create/execute/list,
get template, and both the YAML function and atmos.Component().outputs
resolving a second component's real deployed Output value. drift detect and
get policy hit genuine Floci/LocalStack-community limitations (confirmed via
matching failures on raw AWS CLI calls against the same endpoint, not Atmos
bugs) — DetectStackDrift is unimplemented and GetStackPolicy returns a
malformed response Floci's own SDK client can't parse either.

Every new/renamed YAML function tag required updates across five independent
registries that must stay in sync with pkg/utils/yaml_utils.go's
AtmosYamlFunc* constants: pkg/function/tag (the format-agnostic tag catalog),
pkg/tags/selector.go (functions forbidden in metadata.tags/labels — this one
matters: cross-component output resolution requires auth and a live API call,
so it must be excluded from tag/label selector evaluation same as
!terraform.output), pkg/config/schema/ratchet_test.go (atmos.yaml
preprocessing classification), and cmd/list/utils.go (functions skipped
during inventory upload when no AuthManager is available).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pkg/component/aws/cloudformation was at 74.6% after the Phase 2 commit added
changeset_verbs.go/drift.go/get.go with no tests of their own; pkg/aws/cloudformation
(the new leaf package backing the YAML/template function interop bridge) had
none at all. Now 93.5% and 100% respectively, plus full coverage on the new
internal/exec functions (cloudformation_output_getter.go, the
!aws.cloudformation.output YAML function, and componentFunc's new CFN branch).

Two small testability seams, matching each file's own existing convention:
- pkg/aws/cloudformation/outputs.go had none at all — extracted a
  cloudFormationAPI interface + loadAWSConfig/newCloudFormationClient vars,
  mirroring pkg/aws/organization's identical pattern.
- drift.go's poll interval/timeout became vars instead of consts so tests can
  shrink them, rather than requiring a real 15-minute wait to exercise the
  timeout branch.

New fixture: tests/fixtures/scenarios/aws-cloudformation-outputs/, needed
because the YAML/template function tests call through the real
ExecuteDescribeComponent (no existing seam for it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…se 2)

- atmos aws cloudformation fmt [--check]: native, dependency-free
  comment-preserving YAML round-trip via yaml.v3's Node API (no cfn-format
  binary exists to shell out to, and Rain's own formatter is archived along
  with the rest of Rain). Both fmt and render are exempted from the
  auth-manager setup every other operation requires — neither calls the
  CloudFormation API.
- atmos aws cloudformation list [--status ...] [--region ...]: account-wide
  ListStacks, annotated "managed"/"unmanaged" against the queried stack's
  configured aws/cloudformation components by stack_name. Unlike every other
  verb this isn't scoped to one component, so it bypasses
  ComponentProvider.Execute entirely and calls the new
  pkg/component/aws/cloudformation.ListDeployedStacks directly.
- atmos aws cloudformation source pull/list/describe/delete: wired onto the
  existing generic pkg/provisioner/source/cmd builders already shared by
  terraform/helmfile/packer — needed one addition, Config.CLIName, since those
  three all happen to have a CLI command name identical to their internal
  ComponentType string ("terraform" IS "terraform"), which "aws/cloudformation"
  (type) vs. "aws cloudformation" (CLI path) breaks; CLIName defaults to
  ComponentType so the other three callers are unaffected.

Verified live against the Floci AWS emulator: fmt --check/apply against the
example's template (confirmed short-form intrinsic tags like !Sub/!Ref
round-trip correctly, comments survive), list correctly marking a deployed
stack "managed" against its configured stack_name, and source describe
correctly reporting "not configured" for a component with no source: section.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pkg/component/aws/cloudformation dropped to 86.6% after the fmt/list/source
commit added fmt.go/list.go with no tests; now 93.7%. cmd/aws/cloudformation
went from 0% (no test file existed) to 50%, covering the pure extraction
logic (cloudFormationComponentStackName, configuredCloudFormationStackNames)
that list's managed/unmanaged annotation depends on — the remaining gap there
is mostly RunE plumbing (real auth/config init), consistent with how the rest
of this file is tested.

One small extraction for testability: list.go's Managed-annotation loop is
now its own pure function, annotateManagedStacks, so the managed/unmanaged
boolean per stack is asserted directly rather than only through a full
AWS-client round-trip.

cmd/aws/cloudformation/source has no test file, matching
cmd/terraform/source's own precedent — it's a config literal plus four
delegated AddCommand calls with nothing to assert beyond "it builds," which
the parent package's own tests already exercise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CLI docs for changeset create/execute/list/delete, drift detect/describe,
get template/policy, fmt, list, and source pull/list/describe/delete
(mirroring the existing terraform/helmfile/packer source docs pattern).

Also documents the Terraform<->CloudFormation interop bridge:
!aws.cloudformation.output alongside the other YAML functions (including its
forbidden-in-selectors restriction, same as !terraform.output), and
atmos.Component(...).outputs now resolving aws/cloudformation targets.

No config/schema reference changes needed — Phase 2 added CLI verbs only, no
new stack-manifest or atmos.yaml fields.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dentity

componentFunc cached its result under stack+component alone, computed after
resolving the nested target's AuthManager. Two calls for the same
component/stack under two different resolved identities (e.g. two callers
passing different --identity, or a target whose own auth section only
applies on one call) could silently return the other identity's/region's
cached outputs. Move the AuthManager resolution ahead of the cache lookup
and fold the resolved identity/region into the cache key.

Found via CodeRabbit review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t confirmed-drift/changeset ones

DetectStackDrift/DescribeStackDriftDetectionStatus/DescribeStackResourceDrifts
API failures and detection timeouts were wrapped in
ErrAwsCloudFormationDriftDetected, which should mean drift was actually
confirmed (runDriftDetect's --fail-on-drift case, left unchanged). Likewise
GetTemplate/GetStackPolicy failures were wrapped in
ErrAwsCloudFormationChangeSetFailed despite having nothing to do with a
changeset. Swapped both to ErrAwsCloudFormationAPICallFailed. Found via
CodeRabbit review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… strengthen tests

- GetAvailableCommands() now derives its list from subCommandOperations
  instead of a hand-maintained literal, which was missing "destroy",
  "outputs", and "fmt" — those verbs were dispatchable via Execute but
  rejected by pkg/composition's verb-allowlist check (GetAvailableCommands
  is its source of truth).
- pkg/aws/cloudformation's GetOutputs (the !aws.cloudformation.output
  interop bridge) wrapped both a DescribeStacks API failure and a genuine
  stack-not-found result in the same ErrAwsCloudFormationChangeSetFailed,
  despite neither being changeset-related. Split into
  ErrAwsCloudFormationAPICallFailed and a new ErrAwsCloudFormationStackNotFound.
- Fixed a stale doc comment on runChangesetList (said "creation time",
  renders "description").
- Strengthened TestRenderDeployedStacksList_Populated: "managed" is a
  substring of "unmanaged", so the old assert.Contains checks would still
  pass even if both rows rendered as unmanaged. Now asserts full rendered
  lines.

Found via CodeRabbit review. Several other findings from the same pass were
verified against current code and are false positives (not fixed): the
--affected/--all doc examples correctly omit --stack per this codebase's
bulk-selection convention; `plan` is a genuinely registered alias for
`diff`; `list`'s empty --stack behavior matches every other describe-stacks
command's "no filter = all stacks" convention; the NextToken-nil pagination
check matches every other pagination loop in this package and the AWS SDK's
documented contract; and yaml_func_aws.go's discarded second return value
from resolveNestedOutputAuth is the resolved auth manager, not an error —
the function has no error return at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This branch's own docs (changeset/drift/fmt/get/list/source, 18 verbs)
referenced screengrab casts that were never recorded, and cli.yaml's
command manifest never listed them — the same gap fixed cumulatively on
the top of the stack, but each PR in this stack is validated against its
own branch tip, not the final merged result, so this layer needs its own
fix. Also recorded the original 8 top-level verbs' casts (render/plan/
diff/apply/deploy/delete/validate/output) plus --help, which had never
been committed at all on this branch either.

atmos --chdir=demo/casts casts generate screengrabs cli --filter=cloudformation

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.83886% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.58%. Comparing base (a9a56c6) to head (430dd87).

Files with missing lines Patch % Lines
cmd/aws/cloudformation/list.go 69.11% 20 Missing and 1 partial ⚠️
internal/exec/template_funcs_component.go 73.91% 6 Missing ⚠️
cmd/aws/cloudformation/cloudformation.go 94.93% 1 Missing and 3 partials ⚠️
pkg/component/aws/cloudformation/fmt.go 87.50% 2 Missing and 2 partials ⚠️
internal/exec/yaml_func_utils.go 0.00% 2 Missing and 1 partial ⚠️
pkg/component/aws/cloudformation/list.go 98.11% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                          Coverage Diff                           @@
##           osterman/cfn-phase1-core-lifecycle    #3000      +/-   ##
======================================================================
+ Coverage                               83.55%   83.58%   +0.03%     
======================================================================
  Files                                    1963     1973      +10     
  Lines                                  191390   191947     +557     
======================================================================
+ Hits                                   159911   160448     +537     
- Misses                                  23477    23490      +13     
- Partials                                 8002     8009       +7     
Flag Coverage Δ
unittests 83.58% <93.83%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/aws/cloudformation/source/source.go 100.00% <100.00%> (ø)
cmd/list/utils.go 79.64% <100.00%> (+0.12%) ⬆️
errors/errors.go 100.00% <ø> (ø)
internal/exec/cloudformation_output_getter.go 100.00% <100.00%> (ø)
internal/exec/yaml_func_aws.go 82.60% <100.00%> (+11.18%) ⬆️
pkg/aws/cloudformation/outputs.go 100.00% <100.00%> (ø)
...kg/component/aws/cloudformation/changeset_verbs.go 100.00% <100.00%> (ø)
pkg/component/aws/cloudformation/client.go 100.00% <ø> (ø)
pkg/component/aws/cloudformation/cloudformation.go 91.48% <100.00%> (-1.11%) ⬇️
pkg/component/aws/cloudformation/confirm.go 96.29% <100.00%> (-0.26%) ⬇️
... and 20 more

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-release Do not create a new release (wait for additional code changes) size/xl Extra large size PR triage Needs triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant