Add deploy API and unify with the webhook into deploy-server#14
Merged
Conversation
The last Code Manager reflex customers reach for is the programmatic deploy:
POST a deploy, get an id, poll status. This adds it — and unifies it with the
webhook into one control plane, because both must share one deploy history to
answer 'what deployed, however it was triggered', and one worker so they do not
run r10k over each other.
New internal/deployserver owns the deploy queue, a single worker, and an
in-memory bounded history, and routes both front doors:
POST /v1/deploys trigger a deploy (bearer token); returns a record
with an id, or blocks with {"wait":true}
GET /v1/deploys/{id} one deploy's status
GET /v1/deploys history, newest first
POST /v1/webhook push -> deploy (shared secret), now recorded too
A record carries status (queued/running/complete/failed), source (api or
webhook), per-environment code_id and commit, and lifecycle timestamps. complete
means the deploy landed on the primary — r10k ran, sealed, publisher signaled;
compilers converge by polling, so the record does not wait on them.
internal/webhook shrinks to a pure request parser (Authenticate + Environment);
its queue and worker move into deployserver, so there is exactly one of each.
The command becomes deploy-server, enabling the API when --api-token is set and
the webhook when --secret is; codavox webhook stays as an alias that runs it
with only the webhook, so nothing that shipped breaks.
Auth is a bearer token from a file (constant-time), matching Code Manager's
token model and working for CI with no Puppet cert; TLS client-auth is
per-connection not per-route, so mixing it with the webhook's no-client-cert TLS
on one listener is not possible, and application-level auth is the right fit.
Tested against a fake Deployer with no r10k: async and wait, all vs
environments, token missing/wrong, validation, get/list/404, failed-deploy
recording, disabled-route 404, and the webhook route recording into the shared
history. A race on the async response — the handler read the record pointer the
worker was mutating — is fixed by returning a locked snapshot from submit.
Signed-off-by: Michael Harp <mike@mikeharp.com>
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.
The last Code Manager reflex is the programmatic deploy:
POSTa deploy, get an id, poll status. This adds it — and unifies it with the webhook into one control plane, because both need one shared history ("what deployed, however triggered") and one worker (so they don't run r10k over each other).internal/deployserverOwns the deploy queue, a single worker, and an in-memory bounded history, routing both front doors:
POST /v1/deploysid, or blocks with{"wait":true}GET /v1/deploys/{id}GET /v1/deploysPOST /v1/webhookA record carries
status(queued/running/complete/failed),source(api or webhook), per-environmentcode_idandcommit, and lifecycle timestamps.completemeans the deploy landed on the primary (r10k ran, sealed, publisher signaled); compilers converge by polling, so the record doesn't wait on them.Unification
internal/webhookshrinks to a pure request parser (Authenticate+Environment); its queue and worker move intodeployserver, so there's exactly one of each. The command becomesdeploy-server(API when--api-tokenis set, webhook when--secretis);codavox webhookstays as an alias running it webhook-only, so nothing that shipped breaks.Auth
Bearer token from a file (constant-time), matching Code Manager's token model and working for CI with no Puppet cert. TLS client-auth is per-connection not per-route, so mixing mTLS with the webhook's no-client-cert TLS on one listener isn't possible — application-level auth is the right fit. Server TLS uses the node cert;
--no-tlsfor behind-a-proxy.Safety
Serialized deploys: the single worker plus the staging
flockfrom #13 mean an API deploy, a webhook deploy, and a CLIcodavox deploynever overlap r10k. An end-to-end smoke confirmed the API deploys,GET /v1/deploysshows a unified api+webhook history, and a missing token → 401.Tests
deployserveragainst a fakeDeployer(no r10k): async andwait,allvsenvironments, token missing/wrong, validation, get/list/404, failed-deploy recording, disabled-route 404, and the webhook route recording into the shared history. A race on the async response — handler reading the record pointer the worker was mutating — is fixed by returning a locked snapshot fromsubmit, verified under-race.Docs:
webhooks.md→ deploy-server.md (API + webhook + records); commands.md/README updated.Verified:
go test -race ./...,golangci-lint0 issues, gofmt/vet clean, markdownlint 0 issues.🤖 Generated with Claude Code