terraform-aws-modules/terraform-aws-s3-bucket examples/complete (tag v5.9.1), its flagship example

Source: https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git at v5.9.1.

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

Not clear yet.

StageVerdictDetail
Cold deploypass30 resources added by plain terraform, 4 buckets confirmed live, no tofu-address tag
Migratepass6 of 30 stamped, 1 recorded (random_pet, issue #340), 23 skipped (untaggable), 0 failed, 26 identities recorded (#364 unit A2); markers survived the residue-classification apply
Replan from nothingpassno resource action proposed; 29 rendered identity occurrences (11 distinct), all naming known roots
No-op applypassno-op apply (0 added, 0 changed, 0 destroyed); bucket count unchanged at 4
Drift and reconvergepassaccelerate config drifted to Enabled, exactly 1 change proposed and applied, reconverged to Suspended, final plan empty
Renamepassmoved block: module.cloudfront_log_bucket renamed to module.cloudfront_log_bucket_renamed with zero churn (0 add, 1 change, 0 destroy), the bucket’s tofu-address marker rewritten in place; live-mv: module.simple_bucket renamed to module.simple_bucket_renamed with zero churn, marker rewritten in place; both live bucket names unchanged, read via the AWS CLI; the post-rename plan proposes no resource action
Remove a blockpasschoudoufu: deleting module.simple_bucket_renamed’s block proposed exactly two destroys (0 add, 0 change, 2 destroy: the bucket and its untaggable public_access_block child), applied cleanly (0 added, 0 changed, 2 destroyed), the bucket is genuinely gone from the live account (head-bucket on simple-welcomed-gnu now fails, read via the AWS CLI, not choudoufu’s own report), and the next plan proposes no resource action; stock oracle on cold_deploy’s own state also proposes exactly the same two destroys for the same two objects; the target was chosen to avoid issue #404’s shape (a sibling policy re-reading the removed bucket’s own ARN) - module.log_bucket and module.s3_bucket are both left untouched
Change count (planned)not run
Replace with create_before_destroynot run
Crash between create and destroy (planned)not run
Teardown (planned)not run
Plan, review, apply (planned)not run
Greenfield applypass29 resources from nothing (SCOPE REDUCTION’s own reduced count, random_pet pinned to a literal on both sides), 3 of 4 bucket markers verified via the AWS CLI, 26 records in the local record store (#364 A2), replan empty, stock oracle in its own namespace matches structurally on all 4 buckets (versioning, default encryption, policy presence)
Strict profile (planned)not run

Last run at commit fa5a6bd3bd on 2026-08-25T17:34:52Z, exit code 0.

Landed 1398574b03 (2026-08-18); re-verified for real 2026-08-18 in an isolated worktree against local main bcf78bacbd, full pipeline reproduced clean twice in a row. #306 is fixed (lex00/floci S3ControlController.tagResource now reads-merges-writes; reconciled into the currently-pinned floci image; internal/live/lifecycle/marker_tag_merge_live_test.go’s TestMarkerSurvivesIncrementalTagUpdate passes live against it). DELTA 6 (the tags-removal workaround) reverted - module.s3_bucket’s real tags = { Owner = “Anton” } is back, unmodified, and the stage 2c residue-classification apply leaves all four buckets’ markers intact with it restored. DELTA 5 (expected_bucket_owner) is UNCHANGED, for a reason independent of #306: it is ForceNew on most of these sub-resource types with no live representation at all (a request-header assertion, not a stored property), so a discovery-rebuilt prior would force a real replace on the very first onboarding apply - the same shape as DELTA 3’s random_pet. The prior investigation’s other open question - module.s3_bucket’s canned acl and website.routing_rules - was checked for real rather than assumed fixed, and DOES still block test_plan after #306’s fix, confirmed reproducibly. Traced past “the provider’s Read() needs a genuinely-remembered prior” (the prior investigation’s framing, which attributed this to #306 too - a conflation this session’s trace evidence rules out, since the mechanism is unrelated to tags/floci entirely): issue #275’s residue mechanism (internal/live/projection/residue.go) DOES run here and DOES try to classify acl - TF_LOG=trace on the stage 2c apply shows ‘residue candidate “acl”: readA=cty.StringVal(“private”) readB=cty.StringVal(“private”) applied=cty.StringVal(“private”)’, and classifyResidue’s own documented rule reads that as “the provider answers this from the remote”, so nothing is recorded as residue. Yet the SAME attribute, read moments later by internal/live/projection/build.go’s importAndRead (the function stage 3’s plan actually uses), comes back empty and proposes ‘+ acl = “private”’. The two reads disagree because their prior is built two different ways: residue.go’s identityOnly nulls every non-identity attribute outright, while importAndRead’s prior is whatever provider.ImportResourceState() returned - a zero-value stub (acl = “”, not null) for this SDKv2 resource. The provider’s Read() treats an explicit null differently from an SDKv2 zero-value string for this attribute; reconciling the two prior constructions is real work but importAndRead is the read path every projected resource in this fork goes through, so a fix needs corpus-wide validation, not a one-estate patch - out of scope here. SCOPED OUT instead (module.s3_bucket’s acl and website inputs removed, applied to BOTH the cold-deploy and choudoufu copies so the crossing tests one genuinely-reduced estate throughout, not choudoufu silently abandoning management of something the cold deploy still created) - same discipline corpus-sumaform-aws uses for its own two structural refusals. Estate is now 30 instances across 14 aws_s3_bucket_* types (down from 32/15; aws_s3_bucket_acl survives elsewhere via module.cloudfront_log_bucket’s own grant/owner-driven ACL). Also hit and fixed IN THIS SCRIPT (no choudoufu/floci change needed): THE OUTPUTS QUIRK already documented in corpus-iam-read-only-policy and corpus-root-dns-zones - module.s3_bucket’s outputs, re-exported at root, have no prior baseline across a stateless live-plan, so every run shows a permanent ‘Changes to Outputs’ section and OpenTofu’s renderer never prints a ‘Plan: N to add…’ line while that is true, empty of resource changes or not. Stage 3/5’s empty-plan assertions now check for the absence of a resource action header (‘will be (created|updated|destroyed)’) instead of grepping for ‘No changes.’/‘Plan: 0 to add…’, matching those two scripts’ own pattern; this was never exercised before since stage 3 had never previously reached an actually-empty-of-resource-changes plan. Also fixed the rendered-import-identity count check (N_IDS), which had silently never been exercised either: several of module.s3_bucket’s own sub-resource types (ownership_controls, versioning, policy, logging, cors_configuration, lifecycle_configuration, object_lock_configuration, public_access_block, accelerate_configuration, request_payment_configuration) import by the bucket’s own id alone, so after sort -u only 10 distinct strings remain for 30 instances - the raw (pre-dedup) occurrence count (29) is what actually approximates “one per instance” and is now what the sanity floor checks; the deduplicated set is still what ids_all_name_known_roots validates.

Reproduce it#

go run ./tools/gauntlet run corpus-s3-bucket-complete

Needs 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-s3-bucket-complete/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.