Documentation
¶
Overview ¶
Package webhook persists incoming GitHub webhook deliveries and reconciles them asynchronously.
EventProcessor stores each delivery as a PENDING Event and returns 200 immediately, then dispatches each Event to the Reconciler registered for its event type. The storage is abstracted by Store so that each application can keep the events in its own table.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDuplicateDelivery = xerrors.New("duplicate delivery")
ErrDuplicateDelivery is returned by Store.Create when the delivery ID is already stored.
Functions ¶
This section is empty.
Types ¶
type Event ¶
type Event struct {
Id int32
DeliveryId string
EventType string
Action string
Payload []byte
State State
Status []byte
LastError string
CreatedAt time.Time
UpdatedAt time.Time
}
Event is a single webhook delivery.
func (*Event) Finalize ¶
Finalize decides the state of e after Reconcile returned recErr.
- recErr != nil: FAILED
- e.State is PROCESSING: SUCCEEDED
- otherwise: the state set by the reconciler is preserved
func (*Event) ReadStatus ¶
ReadStatus unmarshals the status of e into out. An empty status leaves out at its zero value.
func (*Event) WriteStatus ¶
WriteStatus marshals in into the status of e.
type EventProcessor ¶
type EventProcessor struct {
// contains filtered or unexported fields
}
EventProcessor receives GitHub webhook deliveries with ServeHTTP and reconciles them with Run.
ServeHTTP stores each delivery as a PENDING event and hands it to Run in the same process. Run also polls the Store for PENDING and FAILED events to retry them and to process the events received by other processes. Each event is dispatched to the Reconciler registered for the event type.
Run can be called concurrently in multiple processes against the same Store. An event is processed only by the process that claimed it. An event waiting longer than maxProcessingDuration since it was created is moved to EXPIRED. An event left in PROCESSING longer than stuckProcessingTimeout is moved back to PENDING.
func NewEventProcessor ¶
func NewEventProcessor(store Store, interval, maxProcessingDuration time.Duration) *EventProcessor
func (*EventProcessor) Register ¶
func (p *EventProcessor) Register[T any](rec Reconciler[T])
Register adds rec. An event whose payload can not be decoded into T is marked as FAILED.
func (*EventProcessor) Run ¶
func (p *EventProcessor) Run(ctx context.Context)
Run reconciles the stored events until ctx is cancelled.
func (*EventProcessor) ServeHTTP ¶
func (p *EventProcessor) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP stores a GitHub webhook delivery as a PENDING event.
type Reconciler ¶
type Reconciler[T any] interface { EventType() string Reconcile(ctx context.Context, ev *Event, payload *T) error }
Reconciler processes an Event of a single event type. payload is the payload of ev decoded into T.
Implementations must be idempotent. Progress should be checkpointed into ev.Status with Event.WriteStatus so that re-running resumes the work instead of duplicating side effects. A reconciler may set ev.State to a terminal state (e.g. StateSkipped) or back to StatePending to be retried. Otherwise the state is decided by Event.Finalize.
type State ¶
type State int32
State is the lifecycle state of an Event. The values are persisted as is.
type Store ¶
type Store interface {
// Create inserts ev and sets ev.Id. It returns ErrDuplicateDelivery if the delivery ID is already stored.
Create(ctx context.Context, ev *Event) error
// ListByState returns the events in any of the given states.
ListByState(ctx context.Context, states ...State) ([]*Event, error)
// Claim updates the state of ev to PROCESSING only if it is PENDING or FAILED, and reports whether it was updated.
Claim(ctx context.Context, ev *Event) (bool, error)
// Update persists State, Status and LastError of ev.
Update(ctx context.Context, ev *Event) error
}
Store persists Events.