Skip to content

Commit 38c5a45

Browse files
committed
Improve reliability, linting, add deploy target
1 parent 1eec029 commit 38c5a45

6 files changed

Lines changed: 42 additions & 36 deletions

File tree

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: all build test lint clean fmt vet
1+
.PHONY: all build test lint clean fmt vet deploy
22

33
all: fmt vet lint test build
44

@@ -24,6 +24,9 @@ run-server:
2424

2525
run-client:
2626
go run ./cmd/client
27+
28+
deploy:
29+
./hacks/deploy.sh cmd/server/
2730
# BEGIN: lint-install .
2831
# http://github.com/codeGROOVE-dev/lint-install
2932

cmd/server/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,19 @@ func main() {
4848
flag.Parse()
4949

5050
ctx, cancel := context.WithCancel(context.Background())
51-
defer cancel()
5251

5352
// Get webhook secret from flag or environment variable
5453
webhookSecretValue := *webhookSecret
5554

5655
// Validate webhook secret is configured (REQUIRED for security)
5756
if webhookSecretValue == "" {
57+
cancel()
5858
log.Fatal("ERROR: Webhook secret is required for security. Set -webhook-secret or GITHUB_WEBHOOK_SECRET environment variable.")
5959
}
6060

6161
// Validate allowed events is configured (REQUIRED)
6262
if *allowedEvents == "" {
63+
cancel()
6364
log.Fatal("ERROR: Allowed events must be specified. Set -allowed-events or " +
6465
"ALLOWED_WEBHOOK_EVENTS environment variable. Use '*' to allow all events.")
6566
}
@@ -77,6 +78,9 @@ func main() {
7778
log.Printf("Allowing webhook event types: %v", allowedEventTypes)
7879
}
7980

81+
// Defer cancel after all fatal validations
82+
defer cancel()
83+
8084
// CORS support removed - WebSocket clients should handle auth via Authorization header
8185

8286
h := hub.NewHub()

hacks/deploy.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
#!/usr/bin/env bash -eux -o pipefail
1+
#!/usr/bin/env bash
2+
set -eux -o pipefail
23
# Deploys Go program to Cloud Run - from cwd or arg[1].
34
cd "${1:-.}"
45

@@ -10,7 +11,7 @@ SA="$APP@$PROJECT.iam.gserviceaccount.com"
1011
gcloud iam service-accounts describe "$SA" &>/dev/null ||
1112
gcloud iam service-accounts create "$APP" --project="$PROJECT"
1213

13-
grep -q gcr.io $HOME/.docker/config.json ||
14+
grep -q gcr.io "$HOME"/.docker/config.json ||
1415
gcloud auth configure-docker gcr.io
1516

1617
KO_DOCKER_REPO="gcr.io/$PROJECT/$APP" ko publish . |

pkg/client/example_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package client_test
33
import (
44
"context"
55
"fmt"
6-
"io"
76
"log"
87
"log/slog"
98
"os"
@@ -81,7 +80,7 @@ func ExampleClient_gracefulShutdown() {
8180

8281
func ExampleClient_customLogger() {
8382
// Example 1: Silence all logs
84-
silentLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
83+
silentLogger := slog.New(slog.DiscardHandler)
8584

8685
// Example 2: JSON logging to a file
8786
logFile, err := os.Create("client.log")

pkg/hub/websocket.go

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
// Constants for WebSocket timeouts and limits.
2323
const (
2424
pingInterval = 54 * time.Second
25-
readDeadline = 60 * time.Second
25+
readTimeout = 60 * time.Second // Must be > pingInterval to avoid false timeouts
2626
writeTimeout = 10 * time.Second
2727
minTokenLength = 40 // Minimum GitHub token length
2828
maxTokenLength = 255 // Maximum GitHub token length
@@ -376,22 +376,6 @@ func (h *WebSocketHandler) validateAuth(ctx context.Context, ws *websocket.Conn,
376376
return userOrgs, nil
377377
}
378378

379-
// handleClientPing responds to a ping message from the client.
380-
func (*WebSocketHandler) handleClientPing(ws *websocket.Conn, msgMap map[string]any, clientID string) {
381-
pong := map[string]any{"type": "pong"}
382-
// Echo back any sequence number if present
383-
if seq, ok := msgMap["seq"]; ok {
384-
pong["seq"] = seq
385-
}
386-
if err := ws.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {
387-
log.Printf("failed to set write deadline for pong to client %s: %v", clientID, err)
388-
return
389-
}
390-
if err := websocket.JSON.Send(ws, pong); err != nil {
391-
log.Printf("failed to send pong to client %s: %v", clientID, err)
392-
}
393-
}
394-
395379
// Handle handles a WebSocket connection.
396380
//
397381
//nolint:funlen,gocyclo // This function orchestrates the complete WebSocket lifecycle and cannot be split without losing clarity
@@ -495,7 +479,7 @@ func (h *WebSocketHandler) Handle(ws *websocket.Conn) {
495479
}
496480
defer h.connLimiter.Remove(ip)
497481

498-
// Set read deadline for initial subscription
482+
// Set read deadline for initial subscription (shorter timeout for handshake)
499483
if err := ws.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
500484
log.Printf("failed to set deadline for %s: %v", ip, err)
501485
return
@@ -676,18 +660,15 @@ func (h *WebSocketHandler) Handle(ws *websocket.Conn) {
676660
go client.Run(ctx, pingInterval, writeTimeout)
677661

678662
// Handle incoming messages with responsive shutdown
679-
// Use a shorter read timeout to make shutdown more responsive
680-
readTimeout := 2 * time.Second
681-
682663
// Create a ticker for periodic context checks during blocking reads
683664
contextCheckTicker := time.NewTicker(1 * time.Second)
684665
defer contextCheckTicker.Stop()
685666

667+
// Set initial read deadline - must be longer than pingInterval to avoid false timeouts
686668
if err := ws.SetReadDeadline(time.Now().Add(readTimeout)); err != nil {
687669
log.Printf("failed to set read deadline for %s: %v", ip, err)
688670
return
689671
}
690-
// Read deadline set for responsive shutdown
691672

692673
// Message read loop with responsive shutdown
693674
for {
@@ -717,7 +698,7 @@ func (h *WebSocketHandler) Handle(ws *websocket.Conn) {
717698
log.Printf("client %s connection already closed", client.ID)
718699
case strings.Contains(err.Error(), "i/o timeout"):
719700
log.Printf("TIMEOUT: client %s read timeout at %s (no messages received for %v)",
720-
client.ID, time.Now().Format(time.RFC3339), readDeadline)
701+
client.ID, time.Now().Format(time.RFC3339), readTimeout)
721702
default:
722703
log.Printf("client %s read error: %v", client.ID, err)
723704
}
@@ -739,7 +720,17 @@ func (h *WebSocketHandler) Handle(ws *websocket.Conn) {
739720
continue
740721
case "ping":
741722
// Client sent us a ping, send pong back
742-
h.handleClientPing(ws, msgMap, client.ID)
723+
pong := map[string]any{"type": "pong"}
724+
if seq, ok := msgMap["seq"]; ok {
725+
pong["seq"] = seq
726+
}
727+
if err := ws.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {
728+
log.Printf("failed to set write deadline for pong to client %s: %v", client.ID, err)
729+
continue
730+
}
731+
if err := websocket.JSON.Send(ws, pong); err != nil {
732+
log.Printf("failed to send pong to client %s: %v", client.ID, err)
733+
}
743734
continue
744735
case "keepalive", "heartbeat":
745736
// Common keepalive messages - just acknowledge receipt

pkg/webhook/handler.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
138138
prURL := ExtractPRURL(eventType, payload)
139139
if prURL == "" {
140140
// Log full payload to understand the structure
141-
payloadJSON, _ := json.MarshalIndent(payload, "", " ")
142-
logger.Info("no PR URL found in event - full payload", logger.Fields{
143-
"event_type": eventType,
144-
"delivery_id": deliveryID,
145-
"payload": string(payloadJSON),
146-
})
141+
payloadJSON, err := json.MarshalIndent(payload, "", " ")
142+
if err != nil {
143+
logger.Warn("failed to marshal payload for logging", logger.Fields{
144+
"event_type": eventType,
145+
"delivery_id": deliveryID,
146+
"error": err.Error(),
147+
})
148+
} else {
149+
logger.Info("no PR URL found in event - full payload", logger.Fields{
150+
"event_type": eventType,
151+
"delivery_id": deliveryID,
152+
"payload": string(payloadJSON),
153+
})
154+
}
147155
w.WriteHeader(http.StatusOK)
148156
return
149157
}

0 commit comments

Comments
 (0)