fix: operator changes

This commit is contained in:
sidwebworks
2025-08-14 17:05:10 +05:30
parent ef8a7f1233
commit c34ec8de09
3 changed files with 300 additions and 182 deletions
@@ -601,6 +601,10 @@ func (r *InfisicalSecretReconciler) OpenInstantUpdatesStream(ctx context.Context
Event: "secret:delete", Event: "secret:delete",
Conditions: conditions, Conditions: conditions,
}, },
{
Event: "secret:import-mutation",
Conditions: conditions,
},
}, },
}) })
@@ -612,9 +616,10 @@ func (r *InfisicalSecretReconciler) OpenInstantUpdatesStream(ctx context.Context
headers := map[string]string{ headers := map[string]string{
"User-Agent": api.USER_AGENT_NAME, "User-Agent": api.USER_AGENT_NAME,
"Authorization": fmt.Sprint("Bearer ", token), "Authorization": fmt.Sprint("Bearer ", token),
"Content-Type": "application/json",
} }
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/v1/events/subscribe/project-events", api.API_HOST_URL), strings.NewReader(string(body))) req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/events/subscribe/project-events", api.API_HOST_URL), strings.NewReader(string(body)))
if err != nil { if err != nil {
return nil, err return nil, err
+131 -60
View File
@@ -2,48 +2,61 @@ package sse
import ( import (
"bufio" "bufio"
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"sync"
"time" "time"
) )
type SSEEvent struct { // Event represents a Server-Sent Event
type Event struct {
ID string ID string
Event string Event string
Data string Data string
Retry int
} }
// SSEClient handles SSE connections // Client handles SSE connections with high performance
type SSEClient struct { type Client struct {
URL string httpClient *http.Client
Client *http.Client onPing func() // Callback for ping events
LastHealthCheck time.Time
mu *sync.Mutex // for safe concurrent access to LastHealthCheck
} }
// NewClient creates a new SSE client // NewClient creates a new high-performance SSE client
func NewClient() SSEClient { func NewClient() Client {
return SSEClient{ return Client{
mu: &sync.Mutex{}, httpClient: &http.Client{
Client: &http.Client{
Timeout: 0, // No timeout for streaming Timeout: 0, // No timeout for streaming
Transport: &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
DisableCompression: true, // SSE typically doesn't benefit from compression
},
}, },
} }
} }
// Connect establishes SSE connection and returns a channel of events // WithPingHandler sets a callback for ping events
func (c *SSEClient) Connect(req *http.Request) (<-chan SSEEvent, <-chan error, error) { func (c *Client) WithPingHandler(handler func()) *Client {
// Set required headers for SSE c.onPing = handler
req.Header.Set("Cache-Control", "no-cache") return c
req.Header.Set("Connection", "keep-alive") }
req.Header.Set("Content-Type", "application/json")
resp, err := c.Client.Do(req) // Connect establishes an SSE connection and returns event channels
func (c *Client) Connect(ctx context.Context, req *http.Request) (<-chan Event, <-chan error, error) {
// Configure SSE headers
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Accept", "text/event-stream")
req.Header.Set("Connection", "keep-alive")
// Add context to request
req = req.WithContext(ctx)
resp, err := c.httpClient.Do(req)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, fmt.Errorf("request failed: %w", err)
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
@@ -51,66 +64,124 @@ func (c *SSEClient) Connect(req *http.Request) (<-chan SSEEvent, <-chan error, e
return nil, nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) return nil, nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
} }
eventChan := make(chan SSEEvent) eventChan := make(chan Event, 10)
errorChan := make(chan error) errorChan := make(chan error, 1)
go c.stream(resp.Body, eventChan, errorChan) go c.stream(ctx, resp.Body, eventChan, errorChan)
return eventChan, errorChan, nil return eventChan, errorChan, nil
} }
func (c *SSEClient) stream(body io.ReadCloser, eventChan chan<- SSEEvent, errorChan chan<- error) { func (c *Client) stream(ctx context.Context, body io.ReadCloser, eventChan chan<- Event, errorChan chan<- error) {
defer body.Close() defer body.Close()
defer close(eventChan) defer close(eventChan)
defer close(errorChan) defer close(errorChan)
scanner := bufio.NewScanner(body) scanner := bufio.NewScanner(body)
var event SSEEvent
var currentEvent Event
var dataBuilder strings.Builder
for scanner.Scan() { for scanner.Scan() {
line := strings.TrimSpace(scanner.Text()) select {
case <-ctx.Done():
return
default:
}
// End of event line := scanner.Text()
if line == "" {
if event.Data != "" || event.Event != "" { // Empty line indicates end of event
if strings.TrimSpace(event.Data) == "1" { if len(line) == 0 {
c.mu.Lock() if currentEvent.Data != "" || currentEvent.Event != "" {
c.LastHealthCheck = time.Now() // Finalize data
c.mu.Unlock() if dataBuilder.Len() > 0 {
} else if event.Event != "ping" { currentEvent.Data = dataBuilder.String()
eventChan <- event dataBuilder.Reset()
} }
event = SSEEvent{} // Reset for next event // Handle ping events
if c.isPingEvent(currentEvent) {
if c.onPing != nil {
c.onPing()
}
} else {
// Send non-ping events
select {
case eventChan <- currentEvent:
case <-ctx.Done():
return
}
}
// Reset for next event
currentEvent = Event{}
} }
continue continue
} }
switch { // Parse line efficiently
case strings.HasPrefix(line, "data:"): c.parseLine(line, &currentEvent, &dataBuilder)
data := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
if event.Data != "" {
event.Data += "\n"
}
event.Data += data
case strings.HasPrefix(line, "event:"):
event.Event = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
case strings.HasPrefix(line, "id:"):
event.ID = strings.TrimSpace(strings.TrimPrefix(line, "id:"))
case strings.HasPrefix(line, "retry:"):
// Optional: parse and apply retry interval here
case strings.HasPrefix(line, ":"):
// Comment line — ignored
default:
// Unknown line format — can log/debug if needed
}
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
errorChan <- err select {
case errorChan <- err:
case <-ctx.Done():
}
} }
} }
// parseLine efficiently parses SSE protocol lines
func (c *Client) parseLine(line string, event *Event, dataBuilder *strings.Builder) {
colonIndex := strings.IndexByte(line, ':')
if colonIndex == -1 {
return // Invalid line format
}
field := line[:colonIndex]
value := line[colonIndex+1:]
// Trim leading space from value (SSE spec)
if len(value) > 0 && value[0] == ' ' {
value = value[1:]
}
switch field {
case "data":
if dataBuilder.Len() > 0 {
dataBuilder.WriteByte('\n')
}
dataBuilder.WriteString(value)
case "event":
event.Event = value
case "id":
event.ID = value
case "retry":
// Parse retry value if needed
// This could be used to configure reconnection delay
case "":
// Comment line, ignore
}
}
// isPingEvent checks if an event is a ping/keepalive
func (c *Client) isPingEvent(event Event) bool {
// Check for common ping patterns
if event.Event == "ping" {
return true
}
// Check for heartbeat data (common pattern is "1" or similar)
if event.Event == "" && strings.TrimSpace(event.Data) == "1" {
return true
}
return false
}
// WithHTTPClient sets a custom HTTP client
func (c *Client) WithHTTPClient(client *http.Client) *Client {
c.httpClient = client
return c
}
+163 -121
View File
@@ -5,161 +5,203 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
// ConnectionMeta holds metadata about an SSE connection
type ConnectionMeta struct { type ConnectionMeta struct {
EventChan <-chan SSEEvent EventChan <-chan Event
ErrorChan <-chan error ErrorChan <-chan error
LastPingAt time.Time lastPingAt atomic.Value // stores time.Time
Cancel context.CancelFunc cancel context.CancelFunc
} }
type ConnectionRegistry struct { // LastPing returns the last ping time
Ctx context.Context func (c *ConnectionMeta) LastPing() time.Time {
meta *ConnectionMeta if t, ok := c.lastPingAt.Load().(time.Time); ok {
client SSEClient return t
mu sync.RWMutex }
return time.Time{}
monitorCancel context.CancelFunc
monitorCtx context.Context
} }
func NewConnectionRegistry(ctx context.Context) *ConnectionRegistry { // UpdateLastPing atomically updates the last ping time
monitorCtx, monitorCancel := context.WithCancel(ctx) func (c *ConnectionMeta) UpdateLastPing() {
return &ConnectionRegistry{ c.lastPingAt.Store(time.Now())
Ctx: ctx, }
client: NewClient(),
monitorCtx: monitorCtx, // Cancel terminates the connection
monitorCancel: monitorCancel, func (c *ConnectionMeta) Cancel() {
if c.cancel != nil {
c.cancel()
} }
} }
// create creates a new connection // ConnectionRegistry manages SSE connections with high performance
func (r *ConnectionRegistry) create(req *http.Request) (*ConnectionMeta, error) { type ConnectionRegistry struct {
// Create new connection using provided request ctx context.Context
eventChan, errorChan, err := r.client.Connect(req) client Client
mu sync.RWMutex
conn *ConnectionMeta
monitorOnce sync.Once
monitorStop chan struct{}
}
// NewConnectionRegistry creates a new high-performance connection registry
func NewConnectionRegistry(ctx context.Context) *ConnectionRegistry {
r := &ConnectionRegistry{
ctx: ctx,
monitorStop: make(chan struct{}),
}
// Configure client with ping handler
r.client = NewClient()
r.client.WithPingHandler(func() {
r.UpdateLastPing()
})
return r
}
// Subscribe provides SSE events, creating a connection if needed
func (r *ConnectionRegistry) Subscribe(buildRequest func() (*http.Request, error)) (<-chan Event, <-chan error, error) {
// Fast path: check if connection exists
if conn := r.getConnection(); conn != nil {
return conn.EventChan, conn.ErrorChan, nil
}
// Slow path: create new connection under lock
r.mu.Lock()
defer r.mu.Unlock()
// Double-check after acquiring lock
if r.conn != nil {
return r.conn.EventChan, r.conn.ErrorChan, nil
}
req, err := buildRequest()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to build request: %w", err)
}
conn, err := r.createConnection(req)
if err != nil {
return nil, nil, err
}
r.conn = conn
// Start monitor once
r.monitorOnce.Do(func() {
go r.monitorConnections()
})
return conn.EventChan, conn.ErrorChan, nil
}
// Get retrieves the current connection
func (r *ConnectionRegistry) Get() (*ConnectionMeta, bool) {
conn := r.getConnection()
return conn, conn != nil
}
// IsConnected checks if there's an active connection
func (r *ConnectionRegistry) IsConnected() bool {
return r.getConnection() != nil
}
// UpdateLastPing updates the last ping time for the current connection
func (r *ConnectionRegistry) UpdateLastPing() {
if conn := r.getConnection(); conn != nil {
conn.UpdateLastPing()
}
}
// Close gracefully shuts down the registry
func (r *ConnectionRegistry) Close() {
// Stop monitor first
select {
case <-r.monitorStop:
// Already closed
default:
close(r.monitorStop)
}
// Close connection
r.mu.Lock()
if r.conn != nil {
r.conn.Cancel()
r.conn = nil
}
r.mu.Unlock()
}
// getConnection returns the current connection without locking
func (r *ConnectionRegistry) getConnection() *ConnectionMeta {
r.mu.RLock()
conn := r.conn
r.mu.RUnlock()
return conn
}
// createConnection creates a new SSE connection
func (r *ConnectionRegistry) createConnection(req *http.Request) (*ConnectionMeta, error) {
ctx, cancel := context.WithCancel(r.ctx)
eventChan, errorChan, err := r.client.Connect(ctx, req)
if err != nil {
cancel()
return nil, fmt.Errorf("failed to connect: %w", err) return nil, fmt.Errorf("failed to connect: %w", err)
} }
meta := &ConnectionMeta{ meta := &ConnectionMeta{
EventChan: eventChan, EventChan: eventChan,
ErrorChan: errorChan, ErrorChan: errorChan,
LastPingAt: time.Now(), cancel: cancel,
} }
meta.UpdateLastPing()
r.meta = meta
// Start cleanup monitor for this connection (NON-BLOCKING)
go r.monitor(meta)
println("Creating new connection\n")
return meta, nil return meta, nil
} }
// Get retrieves the existing connection // monitorConnections checks connection health periodically
func (r *ConnectionRegistry) Get() (*ConnectionMeta, bool) { func (r *ConnectionRegistry) monitorConnections() {
r.mu.RLock() const (
defer r.mu.RUnlock() checkInterval = 30 * time.Second
return r.meta, r.meta != nil pingTimeout = 2 * time.Minute
} )
// Close closes the connection ticker := time.NewTicker(checkInterval)
func (r *ConnectionRegistry) Close() {
r.mu.Lock()
defer r.mu.Unlock()
if r.meta != nil {
if r.meta.Cancel != nil {
r.meta.Cancel()
}
r.meta = nil
}
// Cancel the monitor
if r.monitorCancel != nil {
r.monitorCancel()
}
}
// IsConnected returns whether there's an active connection
func (r *ConnectionRegistry) IsConnected() bool {
r.mu.RLock()
defer r.mu.RUnlock()
return r.meta != nil
}
// UpdateLastPing updates the last ping time
func (r *ConnectionRegistry) UpdateLastPing() {
r.mu.Lock()
defer r.mu.Unlock()
if r.meta != nil {
r.meta.LastPingAt = time.Now()
}
}
// monitor watches for connection closure and cleans up
func (r *ConnectionRegistry) monitor(meta *ConnectionMeta) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-r.monitorCtx.Done(): case <-r.monitorStop:
// Context cancelled, exit monitor return
case <-r.ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
r.mu.RLock() r.checkConnectionHealth(pingTimeout)
currentMeta := r.meta
r.mu.RUnlock()
// Check if this monitor is still relevant
if currentMeta != meta {
// This connection has been replaced, exit monitor
return
}
if currentMeta != nil && time.Since(currentMeta.LastPingAt) > 2*time.Minute {
fmt.Println("Last ping was more than 2 minutes ago, closing connection")
r.mu.Lock()
if r.meta == meta { // Double-check under lock
if r.meta.Cancel != nil {
r.meta.Cancel()
}
r.meta = nil
}
r.mu.Unlock()
return // Exit monitor after cleanup
}
} }
} }
} }
// Subscribe provides a convenient way to get events from the connection // checkConnectionHealth verifies connection is still alive
func (r *ConnectionRegistry) Subscribe(build func() (*http.Request, error)) (<-chan SSEEvent, <-chan error, error) { func (r *ConnectionRegistry) checkConnectionHealth(timeout time.Duration) {
r.mu.Lock() conn := r.getConnection()
defer r.mu.Unlock() if conn == nil {
return
// Get existing connection if available
if r.meta != nil {
return r.meta.EventChan, r.meta.ErrorChan, nil
} }
req, err := build() if time.Since(conn.LastPing()) > timeout {
// Connection is stale, close it
if err != nil { r.mu.Lock()
return nil, nil, err if r.conn == conn { // Verify it's still the same connection
r.conn.Cancel()
r.conn = nil
}
r.mu.Unlock()
} }
// Create new connection if none exists
meta, err := r.create(req)
if err != nil {
return nil, nil, err
}
return meta.EventChan, meta.ErrorChan, nil
} }