feat: generate actions, and verify every kind's bindings against the SDK - #38
Merged
Conversation
Phase 5.7. Actions are the third most-used block kind in terraform-provider-microsoft365 -- 43 of them against 4 list resources -- and the simplest, because an action has nothing to reconcile. Three files per action against a resource's five: no construct.go, because an action sends its arguments as call parameters rather than a request body, and no state.go, because it writes nothing back. The pilot gains thousandeyes_disable_endpoint_agent, backed by the pinned SDK's DisableEndpointAgent. Two of my earlier per-kind rules were wrong, and generating a real action is what showed it. BlockKind.Expands returned true for an action, on the reasoning that an action sends values to the API. It does -- as call arguments -- but the emitter generates no construct function for one, so there is no body for an expand to build into. Expands is now resource-only, and an action that genuinely POSTs a payload is a deliberate extension rather than something this quietly claimed to support. The flatten requirement was universal: "every kind reads". An action does not. InvokeResponse has no field to carry a result, so a flatten conversion would convert into somewhere that does not exist -- and requiring one made the first action unrepresentable. There is now a Flattens predicate, and a flatten on an action attribute is refused rather than ignored. Three silent-drop bugs found and fixed: LoadDir merged Resources and DataSources and not Actions, so every action in a split blueprint vanished with no error at all. Found because the first emit wrote nothing. internal/sdkbind walked bp.Resources and nothing else. Data source bindings have been unverified since phase 5.2 and list facets since 5.6, so three quarters of the bindings in a blueprint were unchecked while the command reported a reassuring count -- it even said "resource binding set(s)" while doing it. The accessor check is extracted, the operation check takes a key rather than a resource, and data sources, list facets and actions each get a verifier. A list facet's element type is checked too, because identityFrom and displayNameFrom name fields on one element and a typo there is a compile error in somebody else's provider. 67 bindings now verified where the count was previously partial; confirmed it bites by renaming the action's SDK method and watching it fail with a suggestion. accessorChain required the receiver to be literally "r", which is why the widened checks first reported every non-resource accessor as unverifiable rather than verified. The receiver name is whatever the generated method uses, so it is skipped rather than checked; the field chain from client onwards is the part that is resolved, and that is where a wrong accessor is actually caught. interop export said nothing about actions, so an entire block kind vanished from the exported document in silence. Reported once for the kind rather than per action, since the loss is that the format has no such concept. client.ForAction is added to the hand-written support package: action.ConfigureRequest is its own type, so the resource helper cannot serve it, and the three request types share no interface to write one generic helper against.
te is the import alias the pilot's hand-written client package gives the SDK (te "github.com/deploymenttheory/go-sdk-thousandeyes/thousandeyes"). codespell reads it as a run-together "the", "be", "we" or "to". Surfaced because this branch edits client.go to add ForAction, and super-linter reads changed files only -- the alias has been there since the pilot existed. Added to ignore-words-list rather than skipping the whole pilot directory: the generated output there is not worth spell-checking, but the hand-written support packages are ours and their prose is. Swept the rest of the tree under the same exclusions CI applies; nothing else outstanding.
ShocOne
added a commit
that referenced
this pull request
Jul 30, 2026
feat: generate actions, and verify every kind's bindings against the SDK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 5.7 of the phase 5 plan. Base is
main.Actions are the third most-used block kind in the reference provider — 43 of them against 4 list resources — and the simplest, because an action has nothing to reconcile.
verifyreports 21/21 with no orphans, up from 17, and the provider now satisfiesProviderWithActions.Three files per action against a resource's five: no
construct.go, because an action sends its arguments as call parameters rather than a request body, and nostate.go, because it writes nothing back. The pilot gainsthousandeyes_disable_endpoint_agent, backed by the pinned SDK's realDisableEndpointAgent.Generating a real action showed two of my earlier rules were wrong
Expands()returned true for an action. The reasoning was "an action sends values to the API" — it does, but as call arguments, and the emitter generates no construct function for one, so there is no body for an expand to build into. Now resource-only.InvokeResponsehas no field to carry a result, so a flatten would convert into somewhere that does not exist. Requiring one made the first action unrepresentable. There is now aFlattens()predicate, and a flatten on an action attribute is refused rather than ignored.Three silent-drop bugs
LoadDirmergedResourcesandDataSourcesand notActions. Every action in a split blueprint vanished with no error whatsoever. Found only because the first emit wrote nothing.internal/sdkbindwalkedbp.Resourcesand nothing else. Data source bindings have been unverified since 5.2 and list facets since 5.6 — three quarters of the bindings in a blueprint were unchecked, while the command printed a reassuring count and even said "1 resource binding set(s)" while doing it. The accessor check is extracted,verifyOperationtakes a key rather than a resource, and data sources, list facets and actions each get a verifier. A list facet's element type is checked too, sinceidentityFromanddisplayNameFromname fields on one element and a typo there is a compile error in someone else's provider. 67 bindings now verified. I confirmed it bites by renaming the action's SDK method:interop exportsaid nothing about actions, so an entire block kind vanished from the exported document in silence — the one failure mode the note mechanism exists to prevent. Reported once for the kind rather than per action, since the loss is that the format has no such concept.There was also a fourth, smaller one:
accessorChainrequired the receiver to be literally"r", which is why the widened checks first reported every non-resource accessor as "cannot be verified" rather than verifying it. The receiver name is whatever the generated method uses (d,l,a), so it is skipped rather than checked; the field chain fromclientonwards is the part that resolves, and that is where a wrong accessor is actually caught.Support addition
client.ForAction, becauseaction.ConfigureRequestis its own type — the resource helper cannot serve it, and the three request types share no interface to write one generic helper against. The doc comment says so rather than leaving the duplication looking accidental.Verification
go test ./...clean;golangci-lint run0 issues;gofumptclean (viago run, as CI does).verify21/21 no orphans; pilot builds and vets;bindings67/67 against the pinned SDK;probe -mode verify39/39;mergeidempotent.interfaces.goin the pilot assertsProviderWithActionsalongside the other two, so "the generated registries are what make this work" stays a build failure if it stops being true.internal/blueprint/action_test.go: theExpands/Flattenscorrection stated directly, a refused flatten, six action refusals, and the alias-namespace collision across kinds.Note on the interruption
The full suite reported
internal/sdkbindfailing after 607s partway through. That turned out to be a corrupted local Go build cache, not the action — sdkbind passes in ~50s once cleared. Worth saying because it briefly looked like the action's binding was at fault, and it was not.5.4b is still tracked
Not forgotten, and I want to correct something I said about it: I framed it as needing a fully curated second pilot resource, which over-stated the cost. 5.3b already established a cheaper pattern — ingest a resource from the committed spec, graft the pilot provider block on, emit, and compile it in a scratch directory. That gives genuine end-to-end coverage without committing a second pilot resource. So 5.4b is more tractable than I implied; the remaining work is the IR constraint fields, ingest extraction, render mapping, and
regexp.MustCompilevars forRegexMatches.🤖 Generated with Claude Code