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 "" }