open-nomad/nomad/stream/webhook_sink.go
Drew Bailey 86080e25a9
Send events to EventSinks (#9171)
* Process to send events to configured sinks

This PR adds a SinkManager to a server which is responsible for managing
managed sinks. Managed sinks subscribe to the event broker and send
events to a sink writer (webhook). When changes to the eventstore are
made the sinkmanager and managed sink are responsible for reloading or
starting a new managed sink.

* periodically check in sink progress to raft

Save progress on the last successfully sent index to raft. This allows a
managed sink to resume close to where it left off in the event of a lost
server or leadership change

dereference eventsink so we can accurately use the watchch

When using a pointer to eventsink struct it was updated immediately and our reload logic would not trigger
2020-10-26 17:27:54 -04:00

89 lines
1.8 KiB
Go

package stream
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/nomad/nomad/structs"
)
func defaultHttpClient() *http.Client {
httpClient := cleanhttp.DefaultClient()
transport := httpClient.Transport.(*http.Transport)
transport.TLSHandshakeTimeout = 10 * time.Second
transport.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
}
return httpClient
}
type WebhookSink struct {
client *http.Client
Address string
}
func NewWebhookSink(eventSink *structs.EventSink) (*WebhookSink, error) {
if eventSink.Address == "" {
return nil, fmt.Errorf("invalid address for websink")
} else if _, err := url.Parse(eventSink.Address); err != nil {
return nil, fmt.Errorf("invalid address '%s' : %v", eventSink.Address, err)
}
httpClient := defaultHttpClient()
return &WebhookSink{
Address: eventSink.Address,
client: httpClient,
}, nil
}
func (ws *WebhookSink) Send(ctx context.Context, e *structs.Events) error {
req, err := ws.toRequest(ctx, e)
if err != nil {
return fmt.Errorf("converting event to request: %w", err)
}
err = ws.doRequest(req)
if err != nil {
return fmt.Errorf("sending request to webhook %w", err)
}
return nil
}
func (ws *WebhookSink) doRequest(req *http.Request) error {
_, err := ws.client.Do(req)
if err != nil {
return err
}
return nil
}
func (ws *WebhookSink) toRequest(ctx context.Context, e *structs.Events) (*http.Request, error) {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
if err := enc.Encode(e); err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, ws.Address, buf)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json")
return req, nil
}