Skip to content

Commit 355dafa

Browse files
committed
Add support for setting or disabling custom TURNSERVER and SPRINKLER backends
1 parent 397dd79 commit 355dafa

4 files changed

Lines changed: 280 additions & 79 deletions

File tree

cmd/reviewGOOSE/cache.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ func (app *App) checkCache(cacheFile, url string, updatedAt time.Time) (cachedDa
9696

9797
// turnData fetches Turn API data with caching.
9898
func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (*turn.CheckResponse, bool, error) {
99+
// If Turn API is disabled, return nil without error
100+
if app.turnClient == nil {
101+
slog.Debug("[TURN] Turn API disabled, skipping", "url", url)
102+
return nil, false, nil
103+
}
104+
99105
hasRunningTests := false
100106
// Validate URL before processing
101107
if err := safebrowse.ValidateURL(url); err != nil {

cmd/reviewGOOSE/github.go

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,51 @@ func (app *App) initClients(ctx context.Context) error {
4444
tc := oauth2.NewClient(ctx, ts)
4545
app.client = github.NewClient(tc)
4646

47-
// Initialize Turn client using default backend
48-
turnClient, err := turn.NewDefaultClient()
49-
if err != nil {
50-
return fmt.Errorf("create turn client: %w", err)
47+
// Check for custom turn server hostname (for self-hosting)
48+
// Set TURNSERVER=disabled to run without Turn API
49+
turnServer := os.Getenv("TURNSERVER")
50+
if turnServer == "disabled" {
51+
slog.Info("Turn API disabled via TURNSERVER=disabled")
52+
} else {
53+
var turnClient *turn.Client
54+
if turnServer != "" {
55+
slog.Info("Using custom turn server", "hostname", turnServer)
56+
turnClient, err = turn.NewClient("https://" + turnServer)
57+
} else {
58+
turnClient, err = turn.NewDefaultClient()
59+
}
60+
if err != nil {
61+
return fmt.Errorf("create turn client: %w", err)
62+
}
63+
turnClient.SetAuthToken(token)
64+
app.turnClient = turnClient
5165
}
52-
turnClient.SetAuthToken(token)
53-
app.turnClient = turnClient
5466

5567
// Initialize sprinkler monitor for real-time events
56-
app.sprinklerMonitor = newSprinklerMonitor(app, token)
68+
// Check for custom sprinkler server hostname (for self-hosting)
69+
// Set SPRINKLER=disabled to run without real-time events
70+
sprinklerServer := os.Getenv("SPRINKLER")
71+
if sprinklerServer == "disabled" {
72+
slog.Info("Sprinkler disabled via SPRINKLER=disabled")
73+
} else {
74+
if sprinklerServer != "" {
75+
slog.Info("Using custom sprinkler server", "hostname", sprinklerServer)
76+
}
77+
app.sprinklerMonitor = newSprinklerMonitor(app, token, sprinklerServer)
78+
}
5779

5880
return nil
5981
}
6082

6183
// initSprinklerOrgs fetches the user's organizations and starts sprinkler monitoring.
6284
func (app *App) initSprinklerOrgs(ctx context.Context) error {
63-
if app.client == nil || app.sprinklerMonitor == nil {
64-
return errors.New("client or sprinkler not initialized")
85+
if app.client == nil {
86+
return errors.New("github client not initialized")
87+
}
88+
// If sprinkler is disabled, skip silently
89+
if app.sprinklerMonitor == nil {
90+
slog.Debug("[SPRINKLER] Sprinkler disabled, skipping org initialization")
91+
return nil
6592
}
6693

6794
// Get current user
@@ -80,22 +107,21 @@ func (app *App) initSprinklerOrgs(ctx context.Context) error {
80107

81108
// Fetch all orgs the user is a member of with retry
82109
opts := &github.ListOptions{PerPage: 100}
83-
var allOrgs []string
110+
var orgs []string
84111

85112
for {
86-
var orgs []*github.Organization
113+
var page []*github.Organization
87114
var resp *github.Response
88115

89116
err := retry.Do(func() error {
90-
// Create timeout context for API call
91117
apiCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
92118
defer cancel()
93119

94-
var retryErr error
95-
orgs, resp, retryErr = app.client.Organizations.List(apiCtx, user, opts)
96-
if retryErr != nil {
97-
slog.Debug("[SPRINKLER] Organizations.List failed (will retry)", "error", retryErr, "page", opts.Page)
98-
return retryErr
120+
var err error
121+
page, resp, err = app.client.Organizations.List(apiCtx, user, opts)
122+
if err != nil {
123+
slog.Debug("[SPRINKLER] Organizations.List failed (will retry)", "error", err, "page", opts.Page)
124+
return err
99125
}
100126
return nil
101127
},
@@ -115,9 +141,9 @@ func (app *App) initSprinklerOrgs(ctx context.Context) error {
115141
return nil // Return nil to avoid blocking startup
116142
}
117143

118-
for _, org := range orgs {
119-
if org.Login != nil {
120-
allOrgs = append(allOrgs, *org.Login)
144+
for _, o := range page {
145+
if o.Login != nil {
146+
orgs = append(orgs, *o.Login)
121147
}
122148
}
123149

@@ -129,12 +155,12 @@ func (app *App) initSprinklerOrgs(ctx context.Context) error {
129155

130156
slog.Info("[SPRINKLER] Discovered user organizations",
131157
"user", user,
132-
"orgs", allOrgs,
133-
"count", len(allOrgs))
158+
"orgs", orgs,
159+
"count", len(orgs))
134160

135161
// Update sprinkler with all orgs at once
136-
if len(allOrgs) > 0 {
137-
app.sprinklerMonitor.updateOrgs(allOrgs)
162+
if len(orgs) > 0 {
163+
app.sprinklerMonitor.updateOrgs(orgs)
138164
if err := app.sprinklerMonitor.start(ctx); err != nil {
139165
return fmt.Errorf("start sprinkler: %w", err)
140166
}
@@ -387,79 +413,78 @@ func (app *App) fetchPRsInternal(ctx context.Context) (incoming []PR, outgoing [
387413
searchStart := time.Now()
388414

389415
// Run both queries in parallel
390-
type queryResult struct {
416+
type qResult struct {
391417
err error
392418
query string
393419
issues []*github.Issue
394420
}
395421

396-
queryResults := make(chan queryResult, 2)
422+
results := make(chan qResult, 2)
397423

398424
// Query 1: PRs involving the user
399425
go func() {
400-
query := fmt.Sprintf("is:open is:pr involves:%s archived:false", user)
401-
slog.Debug("[GITHUB] Searching for PRs", "query", query)
426+
q := fmt.Sprintf("is:open is:pr involves:%s archived:false", user)
427+
slog.Debug("[GITHUB] Searching for PRs", "query", q)
402428

403-
result, err := app.executeGitHubQuery(ctx, query, opts)
429+
res, err := app.executeGitHubQuery(ctx, q, opts)
404430
if err != nil {
405-
queryResults <- queryResult{err: err, query: query}
431+
results <- qResult{err: err, query: q}
406432
} else {
407-
queryResults <- queryResult{issues: result.Issues, query: query}
433+
results <- qResult{issues: res.Issues, query: q}
408434
}
409435
}()
410436

411437
// Query 2: PRs in user-owned repos with no reviewers
412438
go func() {
413-
query := fmt.Sprintf("is:open is:pr user:%s review:none archived:false", user)
414-
slog.Debug("[GITHUB] Searching for PRs", "query", query)
439+
q := fmt.Sprintf("is:open is:pr user:%s review:none archived:false", user)
440+
slog.Debug("[GITHUB] Searching for PRs", "query", q)
415441

416-
result, err := app.executeGitHubQuery(ctx, query, opts)
442+
res, err := app.executeGitHubQuery(ctx, q, opts)
417443
if err != nil {
418-
queryResults <- queryResult{err: err, query: query}
444+
results <- qResult{err: err, query: q}
419445
} else {
420-
queryResults <- queryResult{issues: result.Issues, query: query}
446+
results <- qResult{issues: res.Issues, query: q}
421447
}
422448
}()
423449

424450
// Collect results from both queries
425-
var allIssues []*github.Issue
426-
seenURLs := make(map[string]bool)
427-
var queryErrors []error
451+
var issues []*github.Issue
452+
seen := make(map[string]bool)
453+
var errs []error
428454

429455
for range 2 {
430-
result := <-queryResults
431-
if result.err != nil {
432-
slog.Error("[GITHUB] Query failed", "query", result.query, "error", result.err)
433-
queryErrors = append(queryErrors, result.err)
434-
// Continue processing other query results even if one fails
456+
r := <-results
457+
if r.err != nil {
458+
slog.Error("[GITHUB] Query failed", "query", r.query, "error", r.err)
459+
errs = append(errs, r.err)
435460
continue
436461
}
437-
slog.Debug("[GITHUB] Query completed", "query", result.query, "prCount", len(result.issues))
462+
slog.Debug("[GITHUB] Query completed", "query", r.query, "prCount", len(r.issues))
438463

439464
// Deduplicate PRs based on URL
440-
for _, issue := range result.issues {
465+
for _, issue := range r.issues {
441466
url := issue.GetHTMLURL()
442-
if !seenURLs[url] {
443-
seenURLs[url] = true
444-
allIssues = append(allIssues, issue)
467+
if !seen[url] {
468+
seen[url] = true
469+
issues = append(issues, issue)
445470
}
446471
}
447472
}
448-
slog.Info("[GITHUB] Both searches completed", "duration", time.Since(searchStart), "uniquePRs", len(allIssues))
473+
slog.Info("[GITHUB] Both searches completed", "duration", time.Since(searchStart), "uniquePRs", len(issues))
449474

450475
// If both queries failed, return an error
451-
if len(queryErrors) == 2 {
452-
return nil, nil, fmt.Errorf("all GitHub queries failed: %v", queryErrors)
476+
if len(errs) == 2 {
477+
return nil, nil, fmt.Errorf("all GitHub queries failed: %v", errs)
453478
}
454479

455480
// Limit PRs for performance
456-
if len(allIssues) > maxPRsToProcess {
457-
slog.Info("Limiting PRs for performance", "limit", maxPRsToProcess, "total", len(allIssues))
458-
allIssues = allIssues[:maxPRsToProcess]
481+
if len(issues) > maxPRsToProcess {
482+
slog.Info("Limiting PRs for performance", "limit", maxPRsToProcess, "total", len(issues))
483+
issues = issues[:maxPRsToProcess]
459484
}
460485

461486
// Process GitHub results immediately
462-
for _, issue := range allIssues {
487+
for _, issue := range issues {
463488
if !issue.IsPullRequest() {
464489
continue
465490
}
@@ -503,7 +528,7 @@ func (app *App) fetchPRsInternal(ctx context.Context) (incoming []PR, outgoing [
503528

504529
// Fetch Turn API data
505530
// Always synchronous now for simplicity - Turn API calls are fast with caching
506-
app.fetchTurnDataSync(ctx, allIssues, user, &incoming, &outgoing)
531+
app.fetchTurnDataSync(ctx, issues, user, &incoming, &outgoing)
507532

508533
return incoming, outgoing, nil
509534
}

0 commit comments

Comments
 (0)