Skip to content

wafv2: generate web_acl and rule_group from depth-limited schemas - #3227

Draft
arditti wants to merge 3 commits into
hashicorp:mainfrom
arditti:b-wafv2-derecursed-schemas
Draft

wafv2: generate web_acl and rule_group from depth-limited schemas#3227
arditti wants to merge 3 commits into
hashicorp:mainfrom
arditti:b-wafv2-derecursed-schemas

Conversation

@arditti

@arditti arditti commented Jul 7, 2026

Copy link
Copy Markdown

Community Note

  • Please vote on this pull request by adding a 👍 reaction to the original pull request comment to help the community and maintainers prioritize this request
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for pull request followers and do not help prioritize the request
  • The resources and data sources in this provider are generated from the CloudFormation schema, so they can only support the actions that the underlying schema supports. For this reason submitted bugs should be limited to defects in the generation and runtime code of the provider. Customizing behavior of the resource, or noting a gap in behavior are not valid bugs and should be submitted as enhancements to AWS via the CloudFormation Open Coverage Roadmap.

Relates #95

Rollback Plan

If a change needs to be reverted, we will publish an updated version of the library.

Changes to Security Controls

Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.

No.

Description

Unsuppresses awscc_wafv2_web_acl and awscc_wafv2_rule_group (resource + singular data source) by generating them from depth-limited (de-recursed) local schemas, sidestepping the "Recursive Attribute Definitions" blocker (#95).

Approach

Terraform's type system cannot model recursive attribute definitions, but it can model a fixed nesting depth — the same trade-off terraform-provider-aws makes in its hand-written aws_wafv2_web_acl/aws_wafv2_rule_group schemas. This PR applies it to the generated provider:

  • A new deterministic, idempotent script, tools/derecurse-schema.py, rewrites a CloudFormation schema in place:
    • Recursion cycles among definitions are detected generically as strongly-connected components of the $ref graph (no WAFv2 names hardcoded) — for WebACL the cycle is {AndStatement, ManagedRuleGroupStatement, NotStatement, OrStatement, RateBasedStatement, Statement}.
    • Cycle members are unrolled to a configurable depth (default 3) by cloning definitions with depth suffixes (Statement, StatementLevel2, StatementLevel3).
    • At the deepest level, optional properties that would re-enter the cycle are dropped, and definitions whose required properties would re-enter it (e.g. a level-3 AndStatement) are pruned entirely. The result: a level-3 Statement still offers RateBasedStatement/ManagedRuleGroupStatement (without their ScopeDownStatement) but not AndStatement/OrStatement/NotStatement — mirroring terraform-provider-aws.
    • As a preliminary normalization it also inlines pure alias definitions (AddressField/PhoneNumberFieldFieldIdentifier), which otherwise trip the generator's array-item typing (Resource Suppression: list of undefined is not supported #1515).
  • The de-recursed AWS_WAFv2_WebACL.json / AWS_WAFv2_RuleGroup.json are committed, pinned via internal/update/suppressions_checkout.txt, and annotated in all_schemas.hcl following the aws_lex_bot / aws_emrserverless_application precedent (pinned local schema + note). After a future schema refresh, re-running the script on the fresh schema re-applies the unroll.

Limitation

The unroll depth is 3 levels of Statement, which supports 2 chained logical statements (And/Or/Not) above a match statement — e.g. AndStatement → NotStatement → ByteMatchStatement. For precision: this is one logical level fewer than terraform-provider-aws's hand-written schema, whose level constant of 3 yields 3 chained logical statements. Unrolling to --depth 4 closes that gap in the schema, but the generated Go currently fails to compile at that depth — web_acl_resource_gen.go grows to ~103 MB and the Go compiler hits a per-function limit (internal compiler error: NewBulk too big) because the whole schema is emitted as one function literal. Matching the extra level therefore needs generator changes (deduplicating repeated nested-attribute subtrees) rather than a bigger --depth; I intend to propose that separately.

Behavior at the boundary (verified against a live web ACL nested beyond the schema depth):

  • Config nested deeper than the schema cannot be expressed; the attributes don't exist at that depth.
  • Import of a deeper-nested web ACL succeeds but silently omits the deeper statement levels from state (the runtime's existing skip-unknown-property path in internal/generic/translate.go); the remote resource is not modified, and subsequent plans report no changes.
  • Updating an imported rule whose statements were truncated can produce an incorrect JSON Patch against the remote resource. The new docs pages call this out; making truncation loud (a warning diagnostic on read/import) is a natural follow-up I'm happy to include here or separately.

Deeper nesting can be regained at any time by re-running the script with --depth N and regenerating, once the generated-code size is addressed.

Generated file sizes

The unrolled schema inflates the generated code (expected, called out up front): web_acl_resource_gen.go is ~19.6 MB, rule_group_resource_gen.go ~9.1 MB, singular data sources ~13.3 MB / ~6.1 MB, and the four docs pages total ~11 MB. They compile cleanly (the wafv2 package takes ~2 minutes to build). If that size is unacceptable, the same script/pinning mechanism works at --depth 2, which roughly halves it.

Verification

Gates (all pass):

go build ./...                                          # OK
gofmt -l internal/aws/wafv2/                            # clean
go vet ./internal/aws/wafv2/...                         # OK
go test ./internal/aws/wafv2/... ./internal/provider/...# ok (all pass)
golangci-lint run ./internal/aws/wafv2/...              # 0 issues
make docs                                               # only the 4 new wafv2 pages produced

Acceptance tests (us-east-1):

--- PASS: TestAccAWSWAFv2WebACL_basic (1.16s)
--- PASS: TestAccAWSWAFv2RuleGroup_basic (1.16s)

(The generated *DataSource_NonExistent tests fail identically for the pre-existing awscc_wafv2_ip_set, because WAFv2's Cloud Control identifier is composite Name|Id|Scope and the API returns ValidationException rather than not-found — a pre-existing pattern, not introduced here.)

End-to-end with a locally built provider (real AWS, us-east-1, REGIONAL): created a awscc_wafv2_web_acl with a rule using and_statement → [geo_match_statement, not_statementgeo_match_statement] and a awscc_wafv2_rule_group with or_statement → [geo_match_statement, not_statementgeo_match_statement]; read the web ACL back through the new singular data source; re-plan showed no drift; destroyed cleanly.

The de-recursed schemas allow 3 levels of Statement nesting, i.e. 2 chained
logical statements (And/Or/Not) above a match statement - one logical level
fewer than terraform-provider-aws's hand-written WAFv2 schemas. Unrolling to
--depth 4 currently produces generated files (~103 MB for the web ACL
resource) that exceed a Go per-function compiler limit (internal compiler
error: NewBulk too big), so matching that extra level needs generator
changes first.

Statements nested deeper than the schema are silently omitted from Terraform
state on import/read; the remote resource itself is not modified. Document
both behaviors in the resource docs (via new website templates) and in the
all_schemas.hcl comments.
…k updates that would modify them

Depth-limited (de-recursed) schemas cannot represent statements nested
beyond their maximum depth. Previously such properties were silently
omitted from state on read/import (Info-level log only), plans reported
no changes, and an update touching a truncated rule could corrupt the
remote resource, since the patch is computed from truncated state.

- tools/derecurse-schema.py records the property names it prunes at the
  depth boundary under an x-derecursed key in the rewritten schema.
- The resource generator plumbs them into the resource via a new
  WithUnrepresentableProperties option.
- Read collects properties skipped during Cloud Control-to-Terraform
  translation and emits a warning diagnostic when any declared-
  unrepresentable property was dropped, listing the exact locations.
  Skips of undeclared properties (schema newer than the provider) stay
  log-only, as before.
- Update walks the remote resource model, locates content the schema
  cannot represent, and fails with an actionable error when the patch
  would modify it. Updates elsewhere in the resource proceed normally.

Verified against a live web ACL nested one logical level beyond the
schema: import now warns with the exact dropped path; a structural edit
inside the truncated rule is rejected (previously it silently inserted
a statement into the remote rule); edits to other properties succeed;
within-depth web ACLs see no warning and no behavior change.
@arditti

arditti commented Jul 21, 2026

Copy link
Copy Markdown
Author

Pushed a follow-up commit that makes the depth limitation loud instead of silent:

  • Import/read of a web ACL nested beyond the schema depth now emits a warning diagnostic listing exactly which properties were omitted from state (the de-recurse script records what it prunes under an x-derecursed key in the rewritten schema, and the generator plumbs that into the resource). Skips of properties not on that list — e.g. a schema newer than the provider — stay log-only as before, so no other resource's behavior changes.
  • Updates that would modify a part of the resource containing unrepresentable statements now fail with an actionable error instead of applying a patch computed from truncated state (which could silently corrupt the remote rule — verified against a live web ACL before this change). Updates elsewhere in the resource proceed normally.

Verified live: importing a 3-logical-level web ACL warns with the exact dropped path; a structural edit inside the truncated rule is rejected with the remote resource untouched; description/tag edits succeed; within-depth web ACLs see no warning and no behavior change. Docs updated to describe both behaviors.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant