mirror of
https://github.com/rancher/steve.git
synced 2025-04-27 02:51:10 +00:00
141 lines
2.8 KiB
Go
141 lines
2.8 KiB
Go
package subscribe
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/rancher/steve/pkg/schemaserver/types"
|
|
)
|
|
|
|
type WatchSession struct {
|
|
sync.Mutex
|
|
|
|
apiOp *types.APIRequest
|
|
watchers map[string]func()
|
|
wg sync.WaitGroup
|
|
ctx context.Context
|
|
cancel func()
|
|
}
|
|
|
|
func (s *WatchSession) stop(id string, resp chan<- types.APIEvent) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
if cancel, ok := s.watchers[id]; ok {
|
|
cancel()
|
|
resp <- types.APIEvent{
|
|
Name: "resource.stop",
|
|
ResourceType: id,
|
|
}
|
|
}
|
|
delete(s.watchers, id)
|
|
}
|
|
|
|
func (s *WatchSession) add(resourceType, revision string, resp chan<- types.APIEvent) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
ctx, cancel := context.WithCancel(s.ctx)
|
|
s.watchers[resourceType] = cancel
|
|
|
|
s.wg.Add(1)
|
|
go func() {
|
|
defer s.wg.Done()
|
|
defer s.stop(resourceType, resp)
|
|
|
|
if err := s.stream(ctx, resourceType, revision, resp); err != nil {
|
|
sendErr(resp, err, resourceType)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (s *WatchSession) stream(ctx context.Context, resourceType, revision string, result chan<- types.APIEvent) error {
|
|
schema := s.apiOp.Schemas.LookupSchema(resourceType)
|
|
if schema == nil {
|
|
return fmt.Errorf("failed to find schema %s", resourceType)
|
|
} else if schema.Store == nil {
|
|
return fmt.Errorf("schema %s does not support watching", resourceType)
|
|
}
|
|
|
|
if err := s.apiOp.AccessControl.CanWatch(s.apiOp, schema); err != nil {
|
|
return err
|
|
}
|
|
|
|
c, err := schema.Store.Watch(s.apiOp.WithContext(ctx), schema, types.WatchRequest{Revision: revision})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result <- types.APIEvent{
|
|
Name: "resource.start",
|
|
ResourceType: resourceType,
|
|
}
|
|
|
|
for event := range c {
|
|
result <- event
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewWatchSession(apiOp *types.APIRequest) *WatchSession {
|
|
ws := &WatchSession{
|
|
apiOp: apiOp,
|
|
watchers: map[string]func(){},
|
|
}
|
|
|
|
ws.ctx, ws.cancel = context.WithCancel(apiOp.Request.Context())
|
|
return ws
|
|
}
|
|
|
|
func (s *WatchSession) Watch(conn *websocket.Conn) <-chan types.APIEvent {
|
|
result := make(chan types.APIEvent, 100)
|
|
go func() {
|
|
defer close(result)
|
|
|
|
if err := s.watch(conn, result); err != nil {
|
|
sendErr(result, err, "")
|
|
}
|
|
}()
|
|
return result
|
|
}
|
|
|
|
func (s *WatchSession) Close() {
|
|
s.cancel()
|
|
s.wg.Wait()
|
|
}
|
|
|
|
func (s *WatchSession) watch(conn *websocket.Conn, resp chan types.APIEvent) error {
|
|
defer s.wg.Wait()
|
|
defer s.cancel()
|
|
|
|
for {
|
|
_, r, err := conn.NextReader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var sub Subscribe
|
|
|
|
if err := json.NewDecoder(r).Decode(&sub); err != nil {
|
|
sendErr(resp, err, "")
|
|
continue
|
|
}
|
|
|
|
if sub.Stop {
|
|
s.stop(sub.ResourceType, resp)
|
|
} else if _, ok := s.watchers[sub.ResourceType]; !ok {
|
|
s.add(sub.ResourceType, sub.ResourceVersion, resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendErr(resp chan<- types.APIEvent, err error, resourceType string) {
|
|
resp <- types.APIEvent{
|
|
ResourceType: resourceType,
|
|
Error: err,
|
|
}
|
|
}
|