terraform-provider-llmgw/ is a module of its own, built on terraform-plugin-framework, so the gateway does not carry its dependencies. llmgw_model is a model added at runtime: scaling, class, owner and adapters change in place, anything else replaces it; the definition is kept as written rather than as the catalog filled it in. llmgw_quota and llmgw_guard_attachment keep the scope as written beside the ID it resolved to, and import by that ID. Data sources llmgw_models and llmgw_nodes. The client lists every endpoint it calls and refuses any other; a test checks each, and every field it sends and reads, against docs/admin-api.openapi.json, so a change to the admin API that breaks the provider fails here. Acceptance tests run Terraform against an admin API in memory when TF_ACC is set. CI vets and tests the module. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
3.5 KiB
C19: OpenAPI for the admin API, and a Terraform provider
CHANGELOG
The admin API described, and a Terraform provider
- The admin API in OpenAPI 3.1. Every endpoint under
/api/v1, its path and query parameters, and the schema of each request and answer with the meaning of each field, in docs/admin-api.openapi.json and served by every control plane atGET /api/v1/openapi.json(with the token, like the rest). The bearer token, theX-Llmgw-Actorheader and the error shape are in it. Generated bygo generate ./internal/docgenfrom a route table ininternal/control/adminapi/routes.goand the Go types the handlers use, with the fields' comments as descriptions; a test fails when an endpoint is served and not in the table, another when the committed document is stale. Checked with openapi-spec-validator and openapi-generator-cli's validate. - docs/admin-api.md: how to reach the API (loopback,
an SSH tunnel,
admin_remote), the token at/var/lib/llmgw/admin-token, what a request must carry — an address inHost,application/jsonon every change, even aDELETE— and some calls withcurl. GET /api/v1/models/{name}: one model's whole definition as it is in force, with the defaults the catalog filled in. The listing has only its state and its profiles' names.- A Terraform provider, in
terraform-provider-llmgw/, a module of its own so the gateway does not carry its dependencies.llmgw_model(a model added at runtime: scaling, class, owner and adapters change in place, the rest replaces it),llmgw_quota,llmgw_guard_attachment, and thellmgw_modelsandllmgw_nodesdata sources. Its client is checked against the OpenAPI document, endpoint by endpoint and field by field, so a change to the admin API that breaks it fails its tests; CI runs them. Built and installed by hand for now: see its README.
BACKLOG
Done (branch features-c2): adminapi.Routes and
internal/docgen/openapi generating docs/admin-api.openapi.json (3.1,
bearer security, every route, query parameters, schemas from the Go types
with their comments), served embedded at GET /api/v1/openapi.json, kept true
by a route-table test and a staleness test; validated with
openapi-spec-validator and openapi-generator-cli. GET /api/v1/models/{name}.
docs/admin-api.md. terraform-provider-llmgw/ (terraform-plugin-framework):
llmgw_model, llmgw_quota, llmgw_guard_attachment, data sources
llmgw_models and llmgw_nodes; client tests against a fake admin API and
the OpenAPI document; acceptance tests with Terraform and OpenTofu against the
fake; one apply, empty plan, in-place change and destroy against a real
llmgw server run by hand. Left: organizations, teams, projects, people and
keys are not in the admin API (they are llmgw org/users/keys against the
store), so not in the provider; a model's definition beyond scaling, class,
owner and adapters is changed by replacing it (the API has no edit, and
resources is not used yet); canary, slo, scaling.schedule and
prewarm, guard role and origin are not in llmgw_model; models cannot be
imported; the provider is not published to a registry and has no release
build; the acceptance tests are not run in CI (they need Terraform); the
document's schemas mark nothing required and give no enums; catalog.SLO's
fields have no JSON tags, so they are TTFTp99 and TPOTp99 on the wire.