mirror of https://github.com/artifacthub/hub.git
197 lines
6.1 KiB
Go
197 lines
6.1 KiB
Go
package webhook
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"text/template"
|
|
|
|
"github.com/artifacthub/hub/internal/handlers/helpers"
|
|
"github.com/artifacthub/hub/internal/hub"
|
|
"github.com/artifacthub/hub/internal/notification"
|
|
"github.com/go-chi/chi"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// Handlers represents a group of http handlers in charge of handling webhooks
|
|
// operations.
|
|
type Handlers struct {
|
|
webhookManager hub.WebhookManager
|
|
logger zerolog.Logger
|
|
hc hub.HTTPClient
|
|
}
|
|
|
|
// NewHandlers creates a new Handlers instance.
|
|
func NewHandlers(webhookManager hub.WebhookManager, hc hub.HTTPClient) *Handlers {
|
|
return &Handlers{
|
|
webhookManager: webhookManager,
|
|
logger: log.With().Str("handlers", "webhook").Logger(),
|
|
hc: hc,
|
|
}
|
|
}
|
|
|
|
// Add is an http handler that adds the provided webhook to the database.
|
|
func (h *Handlers) Add(w http.ResponseWriter, r *http.Request) {
|
|
orgName := chi.URLParam(r, "orgName")
|
|
wh := &hub.Webhook{}
|
|
if err := json.NewDecoder(r.Body).Decode(&wh); err != nil {
|
|
h.logger.Error().Err(err).Str("method", "Add").Msg(hub.ErrInvalidInput.Error())
|
|
helpers.RenderErrorJSON(w, hub.ErrInvalidInput)
|
|
return
|
|
}
|
|
if err := h.webhookManager.Add(r.Context(), orgName, wh); err != nil {
|
|
h.logger.Error().Err(err).Str("method", "Add").Send()
|
|
helpers.RenderErrorJSON(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
}
|
|
|
|
// Delete is an http handler that deletes the provided webhook from the database.
|
|
func (h *Handlers) Delete(w http.ResponseWriter, r *http.Request) {
|
|
webhookID := chi.URLParam(r, "webhookID")
|
|
if err := h.webhookManager.Delete(r.Context(), webhookID); err != nil {
|
|
h.logger.Error().Err(err).Str("method", "Delete").Send()
|
|
helpers.RenderErrorJSON(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// Get is an http handler that returns the requested webhook.
|
|
func (h *Handlers) Get(w http.ResponseWriter, r *http.Request) {
|
|
webhookID := chi.URLParam(r, "webhookID")
|
|
dataJSON, err := h.webhookManager.GetJSON(r.Context(), webhookID)
|
|
if err != nil {
|
|
h.logger.Error().Err(err).Str("method", "Get").Send()
|
|
helpers.RenderErrorJSON(w, err)
|
|
return
|
|
}
|
|
helpers.RenderJSON(w, dataJSON, 0, http.StatusOK)
|
|
}
|
|
|
|
// GetOwnedByOrg is an http handler that returns the webhooks owned by the
|
|
// organization provided. The user doing the request must belong to the
|
|
// organization.
|
|
func (h *Handlers) GetOwnedByOrg(w http.ResponseWriter, r *http.Request) {
|
|
orgName := chi.URLParam(r, "orgName")
|
|
dataJSON, err := h.webhookManager.GetOwnedByOrgJSON(r.Context(), orgName)
|
|
if err != nil {
|
|
h.logger.Error().Err(err).Str("method", "GetOwnedByOrg").Send()
|
|
helpers.RenderErrorJSON(w, err)
|
|
return
|
|
}
|
|
helpers.RenderJSON(w, dataJSON, 0, http.StatusOK)
|
|
}
|
|
|
|
// GetOwnedByUser is an http handler that returns the webhooks owned by the
|
|
// user doing the request.
|
|
func (h *Handlers) GetOwnedByUser(w http.ResponseWriter, r *http.Request) {
|
|
dataJSON, err := h.webhookManager.GetOwnedByUserJSON(r.Context())
|
|
if err != nil {
|
|
h.logger.Error().Err(err).Str("method", "GetOwnedByUser").Send()
|
|
helpers.RenderErrorJSON(w, err)
|
|
return
|
|
}
|
|
helpers.RenderJSON(w, dataJSON, 0, http.StatusOK)
|
|
}
|
|
|
|
// TriggerTest is an http handler used to test a webhook before adding or
|
|
// updating it.
|
|
func (h *Handlers) TriggerTest(w http.ResponseWriter, r *http.Request) {
|
|
// Read webhook from request body
|
|
wh := &hub.Webhook{}
|
|
if err := json.NewDecoder(r.Body).Decode(&wh); err != nil {
|
|
helpers.RenderErrorJSON(w, hub.ErrInvalidInput)
|
|
return
|
|
}
|
|
|
|
// Prepare payload
|
|
var tmpl *template.Template
|
|
if wh.Template != "" {
|
|
var err error
|
|
tmpl, err = template.New("").Parse(wh.Template)
|
|
if err != nil {
|
|
err = fmt.Errorf("error parsing template: %w", err)
|
|
helpers.RenderErrorWithCodeJSON(w, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
} else {
|
|
tmpl = notification.DefaultWebhookPayloadTmpl
|
|
}
|
|
var payload bytes.Buffer
|
|
if err := tmpl.Execute(&payload, webhookTestTemplateData); err != nil {
|
|
err = fmt.Errorf("error executing template: %w", err)
|
|
helpers.RenderErrorWithCodeJSON(w, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Call webhook endpoint
|
|
req, _ := http.NewRequest("POST", wh.URL, &payload)
|
|
contentType := wh.ContentType
|
|
if contentType == "" {
|
|
contentType = notification.DefaultPayloadContentType
|
|
}
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-ArtifactHub-Secret", wh.Secret)
|
|
resp, err := h.hc.Do(req)
|
|
if err != nil {
|
|
err = fmt.Errorf("error doing request: %w", err)
|
|
helpers.RenderErrorWithCodeJSON(w, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
err := fmt.Errorf("received unexpected status code: %d", resp.StatusCode)
|
|
helpers.RenderErrorWithCodeJSON(w, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// Update is an http handler that updates the provided webhook in the database.
|
|
func (h *Handlers) Update(w http.ResponseWriter, r *http.Request) {
|
|
wh := &hub.Webhook{}
|
|
if err := json.NewDecoder(r.Body).Decode(&wh); err != nil {
|
|
h.logger.Error().Err(err).Str("method", "Update").Msg(hub.ErrInvalidInput.Error())
|
|
helpers.RenderErrorJSON(w, hub.ErrInvalidInput)
|
|
return
|
|
}
|
|
wh.WebhookID = chi.URLParam(r, "webhookID")
|
|
if err := h.webhookManager.Update(r.Context(), wh); err != nil {
|
|
h.logger.Error().Err(err).Str("method", "Update").Send()
|
|
helpers.RenderErrorJSON(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// webhookTestTemplateData represents the notification template data used by
|
|
// TriggerTest handler.
|
|
var webhookTestTemplateData = &hub.PackageNotificationTemplateData{
|
|
BaseURL: "https://artifacthub.io",
|
|
Event: map[string]interface{}{
|
|
"id": "00000000-0000-0000-0000-000000000001",
|
|
"kind": "package.new-release",
|
|
},
|
|
Package: map[string]interface{}{
|
|
"name": "sample-package",
|
|
"version": "1.0.0",
|
|
"url": "https://artifacthub.io/packages/helm/artifacthub/sample-package/1.0.0",
|
|
"changes": []string{
|
|
"Cool feature",
|
|
"Bug fixed",
|
|
},
|
|
"containsSecurityUpdates": true,
|
|
"prerelease": true,
|
|
"repository": map[string]interface{}{
|
|
"kind": "helm",
|
|
"name": "repo1",
|
|
"publisher": "org1",
|
|
},
|
|
},
|
|
}
|