86080e25a9
* 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
89 lines
1.8 KiB
Go
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
|
|
}
|