mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2025-12-30 21:29:44 -06:00
185 lines
4.7 KiB
Go
185 lines
4.7 KiB
Go
package worker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hatchet-dev/hatchet/internal/signature"
|
|
"github.com/hatchet-dev/hatchet/pkg/client"
|
|
)
|
|
|
|
type WebhookHandlerOptions struct {
|
|
Secret string
|
|
}
|
|
|
|
type HealthCheckResponse struct {
|
|
Actions []string `json:"actions"`
|
|
}
|
|
|
|
func (w *Worker) WebhookHttpHandler(opts WebhookHandlerOptions, workflows ...workflowConverter) http.HandlerFunc {
|
|
return func(writer http.ResponseWriter, r *http.Request) {
|
|
// health check with actions
|
|
if r.Method == http.MethodGet {
|
|
writer.WriteHeader(http.StatusOK)
|
|
_, _ = writer.Write([]byte("OK!"))
|
|
return
|
|
}
|
|
if r.Method != http.MethodPut && r.Method != http.MethodPost {
|
|
writer.WriteHeader(http.StatusMethodNotAllowed)
|
|
_, _ = writer.Write([]byte("Method not allowed"))
|
|
return
|
|
}
|
|
|
|
data, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
w.l.Error().Err(err).Msg("error reading body")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
expected := r.Header.Get("X-Hatchet-Signature")
|
|
actual, err := signature.Sign(string(data), opts.Secret)
|
|
if err != nil {
|
|
w.l.Error().Err(err).Msg("error signing request")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
if expected != actual {
|
|
w.l.Error().Err(fmt.Errorf("expected signature %s, got %s", expected, actual)).Msg("error in request signature")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte("wrong signature"))
|
|
return
|
|
}
|
|
|
|
if r.Method == http.MethodPut {
|
|
for _, wf := range workflows {
|
|
err = w.RegisterWorkflow(wf)
|
|
if err != nil {
|
|
w.l.Error().Err(err).Msg("error registering workflow")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
}
|
|
|
|
var actions []string
|
|
for _, action := range w.actions {
|
|
actions = append(actions, action.Name())
|
|
}
|
|
|
|
res := HealthCheckResponse{
|
|
Actions: actions,
|
|
}
|
|
data, err := json.Marshal(res)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
writer.WriteHeader(http.StatusOK)
|
|
_, _ = writer.Write(data)
|
|
return
|
|
}
|
|
|
|
var action ActionPayload
|
|
if err := json.Unmarshal(data, &action); err != nil {
|
|
w.l.Error().Err(err).Msg("error unmarshalling action")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
actionWithPayload := action.Action
|
|
actionWithPayload.ActionPayload = []byte(action.ActionPayload)
|
|
|
|
timestamp := time.Now().UTC()
|
|
_, err = w.client.Dispatcher().SendStepActionEvent(r.Context(),
|
|
&client.ActionEvent{
|
|
Action: actionWithPayload,
|
|
EventTimestamp: ×tamp,
|
|
EventType: client.ActionEventTypeStarted,
|
|
},
|
|
)
|
|
if err != nil {
|
|
w.l.Error().Err(err).Msg("error dispatching event")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
ctx, err := newHatchetContext(r.Context(), actionWithPayload, w.client, w.l, w)
|
|
if err != nil {
|
|
w.l.Error().Err(err).Msg("error creating context")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
resp, err := w.webhookProcess(ctx)
|
|
if err != nil {
|
|
// FIXME handle error gracefully and send a failed event
|
|
w.l.Error().Err(err).Msg("error processing request")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
timestamp = time.Now().UTC()
|
|
_, err = w.client.Dispatcher().SendStepActionEvent(r.Context(),
|
|
&client.ActionEvent{
|
|
Action: actionWithPayload,
|
|
EventTimestamp: ×tamp,
|
|
EventType: client.ActionEventTypeCompleted,
|
|
EventPayload: resp,
|
|
},
|
|
)
|
|
if err != nil {
|
|
w.l.Error().Err(err).Msg("error dispatching event")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = writer.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
writer.WriteHeader(http.StatusOK)
|
|
_, _ = writer.Write([]byte("OK"))
|
|
}
|
|
}
|
|
|
|
func (w *Worker) webhookProcess(ctx HatchetContext) (interface{}, error) {
|
|
var do Action
|
|
for _, action := range w.actions {
|
|
split := strings.Split(action.Name(), ":") // service:action
|
|
if split[1] == ctx.StepName() {
|
|
do = action
|
|
break
|
|
}
|
|
}
|
|
|
|
if do == nil {
|
|
return nil, fmt.Errorf("fatal: could not find action for step run %s", ctx.StepName())
|
|
}
|
|
|
|
res := do.Run(ctx)
|
|
|
|
if len(res) != 2 {
|
|
return nil, fmt.Errorf("invalid response from action, expected 2 values")
|
|
}
|
|
|
|
if res[1] != nil {
|
|
err, ok := res[1].(error)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid response from action, expected error")
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return res[0], nil
|
|
}
|