All checks were successful
Check / check (pull_request) Successful in 1m45s
- Add Prometheus metrics package (internal/metrics) with deployment, container health, webhook, HTTP request, and audit counters/histograms - Add audit_log SQLite table via migration 007 - Add AuditEntry model with CRUD operations and query methods - Add audit service (internal/service/audit) for recording user actions - Instrument deploy service with deployment duration, count, and in-flight metrics; container health gauge updates on deploy completion - Instrument webhook service with event counters by app/type/matched - Instrument HTTP middleware with request count, duration, and response size metrics; also log response bytes in structured request logs - Add audit logging to all key handler operations: login/logout, app CRUD, deploy, cancel, rollback, restart/stop/start, webhook receipt, and initial setup - Add GET /api/audit endpoint for querying recent audit entries - Make /metrics endpoint always available (optionally auth-protected) - Add comprehensive tests for metrics, audit model, and audit service - Update existing test infrastructure with metrics and audit dependencies - Update README with Observability section documenting all metrics, audit log, and structured logging
86 lines
2.1 KiB
Go
86 lines
2.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"sneak.berlin/go/upaas/internal/models"
|
|
"sneak.berlin/go/upaas/internal/service/audit"
|
|
)
|
|
|
|
// maxWebhookBodySize is the maximum allowed size of a webhook request body (1MB).
|
|
const maxWebhookBodySize = 1 << 20
|
|
|
|
// HandleWebhook handles incoming Gitea webhooks.
|
|
func (h *Handlers) HandleWebhook() http.HandlerFunc { //nolint:funlen // audit logging adds necessary length
|
|
return func(writer http.ResponseWriter, request *http.Request) {
|
|
secret := chi.URLParam(request, "secret")
|
|
if secret == "" {
|
|
http.NotFound(writer, request)
|
|
|
|
return
|
|
}
|
|
|
|
// Find app by webhook secret
|
|
application, findErr := models.FindAppByWebhookSecret(
|
|
request.Context(),
|
|
h.db,
|
|
secret,
|
|
)
|
|
if findErr != nil {
|
|
h.log.Error("failed to find app by webhook secret", "error", findErr)
|
|
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
if application == nil {
|
|
http.NotFound(writer, request)
|
|
|
|
return
|
|
}
|
|
|
|
// Read request body with size limit to prevent memory exhaustion
|
|
body, readErr := io.ReadAll(io.LimitReader(request.Body, maxWebhookBodySize))
|
|
if readErr != nil {
|
|
h.log.Error("failed to read webhook body", "error", readErr)
|
|
http.Error(writer, "Bad Request", http.StatusBadRequest)
|
|
|
|
return
|
|
}
|
|
|
|
// Get event type from header
|
|
eventType := request.Header.Get("X-Gitea-Event")
|
|
if eventType == "" {
|
|
eventType = "push"
|
|
}
|
|
|
|
// Log webhook receipt
|
|
h.audit.LogFromRequest(request.Context(), request, audit.LogEntry{
|
|
Username: "webhook",
|
|
Action: models.AuditActionWebhookReceive,
|
|
ResourceType: models.AuditResourceWebhook,
|
|
ResourceID: application.ID,
|
|
Detail: "webhook from app: " + application.Name + ", event: " + eventType,
|
|
})
|
|
|
|
// Process webhook
|
|
webhookErr := h.webhook.HandleWebhook(
|
|
request.Context(),
|
|
application,
|
|
eventType,
|
|
body,
|
|
)
|
|
if webhookErr != nil {
|
|
h.log.Error("failed to process webhook", "error", webhookErr)
|
|
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
writer.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|