diff --git a/internal/app/deploy_service.go b/internal/app/deploy_service.go index 16d6151..e979b96 100644 --- a/internal/app/deploy_service.go +++ b/internal/app/deploy_service.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "log" "net/http" "net/url" @@ -21,7 +22,7 @@ func (a *App) handleDeployWebhook(w http.ResponseWriter, r *http.Request) { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - path := strings.TrimPrefix(r.URL.Path, "/api/webhooks/deploy/") + path := strings.Trim(strings.TrimPrefix(r.URL.Path, "/api/webhooks/deploy/"), "/") parts := strings.Split(path, "/") if len(parts) != 2 || parts[0] == "" || parts[1] == "" { http.Error(w, "bad path", http.StatusBadRequest) @@ -37,17 +38,22 @@ func (a *App) handleDeployWebhook(w http.ResponseWriter, r *http.Request) { http.Error(w, "unauthorized", http.StatusUnauthorized) return } - var payload struct { - Ref string `json:"ref"` - } - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - http.Error(w, "invalid json", http.StatusBadRequest) + ref, err := parseWebhookRef(r) + if err != nil { + http.Error(w, "invalid webhook payload", http.StatusBadRequest) return } - if payload.Ref != "refs/heads/main" { + ref = strings.TrimSpace(ref) + if ref == "" { + writeJSON(w, http.StatusAccepted, map[string]string{"status": "ignored", "reason": "no ref in payload"}) + return + } + if ref != "refs/heads/main" && ref != "main" { + log.Printf("webhook ignored project_id=%d ref=%q", p.ID, ref) writeJSON(w, http.StatusAccepted, map[string]string{"status": "ignored", "reason": "not main branch"}) return } + log.Printf("webhook deploy accepted project_id=%d ref=%q", p.ID, ref) if err := a.deployProjectFromMain(r.Context(), p); err != nil { log.Printf("deploy failed project_id=%d err=%v", p.ID, err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -56,6 +62,31 @@ func (a *App) handleDeployWebhook(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "deployed"}) } +func parseWebhookRef(r *http.Request) (string, error) { + rawBody, err := io.ReadAll(r.Body) + if err != nil { + return "", err + } + var payload struct { + Ref string `json:"ref"` + } + if err := json.Unmarshal(rawBody, &payload); err == nil { + return payload.Ref, nil + } + formVals, err := url.ParseQuery(string(rawBody)) + if err != nil { + return "", err + } + raw := strings.TrimSpace(formVals.Get("payload")) + if raw == "" { + return "", nil + } + if err := json.Unmarshal([]byte(raw), &payload); err != nil { + return "", err + } + return payload.Ref, nil +} + func (a *App) getProjectForWebhook(ctx context.Context, projectID int64, token string) (Project, error) { var p Project err := a.db.QueryRowContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.service_name, p.route_host, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.created_at