freeleaps-ops/apps/gitea-webhook-ambassador/internal/handler/webhook.go
zhenyus db590f3f27 refactor: update gitea-webhook-ambassador Dockerfile and configuration
- Changed the build process to include a web UI build stage using Node.js.
- Updated Go build stage to copy web UI files to the correct location.
- Removed the main.go file as it is no longer needed.
- Added SQLite database configuration to example config.
- Updated dependencies in go.mod and go.sum, including new packages for JWT and SQLite.
- Modified .gitignore to include new database and configuration files.

Signed-off-by: zhenyus <zhenyus@mathmast.com>
2025-06-10 16:00:52 +08:00

158 lines
4.8 KiB
Go

package handler
import (
"encoding/json"
"io"
"net/http"
"regexp"
"freeleaps.com/gitea-webhook-ambassador/internal/config"
"freeleaps.com/gitea-webhook-ambassador/internal/database"
"freeleaps.com/gitea-webhook-ambassador/internal/logger"
"freeleaps.com/gitea-webhook-ambassador/internal/model"
"freeleaps.com/gitea-webhook-ambassador/internal/worker"
)
// WebhookHandler handles incoming Gitea webhooks
type WebhookHandler struct {
workerPool *worker.Pool
db *database.DB
config *config.Configuration
}
// NewWebhookHandler creates a new webhook handler
func NewWebhookHandler(workerPool *worker.Pool, db *database.DB, config *config.Configuration) *WebhookHandler {
return &WebhookHandler{
workerPool: workerPool,
db: db,
config: config,
}
}
// HandleWebhook processes incoming webhook requests
func (h *WebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Verify signature if secret token is set
secretHeader := h.config.Server.SecretHeader
serverSecretKey := h.config.Server.SecretKey
receivedSecretKey := r.Header.Get(secretHeader)
if receivedSecretKey == "" {
http.Error(w, "No secret key provided", http.StatusUnauthorized)
logger.Warn("No secret key provided in header")
return
}
if receivedSecretKey != serverSecretKey {
http.Error(w, "Invalid secret key", http.StatusUnauthorized)
logger.Warn("Invalid secret key provided")
return
}
// Read and parse the webhook payload
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
logger.Error("Failed to read webhook body: %v", err)
return
}
defer r.Body.Close()
var webhook model.GiteaWebhook
if err := json.Unmarshal(body, &webhook); err != nil {
http.Error(w, "Failed to parse webhook payload", http.StatusBadRequest)
logger.Error("Failed to parse webhook payload: %v", err)
return
}
// Get project mapping from database
project, err := h.db.GetProjectMapping(webhook.Repository.FullName)
if err != nil {
logger.Error("Failed to get project mapping: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if project == nil {
logger.Info("No Jenkins job mapping for repository: %s", webhook.Repository.FullName)
w.WriteHeader(http.StatusOK) // Still return OK to not alarm Gitea
return
}
// Extract branch name from ref
branchName := webhook.GetBranchName()
// Determine which job to trigger based on branch name
jobName := h.determineJobName(project, branchName)
if jobName == "" {
logger.Info("No job configured to trigger for repository %s, branch %s",
webhook.Repository.FullName, branchName)
w.WriteHeader(http.StatusOK)
return
}
// Prepare parameters for Jenkins job
params := map[string]string{
"BRANCH_NAME": branchName,
"COMMIT_SHA": webhook.After,
"REPOSITORY_URL": webhook.Repository.CloneURL,
"REPOSITORY_NAME": webhook.Repository.FullName,
"PUSHER_NAME": webhook.Pusher.Login,
"PUSHER_EMAIL": webhook.Pusher.Email,
}
// Submit the job to the worker pool
job := worker.Job{
Name: jobName,
Parameters: params,
EventID: webhook.GetEventID(),
RepositoryName: webhook.Repository.FullName,
BranchName: branchName,
CommitSHA: webhook.After,
Attempts: 0,
}
if h.workerPool.Submit(job) {
logger.Info("Webhook received and queued for repository %s, branch %s, commit %s, job %s",
webhook.Repository.FullName, branchName, webhook.After, jobName)
w.WriteHeader(http.StatusAccepted)
} else {
logger.Warn("Failed to queue webhook: queue full")
http.Error(w, "Server busy, try again later", http.StatusServiceUnavailable)
}
}
// determineJobName selects the appropriate Jenkins job to trigger based on branch name
func (h *WebhookHandler) determineJobName(project *database.ProjectMapping, branchName string) string {
// First check for exact branch match
for _, job := range project.BranchJobs {
if job.BranchName == branchName {
logger.Debug("Found exact branch match for %s: job %s", branchName, job.JobName)
return job.JobName
}
}
// Then check for pattern-based matches
for _, pattern := range project.BranchPatterns {
matched, err := regexp.MatchString(pattern.Pattern, branchName)
if err != nil {
logger.Error("Error matching branch pattern %s: %v", pattern.Pattern, err)
continue
}
if matched {
logger.Debug("Branch %s matched pattern %s: job %s", branchName, pattern.Pattern, pattern.JobName)
return pattern.JobName
}
}
// Fall back to default job if available
if project.DefaultJob != "" {
logger.Debug("Using default job for branch %s: job %s", branchName, project.DefaultJob)
return project.DefaultJob
}
return ""
}