Repository navigation
feat: generated API clients and facade with retries, RMW PUT and pagination - #10
Open
RogerCecilpot wants to merge 3 commits into
Open
RogerCecilpot wants to merge 3 commits into
RogerCecilpot wants to merge 3 commits into
Conversation
RogerCecilpot
force-pushed
the
feat/236-api-clients
branch
from
October 25, 2026 12:56
23d651b to
2b30855
Compare
RogerCecilpot
changed the base branch from
feat/246-output-errors-exit-codes
to
feat/260-scoop-deb-rpm
October 25, 2026 12:57
hack/specprune turns the platform's private OpenAPI specs into public copies under internal/api/specs, driven by a reviewed allowlist: only the operations the tier 1 and 2 commands use and the components they reference are kept; servers, security requirements and every x- extension are removed; game-server fields are dropped; descriptions that name internal services are rewritten. It refuses to write anything while a description matches a deny pattern or a property name looks internal and has not been reviewed, so #237's automation can run it unattended. hack/vendor-specs.sh (make vendor-specs) fetches the allowlisted files at a pinned ref; the vendored specs record their source commit. Clients are generated with oapi-codegen v2.5.1, the platform's version, into internal/api/gen/<service> (make generate). CI fails if generated code is out of date. Refs devlyft/devlyft#236 Co-Authored-By: Claude Opus 5.5 <[email protected]>
internal/api.Client is every call the tier 1 and 2 commands make, over the generated clients, with a gomock mock in internal/api/mock. - GET, PUT and DELETE retry on 429, 5xx (not 501) and connection errors with exponential backoff and full jitter, honouring Retry-After in seconds or as a date; POST is never retried. Attempts and total wait are capped, cancellation stops retries, and the final error states the attempt count. - The bearer token is asked for on every attempt, so refreshing token sources plug in; tokens are only sent over HTTPS or to loopback. - Non-2xx responses map to clierr errors with the server's message and request ID (X-Request-Id header or body field). - UpdateDeploy reads the deploy, applies a mutation to a full update request and writes it back, sending If-Match when the GET returns an ETag. - Paged collects from limit/offset endpoints; Trim limits the rest. - Each exchange is debug-logged through internal/redact. - EnvVarItem and SecretRefItem build env entries with the right kind: the spec's discriminator has no mapping, so the generated helpers use schema names as the kind, which the API rejects. Contract tests validate every request and response against the vendored specs with kin-openapi and check every vendored operation is used. Refs devlyft/devlyft#236 Co-Authored-By: Claude Opus 5.5 <[email protected]>
RogerCecilpot
force-pushed
the
feat/260-scoop-deb-rpm
branch
from
October 25, 2026 13:00
5f71f7b to
7d3c88e
Compare
RogerCecilpot
force-pushed
the
feat/236-api-clients
branch
from
October 25, 2026 13:00
2b30855 to
87da037
Compare
oapi-codegen v2.5.1 does not build against newer kin-openapi or oasdiff/yaml, so Dependabot must not bump them on their own. Refs devlyft/devlyft#236 Co-Authored-By: Claude Opus 5.5 <[email protected]>
RogerCecilpot
force-pushed
the
feat/236-api-clients
branch
from
October 25, 2026 13:32
87da037 to
5478dce
Compare
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.
Closes devlyft/devlyft#236. Stacked on #5 (which is on #2); retarget to
mainonce those merge.Important
Review the vendored specs before merging — this repo is public. Everything under
internal/api/specs/comes from the private platform repo. What is published is decided byinternal/api/specs/allowlist.yaml; please review that file and the four specs, not just the code.Source:
devlyft/devlyft@bef84e3b430ac77c34dab97cd1c94d976cbdb49d:cmd/apps/{deploys,org,logs,metrics}-api/api/openapi/api.yaml. pods-api is not used (its endpoints are SPIFFE-only); pods are read through org-api.What
hack/specprune+allowlist.yaml: keeps only allowlisted operations and the components they reference. It stripsservers,securityand everyx-extension, drops listed fields, and rewrites listed descriptions. It refuses to write anything while a description matches a deny pattern (SPIFFE, internal service names, Agones/fleet/game server, cluster.local/k8s/vault, …) or a property name looks internal (cluster|node|host|ip|tenant|…) without being dropped or reviewed. That makes it safe for #237 to run unattended.make vendor-specs [REF=<sha>]fetches the allowlisted files withghand re-runs it.internal/api/gen/<service>. Generated withmake generate; CI's lint job fails if generated code or mocks are stale. kin-openapi and oasdiff/yaml are pinned to the platform's versions (oapi-codegen v2.5.1 doesn't build with newer ones) and excluded from Dependabot.internal/api):api.Clientcovers every call the tier 1+2 commands need; gomock mock ininternal/api/mock.New(Options)takes the base URL, aTokenSource(asked on every attempt, so #245/#263 can refresh), an*http.Client(for #244's proxy/CA), a logger and a user agentdevlyft/<version> (<os>/<arch>). Tokens are only sent over HTTPS or to loopback.Retry-Afteris honoured as seconds or as an HTTP date; a longer one stops the retries. Limits are 4 attempts and 30s total waiting, and Ctrl-C stops the wait. POST is never retried. The final error says(after N attempts)and carriesdetails.attempts.clierr.FromHTTPwith the server's message (detail/message/error/title, or short plain text) and request ID (X-Request-Idheader, else arequest_idbody field, ready for #233). An invalid ID is a usage error (exit 2).UpdateDeploy(ctx, id, mutate)GETs the deploy and builds the fullUpdateDeployRequest(build_id,nameandlocation_idalways set). It applies the mutation and PUTs the result, sendingIf-Matchwhen the GET returned an ETag (#239).UpdateRequestFromDeployis exported fordeploy --dry-run. Under HPA, replicas is sent as 0 because the API rejects both together.Pagedfetches only as many limit/offset pages as--limitneeds and survives servers that ignore paging;Trimcovers endpoints without paging.internal/redact. Bodies are read only at debug level.Vendored endpoints
/v1/organizationsorgs list, name resolution/v1/organizations/{organization_id}orgs describe/v1/organizations/{organization_id}/projectsprojects list, name resolution/v1/projects/{project_id}projects describe/v1/projects/{project_id}/environmentsenvs list, name resolution/v1/environments/{environment_id}envs describe/v1/locations--locationname resolution (public, unauthenticated)/v1/projects/{project_id}/environments/{environment_id}/secretssecrets list/v1/projects/{project_id}/environments/{environment_id}/secrets/{secret_name}secrets set/v1/projects/{project_id}/environments/{environment_id}/secrets/{secret_name}secrets delete/v1/projects/{project_id}/podspods list --project/v1/environments/{environment_id}/podspods list(context env)/v1/deploys/{deploy_id}/podspods list --deploy,deploy --wait/v1/pods/{pod_id}pods describe/v1/projects/{project_id}/buildsbuilds list,deploybuild lookup/v1/builds/{build_id}builds describe/v1/projects/{project_id}/buildsbuilds create,deploy/v1/projects/{project_id}/environments/{environment_id}/deploysdeploys list, name resolution,deployupsert/v1/deploys/{deploy_id}deploys describe, RMW update/v1/projects/{project_id}/environments/{environment_id}/deploysdeploys create,deploy/v1/deploys/{deploy_id}deploys update,deploy/v1/deploys/{deploy_id}deploys delete/v1/deploys/{deploy_id}/routesroutes list/v1/deploys/{deploy_id}/routesroutes create/v1/deploys/{deploy_id}/routes/{route_id}routes delete/v1/organizations/{organization_id}/domainsdomains list/v1/organizations/{organization_id}/domainsdomains create/v1/organizations/{organization_id}/domains/{domain}/verifydomains verify/v1/organizations/{organization_id}/domains/{domain}domains delete/v1/deploys/{deployId}/logslogs(history;--followis the WebSocket, #254)/v1/metric-typesmetrics --typevalidation and help/v1/deploys/{deployId}/metricsmetrics <deploy>/v1/pods/{podId}/metricsmetrics <pod>(takes the pod name)Deliberately not vendored (in the source specs, but no CLI v1 command needs them):
GET /v1/builds,GET /v1/deploys,GET /v1/pods.PUT/DELETE /v1/builds/{id}.Fields to review
Build.ports,CreateBuildRequest.ports(andBuildPort)Pod.host_ports(andPodHostPort.host)pods describe. Same as above.Deploy.placement.cluster_namePENDINGvs placed). Recommend keep.Pod.cluster_nameRoute.hostname,CreateRouteRequest.hostnamehostpattern.CustomDomain.verification_tokenDeploy.instance_typedescription exampledevlyft-amd-balanced-2Organization.slugdescription ("usable wherever an organization id is accepted by the CLI")Descriptions rewritten (originals named internal services or SPIFFE hops):
secretslist and set operations.DeployEnvVar,DeploySecretRefandDeploy.env(K8s wording).The overrides are in
allowlist.yaml.Found while doing this (needs platform follow-ups)
Organization.slugsays it is usable wherever an organization ID is accepted, but everyorganization_idpath parameter isformat: uuid. The generated client therefore only takes UUIDs, so #244 must resolve slugs to IDs with a list call. Either the spec format is wrong or the description is.DeployEnvItemdiscriminator has nomapping, so oapi-codegen'sFromDeployEnvVar/ValueByDiscriminatoruse"DeployEnvVar"as thekindinstead of"env", and the API would reject it. The contract tests caught this. The CLI usesapi.EnvVarItem/SecretRefItem/EnvItemValueinstead, and a test fails once the spec gains a mapping, so they can be removed. Fix upstream withmapping: {env: …, secret: …}.{error},{message}). #233 would make this concrete./v1/pods/{podId}/metricstakes the pod name, while/v1/pods/{pod_id}takes the pod ID.Tests
Retry-Afterin seconds and as a dateRetry-AfterIf-Match); required fields carried over; HPA leaves replicas unset; a mutation error stops the write.{error}, plain text, and HTML ignored.httptestserver routes every facade call through kin-openapiopenapi3filter. It validates each request against the vendored specs, and the canned responses too. It also fails if any vendored operation goes unused, so the allowlist can't carry dead surface.hack/specprune. Pruning, extension stripping, drops, overrides, and both refusal paths.Coverage:
internal/api81.7%,hack/specprune74.1%.make lint,make test,make vuln, actionlint and shellcheck all pass.make generateandgo mod tidyproduce no diff.🤖 Generated with Claude Code