mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 13:20:41 +00:00
5.1 KiB
5.1 KiB
Forgejo Webhooks → JobRequest Mapping (Integration Layer)
This document explains how the Forge Integration service maps real Forgejo (Gitea-compatible) webhooks to our internal JobRequest messages and publishes them to RabbitMQ.
Overview
- Service:
crates/forge-integration - Endpoint:
POST /webhooks/forgejo(configurable viaWEBHOOK_PATH) - Auth: HMAC-SHA256 validation using
WEBHOOK_SECRET - Events handled:
push,pull_request(opened,synchronize,reopened) - Output:
JobRequest(JSON) published to exchangesolstice.jobswith routing keyjobrequest.v1
Headers and Security
- Event type header:
X-Gitea-Event(orX-Forgejo-Event) - Delivery ID header (optional, for logs):
X-Gitea-Delivery - Signature header:
X-Gitea-Signature(orX-Forgejo-Signature)- Value is lowercase hex of
HMAC_SHA256(secret, raw_request_body) - If
WEBHOOK_SECRETis set, the service requires a valid signature and returns401on mismatch/missing header. - If unset, the service accepts requests (dev mode) and logs a warning.
- Value is lowercase hex of
Signature example (shell):
SECRET=your_shared_secret
BODY='{"after":"deadbeef", "repository":{}}'
SIG=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256)
# Send:
curl -sS -X POST http://127.0.0.1:8080/webhooks/forgejo \
-H "Content-Type: application/json" \
-H "X-Gitea-Event: push" \
-H "X-Gitea-Signature: $SIG" \
--data "$BODY"
Payload Mapping → JobRequest
We only deserialize the minimal fields required to construct a JobRequest. Unused fields are ignored.
- Push event (
X-Gitea-Event: push):repo_url<-repository.clone_url(fallbackrepository.ssh_url)commit_sha<-after- Ignore branch deletions where
afteris all zeros source=forgejo
Minimal push payload shape:
{
"after": "0123456789abcdef0123456789abcdef01234567",
"repository": {
"clone_url": "https://forge.example.com/org/repo.git",
"ssh_url": "ssh://git@forge.example.com:2222/org/repo.git"
}
}
- Pull request event (
X-Gitea-Event: pull_request):- Only actions:
opened,synchronize,reopened(others → 204 No Content) repo_url<-pull_request.head.repo.clone_url(fallbackssh_url)commit_sha<-pull_request.head.shasource=forgejo
- Only actions:
Minimal PR payload shape:
{
"action": "synchronize",
"pull_request": {
"head": {
"sha": "89abcdef0123456789abcdef0123456789abcd",
"repo": {
"clone_url": "https://forge.example.com/org/repo.git",
"ssh_url": "ssh://git@forge.example.com:2222/org/repo.git"
}
}
}
}
JobRequest fields set now:
schema_version = "jobrequest.v1"request_id = Uuid::v4()source = forgejorepo_urlas abovecommit_shaas aboveworkflow_path = null(may be inferred later)workflow_job_id = nullruns_on = null(future enhancement to infer)submitted_at = now(UTC)
AMQP Publication
- Exchange:
solstice.jobs(durable, direct) - Routing key:
jobrequest.v1 - Queue:
solstice.jobs.v1(declared by both publisher and consumer) - DLX/DLQ:
solstice.dlx/solstice.jobs.v1.dlq - Publisher confirms enabled; messages are persistent (
delivery_mode = 2).
Env/CLI (defaults):
AMQP_URL=amqp://127.0.0.1:5672/%2fAMQP_EXCHANGE=solstice.jobsAMQP_ROUTING_KEY=jobrequest.v1AMQP_QUEUE=solstice.jobs.v1AMQP_DLX=solstice.dlxAMQP_DLQ=solstice.jobs.v1.dlq
Configuration
- HTTP address:
HTTP_ADDR(default0.0.0.0:8080) - Webhook path:
WEBHOOK_PATH(default/webhooks/forgejo) - Shared secret:
WEBHOOK_SECRET(required in prod) - AMQP settings: see above
Example run:
export WEBHOOK_SECRET=devsecret
cargo run -p orchestrator &
cargo run -p forge-integration -- --http-addr 0.0.0.0:8080 --webhook-path /webhooks/forgejo
Forgejo Setup
- In the repository Settings → Webhooks, add a new webhook:
- Target URL:
http://<your-host>:8080/webhooks/forgejo - Content type:
application/json - Secret: your
WEBHOOK_SECRET - Events: check "Just the push event" and "Pull request events" (or their equivalents)
- Target URL:
- Save and use "Test Delivery" to verify a 202 response.
Local Verification via curl
Create a minimal push body and compute signature:
SECRET=devsecret
BODY='{"after":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef","repository":{"clone_url":"https://example/repo.git"}}'
SIG=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256)
curl -i -X POST http://127.0.0.1:8080/webhooks/forgejo \
-H "Content-Type: application/json" \
-H "X-Gitea-Event: push" \
-H "X-Gitea-Signature: $SIG" \
--data "$BODY"
You should see HTTP/1.1 202 Accepted, and the Orchestrator should log a received JobRequest.
Notes & Next Steps
- Add commit status updates back to Forgejo (
pendingon receipt;success/failureon completion). - Consider repo allowlist/branch filters to reduce noise.
- Add idempotency keyed by
X-Gitea-Delivery+repo+shato avoid duplicate enqueues. - Optional: prefer SSH URLs via config if your Orchestrator uses SSH keys for fetch.