- 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>
158 lines
4.8 KiB
Go
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 ""
|
|
}
|