mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-16 01:08:03 +00:00
fix(gh): confirm failing workflows on a new connection
GitHub sometimes answers a workflow run listing from a lagging replica. For one workflow the answer held 135 runs and ended three weeks ago, while the current listing held 414 runs. The client reuses one connection, so a stale replica could answer every request of a scan and the dashboard showed a failure that a newer run had already replaced. A failing workflow is now requested again on a new connection that skips the response cache. The client keeps the union of both answers, so the newest run decides.
This commit is contained in:
+49
-12
@@ -262,24 +262,28 @@ func (c *Client) workflows(ctx context.Context, full, branch string) ([]model.Wo
|
||||
if w.State != "active" || len(out) >= maxWorkflows {
|
||||
continue
|
||||
}
|
||||
var runs struct {
|
||||
WorkflowRuns []rawRun `json:"workflow_runs"`
|
||||
}
|
||||
q := url.Values{
|
||||
"branch": {branch},
|
||||
"exclude_pull_requests": {"true"},
|
||||
"per_page": {fmt.Sprint(runsPerWorkflow)},
|
||||
}
|
||||
path := fmt.Sprintf("/repos/%s/actions/workflows/%d/runs", full, w.ID)
|
||||
if err := c.getJSON(ctx, full, path, q, &runs); err != nil {
|
||||
runs, err := c.workflowRuns(ctx, full, w.ID, branch, false)
|
||||
if err != nil {
|
||||
if first == nil {
|
||||
first = fmt.Errorf("%s: %w", w.Name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if wf, ok := buildWorkflow(w.Name, w.HTMLURL, runs.WorkflowRuns); ok {
|
||||
out = append(out, wf)
|
||||
wf, ok := buildWorkflow(w.Name, w.HTMLURL, runs)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// GitHub sometimes answers a run listing from a lagging replica, and a reused connection
|
||||
// keeps hitting it. A failure is what raises an alert, so confirm it on a new connection
|
||||
// and keep the union of both answers.
|
||||
if wf.LastDone != nil && wf.LastDone.Failed() {
|
||||
if again, err := c.workflowRuns(ctx, full, w.ID, branch, true); err == nil {
|
||||
if merged, ok := buildWorkflow(w.Name, w.HTMLURL, mergeRuns(runs, again)); ok {
|
||||
wf = merged
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, wf)
|
||||
}
|
||||
return out, first
|
||||
}
|
||||
@@ -343,3 +347,36 @@ func (c *Client) latestRelease(ctx context.Context, full, repoURL string) (*mode
|
||||
FromTag: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) workflowRuns(ctx context.Context, full string, id int64, branch string, fresh bool) ([]rawRun, error) {
|
||||
var out struct {
|
||||
WorkflowRuns []rawRun `json:"workflow_runs"`
|
||||
}
|
||||
q := url.Values{
|
||||
"branch": {branch},
|
||||
"exclude_pull_requests": {"true"},
|
||||
"per_page": {fmt.Sprint(runsPerWorkflow)},
|
||||
}
|
||||
path := fmt.Sprintf("/repos/%s/actions/workflows/%d/runs", full, id)
|
||||
get := c.getJSON
|
||||
if fresh {
|
||||
get = c.getJSONFresh
|
||||
}
|
||||
if err := get(ctx, full, path, q, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.WorkflowRuns, nil
|
||||
}
|
||||
|
||||
// mergeRuns returns the runs of a and b once each, keyed by run ID.
|
||||
func mergeRuns(a, b []rawRun) []rawRun {
|
||||
seen := make(map[int64]bool, len(a)+len(b))
|
||||
out := make([]rawRun, 0, len(a)+len(b))
|
||||
for _, r := range slices.Concat(a, b) {
|
||||
if !seen[r.ID] {
|
||||
seen[r.ID] = true
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user