Operator

Webhooks

Operator webhook receiver — GitHub push events

Webhooks

The operator runs an HTTP webhook receiver alongside the controller. GitHub pushes patch a Sync operation onto matching Apps — the generation bump reconciles immediately, no waiting for the 60s resync.

Receiver Endpoints

EndpointProviderStatus
POST /webhook/githubGitHub✅ Works — no signature validation
POST /webhook/gitlabGitLab� Accepts 200 but parses nothing

Bind: --webhook-bind-address, default :8082.

GitHub Push Flow

GitHub push
    │
    ▼
POST /webhook/github
    │
    ├─ 1. Parse JSON body (repository.clone_url, ref, after, pusher)
    ├─ 2. List all Apps (every namespace on the mgmt cluster)
    ├─ 3. Match: source.git.repoURL == clone_url
    │          AND source.git.branch == pushed ref
    │          AND spec.webhook.enabled == true
    └─ 4. Merge-patch spec.operation on each match:
         {kind: Sync, initiatedAt: now, triggeredBy: "webhook:github"}
              │
              ▼
        AppReconciler fires immediately (generation bump)

Response: {"matched": N} — no apps list in the body.

Match Rules

CheckDetail
Reporepository.clone_url must equal source.git.repoURL exactly — .git suffix and scheme both matter
Branchref (refs/heads/main) must equal source.git.branch
webhook.enabledRequired — apps without it never match
source.typeGit only — image apps aren't matched

Signature Validation — 📋 planned

Not implemented. spec.webhook.secretRef exists in the schema but the handler never reads it — X-Hub-Signature-256 isn't verified. Anyone who can reach :8082 can trigger syncs on matching apps. Put the receiver behind a private ingress or network policy until validation lands. Rate limiting is also 📋 (in-handler TODO).

The intended flow once wired:

  1. Load the secret named by webhook.secretRef
  2. HMAC-SHA256 the body, compare to X-Hub-Signature-256
  3. 401 on mismatch

Exposing the Receiver

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wardn-webhook
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
    - hosts: [webhook.wardn.example.com]
  rules:
    - host: webhook.wardn.example.com
      http:
        paths:
          - path: /webhook/github
            pathType: Prefix
            backend:
              service:
                name: wardn-operator
                port:
                  number: 8082

Testing

curl -X POST http://localhost:8082/webhook/github \
  -H "Content-Type: application/json" \
  -d '{
    "repository": { "clone_url": "https://github.com/myorg/my-app.git" },
    "ref": "refs/heads/main",
    "after": "abc123"
  }'
# {"matched":1}

Then confirm the op landed:

kubectl get app my-app -n org-my-org -o jsonpath='{.spec.operation.triggeredBy}'
# webhook:github

GitLab

POST /webhook/gitlab returns 200 without acting — full payload parsing is planned.

On this page