From 94211772c91a1821baf1febf5f872e4484628c9a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 25 Apr 2026 11:37:38 -0500 Subject: [PATCH] Fix drift counts from runner group inventory --- config/pools.yaml | 5 +++ src/lib/drift.ts | 24 ++++++++----- src/lib/github.ts | 71 +++++++++++++++++++++++++++++++++++++++ test/drift-detect.test.ts | 12 ++----- 4 files changed, 94 insertions(+), 18 deletions(-) diff --git a/config/pools.yaml b/config/pools.yaml index f16b747..10369c4 100644 --- a/config/pools.yaml +++ b/config/pools.yaml @@ -28,8 +28,13 @@ pools: runnerGroup: synology-public repositoryAccess: selected allowedRepositories: + - omt-global/apw-cli - omt-global/axiom + - omt-global/bootstrap + - omt-global/gh-attest - omt-global/github-runner-fleet + - omt-global/home-tv-channel-list + - omt-global/Screensaver labels: - synology - shell-only diff --git a/src/lib/drift.ts b/src/lib/drift.ts index d1a9bf8..0e3ccdc 100644 --- a/src/lib/drift.ts +++ b/src/lib/drift.ts @@ -1,5 +1,6 @@ import type { PoolConfig } from "./config.js"; import { + fetchOrganizationRunnerGroupRunners, fetchOrganizationRunnerGroups, fetchOrganizationRunners, type FetchLike @@ -240,10 +241,12 @@ export async function collectGitHubActualPoolState( const poolsByOrganization = groupByOrganization(desiredPools); for (const [organization, pools] of poolsByOrganization.entries()) { - const [groups, runners] = await Promise.all([ - fetchOrganizationRunnerGroups(apiUrl, organization, token, fetchImpl), - fetchOrganizationRunners(apiUrl, organization, token, fetchImpl) - ]); + const groups = await fetchOrganizationRunnerGroups( + apiUrl, + organization, + token, + fetchImpl + ); for (const pool of pools) { const group = groups.find((entry) => entry.name === pool.runnerGroup); @@ -254,12 +257,17 @@ export async function collectGitHubActualPoolState( ); } + const runners = await fetchOrganizationRunnerGroupRunners( + apiUrl, + organization, + group.id, + token, + fetchImpl + ); + actualPools.push({ name: pool.name, - actual: runners.filter( - (runner) => - runner.runnerGroupId === group.id && runner.status === "online" - ).length + actual: runners.filter((runner) => runner.status === "online").length }); } } diff --git a/src/lib/github.ts b/src/lib/github.ts index e912d1c..31a1b4c 100644 --- a/src/lib/github.ts +++ b/src/lib/github.ts @@ -332,6 +332,77 @@ export async function fetchOrganizationRunners( } } +export async function fetchOrganizationRunnerGroupRunners( + apiUrl: string, + organization: string, + runnerGroupId: number, + token: string, + fetchImpl: FetchLike = fetch as FetchLike +): Promise { + const runners: GitHubRunner[] = []; + + for (let page = 1; ; page += 1) { + const response = await fetchImpl( + `${trimApiUrl(apiUrl)}/orgs/${organization}/actions/runner-groups/${runnerGroupId}/runners?per_page=100&page=${page}`, + { + method: "GET", + headers: buildGitHubApiHeaders(token) + } + ); + + const body = await response.text(); + if (!response.ok) { + throw new Error( + `GitHub runner group runner lookup failed for ${organization}/${runnerGroupId} with ${response.status}: ${body}` + ); + } + + const payload = JSON.parse(body) as { + runners?: Array<{ + id?: number; + name?: string; + status?: string; + busy?: boolean; + runner_group_id?: number; + labels?: Array<{ name?: string }>; + }>; + }; + + if (!Array.isArray(payload.runners)) { + throw new Error( + `GitHub runner group runner response for ${organization}/${runnerGroupId} did not include runners` + ); + } + + runners.push( + ...payload.runners.map((runner) => { + if (typeof runner.id !== "number" || !runner.name || !runner.status) { + throw new Error( + `GitHub runner group runner response for ${organization}/${runnerGroupId} included an invalid runner entry` + ); + } + + return { + id: runner.id, + name: runner.name, + status: runner.status, + busy: runner.busy, + runnerGroupId: runner.runner_group_id ?? runnerGroupId, + labels: Array.isArray(runner.labels) + ? runner.labels + .map((label) => label.name) + .filter((name): name is string => typeof name === "string") + : [] + }; + }) + ); + + if (payload.runners.length < 100) { + return runners; + } + } +} + export async function deleteOrganizationRunner( apiUrl: string, organization: string, diff --git a/test/drift-detect.test.ts b/test/drift-detect.test.ts index e78734a..f9c6214 100644 --- a/test/drift-detect.test.ts +++ b/test/drift-detect.test.ts @@ -150,20 +150,12 @@ describe("drift detection", () => { { id: 1, name: "runner-1", - status: "online", - runner_group_id: 10 + status: "online" }, { id: 2, name: "runner-2", - status: "offline", - runner_group_id: 10 - }, - { - id: 3, - name: "runner-3", - status: "online", - runner_group_id: 11 + status: "offline" } ] })