2020-10-02 18:23:30 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2020-10-06 20:21:58 +00:00
|
|
|
// Events is a set of events for a corresponding index. Events returned for the
|
2020-10-02 18:37:37 +00:00
|
|
|
// index depend on which topics are subscribed to when a request is made.
|
2020-10-02 18:23:30 +00:00
|
|
|
type Events struct {
|
|
|
|
Index uint64
|
|
|
|
Events []Event
|
2020-10-06 15:08:12 +00:00
|
|
|
Err error
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-02 18:37:37 +00:00
|
|
|
// Topic is an event Topic
|
2020-10-02 18:23:30 +00:00
|
|
|
type Topic string
|
|
|
|
|
2020-10-02 18:37:37 +00:00
|
|
|
// Event holds information related to an event that occurred in Nomad.
|
|
|
|
// The Payload is a hydrated object related to the Topic
|
2020-10-02 18:23:30 +00:00
|
|
|
type Event struct {
|
|
|
|
Topic Topic
|
|
|
|
Type string
|
|
|
|
Key string
|
|
|
|
FilterKeys []string
|
|
|
|
Index uint64
|
2020-10-06 15:08:12 +00:00
|
|
|
Payload map[string]interface{}
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-06 15:08:12 +00:00
|
|
|
// IsHeartbeat specifies if the event is an empty heartbeat used to
|
2020-10-02 18:37:37 +00:00
|
|
|
// keep a connection alive.
|
2020-10-06 15:08:12 +00:00
|
|
|
func (e *Events) IsHeartbeat() bool {
|
2020-10-02 18:23:30 +00:00
|
|
|
return e.Index == 0 && len(e.Events) == 0
|
|
|
|
}
|
|
|
|
|
2020-10-02 18:37:37 +00:00
|
|
|
// EventStream is used to stream events from Nomad
|
2020-10-02 18:23:30 +00:00
|
|
|
type EventStream struct {
|
|
|
|
client *Client
|
|
|
|
}
|
|
|
|
|
2020-10-02 18:37:37 +00:00
|
|
|
// EventStream returns a handle to the Events endpoint
|
2020-10-02 18:23:30 +00:00
|
|
|
func (c *Client) EventStream() *EventStream {
|
|
|
|
return &EventStream{client: c}
|
|
|
|
}
|
|
|
|
|
2020-10-02 18:37:37 +00:00
|
|
|
// Stream establishes a new subscription to Nomad's event stream and streams
|
|
|
|
// results back to the returned channel.
|
2020-10-06 15:08:12 +00:00
|
|
|
func (e *EventStream) Stream(ctx context.Context, topics map[Topic][]string, index uint64, q *QueryOptions) (<-chan *Events, error) {
|
2020-10-02 18:23:30 +00:00
|
|
|
r, err := e.client.newRequest("GET", "/v1/event/stream")
|
|
|
|
if err != nil {
|
2020-10-06 15:08:12 +00:00
|
|
|
return nil, err
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
2020-10-08 18:27:52 +00:00
|
|
|
q = q.WithContext(ctx)
|
2020-10-02 18:23:30 +00:00
|
|
|
r.setQueryOptions(q)
|
|
|
|
|
|
|
|
// Build topic query params
|
|
|
|
for topic, keys := range topics {
|
|
|
|
for _, k := range keys {
|
|
|
|
r.params.Add("topic", fmt.Sprintf("%s:%s", topic, k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, resp, err := requireOK(e.client.doRequest(r))
|
|
|
|
|
|
|
|
if err != nil {
|
2020-10-06 15:08:12 +00:00
|
|
|
return nil, err
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
eventsCh := make(chan *Events, 10)
|
|
|
|
go func() {
|
|
|
|
defer resp.Body.Close()
|
2020-10-06 15:08:12 +00:00
|
|
|
defer close(eventsCh)
|
2020-10-02 18:23:30 +00:00
|
|
|
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
|
2020-10-06 15:08:12 +00:00
|
|
|
for ctx.Err() == nil {
|
2020-10-02 18:23:30 +00:00
|
|
|
// Decode next newline delimited json of events
|
|
|
|
var events Events
|
|
|
|
if err := dec.Decode(&events); err != nil {
|
2020-10-06 20:21:58 +00:00
|
|
|
// set error and fallthrough to
|
|
|
|
// select eventsCh
|
2020-10-06 15:08:12 +00:00
|
|
|
events = Events{Err: err}
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
2020-10-08 18:27:52 +00:00
|
|
|
if events.Err == nil && events.IsHeartbeat() {
|
2020-10-02 18:23:30 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-10-06 15:08:12 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case eventsCh <- &events:
|
|
|
|
}
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-10-06 15:08:12 +00:00
|
|
|
return eventsCh, nil
|
2020-10-02 18:23:30 +00:00
|
|
|
}
|