Files
tiny-forge/plans/docker-watcher-core/phase-6-webhook-handler.md
T
alexei.dolgolyov 90be636d66 feat(docker-watcher): phase 5 - registry client & poller
Gitea registry client with tag listing and pattern matching, cron-based
polling scheduler with first-poll safety, poll state persistence.
DeployTriggerer interface for decoupled deploy triggering.
2026-03-27 21:34:09 +03:00

3.7 KiB

Phase 6: Webhook Handler

Status: Complete Parent plan: PLAN.md Domain: backend

Objective

Implement the secret UUID-based webhook endpoint that receives image push notifications from CI systems, with auto-creation of unknown projects.

Tasks

  • Task 1: Implement webhook HTTP handler — POST /api/webhook/:secret-uuid
  • Task 2: Validate incoming payload — extract image name and tag
  • Task 3: Look up project by image name in store — match against configured project images
  • Task 4: If known project: match tag to stage via tag patterns, determine if auto_deploy
  • Task 5: If unknown project: auto-create project with defaults from image inspection (EXPOSE port, labels)
  • Task 6: Generate and store webhook secret UUID in settings (on first launch)
  • Task 7: Implement webhook URL regeneration (new UUID, invalidates old one)
  • Task 8: Define webhook payload struct ({"image": "registry/org/app:tag"})

Files to Modify/Create

  • internal/webhook/handler.go — webhook HTTP handler + payload parsing
  • internal/webhook/matcher.go — project/stage matching logic
  • internal/webhook/autocreate.go — auto-create project from unknown image

Acceptance Criteria

  • Valid webhook URL with correct UUID triggers processing
  • Invalid/missing UUID returns 404 (no information leak)
  • Known images are matched to projects and stages
  • Unknown images trigger auto-creation with sensible defaults
  • Webhook URL can be regenerated

Notes

  • Webhook URL format: POST /api/webhook/d8f2a1e9-...
  • No authentication needed beyond the secret UUID
  • Auto-created projects use: image EXPOSE port, "dev" as default stage, auto_deploy: true
  • The webhook handler calls into the deployer (Phase 7) — for now, define the interface/callback
  • Keep the handler thin — it matches and delegates

Review Checklist

  • All tasks completed
  • No information leak on invalid UUIDs
  • Payload validation rejects malformed input
  • Auto-creation uses safe defaults
  • Handler is stateless (delegates to store/deployer)

Handoff to Next Phase

Exported API

  • webhook.NewHandler(store, deployer, inspector) — creates the HTTP handler
  • webhook.Handler.Route() — returns a chi.Router to mount at /api/webhook
  • webhook.EnsureWebhookSecret(store) — generates UUID on first launch, returns current secret
  • webhook.RegenerateWebhookSecret(store) — replaces secret with new UUID, invalidates old one
  • webhook.ParseImageRef(ref) — parses registry/owner/name:tag into components

Interfaces Defined

  • webhook.DeployTriggererTriggerDeploy(ctx, projectID, stageID, imageTag) error (mirrors registry.DeployTriggerer)
  • webhook.ImageInspectorInspectImage(ctx, imageRef) (docker.ImageInfo, error) (wraps docker.Client)

Integration Points

  • Mount the webhook router: r.Mount("/api/webhook", webhookHandler.Route())
  • Call webhook.EnsureWebhookSecret(store) at application startup to generate the secret on first launch
  • The deployer must implement webhook.DeployTriggerer (same signature as registry.DeployTriggerer)
  • The Docker client (*docker.Client) satisfies webhook.ImageInspector directly

Auto-Create Behavior

  • Unknown images create a project with name from image name, port from EXPOSE, healthcheck from image metadata
  • A default "dev" stage is created with tag_pattern: "*", auto_deploy: true, max_instances: 1
  • If image inspection fails (not pulled locally), project is created with port=0 and empty healthcheck

Tag Matching

  • Uses path.Match (glob semantics) — same approach as the registry poller
  • Stages are checked in name-sorted order; first matching stage wins