Skip to content

fix: a list resource is the list capability of a resource - #99

Merged
ShocOne merged 1 commit into
mainfrom
feat/list-resource-needs-a-resource
Aug 14, 2026
Merged

fix: a list resource is the list capability of a resource#99
ShocOne merged 1 commit into
mainfrom
feat/list-resource-needs-a-resource

Conversation

@ShocOne

@ShocOne ShocOne commented Aug 14, 2026

Copy link
Copy Markdown
Member

Phase 1 of making the generated providers' own tests pass. Two defects, both of
which cost the entire provider, and neither of which postcheck can see —
it builds the tree and never loads it.

1. Every emitted list resource was unmatched

HashiCorp's documentation:

"When implementing list there needs to be a corresponding resource
implementation since the results that are returned rely on the pre-existing
definitions of the resource's identity and schema."

fwserver/server_listresources.go:98 enforces it as resourceFuncs[typeName],
and an unmatched list resource fails the provider's whole schema load:

Error: Failed to load plugin schemas
  Could not load the schema for provider …/github: 134 problems:
  - ListResource Type Defined without a Matching Managed Resource Type

classify.go:183 made a match impossible — resourceOK needs an item GET,
listOnlyShape needs its absence — so the resource/list-resource overlap was
exactly zero on all three pilots. The entities concerned are read-only
collections: GET /app, GET /v1/activation-code/history,
GET /v1/api-role-privileges. None is manageable.

Now: an entity that is enumerable but not addressable is a datasource.
The list capability belongs to a resource the API can enumerate, sharing its
terraform type — which Names already give, being computed per entity before
the kind dispatch.

And the other direction: a resource the bindings or emission refuse takes its
list resource with it. Six github list resources were left unmatched that way
even after reclassification — orgs_actions_runner_group among them, whose
resource is refused for a path parameter the SDK types differently. The pair is
what terraform requires, so the pair goes together.

2. Terraform reserves seven root attribute names

A second, independent whole-provider failure:

"count" is a reserved root attribute/block name        (jamfpro)
"connection" is a reserved root attribute/block name   (thousandeyes)

fwschema.ReservedResourceAttributeNames is connection, count,
depends_on, for_each, lifecycle, provider, provisioner — applied by
resource/schema, datasource/schema, action/schema and
list/schema alike, at root depth only.

A root attribute so named is refused, with the remedy named in the reason: a
correction renaming the property. Refused rather than renamed here, because the
name is what a practitioner writes and choosing another belongs in a correction,
not in a rule. Nested, the same name is an ordinary field and stands.

Measured effect

Zero schema-load failures on all three pilots, from 712 / 530 / 258.

pilot resources datasources list resources actions
github 77 160 → 417 257 → 40 80
jamfpro 81 104 → 245 141 → 44 104
thousandeyes 35 60 → 102 42 → 34 51

440 invalid list resources → 118 valid ones. No entity was lost: they moved
kind. provider verify reports no drift (4390 / 3895 / 1798 files) and
postcheck passes.

Refusals: github 817 → 834 (+17: six list resources whose resource is not
served, plus reclassification), jamfpro 290 → 255 (−35), thousandeyes 343 → 361
(+18). Four count refusals on jamfpro and one connection on thousandeyes are
the reserved names.

Naming

docs/glossary.md: list resource is redefined as "the list capability of a
managed resource", with the old meaning recorded as retired the way the v1
vocabulary is, and a resource identity schema entry added for Phase 2.

Tests

Toolkit tests were updated to the new semantics, not weakened — several asserted
the old taxonomy directly. New: TestUnit_ListResource_GoesWithTheResourceItLists,
TestUnit_Attribute_RefusesAReservedRootName, TestDerive_ListOnlyEntityIsADatasource,
and two classification cases pinning that neither half of the pair fires alone.
The IR fixture's count attribute was renamed rather than special-cased, so the
tests that use it keep their meaning.

Gates

  • gofmt, repo_hygiene_gate.sh, golangci-lint (0 issues), go build/go vet — pass
  • go test ./... — pass, coverage 91.5% total
  • provider generate + provider verify on all three — no drift

Still to come

Phase 2 emits the resource identity schema — NewListResult dereferences it, so
real terraform list would still panic. Phase 3 makes fixtures respect format.

🤖 Generated with Claude Code

Terraform matches a list resource to a managed resource by type name and
refuses to load a provider whose list resource names none — refusing the
whole provider, not the one entity. Every list resource emitted was
unmatched: classification produced one exactly where an entity had a
collection GET and no item GET, which is the shape no resource can have.

So the entity that is enumerable but not addressable is a datasource,
and the list capability belongs to a resource the API can enumerate. A
resource the bindings or emission refuse takes its list resource with
it, because the pair is what terraform requires.

Terraform also reserves seven names at the root of a schema, and rejects
a provider declaring one. A root attribute so named is refused with the
remedy named: a correction renaming the property. Nested, the same name
is an ordinary field and stands.

Neither defect is visible to postcheck, which builds the tree and never
loads it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ShocOne
ShocOne merged commit 2d40846 into main Aug 14, 2026
2 checks passed
@ShocOne
ShocOne deleted the feat/list-resource-needs-a-resource branch August 14, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant