terraform-aws-modules/terraform-aws-alb examples/complete-alb (tag v9.9.0), its flagship application-load-balancer example
Source: https://github.com/terraform-aws-modules/terraform-aws-alb.git at v9.9.0.
Set: core. Lane: terraform-popular.
Why it is in the core set: a most-downloaded terraform-aws-modules example, pinned by tag; the shape most people deploy
Clear. Every headline stage passes.
| Stage | Verdict | Duration | Detail |
|---|---|---|---|
| Cold deploy | pass | 1m26s | 80 resources, once for real (floci fixes #58, #61, #62) |
| Migrate | pass | 1m7s | 51 of 80 stamped, 1 recorded, 0 failed, 28 skipped |
| Replan from nothing | pass | 4s | empty live-plan with no state file; 0 Error diagnostics; the two record-rung aws_route53_record.validation identities verified by value against route53 list-resource-record-sets |
| No-op apply | pass | 5s | genuine no-op (0 added, 0 changed, 0 destroyed); 50 tofu-estate-tagged objects before, 50 after |
| Drift and reconverge | pass | 39s | one object tampered (the ALB’s Example tag), plan proposed fixing exactly module.alb.aws_lb.this[0], apply changed 1 and the Example tag reconverged |
| Rename | pass | 16s | moved block: aws_instance.this renamed with zero churn (0 add, 1 change, 0 destroy), marker rewritten in place; live-mv: aws_instance.other renamed with zero churn, marker rewritten in place; stock oracle over the same two-object rename on cold_deploy’s own state (positioned right after stage 1, before migrate ever touches these shared objects) also shows zero churn (0 add, 0 change, 0 destroy); both live ids unchanged, read via the AWS CLI |
| Remove a block | pass | 22s | choudoufu: deleting aws_instance.other_renamed’s block (and its one target-group-attachment reference) proposed exactly 2 destroys (0 add, 0 change, 2 destroy), matching the stock oracle and applied cleanly; the instance is confirmed terminated via the AWS CLI, not through choudoufu’s own report; the next plan proposes no resource action; stock oracle on cold_deploy’s own state (D-REMOVE-ORACLE) also proposes exactly 2 destroys for the same two objects |
| Change count | pass | 47s | choudoufu: scaling aws_security_group.count_test from 2 to 1 destroyed exactly count_test[1] (Plan: 0 to add, 0 to change, 1 to destroy.) and left count_test[0] untouched; scaling back from 1 to 2 created exactly count_test[1] (Plan: 1 to add, 0 to change, 0 to destroy.); the next plan is empty. Read back through the AWS CLI, never through choudoufu’s own report: the destroyed count_test[1] (sg-589ce26be1d1d7994) is absent after the scale-down and comes back under a NEW server-minted GroupId (sg-be3050759f37923ef) carrying tofu-address=aws_security_group.count_test:1, while the survivor count_test[0] keeps BOTH its GroupId (sg-5ff88684bbbf4be62) and its tofu-address=aws_security_group.count_test:0 marker across both moves. What witnesses the destroy was measured against the pinned emulator with no terraform in the loop before this section was written: floci sha256:c55d74e1 never reuses a deleted group’s GroupId, not even for the same group-name in the same VPC. G-ORACLE is a real stock oracle for the same shape - stock never had this count block, so it was stood up for real with the stock terraform binary in its own working directory and state against its own idle account on :20400 - and stock shows the IDENTICAL shape: destroy the higher index only (sg-51d4015748fb1c6a7, then absent), create it back under a new id (sg-b8e32017c3badd7b1), count_test[0]=sg-df1d0286535b9d8df unchanged throughout (stock’s own plan lines: “Plan: 0 to add, 0 to change, 1 to destroy.” down, “Plan: 1 to add, 0 to change, 0 to destroy.” up). SYNTHETIC BLOCK, and why: this estate declares no scalable count of its own - examples/complete-alb has no root-level count or for_each at all, terraform-aws-alb’s only count is its local.create ? 1 : 0 boolean create toggle, and everything the module fans out (6 listeners, 7 listener rules, 3 target groups, 2 security-group ingress rules) is for_each over a STRING-KEYED map, which has no index whose slot binding could be scaled and over which “the higher index is destroyed” is not expressible - so the sanctioned fallback applies (reference-ec2-vpc Part F, corpus-iam-policy Part G), with aws_security_group, a type this estate already exercises. BREAK_COUNT=1 confirms the check has teeth: asserting the WRONG instance (count_test[0]) was destroyed makes this stage report fail. One emulator divergence noted in passing, harmless to this stage and asserted around rather than papered over: floci answers DescribeSecurityGroups for a deleted group-id with an empty list and exit 0, where real EC2 raises InvalidGroup.NotFound. |
| Replace with create_before_destroy | pass | 33s | choudoufu: changing aws_instance.this_renamed’s ForceNew ami argument proposed a forced replace at the same declared address (Plan: 2 to add, 0 to change, 2 to destroy.), applied cleanly; the old instance (i-4f6658473f610211b) is confirmed terminated and the new instance (i-b5197ffacd33636c3) carries the marker, both via the AWS CLI; the local record store’s record at the same address now names the new instance’s id, not the terminated one (i-4f6658473f610211b -> i-b5197ffacd33636c3); the next plan proposes no resource action; stock oracle on cold_deploy’s own state (day2_replace ORACLE) also proposes replacing aws_instance.this at the same address (plan only, not applied - it shares floci’s account with $ADOPTED_EST); BREAK=replace confirms a manufactured marker collision is reported loudly (“Two live resources claiming one address”, naming both live instances) rather than silently proposed as nothing - internal/live/discovery/supersededclaimant.go (#849) tombstones only what an apply actually destroyed, so a live duplicate with no tombstone is never pruned away. Scope note: this exercises OpenTofu’s default destroy-then-create ordering, not the create_before_destroy variant the stage’s Title names - see this section’s own header comment. |
| Crash between create and destroy | not run | ||
| Teardown | not run | ||
| Plan, review, apply | pass | 22s | one argument edited (the “ex-instance” target group’s own InstanceTargetGroupTag tag, baz -> reviewed - the one tags argument in examples/complete-alb that reaches exactly one instance, with no dependent resource or data source behind it), “plan -out=approved.tfplan” wrote a 159685-byte stock-format plan file whose whole change set is one update on module.alb.aws_lb_target_group.this[“ex-instance”]; the world then moved out of band (arn:aws:elasticloadbalancing:eu-west-1:000000000000:loadbalancer/app/ex-complete-alb/e4e280fd65bc4584’s Example tag, through the AWS CLI, never through choudoufu - the same mutation stage 5 uses) and “apply approved.tfplan” refused with “The approved plan no longer matches the live system” at exit 3, classifying the drift under “This apply would do, and the approved plan does not include:” and naming both module.alb.aws_lb.this[0] and the live arn:aws:elasticloadbalancing:eu-west-1:000000000000:loadbalancer/app/ex-complete-alb/e4e280fd65bc4584 it was computed against, with “Exit status 3” spelled out for a pipeline; nothing was applied - arn:aws:elasticloadbalancing:eu-west-1:000000000000:targetgroup/h193581408b940e3026b5904cb52/68fb62eb604a4071 still read InstanceTargetGroupTag=baz through elbv2 describe-tags, not from the absence of an “Apply complete!” line. Inverted control on the same run (the shape live/smoke/scenarios/apply-what-was-approved.sh reasons out): with the ALB’s tag put back and nothing else changed, the IDENTICAL file applied - 0 added, 1 changed, 0 destroyed - and arn:aws:elasticloadbalancing:eu-west-1:000000000000:targetgroup/h193581408b940e3026b5904cb52/68fb62eb604a4071 read back with InstanceTargetGroupTag=reviewed, so the refusal is earned by the drift and not handed out to every plan file. The edit was then reverted, re-applied and the estate replanned empty, so PART D starts where it would have. BREAK_APPROVAL=1 asserts stage 12’s own recorded Break line (apply the planfile after a mutation and expect success) and correctly fails |
| Greenfield apply | pass | 1m34s | 80 resources from nothing, matching stock’s own cold-deploy count; the ALB’s markers verified via the AWS CLI; 80 records in the local record store including untaggable types; replan empty; a representative EC2 instance’s own shape (type/ami) matches stock’s cold deploy, via the AWS CLI on both endpoints, marker tags never compared; 50 objects carry the estate tag |
| Strict profile (not a headline stage) | not run |
Last run at commit 0a3d554c04 on 2026-09-09T01:54:39Z, exit code 0, against emulator image ghcr.io/lex00/floci@sha256:d9207de14c919f4bfa50e956376cc441970f3679aabfdd43f3dbf4b779b20805. Total run time 7m15.2s.
Oracle: stock terraform 1.16.1, stock tofu 1.12.6 (matches the current pin).
Landed 2c654caa23 (2026-08-18), 80 resources. cold_deploy and migrate genuinely pass (48 of 80 eligible, 44 stamped - 4 failed to stamp on a real, filed floci gap, lex00/floci#65: ELBv2 DescribeListeners/DescribeRules drop AuthenticateCognitoConfig/AuthenticateOidcConfig entirely, so those 4 listener-rule sites can’t round-trip; 32 skipped - 28 untaggable by design, 4 unadmitted). test_plan blocked by #305 (3 sites) and a NEW one, filed as #309: aws_cognito_user_pool_client is unadmitted despite a parent-scoped ListUserPoolClients existing (via the already-admitted aws_cognito_user_pool parent) - a concrete lead for a fix, not attempted here. Three real floci gaps found, fixed, and merged to floci main (9ff82512, already reconciled into that history - not a pending PR): #58 (ACM wildcard-SAN DNS validation record kept a literal “.” in its name), #61 (S3 log-delivery-write canned ACL rejected), #62 (EC2 instance-type catalog missing t3.nano). One floci gap found and left open as real feature work: #63 (Cognito CreateUserPoolDomain entirely unimplemented), worked around with a documented delta. Deliberately did NOT re-pin live/floci-image here - bumping the pin without a matching floci-capabilities.json regen breaks TestFlociServiceCapability/TestFlociTypeCapability, and several other crossings tonight need the same regen, so this was left for a single centralized reconciliation rather than each crossing re-pinning independently and colliding. Verified twice via FLOCI_IMAGE override pointed at the real published digest (sha256:217f859688b5…) instead. Follow-up pass 2026-08-18 (#313 cross-check, d7e44cbfda/29095a37b7): the committed run.sh still asserted pre-#305 counts (48/80 eligible, 44 stamped, 4 unadmitted-type including the default_ trio) and failed before ever reaching stage 3; updated to the real current numbers - 51 eligible, 47 stamped, #309 (aws_cognito_user_pool_client) now the SOLE unadmitted-type/test_plan blocker, #305’s trio fully resolved - re-verified against real floci twice plus a BREAK=1 negative control. #313’s diagnostic does not appear anywhere in the output despite this estate also declaring data.aws_availability_zones feeding module.vpc; confirmed not the same wall. Commented on #313 and #309. RE-VERIFIED 2026-08-21 (this session, worktree live/reverify-limitations, floci pinned at the current e61a987/d65baf42 image): stages 1-2 unchanged byte-for-byte (80 cold-deployed; dry run 51/80 eligible, 41 VERIFIED + 10 DRIFTED, 29 skipped; -approve 47 stamped, 1 recorded, 4 failed on the still-open lex00/floci#65, 28 skipped). Stage 3’s underlying wall is UNCHANGED in substance - still exactly one site, aws_cognito_user_pool_client.this, still #309, still label 3 - but its diagnostic TEXT has moved: commit 5444949ceb (2026-08-19 22:57, “widen the markerless veto to a mixed primary identifier”, #309’s own chain) added aws_cognito_user_pool_client to identity.MarkerlessTypes, which reclassifies the refusal from RuleUnadmittedType (“Resource type is outside the live-markers subset”) to the more specific RuleMarkerlessType (“Resource type has nowhere to write an ownership marker”) - same site, same practical block, different rule ID and error string. That commit landed after run.sh (c926095b49) was last edited, so run.sh’s own hard-coded stage-3 assertion (grep for the old RuleUnadmittedType text) no longer matches and the script now hard-fails at its own assertion check (“expected 1 unadmitted-type sites (#309), got 0”) before ever reaching its SUMMARY block, rather than reaching a clean itemized stage-3 refusal. Stage 2’s live-import output still labels the same site UNADMITTED_TYPE, so stage 2 and stage 3 now use inconsistent labels for the identical site - a real, if narrow, drift worth fixing when someone next touches this script (update the grep target to RuleMarkerlessType’s text), not attempted here per this session’s verification-only scope. Separately confirmed: run.sh’s header (lines 92-131) still does not state the maintainer’s 2026-08-21 “Label 3, OpenTofu was never asked this question, stop here” ruling in as many words - HANDOFF.md’s “What to do next” item 2 documentation task is still open. #337 (composite-identity route, 18 types) is confirmed NOT implicated - the responsible commit is on the #309 chain itself, predating #337. #331/#340/#343/#344 did not touch this estate.
Reproduce it#
go run ./tools/gauntlet run corpus-alb-completeNeeds Docker (the emulator is pulled at the pinned digest), the AWS CLI, and a
stock terraform or tofu binary on PATH for the cold deploy. The script is
live/e2e/corpus-alb-complete/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.