From 8fc16a269cdd9eff8084140e437dde8022526231 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 17:11:28 +0200 Subject: [PATCH 01/21] refactor(runners): centralize provider plugins --- lambdas/functions/control-plane/package.json | 2 +- .../control-plane/src/pool/pool-provider.ts | 37 +--- .../control-plane/src/pool/pool.test.ts | 23 ++- .../functions/control-plane/src/pool/pool.ts | 9 +- .../control-plane/src/provider-registry.ts | 7 + .../src/runner-provider-registry.test.ts | 53 ------ .../src/runner-provider-registry.ts | 34 ---- .../src/scale-runners/scale-down-provider.ts | 31 +--- .../src/scale-runners/scale-down.test.ts | 36 ++-- .../src/scale-runners/scale-down.ts | 9 +- .../src/scale-runners/scale-up-provider.ts | 40 +--- .../src/scale-runners/scale-up.ts | 9 +- .../control-plane/src/scale-runners/types.ts | 22 +-- lambdas/functions/webhook/package.json | 2 +- .../src/runners/aws-dynamic-labels-policy.ts | 16 +- .../runners/aws-dynamic-labels-provider.ts | 19 -- .../src/runners/aws-dynamic-labels.test.ts | 2 +- .../webhook/src/runners/aws-dynamic-labels.ts | 18 +- lambdas/functions/webhook/src/sqs/index.ts | 2 +- lambdas/libs/runner-provider/package.json | 23 --- lambdas/libs/runner-provider/src/index.ts | 28 --- lambdas/libs/runner-provider/tsconfig.json | 5 - lambdas/libs/runner-provider/vitest.config.ts | 17 -- .../runner-providers/aws/ec2/control-plane.ts | 19 ++ .../libs/runner-providers/aws/ec2/index.ts | 13 ++ .../ec2/src/control-plane/dynamic-labels.ts} | 2 +- .../aws/ec2/src/control-plane/pool.test.ts} | 8 +- .../aws/ec2/src/control-plane/pool.ts} | 31 ++-- .../ec2/src/control-plane/runner-config.ts} | 17 +- .../aws/ec2/src/control-plane/runners.d.ts} | 2 +- .../ec2/src/control-plane/runners.test.ts} | 6 +- .../aws/ec2/src/control-plane/runners.ts} | 2 +- .../ec2/src/control-plane/scale-down.test.ts} | 16 +- .../aws/ec2/src/control-plane/scale-down.ts} | 6 +- .../ec2/src/control-plane/scale-up.test.ts} | 22 +-- .../aws/ec2/src/control-plane/scale-up.ts} | 42 +++-- .../aws/ec2/src/environment.d.ts | 18 ++ .../webhook/dynamic-labels-policy.test.ts} | 2 +- .../ec2/src/webhook/dynamic-labels-policy.ts} | 2 +- .../ec2/src/webhook/dynamic-labels.test.ts} | 6 +- .../aws/ec2/src/webhook/dynamic-labels.ts} | 14 +- .../aws/ec2/src/webhook/types.ts | 7 + .../libs/runner-providers/aws/ec2/webhook.ts | 9 + lambdas/libs/runner-providers/contracts.ts | 49 +++++ .../libs/runner-providers/control-plane.ts | 16 ++ .../core}/index.test.ts | 22 ++- lambdas/libs/runner-providers/core/index.ts | 171 ++++++++++++++++++ lambdas/libs/runner-providers/package.json | 37 ++++ .../libs/runner-providers/providers.config.ts | 5 + .../libs/runner-providers/registry.test.ts | 21 +++ lambdas/libs/runner-providers/tsconfig.json | 5 + .../libs/runner-providers/vitest.config.ts | 8 + lambdas/libs/runner-providers/webhook.ts | 13 ++ lambdas/yarn.lock | 20 +- 54 files changed, 618 insertions(+), 437 deletions(-) create mode 100644 lambdas/functions/control-plane/src/provider-registry.ts delete mode 100644 lambdas/functions/control-plane/src/runner-provider-registry.test.ts delete mode 100644 lambdas/functions/control-plane/src/runner-provider-registry.ts delete mode 100644 lambdas/functions/webhook/src/runners/aws-dynamic-labels-provider.ts delete mode 100644 lambdas/libs/runner-provider/package.json delete mode 100644 lambdas/libs/runner-provider/src/index.ts delete mode 100644 lambdas/libs/runner-provider/tsconfig.json delete mode 100644 lambdas/libs/runner-provider/vitest.config.ts create mode 100644 lambdas/libs/runner-providers/aws/ec2/control-plane.ts create mode 100644 lambdas/libs/runner-providers/aws/ec2/index.ts rename lambdas/{functions/control-plane/src/scale-runners/ec2-labels.ts => libs/runner-providers/aws/ec2/src/control-plane/dynamic-labels.ts} (99%) rename lambdas/{functions/control-plane/src/pool/ec2-pool.test.ts => libs/runner-providers/aws/ec2/src/control-plane/pool.test.ts} (88%) rename lambdas/{functions/control-plane/src/pool/ec2-pool.ts => libs/runner-providers/aws/ec2/src/control-plane/pool.ts} (74%) rename lambdas/{functions/control-plane/src/scale-runners/ec2.ts => libs/runner-providers/aws/ec2/src/control-plane/runner-config.ts} (93%) rename lambdas/{functions/control-plane/src/aws/ec2-runners.d.ts => libs/runner-providers/aws/ec2/src/control-plane/runners.d.ts} (96%) rename lambdas/{functions/control-plane/src/aws/ec2-runners.test.ts => libs/runner-providers/aws/ec2/src/control-plane/runners.test.ts} (99%) rename lambdas/{functions/control-plane/src/aws/ec2-runners.ts => libs/runner-providers/aws/ec2/src/control-plane/runners.ts} (99%) rename lambdas/{functions/control-plane/src/scale-runners/ec2-scale-down.test.ts => libs/runner-providers/aws/ec2/src/control-plane/scale-down.test.ts} (97%) rename lambdas/{functions/control-plane/src/scale-runners/ec2-scale-down.ts => libs/runner-providers/aws/ec2/src/control-plane/scale-down.ts} (84%) rename lambdas/{functions/control-plane/src/scale-runners/ec2-scale-up.test.ts => libs/runner-providers/aws/ec2/src/control-plane/scale-up.test.ts} (99%) rename lambdas/{functions/control-plane/src/scale-runners/ec2-scale-up.ts => libs/runner-providers/aws/ec2/src/control-plane/scale-up.ts} (73%) create mode 100644 lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts rename lambdas/{functions/webhook/src/runners/ec2-dynamic-labels-policy.test.ts => libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.test.ts} (99%) rename lambdas/{functions/webhook/src/runners/ec2-dynamic-labels-policy.ts => libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts} (98%) rename lambdas/{functions/webhook/src/runners/ec2-dynamic-labels.test.ts => libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts} (90%) rename lambdas/{functions/webhook/src/runners/ec2-dynamic-labels.ts => libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts} (76%) create mode 100644 lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts create mode 100644 lambdas/libs/runner-providers/aws/ec2/webhook.ts create mode 100644 lambdas/libs/runner-providers/contracts.ts create mode 100644 lambdas/libs/runner-providers/control-plane.ts rename lambdas/libs/{runner-provider/src => runner-providers/core}/index.test.ts (61%) create mode 100644 lambdas/libs/runner-providers/core/index.ts create mode 100644 lambdas/libs/runner-providers/package.json create mode 100644 lambdas/libs/runner-providers/providers.config.ts create mode 100644 lambdas/libs/runner-providers/registry.test.ts create mode 100644 lambdas/libs/runner-providers/tsconfig.json create mode 100644 lambdas/libs/runner-providers/vitest.config.ts create mode 100644 lambdas/libs/runner-providers/webhook.ts diff --git a/lambdas/functions/control-plane/package.json b/lambdas/functions/control-plane/package.json index f3a3481b50..e886920b1f 100644 --- a/lambdas/functions/control-plane/package.json +++ b/lambdas/functions/control-plane/package.json @@ -32,7 +32,7 @@ "dependencies": { "@aws-github-runner/aws-powertools-util": "*", "@aws-github-runner/aws-ssm-util": "*", - "@aws-github-runner/runner-provider": "*", + "@aws-github-runner/runner-providers": "*", "@aws-lambda-powertools/parameters": "^2.31.0", "@aws-sdk/client-ec2": "^3.1009.0", "@aws-sdk/client-sqs": "^3.1009.0", diff --git a/lambdas/functions/control-plane/src/pool/pool-provider.ts b/lambdas/functions/control-plane/src/pool/pool-provider.ts index fa12b43a5e..9c5638a158 100644 --- a/lambdas/functions/control-plane/src/pool/pool-provider.ts +++ b/lambdas/functions/control-plane/src/pool/pool-provider.ts @@ -1,31 +1,6 @@ -import type { Octokit } from '@octokit/rest'; -import type { RunnerProvider } from '@aws-github-runner/runner-provider'; - -import type { CreateGitHubRunnerConfig, GitHubRunnerType } from '../scale-runners/types'; - -export interface RunnerStatus { - busy: boolean; - status: string; -} - -export interface ListPoolRunnersInput { - environment: string; - runnerOwner: string; - runnerType: GitHubRunnerType; -} - -export interface CreatePoolRunnersInput { - githubRunnerConfig: CreateGitHubRunnerConfig; - numberOfRunners: number; - githubInstallationClient: Octokit; -} - -export interface PoolRunnerProvider extends RunnerProvider { - listRunners(input: ListPoolRunnersInput): Promise; - countAvailableRunners( - runners: TRunner[], - runnerStatus: Map, - includeBusyRunners: boolean, - ): number; - createRunners(input: CreatePoolRunnersInput): Promise; -} +export type { + CreatePoolRunnersInput, + ListPoolRunnersInput, + PoolRunnerProvider, + RunnerStatus, +} from '@aws-github-runner/runner-providers/core'; diff --git a/lambdas/functions/control-plane/src/pool/pool.test.ts b/lambdas/functions/control-plane/src/pool/pool.test.ts index 643cbaffe4..fe820d3533 100644 --- a/lambdas/functions/control-plane/src/pool/pool.test.ts +++ b/lambdas/functions/control-plane/src/pool/pool.test.ts @@ -2,9 +2,9 @@ import { Octokit } from '@octokit/rest'; import moment from 'moment-timezone'; import * as nock from 'nock'; -import { listEC2Runners } from '../aws/ec2-runners'; +import { createRunners } from '@aws-github-runner/runner-providers/aws/ec2/control-plane/runner-config'; +import { listEC2Runners } from '@aws-github-runner/runner-providers/aws/ec2/control-plane/runners'; import * as ghAuth from '../github/auth'; -import { createRunners } from '../scale-runners/ec2'; import { getGitHubEnterpriseApiUrl } from '../scale-runners/github-runner'; import { adjust } from './pool'; import { describe, it, expect, beforeEach, vi, MockedClass } from 'vitest'; @@ -26,7 +26,7 @@ vi.mock('@octokit/rest', () => ({ }), })); -vi.mock('./../aws/ec2-runners', async () => ({ +vi.mock('@aws-github-runner/runner-providers/aws/ec2/control-plane/runners', async () => ({ listEC2Runners: vi.fn(), // Include any other functions from the module that might be used bootTimeExceeded: vi.fn(), @@ -37,12 +37,15 @@ vi.mock('./../github/auth', async () => ({ createOctokitClient: vi.fn(), })); -vi.mock('../scale-runners/ec2', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('@aws-github-runner/runner-providers/aws/ec2/control-plane/runner-config', async (importOriginal) => ({ + ...(await importOriginal< + typeof import('@aws-github-runner/runner-providers/aws/ec2/control-plane/runner-config') + >()), createRunners: vi.fn(), })); vi.mock('../scale-runners/github-runner', async () => ({ + createStartRunnerConfig: vi.fn(), getGitHubEnterpriseApiUrl: vi.fn().mockReturnValue({ ghesApiUrl: '', ghesBaseUrl: '', @@ -206,6 +209,7 @@ describe('Test simple pool.', () => { expect.anything(), 1, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -223,6 +227,7 @@ describe('Test simple pool.', () => { expect.anything(), 8, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -234,6 +239,7 @@ describe('Test simple pool.', () => { expect.anything(), 8, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -323,6 +329,7 @@ describe('Test simple pool.', () => { expect.anything(), 3, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -344,6 +351,7 @@ describe('Test simple pool.', () => { expect.anything(), 3, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -400,6 +408,7 @@ describe('Test simple pool.', () => { expect.anything(), 1, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -437,6 +446,7 @@ describe('Test simple pool.', () => { expect.anything(), 2, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -451,6 +461,7 @@ describe('Test simple pool.', () => { expect.anything(), 1, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -464,6 +475,7 @@ describe('Test simple pool.', () => { expect.anything(), 8, expect.anything(), + expect.anything(), 'pool-lambda', ); }); @@ -488,6 +500,7 @@ describe('Test simple pool.', () => { expect.anything(), 2, expect.anything(), + expect.anything(), 'pool-lambda', ); }); diff --git a/lambdas/functions/control-plane/src/pool/pool.ts b/lambdas/functions/control-plane/src/pool/pool.ts index 5ef5fa897f..8577918e62 100644 --- a/lambdas/functions/control-plane/src/pool/pool.ts +++ b/lambdas/functions/control-plane/src/pool/pool.ts @@ -1,10 +1,10 @@ import { Octokit } from '@octokit/rest'; import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { resolveRunnerProviderType } from '@aws-github-runner/runner-provider'; +import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/core'; import yn from 'yn'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; -import { createPoolRunnerProvider } from '../runner-provider-registry'; +import { controlPlaneProviderRegistry } from '../provider-registry'; import { getGitHubEnterpriseApiUrl, validateSsmParameterStoreTags } from '../scale-runners/github-runner'; import type { RunnerStatus } from './pool-provider'; @@ -17,7 +17,10 @@ export interface PoolEvent { export async function adjust(event: PoolEvent): Promise { const runnerProviderType = resolveRunnerProviderType(event.type); - const runnerProvider = createPoolRunnerProvider(runnerProviderType); + const runnerProvider = { + ...controlPlaneProviderRegistry.capability(runnerProviderType, 'pool')(), + type: runnerProviderType, + }; logger.info(`Checking current ${runnerProvider.type} pool size against pool of size: ${event.poolSize}`); const runnerLabels = process.env.RUNNER_LABELS || ''; const runnerGroup = process.env.RUNNER_GROUP_NAME || ''; diff --git a/lambdas/functions/control-plane/src/provider-registry.ts b/lambdas/functions/control-plane/src/provider-registry.ts new file mode 100644 index 0000000000..383f872a03 --- /dev/null +++ b/lambdas/functions/control-plane/src/provider-registry.ts @@ -0,0 +1,7 @@ +import { createControlPlaneProviderRegistry } from '@aws-github-runner/runner-providers/control-plane'; + +import { createStartRunnerConfig } from './scale-runners/github-runner'; + +export const controlPlaneProviderRegistry = createControlPlaneProviderRegistry(createStartRunnerConfig); + +export const runnerProviderTypes = controlPlaneProviderRegistry.types; diff --git a/lambdas/functions/control-plane/src/runner-provider-registry.test.ts b/lambdas/functions/control-plane/src/runner-provider-registry.test.ts deleted file mode 100644 index f22de57530..0000000000 --- a/lambdas/functions/control-plane/src/runner-provider-registry.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; - -import { createEc2PoolProvider } from './pool/ec2-pool'; -import type { PoolRunnerProvider } from './pool/pool-provider'; -import { - createPoolRunnerProvider, - createScaleDownRunnerProvider, - createScaleUpRunnerProvider, -} from './runner-provider-registry'; -import { createEc2ScaleDownProvider } from './scale-runners/ec2-scale-down'; -import { createEc2ScaleUpProvider } from './scale-runners/ec2-scale-up'; -import type { ScaleDownRunnerProvider } from './scale-runners/scale-down-provider'; -import type { ScaleUpRunnerProvider } from './scale-runners/scale-up-provider'; - -vi.mock('./pool/ec2-pool', () => ({ createEc2PoolProvider: vi.fn() })); -vi.mock('./scale-runners/ec2-scale-down', () => ({ createEc2ScaleDownProvider: vi.fn() })); -vi.mock('./scale-runners/ec2-scale-up', () => ({ createEc2ScaleUpProvider: vi.fn() })); - -const poolImplementation = { - listRunners: vi.fn(async () => []), - countAvailableRunners: vi.fn(() => 0), - createRunners: vi.fn(async () => []), -} satisfies Omit; - -const scaleUpImplementation = { - prepareGroup: vi.fn(async () => ({ runnerLabels: [], state: undefined })), - getCurrentRunners: vi.fn(async () => 0), - createRunners: vi.fn(async () => []), -} satisfies Omit; - -const scaleDownImplementation = { - list: vi.fn(async () => []), - bootTimeExceeded: vi.fn(() => false), - markOrphan: vi.fn(async () => undefined), - unmarkOrphan: vi.fn(async () => undefined), - terminate: vi.fn(async () => undefined), -} satisfies Omit; - -describe('runner provider registry', () => { - it('routes EC2 capabilities and injects the provider type', () => { - vi.mocked(createEc2PoolProvider).mockReturnValue(poolImplementation); - vi.mocked(createEc2ScaleUpProvider).mockReturnValue(scaleUpImplementation); - vi.mocked(createEc2ScaleDownProvider).mockReturnValue(scaleDownImplementation); - - expect(createPoolRunnerProvider('ec2')).toStrictEqual({ ...poolImplementation, type: 'ec2' }); - expect(createScaleUpRunnerProvider('ec2')).toStrictEqual({ ...scaleUpImplementation, type: 'ec2' }); - expect(createScaleDownRunnerProvider('ec2')).toStrictEqual({ ...scaleDownImplementation, type: 'ec2' }); - - expect(createEc2PoolProvider).toHaveBeenCalledTimes(1); - expect(createEc2ScaleUpProvider).toHaveBeenCalledTimes(1); - expect(createEc2ScaleDownProvider).toHaveBeenCalledTimes(1); - }); -}); diff --git a/lambdas/functions/control-plane/src/runner-provider-registry.ts b/lambdas/functions/control-plane/src/runner-provider-registry.ts deleted file mode 100644 index 95c342ebdf..0000000000 --- a/lambdas/functions/control-plane/src/runner-provider-registry.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { RunnerProviderType } from '@aws-github-runner/runner-provider'; - -import { createEc2PoolProvider } from './pool/ec2-pool'; -import type { PoolRunnerProvider } from './pool/pool-provider'; -import { createEc2ScaleDownProvider } from './scale-runners/ec2-scale-down'; -import { createEc2ScaleUpProvider } from './scale-runners/ec2-scale-up'; -import type { ScaleDownRunnerProvider } from './scale-runners/scale-down-provider'; -import type { ScaleUpRunnerProvider } from './scale-runners/scale-up-provider'; - -interface RunnerProviderFactory { - pool: () => Omit; - scaleUp: () => Omit; - scaleDown: () => Omit; -} - -const runnerProviderFactories: Record = { - ec2: { - pool: createEc2PoolProvider, - scaleUp: createEc2ScaleUpProvider, - scaleDown: createEc2ScaleDownProvider, - }, -}; - -export function createPoolRunnerProvider(type: RunnerProviderType): PoolRunnerProvider { - return { ...runnerProviderFactories[type].pool(), type }; -} - -export function createScaleUpRunnerProvider(type: RunnerProviderType): ScaleUpRunnerProvider { - return { ...runnerProviderFactories[type].scaleUp(), type }; -} - -export function createScaleDownRunnerProvider(type: RunnerProviderType): ScaleDownRunnerProvider { - return { ...runnerProviderFactories[type].scaleDown(), type }; -} diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down-provider.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down-provider.ts index c4966201aa..54490e482c 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down-provider.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down-provider.ts @@ -1,26 +1,5 @@ -import type { RunnerProvider } from '@aws-github-runner/runner-provider'; - -export interface RunnerList { - id: string; - launchTime?: Date; - owner?: string; - type?: string; - repo?: string; - org?: string; - orphan?: boolean; - githubRunnerId?: string; - bypassRemoval?: boolean; -} - -export interface RunnerInfo extends RunnerList { - owner: string; - type: string; -} - -export interface ScaleDownRunnerProvider extends RunnerProvider { - list(environment: string, orphan?: boolean): Promise; - bootTimeExceeded(runner: RunnerInfo): boolean; - markOrphan(id: string): Promise; - unmarkOrphan(id: string): Promise; - terminate(id: string): Promise; -} +export type { + ScaleDownRunnerInfo as RunnerInfo, + ScaleDownRunnerList as RunnerList, + ScaleDownRunnerProvider, +} from '@aws-github-runner/runner-providers/core'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down.test.ts index b53feeee5a..c11c74112d 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down.test.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down.test.ts @@ -1,31 +1,31 @@ import moment from 'moment'; import { describe, expect, it } from 'vitest'; -import { RunnerInfo } from '../aws/ec2-runners.d'; import { newestFirstStrategy, oldestFirstStrategy } from './scale-down'; +import type { RunnerInfo } from './scale-down-provider'; describe('When runners are sorted', () => { const runners: RunnerInfo[] = [ { - instanceId: '1', + id: '1', launchTime: moment(new Date()).subtract(1, 'minute').toDate(), owner: 'owner', type: 'type', }, { - instanceId: '3', + id: '3', launchTime: moment(new Date()).subtract(3, 'minute').toDate(), owner: 'owner', type: 'type', }, { - instanceId: '2', + id: '2', launchTime: moment(new Date()).subtract(2, 'minute').toDate(), owner: 'owner', type: 'type', }, { - instanceId: '0', + id: '0', launchTime: moment(new Date()).subtract(0, 'minute').toDate(), owner: 'owner', type: 'type', @@ -34,31 +34,31 @@ describe('When runners are sorted', () => { it('Should sort runners descending for eviction strategy oldest first te keep the youngest.', () => { runners.sort(oldestFirstStrategy); - expect(runners[0].instanceId).toEqual('0'); - expect(runners[1].instanceId).toEqual('1'); - expect(runners[2].instanceId).toEqual('2'); - expect(runners[3].instanceId).toEqual('3'); + expect(runners[0].id).toEqual('0'); + expect(runners[1].id).toEqual('1'); + expect(runners[2].id).toEqual('2'); + expect(runners[3].id).toEqual('3'); }); it('Should sort runners ascending for eviction strategy newest first te keep oldest.', () => { runners.sort(newestFirstStrategy); - expect(runners[0].instanceId).toEqual('3'); - expect(runners[1].instanceId).toEqual('2'); - expect(runners[2].instanceId).toEqual('1'); - expect(runners[3].instanceId).toEqual('0'); + expect(runners[0].id).toEqual('3'); + expect(runners[1].id).toEqual('2'); + expect(runners[2].id).toEqual('1'); + expect(runners[3].id).toEqual('0'); }); it('Should sort runners with equal launch time.', () => { const runnersTest = [...runners]; const same = moment(new Date()).subtract(4, 'minute').toDate(); runnersTest.push({ - instanceId: '4', + id: '4', launchTime: same, owner: 'owner', type: 'type', }); runnersTest.push({ - instanceId: '5', + id: '5', launchTime: same, owner: 'owner', type: 'type', @@ -77,19 +77,19 @@ describe('When runners are sorted', () => { it('Should sort runners even when launch time is undefined.', () => { const runnersTest = [ { - instanceId: '0', + id: '0', launchTime: undefined, owner: 'owner', type: 'type', }, { - instanceId: '1', + id: '1', launchTime: moment(new Date()).subtract(3, 'minute').toDate(), owner: 'owner', type: 'type', }, { - instanceId: '0', + id: '0', launchTime: undefined, owner: 'owner', type: 'type', diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down.ts index fdadc9fcc0..027b8ef121 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down.ts @@ -2,11 +2,11 @@ import { Octokit } from '@octokit/rest'; import { Endpoints } from '@octokit/types'; import { RequestError } from '@octokit/request-error'; import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { resolveRunnerProviderType } from '@aws-github-runner/runner-provider'; +import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/core'; import moment from 'moment'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; -import { createScaleDownRunnerProvider } from '../runner-provider-registry'; +import { controlPlaneProviderRegistry } from '../provider-registry'; import { GhRunners, githubCache } from './cache'; import { ScalingDownConfigList, getEvictionStrategy, getIdleRunnerCount } from './scale-down-config'; import { metricGitHubAppRateLimit } from '../github/rate-limit'; @@ -354,7 +354,10 @@ export async function scaleDown(): Promise { const environment = process.env.ENVIRONMENT; const scaleDownConfigs = JSON.parse(process.env.SCALE_DOWN_CONFIG) as ScalingDownConfigList; const runnerProviderType = resolveRunnerProviderType(process.env.RUNNER_PROVIDER_TYPE); - const runnerProvider = createScaleDownRunnerProvider(runnerProviderType); + const runnerProvider = { + ...controlPlaneProviderRegistry.capability(runnerProviderType, 'scaleDown')(), + type: runnerProviderType, + }; // first runners marked to be orphan. await terminateOrphan(environment, runnerProvider); diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up-provider.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up-provider.ts index 52ee07d2b5..5121d80238 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-up-provider.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up-provider.ts @@ -1,33 +1,7 @@ -import type { Octokit } from '@octokit/rest'; -import type { RunnerProvider } from '@aws-github-runner/runner-provider'; - -import type { CreateGitHubRunnerConfig, GitHubRunnerType } from './types'; - -export interface CurrentRunnersInput { - runnerType: GitHubRunnerType; - runnerOwner: string; -} - -export interface CreateScaleUpRunnersInput { - githubRunnerConfig: CreateGitHubRunnerConfig; - numberOfRunners: number; - githubInstallationClient: Octokit; - state: TState; -} - -export interface PreparedScaleUpRunnerGroup { - runnerLabels: string[]; - state: TState; -} - -export interface CreateScaleUpRunnersResult { - instances: string[]; - retryableErrorCount: number; - nonRetryableErrorCount: number; -} - -export interface ScaleUpRunnerProvider extends RunnerProvider { - prepareGroup(messageLabels: string[]): Promise>; - getCurrentRunners(state: TState, input: CurrentRunnersInput): Promise; - createRunners(input: CreateScaleUpRunnersInput): Promise; -} +export type { + CreateScaleUpRunnersInput, + CreateScaleUpRunnersResult, + CurrentRunnersInput, + PreparedScaleUpRunnerGroup, + ScaleUpRunnerProvider, +} from '@aws-github-runner/runner-providers/core'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up.ts index 4b363f4e55..d2eb743c84 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-up.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up.ts @@ -1,10 +1,10 @@ import { addPersistentContextToChildLogger, createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { resolveRunnerProviderType } from '@aws-github-runner/runner-provider'; +import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/core'; import { Octokit } from '@octokit/rest'; import yn from 'yn'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; -import { createScaleUpRunnerProvider } from '../runner-provider-registry'; +import { controlPlaneProviderRegistry } from '../provider-registry'; import { getGitHubEnterpriseApiUrl, getInstallationId, @@ -87,7 +87,10 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise; -} +export type { AwsDynamicLabelsPolicy, AwsDynamicLabelsValueRule } from '@aws-github-runner/runner-providers'; diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels-provider.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels-provider.ts deleted file mode 100644 index bd2d8faa7a..0000000000 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels-provider.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { RunnerProviderType } from '@aws-github-runner/runner-provider'; - -import type { RunnerMatcherConfig } from '../sqs'; - -export interface AwsDynamicLabelDispatchTarget { - queue: RunnerMatcherConfig; - labels: string[]; -} - -export interface SelectAwsDynamicLabelQueueInput { - queue: RunnerMatcherConfig; - nonGhrLabels: string[]; - sanitizedGhrLabels: string[]; -} - -export interface AwsDynamicLabelProviderStrategy { - type: RunnerProviderType; - selectQueue(input: SelectAwsDynamicLabelQueueInput): AwsDynamicLabelDispatchTarget | undefined; -} diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts index 19e5256cb3..fc9ad189d6 100644 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts +++ b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts @@ -1,4 +1,4 @@ -import type { RunnerProviderType } from '@aws-github-runner/runner-provider'; +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; import { describe, expect, it } from 'vitest'; import type { RunnerMatcherConfig } from '../sqs'; diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts index ccfb2e7462..0bf3729164 100644 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts +++ b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts @@ -1,31 +1,27 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { normalizeRunnerProviderType } from '@aws-github-runner/runner-provider'; +import type { DynamicLabelDispatchTarget } from '@aws-github-runner/runner-providers'; +import { normalizeRunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import { webhookProviderRegistry } from '@aws-github-runner/runner-providers/webhook'; import type { RunnerMatcherConfig } from '../sqs'; -import { AwsDynamicLabelDispatchTarget, AwsDynamicLabelProviderStrategy } from './aws-dynamic-labels-provider'; -import { ec2DynamicLabelProviderStrategy } from './ec2-dynamic-labels'; const logger = createChildLogger('handler'); -const awsDynamicLabelProviderStrategies: AwsDynamicLabelProviderStrategy[] = [ec2DynamicLabelProviderStrategy]; - export function selectAwsDynamicLabelQueue( matches: RunnerMatcherConfig[], nonGhrLabels: string[], sanitizedGhrLabels: string[], -): AwsDynamicLabelDispatchTarget | undefined { +): DynamicLabelDispatchTarget | undefined { for (const queue of matches) { const provider = normalizeRunnerProviderType(queue.runnerProvider); - const strategy = provider - ? awsDynamicLabelProviderStrategies.find((strategy) => strategy.type === provider) - : undefined; + const dynamicLabels = provider ? webhookProviderRegistry.capability(provider, 'dynamicLabels') : undefined; - if (!strategy) { + if (!dynamicLabels) { logger.warn(`Queue ${queue.id} has unsupported runner provider '${provider ?? String(queue.runnerProvider)}'`); continue; } - const target = strategy.selectQueue({ queue, nonGhrLabels, sanitizedGhrLabels }); + const target = dynamicLabels.selectQueue({ queue, nonGhrLabels, sanitizedGhrLabels }); if (target) return target; } diff --git a/lambdas/functions/webhook/src/sqs/index.ts b/lambdas/functions/webhook/src/sqs/index.ts index ba805ce88c..0d21f81427 100644 --- a/lambdas/functions/webhook/src/sqs/index.ts +++ b/lambdas/functions/webhook/src/sqs/index.ts @@ -1,7 +1,7 @@ import { SQS, SendMessageCommandInput } from '@aws-sdk/client-sqs'; import { WorkflowJobEvent } from '@octokit/webhooks-types'; import { createChildLogger, getTracedAWSV3Client } from '@aws-github-runner/aws-powertools-util'; -import type { RunnerProviderType } from '@aws-github-runner/runner-provider'; +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; import type { AwsDynamicLabelsPolicy } from '../runners/aws-dynamic-labels-policy'; diff --git a/lambdas/libs/runner-provider/package.json b/lambdas/libs/runner-provider/package.json deleted file mode 100644 index 6ccf3983d8..0000000000 --- a/lambdas/libs/runner-provider/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@aws-github-runner/runner-provider", - "version": "1.0.0", - "main": "src/index.ts", - "type": "module", - "license": "MIT", - "scripts": { - "test": "NODE_ENV=test nx test", - "test:watch": "NODE_ENV=test nx test --watch", - "lint": "eslint src", - "format": "prettier --write \"**/*.ts\"", - "format-check": "prettier --check \"**/*.ts\"", - "all": "yarn format && yarn lint && yarn test" - }, - "nx": { - "includedScripts": [ - "format", - "format-check", - "lint", - "all" - ] - } -} diff --git a/lambdas/libs/runner-provider/src/index.ts b/lambdas/libs/runner-provider/src/index.ts deleted file mode 100644 index bb73cfd983..0000000000 --- a/lambdas/libs/runner-provider/src/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -// TODO: Add MicroVM when its webhook and control-plane provider implementations are available. -const runnerProviderTypes = ['ec2'] as const; -export type RunnerProviderType = (typeof runnerProviderTypes)[number]; - -const defaultRunnerProvider: RunnerProviderType = 'ec2'; - -export function normalizeRunnerProviderType(type: unknown): RunnerProviderType | undefined { - if (type === undefined) return defaultRunnerProvider; - if (typeof type !== 'string') return undefined; - - const normalizedType = type.trim().toLowerCase(); - if (!normalizedType) return defaultRunnerProvider; - - return runnerProviderTypes.find((runnerProviderType) => runnerProviderType === normalizedType); -} - -export function resolveRunnerProviderType(type: unknown): RunnerProviderType { - const normalizedType = normalizeRunnerProviderType(type); - if (!normalizedType) { - throw new Error(`Unsupported runner provider type '${String(type)}'`); - } - - return normalizedType; -} - -export interface RunnerProvider { - type: RunnerProviderType; -} diff --git a/lambdas/libs/runner-provider/tsconfig.json b/lambdas/libs/runner-provider/tsconfig.json deleted file mode 100644 index 9d616eca0f..0000000000 --- a/lambdas/libs/runner-provider/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*"], - "exclude": ["src/**/*.test.ts"] -} diff --git a/lambdas/libs/runner-provider/vitest.config.ts b/lambdas/libs/runner-provider/vitest.config.ts deleted file mode 100644 index 5179569242..0000000000 --- a/lambdas/libs/runner-provider/vitest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { mergeConfig } from 'vitest/config'; -import defaultConfig from '../../vitest.base.config'; - -export default mergeConfig(defaultConfig, { - test: { - coverage: { - include: ['src/**/*.ts'], - exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'], - thresholds: { - statements: 100, - branches: 100, - functions: 100, - lines: 100, - }, - }, - }, -}); diff --git a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts new file mode 100644 index 0000000000..8b4cc7edf0 --- /dev/null +++ b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts @@ -0,0 +1,19 @@ +import type { CreateStartRunnerConfig, RunnerProviderPlugin } from '../../core'; + +import type { ControlPlaneProviderCapabilities } from '../../contracts'; +import { createEc2PoolProvider } from './src/control-plane/pool'; +import { createEc2ScaleDownProvider } from './src/control-plane/scale-down'; +import { createEc2ScaleUpProvider } from './src/control-plane/scale-up'; + +export function createEc2ControlPlanePlugin( + createStartRunnerConfig: CreateStartRunnerConfig, +): RunnerProviderPlugin { + return { + type: 'ec2', + capabilities: { + pool: () => createEc2PoolProvider(createStartRunnerConfig), + scaleUp: () => createEc2ScaleUpProvider(createStartRunnerConfig), + scaleDown: createEc2ScaleDownProvider, + }, + }; +} diff --git a/lambdas/libs/runner-providers/aws/ec2/index.ts b/lambdas/libs/runner-providers/aws/ec2/index.ts new file mode 100644 index 0000000000..7f7ec3cccd --- /dev/null +++ b/lambdas/libs/runner-providers/aws/ec2/index.ts @@ -0,0 +1,13 @@ +export { createEc2ControlPlanePlugin } from './control-plane'; +export { ec2WebhookPlugin } from './webhook'; + +export * from './src/control-plane/dynamic-labels'; +export * from './src/control-plane/pool'; +export * from './src/control-plane/runner-config'; +export * from './src/control-plane/runners'; +export type * from './src/control-plane/runners.d'; +export * from './src/control-plane/scale-down'; +export * from './src/control-plane/scale-up'; +export * from './src/webhook/dynamic-labels'; +export * from './src/webhook/dynamic-labels-policy'; +export type * from './src/webhook/types'; diff --git a/lambdas/functions/control-plane/src/scale-runners/ec2-labels.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/dynamic-labels.ts similarity index 99% rename from lambdas/functions/control-plane/src/scale-runners/ec2-labels.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/dynamic-labels.ts index 496b7dab46..d3e31a74f9 100644 --- a/lambdas/functions/control-plane/src/scale-runners/ec2-labels.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/dynamic-labels.ts @@ -32,7 +32,7 @@ import { } from '@aws-sdk/client-ec2'; import { getTracedAWSV3Client } from '@aws-github-runner/aws-powertools-util'; -import { Ec2OverrideConfig } from './../aws/ec2-runners.d'; +import { Ec2OverrideConfig } from './runners.d'; const EC2_OVERRIDE_LIST_VALUE_SEPARATOR = ';'; diff --git a/lambdas/functions/control-plane/src/pool/ec2-pool.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/pool.test.ts similarity index 88% rename from lambdas/functions/control-plane/src/pool/ec2-pool.test.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/pool.test.ts index 6054811a24..6fd21d6924 100644 --- a/lambdas/functions/control-plane/src/pool/ec2-pool.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/pool.test.ts @@ -1,9 +1,9 @@ -import { bootTimeExceeded } from '../aws/ec2-runners'; -import type { RunnerList } from '../aws/ec2-runners.d'; -import { calculateEc2PoolSize } from './ec2-pool'; +import { bootTimeExceeded } from './runners'; +import type { RunnerList } from './runners.d'; +import { calculateEc2PoolSize } from './pool'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -vi.mock('../aws/ec2-runners', () => ({ +vi.mock('./runners', () => ({ bootTimeExceeded: vi.fn(), })); diff --git a/lambdas/functions/control-plane/src/pool/ec2-pool.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/pool.ts similarity index 74% rename from lambdas/functions/control-plane/src/pool/ec2-pool.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/pool.ts index f4ba8827d4..6578e66496 100644 --- a/lambdas/functions/control-plane/src/pool/ec2-pool.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/pool.ts @@ -1,9 +1,14 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; - -import { bootTimeExceeded, listEC2Runners } from '../aws/ec2-runners'; -import type { RunnerList } from '../aws/ec2-runners.d'; -import { createRunners, loadEc2ProviderConfig } from '../scale-runners/ec2'; -import type { CreatePoolRunnersInput, ListPoolRunnersInput, PoolRunnerProvider, RunnerStatus } from './pool-provider'; +import type { + CreateStartRunnerConfig, + CreatePoolRunnersInput, + ListPoolRunnersInput, + PoolRunnerProvider, + RunnerStatus, +} from '../../../../core'; +import { createRunners, loadEc2ProviderConfig } from './runner-config'; +import { bootTimeExceeded, listEC2Runners } from './runners'; +import type { RunnerList } from './runners.d'; const logger = createChildLogger('pool'); @@ -20,11 +25,10 @@ async function listEc2PoolRunners({ }); } -async function createEc2PoolRunners({ - githubRunnerConfig, - numberOfRunners, - githubInstallationClient, -}: CreatePoolRunnersInput): Promise { +async function createEc2PoolRunners( + { githubRunnerConfig, numberOfRunners, githubInstallationClient }: CreatePoolRunnersInput, + createStartRunnerConfig: CreateStartRunnerConfig, +): Promise { const config = loadEc2ProviderConfig(); const { instances } = await createRunners( @@ -41,16 +45,19 @@ async function createEc2PoolRunners({ }, numberOfRunners, githubInstallationClient, + createStartRunnerConfig, 'pool-lambda', ); return instances; } -export function createEc2PoolProvider(): Omit { +export function createEc2PoolProvider( + createStartRunnerConfig: CreateStartRunnerConfig, +): Omit { return { listRunners: listEc2PoolRunners, countAvailableRunners: calculateEc2PoolSize, - createRunners: createEc2PoolRunners, + createRunners: (input) => createEc2PoolRunners(input, createStartRunnerConfig), }; } diff --git a/lambdas/functions/control-plane/src/scale-runners/ec2.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runner-config.ts similarity index 93% rename from lambdas/functions/control-plane/src/scale-runners/ec2.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/runner-config.ts index 43c14ea55b..15139b72ab 100644 --- a/lambdas/functions/control-plane/src/scale-runners/ec2.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runner-config.ts @@ -1,14 +1,18 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; +import type { + CreateGitHubRunnerConfig, + CreateScaleUpRunnersResult, + CreateStartRunnerConfig, + GitHubRunnerMetadata, + LambdaRunnerSource, + StartRunnerConfigOptions, +} from '../../../../core'; import { Octokit } from '@octokit/rest'; import type { Tag } from '@aws-sdk/client-ec2'; import yn from 'yn'; -import { createRunner, tag, terminateRunner } from '../aws/ec2-runners'; -import type { RunnerInputParameters } from '../aws/ec2-runners.d'; -import { createStartRunnerConfig } from './github-runner'; -import type { GitHubRunnerMetadata, StartRunnerConfigOptions } from './github-runner'; -import type { CreateScaleUpRunnersResult } from './scale-up-provider'; -import type { CreateGitHubRunnerConfig, LambdaRunnerSource } from './types'; +import { createRunner, tag, terminateRunner } from './runners'; +import type { RunnerInputParameters } from './runners.d'; const logger = createChildLogger('ec2-runners'); const RUNNER_LABELS_TAG_KEY = 'ghr:runner_labels'; @@ -61,6 +65,7 @@ export async function createRunners( ec2RunnerConfig: CreateEC2RunnerConfig, numberOfRunners: number, ghClient: Octokit, + createStartRunnerConfig: CreateStartRunnerConfig, source: LambdaRunnerSource = 'scale-up-lambda', ): Promise { let result: CreateScaleUpRunnersResult; diff --git a/lambdas/functions/control-plane/src/aws/ec2-runners.d.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.d.ts similarity index 96% rename from lambdas/functions/control-plane/src/aws/ec2-runners.d.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.d.ts index 5a1b1b5cdf..3332ccd246 100644 --- a/lambdas/functions/control-plane/src/aws/ec2-runners.d.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.d.ts @@ -7,7 +7,7 @@ import { Placement, FleetBlockDeviceMappingRequest, } from '@aws-sdk/client-ec2'; -import type { LambdaRunnerSource } from '../scale-runners/types'; +import type { LambdaRunnerSource } from '../../../../core'; export type RunnerType = 'Org' | 'Repo'; diff --git a/lambdas/functions/control-plane/src/aws/ec2-runners.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.test.ts similarity index 99% rename from lambdas/functions/control-plane/src/aws/ec2-runners.test.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.test.ts index 2b8abbd03d..efda4e7327 100644 --- a/lambdas/functions/control-plane/src/aws/ec2-runners.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.test.ts @@ -21,9 +21,9 @@ import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest/vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { createRunner, listEC2Runners, tag, terminateRunner, untag } from './ec2-runners'; -import type { Ec2OverrideConfig, RunnerInfo, RunnerInputParameters, RunnerType } from './ec2-runners.d'; -import type { LambdaRunnerSource } from '../scale-runners/types'; +import type { LambdaRunnerSource } from '../../../../core'; +import { createRunner, listEC2Runners, tag, terminateRunner, untag } from './runners'; +import type { Ec2OverrideConfig, RunnerInfo, RunnerInputParameters, RunnerType } from './runners.d'; process.env.AWS_REGION = 'eu-east-1'; const mockEC2Client = mockClient(EC2Client); diff --git a/lambdas/functions/control-plane/src/aws/ec2-runners.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.ts similarity index 99% rename from lambdas/functions/control-plane/src/aws/ec2-runners.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.ts index 432770c900..a4eacae064 100644 --- a/lambdas/functions/control-plane/src/aws/ec2-runners.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/runners.ts @@ -22,7 +22,7 @@ import { getTracedAWSV3Client, tracer } from '@aws-github-runner/aws-powertools- import { getParameter } from '@aws-github-runner/aws-ssm-util'; import moment from 'moment'; -import * as Runners from './ec2-runners.d'; +import * as Runners from './runners.d'; const logger = createChildLogger('runners'); diff --git a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-down.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-down.test.ts similarity index 97% rename from lambdas/functions/control-plane/src/scale-runners/ec2-scale-down.test.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-down.test.ts index 04dae3e0ee..509cfc61ba 100644 --- a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-down.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-down.test.ts @@ -3,11 +3,11 @@ import { RequestError } from '@octokit/request-error'; import moment from 'moment'; import nock from 'nock'; -import { RunnerList } from '../aws/ec2-runners.d'; -import * as ghAuth from '../github/auth'; -import { listEC2Runners, terminateRunner, tag, untag } from './../aws/ec2-runners'; -import { githubCache } from './cache'; -import { scaleDown } from './scale-down'; +import * as ghAuth from '../../../../../../functions/control-plane/src/github/auth'; +import { githubCache } from '../../../../../../functions/control-plane/src/scale-runners/cache'; +import { scaleDown } from '../../../../../../functions/control-plane/src/scale-runners/scale-down'; +import { listEC2Runners, terminateRunner, tag, untag } from './runners'; +import { RunnerList } from './runners.d'; import { describe, it, expect, beforeEach, vi } from 'vitest'; const mockOctokit = { @@ -31,7 +31,7 @@ vi.mock('@octokit/rest', () => ({ }), })); -vi.mock('./../aws/ec2-runners', async (importOriginal) => { +vi.mock('./runners', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, @@ -41,13 +41,13 @@ vi.mock('./../aws/ec2-runners', async (importOriginal) => { listEC2Runners: vi.fn(), }; }); -vi.mock('./../github/auth', async () => ({ +vi.mock('../../../../../../functions/control-plane/src/github/auth', async () => ({ createGithubAppAuth: vi.fn(), createGithubInstallationAuth: vi.fn(), createOctokitClient: vi.fn(), })); -vi.mock('./cache', async () => ({ +vi.mock('../../../../../../functions/control-plane/src/scale-runners/cache', async () => ({ githubCache: { getRunner: vi.fn(), addRunner: vi.fn(), diff --git a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-down.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-down.ts similarity index 84% rename from lambdas/functions/control-plane/src/scale-runners/ec2-scale-down.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-down.ts index 554ea375b7..a80a9db8f7 100644 --- a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-down.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-down.ts @@ -1,6 +1,6 @@ -import { bootTimeExceeded, listEC2Runners, tag, terminateRunner, untag } from './../aws/ec2-runners'; -import type { RunnerList } from './../aws/ec2-runners.d'; -import type { RunnerList as ScaleDownRunnerList, ScaleDownRunnerProvider } from './scale-down-provider'; +import type { ScaleDownRunnerList, ScaleDownRunnerProvider } from '../../../../core'; +import { bootTimeExceeded, listEC2Runners, tag, terminateRunner, untag } from './runners'; +import type { RunnerList } from './runners.d'; async function listEc2ScaleDownRunners(environment: string, orphan?: boolean): Promise { return (await listEC2Runners({ environment, orphan })).map(toScaleDownRunner); diff --git a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-up.test.ts similarity index 99% rename from lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.test.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-up.test.ts index a2ad607b0e..61287c89e5 100644 --- a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-up.test.ts @@ -6,17 +6,17 @@ import 'aws-sdk-client-mock-jest/vitest'; import nock from 'nock'; import { performance } from 'perf_hooks'; -import * as ghAuth from '../github/auth'; -import { createRunner, listEC2Runners, tag, terminateRunner } from './../aws/ec2-runners'; -import { RunnerInputParameters } from './../aws/ec2-runners.d'; -import * as scaleUpModule from './scale-up'; -import { parseEc2OverrideConfig } from './ec2-labels'; -import { EC2_TAG_VALUE_MAX_LENGTH, RUNNER_LABELS_TAG_MAX_COUNT } from './ec2'; +import * as ghAuth from '../../../../../../functions/control-plane/src/github/auth'; +import { publishRetryMessage } from '../../../../../../functions/control-plane/src/scale-runners/job-retry'; +import * as scaleUpModule from '../../../../../../functions/control-plane/src/scale-runners/scale-up'; +import type { ActionRequestMessageSQS } from '../../../../../../functions/control-plane/src/scale-runners/types'; +import { parseEc2OverrideConfig } from './dynamic-labels'; +import { EC2_TAG_VALUE_MAX_LENGTH, RUNNER_LABELS_TAG_MAX_COUNT } from './runner-config'; +import { createRunner, listEC2Runners, tag, terminateRunner } from './runners'; +import { RunnerInputParameters } from './runners.d'; import { getParameter } from '@aws-github-runner/aws-ssm-util'; -import { publishRetryMessage } from './job-retry'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import type { Octokit } from '@octokit/rest'; -import type { ActionRequestMessageSQS } from './types'; const mockOctokit = { paginate: vi.fn(), @@ -53,14 +53,14 @@ vi.mock('@octokit/rest', () => ({ }), })); -vi.mock('./../aws/ec2-runners', async () => ({ +vi.mock('./runners', async () => ({ createRunner: vi.fn(), listEC2Runners: vi.fn(), tag: vi.fn(), terminateRunner: vi.fn(), })); -vi.mock('./../github/auth', async () => ({ +vi.mock('../../../../../../functions/control-plane/src/github/auth', async () => ({ createGithubAppAuth: vi.fn(), createGithubInstallationAuth: vi.fn(), createOctokitClient: vi.fn(), @@ -77,7 +77,7 @@ vi.mock('@aws-github-runner/aws-ssm-util', async () => { }; }); -vi.mock('./job-retry', () => ({ +vi.mock('../../../../../../functions/control-plane/src/scale-runners/job-retry', () => ({ publishRetryMessage: vi.fn(), checkAndRetryJob: vi.fn(), })); diff --git a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.ts b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-up.ts similarity index 73% rename from lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.ts rename to lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-up.ts index 52ce26d888..7f57df4838 100644 --- a/lambdas/functions/control-plane/src/scale-runners/ec2-scale-up.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/control-plane/scale-up.ts @@ -1,22 +1,23 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import yn from 'yn'; - -import { listEC2Runners } from '../aws/ec2-runners'; -import type { Ec2OverrideConfig } from './../aws/ec2-runners.d'; -import { - getDefaultBlockDeviceNameFromLaunchTemplate, - parseEc2OverrideConfig, - shouldLoadLaunchTemplateBlockDeviceName, -} from './ec2-labels'; -import { createRunners, loadEc2ProviderConfig } from './ec2'; -import type { CreateEC2RunnerConfig } from './ec2'; import type { CreateScaleUpRunnersInput, CreateScaleUpRunnersResult, + CreateStartRunnerConfig, CurrentRunnersInput, PreparedScaleUpRunnerGroup, ScaleUpRunnerProvider, -} from './scale-up-provider'; +} from '../../../../core'; +import yn from 'yn'; + +import { listEC2Runners } from './runners'; +import type { Ec2OverrideConfig } from './runners.d'; +import { + getDefaultBlockDeviceNameFromLaunchTemplate, + parseEc2OverrideConfig, + shouldLoadLaunchTemplateBlockDeviceName, +} from './dynamic-labels'; +import { createRunners, loadEc2ProviderConfig } from './runner-config'; +import type { CreateEC2RunnerConfig } from './runner-config'; const logger = createChildLogger('ec2-scale-up'); @@ -61,12 +62,10 @@ async function getCurrentEc2Runners( return (await listEC2Runners({ environment: process.env.ENVIRONMENT, runnerType, runnerOwner })).length; } -async function createEc2ScaleUpRunners({ - githubRunnerConfig, - numberOfRunners, - githubInstallationClient, - state, -}: CreateScaleUpRunnersInput): Promise { +async function createEc2ScaleUpRunners( + { githubRunnerConfig, numberOfRunners, githubInstallationClient, state }: CreateScaleUpRunnersInput, + createStartRunnerConfig: CreateStartRunnerConfig, +): Promise { const config = loadEc2ScaleUpProviderConfig(); return await createRunners( @@ -77,14 +76,17 @@ async function createEc2ScaleUpRunners({ }, numberOfRunners, githubInstallationClient, + createStartRunnerConfig, 'scale-up-lambda', ); } -export function createEc2ScaleUpProvider(): Omit, 'type'> { +export function createEc2ScaleUpProvider( + createStartRunnerConfig: CreateStartRunnerConfig, +): Omit, 'type'> { return { prepareGroup: prepareEc2ScaleUpGroup, getCurrentRunners: getCurrentEc2Runners, - createRunners: createEc2ScaleUpRunners, + createRunners: (input) => createEc2ScaleUpRunners(input, createStartRunnerConfig), }; } diff --git a/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts b/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts new file mode 100644 index 0000000000..1cd1c61108 --- /dev/null +++ b/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts @@ -0,0 +1,18 @@ +declare namespace NodeJS { + export interface ProcessEnv { + ENVIRONMENT: string; + LAUNCH_TEMPLATE_NAME: string; + SUBNET_IDS: string; + INSTANCE_TYPES: string; + INSTANCE_TARGET_CAPACITY_TYPE: 'on-demand' | 'spot'; + INSTANCE_MAX_SPOT_PRICE: string | undefined; + INSTANCE_ALLOCATION_STRATEGY: + | 'lowest-price' + | 'price-capacity-optimized' + | 'diversified' + | 'capacity-optimized' + | 'capacity-optimized-prioritized' + | 'prioritized'; + SCALE_ERRORS: string; + } +} diff --git a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels-policy.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.test.ts similarity index 99% rename from lambdas/functions/webhook/src/runners/ec2-dynamic-labels-policy.test.ts rename to lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.test.ts index 0497bee33a..d9d3c9325d 100644 --- a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels-policy.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { violationsAgainstPolicy, type Ec2DynamicLabelsPolicy } from './ec2-dynamic-labels-policy'; +import { violationsAgainstPolicy, type Ec2DynamicLabelsPolicy } from './dynamic-labels-policy'; describe('violationsAgainstPolicy', () => { it('returns [] when policy is null/undefined', () => { diff --git a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels-policy.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts similarity index 98% rename from lambdas/functions/webhook/src/runners/ec2-dynamic-labels-policy.ts rename to lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts index 15b6f4235d..426c78028c 100644 --- a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels-policy.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts @@ -1,4 +1,4 @@ -import type { AwsDynamicLabelsPolicy, AwsDynamicLabelsValueRule } from './aws-dynamic-labels-policy'; +import type { AwsDynamicLabelsPolicy, AwsDynamicLabelsValueRule } from './types'; export type Ec2DynamicLabelsValueRule = AwsDynamicLabelsValueRule; diff --git a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts similarity index 90% rename from lambdas/functions/webhook/src/runners/ec2-dynamic-labels.test.ts rename to lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts index f09ea6967b..db478968f8 100644 --- a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { RunnerMatcherConfig } from '../sqs'; -import { selectEc2DynamicLabelQueue } from './ec2-dynamic-labels'; +import { selectEc2DynamicLabelQueue } from './dynamic-labels'; +import type { Ec2RunnerMatcherConfig } from './types'; describe('selectEc2DynamicLabelQueue', () => { it('enforces a legacy EC2 dynamic labels policy when the new key is absent', () => { @@ -43,7 +43,7 @@ describe('selectEc2DynamicLabelQueue', () => { }); }); -function runnerQueue(id: string): RunnerMatcherConfig { +function runnerQueue(id: string): Ec2RunnerMatcherConfig { return { id, arn: `arn:${id}`, diff --git a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts similarity index 76% rename from lambdas/functions/webhook/src/runners/ec2-dynamic-labels.ts rename to lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts index 713a1f0348..5053f703dd 100644 --- a/lambdas/functions/webhook/src/runners/ec2-dynamic-labels.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts @@ -1,14 +1,13 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { RunnerMatcherConfig } from '../sqs'; -import { AwsDynamicLabelDispatchTarget, AwsDynamicLabelProviderStrategy } from './aws-dynamic-labels-provider'; -import { violationsAgainstPolicy } from './ec2-dynamic-labels-policy'; +import { violationsAgainstPolicy } from './dynamic-labels-policy'; +import type { DynamicLabelDispatchTarget, DynamicLabelProvider, Ec2RunnerMatcherConfig } from './types'; const logger = createChildLogger('handler'); -export type Ec2DynamicLabelDispatchTarget = AwsDynamicLabelDispatchTarget; +export type Ec2DynamicLabelDispatchTarget = DynamicLabelDispatchTarget; -function resolveEc2DynamicLabelsPolicy(queue: RunnerMatcherConfig) { +function resolveEc2DynamicLabelsPolicy(queue: Ec2RunnerMatcherConfig) { const hasLegacyEc2DynamicLabelsPolicy = Object.prototype.hasOwnProperty.call( queue.matcherConfig, 'ec2DynamicLabelsPolicy', @@ -25,7 +24,7 @@ function resolveEc2DynamicLabelsPolicy(queue: RunnerMatcherConfig) { } export function selectEc2DynamicLabelQueue( - matches: RunnerMatcherConfig[], + matches: Ec2RunnerMatcherConfig[], nonGhrLabels: string[], sanitizedGhrLabels: string[], ): Ec2DynamicLabelDispatchTarget | undefined { @@ -53,8 +52,7 @@ export function selectEc2DynamicLabelQueue( return undefined; } -export const ec2DynamicLabelProviderStrategy: AwsDynamicLabelProviderStrategy = { - type: 'ec2', +export const ec2DynamicLabelProvider: DynamicLabelProvider = { selectQueue: ({ queue, nonGhrLabels, sanitizedGhrLabels }) => selectEc2DynamicLabelQueue([queue], nonGhrLabels, sanitizedGhrLabels), }; diff --git a/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts new file mode 100644 index 0000000000..16edb804d6 --- /dev/null +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts @@ -0,0 +1,7 @@ +export type { + AwsDynamicLabelsPolicy, + AwsDynamicLabelsValueRule, + AwsRunnerMatcherConfig as Ec2RunnerMatcherConfig, + DynamicLabelDispatchTarget, + DynamicLabelProvider, +} from '../../../../contracts'; diff --git a/lambdas/libs/runner-providers/aws/ec2/webhook.ts b/lambdas/libs/runner-providers/aws/ec2/webhook.ts new file mode 100644 index 0000000000..e1418e2d08 --- /dev/null +++ b/lambdas/libs/runner-providers/aws/ec2/webhook.ts @@ -0,0 +1,9 @@ +import type { RunnerProviderPlugin } from '../../core'; + +import type { WebhookProviderCapabilities } from '../../contracts'; +import { ec2DynamicLabelProvider } from './src/webhook/dynamic-labels'; + +export const ec2WebhookPlugin: RunnerProviderPlugin = { + type: 'ec2', + capabilities: { dynamicLabels: ec2DynamicLabelProvider }, +}; diff --git a/lambdas/libs/runner-providers/contracts.ts b/lambdas/libs/runner-providers/contracts.ts new file mode 100644 index 0000000000..c9ca8d2d01 --- /dev/null +++ b/lambdas/libs/runner-providers/contracts.ts @@ -0,0 +1,49 @@ +import type { PoolRunnerProvider, RunnerProviderType, ScaleDownRunnerProvider, ScaleUpRunnerProvider } from './core'; + +export interface AwsDynamicLabelsValueRule { + allowed?: string[]; + denied?: string[]; + max?: number | string; +} + +export interface AwsDynamicLabelsPolicy { + blocked_keys?: string[]; + restricted_keys?: Record; +} + +export interface AwsRunnerMatcherConfig { + id: string; + arn: string; + runnerProvider?: RunnerProviderType; + matcherConfig: { + labelMatchers: string[][]; + exactMatch: boolean; + bidirectionalLabelMatch?: boolean; + enableDynamicLabels?: boolean; + awsDynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; + ec2DynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; + }; +} + +export interface DynamicLabelDispatchTarget { + queue: AwsRunnerMatcherConfig; + labels: string[]; +} + +export interface DynamicLabelProvider { + selectQueue(input: { + queue: AwsRunnerMatcherConfig; + nonGhrLabels: string[]; + sanitizedGhrLabels: string[]; + }): DynamicLabelDispatchTarget | undefined; +} + +export interface ControlPlaneProviderCapabilities { + pool: () => Omit; + scaleUp: () => Omit; + scaleDown: () => Omit; +} + +export interface WebhookProviderCapabilities { + dynamicLabels: DynamicLabelProvider; +} diff --git a/lambdas/libs/runner-providers/control-plane.ts b/lambdas/libs/runner-providers/control-plane.ts new file mode 100644 index 0000000000..f8b3b5a4f2 --- /dev/null +++ b/lambdas/libs/runner-providers/control-plane.ts @@ -0,0 +1,16 @@ +import type { CreateStartRunnerConfig } from './core'; +import { createRunnerProviderRegistry } from './core'; + +import { createEc2ControlPlanePlugin } from './aws/ec2/control-plane'; +import type { ControlPlaneProviderCapabilities } from './contracts'; +import { enabledRunnerProviderTypes } from './providers.config'; + +const installedControlPlaneProviders = { + ec2: createEc2ControlPlanePlugin, +}; + +export function createControlPlaneProviderRegistry(createStartRunnerConfig: CreateStartRunnerConfig) { + return createRunnerProviderRegistry( + enabledRunnerProviderTypes.map((type) => installedControlPlaneProviders[type](createStartRunnerConfig)), + ); +} diff --git a/lambdas/libs/runner-provider/src/index.test.ts b/lambdas/libs/runner-providers/core/index.test.ts similarity index 61% rename from lambdas/libs/runner-provider/src/index.test.ts rename to lambdas/libs/runner-providers/core/index.test.ts index 616ed40d37..e8dcfa92bc 100644 --- a/lambdas/libs/runner-provider/src/index.test.ts +++ b/lambdas/libs/runner-providers/core/index.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { normalizeRunnerProviderType, resolveRunnerProviderType } from './index'; +import { createRunnerProviderRegistry, normalizeRunnerProviderType, resolveRunnerProviderType } from './index'; describe('runner provider normalization', () => { it.each([ @@ -31,3 +31,23 @@ describe('runner provider resolution', () => { expect(() => resolveRunnerProviderType(type)).toThrow(`Unsupported runner provider type '${String(type)}'`); }); }); + +describe('runner provider registry', () => { + const plugin = { + type: 'ec2' as const, + capabilities: { + scaleUp: () => 'scale-up', + pool: () => 'pool', + }, + }; + const registry = createRunnerProviderRegistry([plugin]); + + it('lists the registered provider types', () => { + expect(registry.types).toEqual(['ec2']); + }); + + it('resolves capabilities dynamically', () => { + expect(registry.capability('ec2', 'scaleUp')()).toBe('scale-up'); + expect(registry.capability('ec2', 'pool')()).toBe('pool'); + }); +}); diff --git a/lambdas/libs/runner-providers/core/index.ts b/lambdas/libs/runner-providers/core/index.ts new file mode 100644 index 0000000000..ebb3fb77b2 --- /dev/null +++ b/lambdas/libs/runner-providers/core/index.ts @@ -0,0 +1,171 @@ +import type { Octokit } from '@octokit/rest'; + +import { enabledRunnerProviderTypes } from '../providers.config'; + +export const runnerProviderTypes = enabledRunnerProviderTypes; +export type RunnerProviderType = (typeof runnerProviderTypes)[number]; + +const defaultRunnerProvider: RunnerProviderType = 'ec2'; + +export function normalizeRunnerProviderType(type: unknown): RunnerProviderType | undefined { + if (type === undefined) return defaultRunnerProvider; + if (typeof type !== 'string') return undefined; + + const normalizedType = type.trim().toLowerCase(); + if (!normalizedType) return defaultRunnerProvider; + + return runnerProviderTypes.find((runnerProviderType) => runnerProviderType === normalizedType); +} + +export function resolveRunnerProviderType(type: unknown): RunnerProviderType { + const normalizedType = normalizeRunnerProviderType(type); + if (!normalizedType) { + throw new Error(`Unsupported runner provider type '${String(type)}'`); + } + + return normalizedType; +} + +export interface RunnerProvider { + type: RunnerProviderType; +} + +export type LambdaRunnerSource = 'scale-up-lambda' | 'pool-lambda'; +export type GitHubRunnerType = 'Org' | 'Repo'; + +export interface CreateGitHubRunnerConfig { + ephemeral: boolean; + ghesBaseUrl?: string; + enableJitConfig: boolean; + runnerLabels: string; + runnerGroup: string; + runnerNamePrefix: string; + runnerOwner: string; + runnerType: GitHubRunnerType; + disableAutoUpdate: boolean; + ssmTokenPath: string; + ssmConfigPath: string; + ssmParameterStoreTags: { Key: string; Value: string }[]; +} + +export interface GitHubRunnerMetadata { + githubRunnerId: string; + runnerLabels: string[]; +} + +export interface StartRunnerConfigOptions { + getSsmParameterTags?: (runnerId: string) => { Key: string; Value: string }[]; + onJitConfigCreated?: (runnerId: string, metadata: GitHubRunnerMetadata) => Promise; +} + +export type CreateStartRunnerConfig = ( + githubRunnerConfig: CreateGitHubRunnerConfig, + runnerIds: string[], + ghClient: Octokit, + options?: StartRunnerConfigOptions, +) => Promise; + +export interface CurrentRunnersInput { + runnerType: GitHubRunnerType; + runnerOwner: string; +} + +export interface CreateScaleUpRunnersInput { + githubRunnerConfig: CreateGitHubRunnerConfig; + numberOfRunners: number; + githubInstallationClient: Octokit; + state: TState; +} + +export interface PreparedScaleUpRunnerGroup { + runnerLabels: string[]; + state: TState; +} + +export interface CreateScaleUpRunnersResult { + instances: string[]; + retryableErrorCount: number; + nonRetryableErrorCount: number; +} + +export interface ScaleUpRunnerProvider extends RunnerProvider { + prepareGroup(messageLabels: string[]): Promise>; + getCurrentRunners(state: TState, input: CurrentRunnersInput): Promise; + createRunners(input: CreateScaleUpRunnersInput): Promise; +} + +export interface ScaleDownRunnerList { + id: string; + launchTime?: Date; + owner?: string; + type?: string; + repo?: string; + org?: string; + orphan?: boolean; + githubRunnerId?: string; + bypassRemoval?: boolean; +} + +export interface ScaleDownRunnerInfo extends ScaleDownRunnerList { + owner: string; + type: string; +} + +export interface ScaleDownRunnerProvider extends RunnerProvider { + list(environment: string, orphan?: boolean): Promise; + bootTimeExceeded(runner: ScaleDownRunnerInfo): boolean; + markOrphan(id: string): Promise; + unmarkOrphan(id: string): Promise; + terminate(id: string): Promise; +} + +export interface RunnerStatus { + busy: boolean; + status: string; +} + +export interface ListPoolRunnersInput { + environment: string; + runnerOwner: string; + runnerType: GitHubRunnerType; +} + +export interface CreatePoolRunnersInput { + githubRunnerConfig: CreateGitHubRunnerConfig; + numberOfRunners: number; + githubInstallationClient: Octokit; +} + +export interface PoolRunnerProvider extends RunnerProvider { + listRunners(input: ListPoolRunnersInput): Promise; + countAvailableRunners( + runners: TRunner[], + runnerStatus: Map, + includeBusyRunners: boolean, + ): number; + createRunners(input: CreatePoolRunnersInput): Promise; +} + +export interface RunnerProviderPlugin { + type: TType; + capabilities: TCapabilities; +} + +export function createRunnerProviderRegistry( + plugins: readonly RunnerProviderPlugin[], +) { + const pluginsByType = new Map(plugins.map((plugin) => [plugin.type, plugin])); + + function get(type: TType): RunnerProviderPlugin { + const plugin = pluginsByType.get(type); + if (!plugin) throw new Error(`No runner provider plugin registered for '${type}'`); + return plugin; + } + + return { + types: plugins.map(({ type }) => type), + get, + capability: (type: TType, capability: TKey): TCapabilities[TKey] => + get(type).capabilities[capability], + }; +} diff --git a/lambdas/libs/runner-providers/package.json b/lambdas/libs/runner-providers/package.json new file mode 100644 index 0000000000..27f9594a0d --- /dev/null +++ b/lambdas/libs/runner-providers/package.json @@ -0,0 +1,37 @@ +{ + "name": "@aws-github-runner/runner-providers", + "version": "1.0.0", + "main": "contracts.ts", + "exports": { + ".": "./contracts.ts", + "./control-plane": "./control-plane.ts", + "./core": "./core/index.ts", + "./webhook": "./webhook.ts", + "./aws/ec2": "./aws/ec2/index.ts", + "./aws/ec2/control-plane/runners": "./aws/ec2/src/control-plane/runners.ts", + "./aws/ec2/control-plane/runner-config": "./aws/ec2/src/control-plane/runner-config.ts" + }, + "type": "module", + "license": "MIT", + "scripts": { + "test": "NODE_ENV=test nx test", + "test:watch": "NODE_ENV=test nx test --watch", + "lint": "eslint .", + "format": "prettier --write \"**/*.ts\"", + "format-check": "prettier --check \"**/*.ts\"", + "all": "yarn format && yarn lint && yarn test" + }, + "dependencies": { + "@aws-github-runner/aws-powertools-util": "*", + "@aws-github-runner/aws-ssm-util": "*", + "@aws-sdk/client-ec2": "^3.1009.0", + "@octokit/rest": "22.0.1", + "moment": "2.29.4", + "yn": "3.1.1" + }, + "devDependencies": { + "aws-sdk-client-mock": "^4.1.0", + "aws-sdk-client-mock-jest": "^4.1.0", + "nock": "^14.0.10" + } +} diff --git a/lambdas/libs/runner-providers/providers.config.ts b/lambdas/libs/runner-providers/providers.config.ts new file mode 100644 index 0000000000..3d42f9cbda --- /dev/null +++ b/lambdas/libs/runner-providers/providers.config.ts @@ -0,0 +1,5 @@ +/** + * Provider types enabled in webhook and control-plane bundles. + * Installing a provider does not activate it; add its type to this list explicitly. + */ +export const enabledRunnerProviderTypes = ['ec2'] as const; diff --git a/lambdas/libs/runner-providers/registry.test.ts b/lambdas/libs/runner-providers/registry.test.ts new file mode 100644 index 0000000000..df9b2f81d0 --- /dev/null +++ b/lambdas/libs/runner-providers/registry.test.ts @@ -0,0 +1,21 @@ +import { expect, it, vi } from 'vitest'; + +import { createControlPlaneProviderRegistry } from './control-plane'; +import { enabledRunnerProviderTypes } from './providers.config'; +import { webhookProviderRegistry } from './webhook'; + +it('enables every configured provider in both runtime registries', () => { + const createStartRunnerConfig = vi.fn(async () => []); + const controlPlaneRegistry = createControlPlaneProviderRegistry(createStartRunnerConfig); + const enabledTypes = enabledRunnerProviderTypes; + + expect(controlPlaneRegistry.types).toEqual(enabledTypes); + expect(webhookProviderRegistry.types).toEqual(enabledTypes); + + for (const type of enabledTypes) { + expect(controlPlaneRegistry.capability(type, 'pool')).toEqual(expect.any(Function)); + expect(controlPlaneRegistry.capability(type, 'scaleUp')).toEqual(expect.any(Function)); + expect(controlPlaneRegistry.capability(type, 'scaleDown')).toEqual(expect.any(Function)); + expect(webhookProviderRegistry.capability(type, 'dynamicLabels').selectQueue).toEqual(expect.any(Function)); + } +}); diff --git a/lambdas/libs/runner-providers/tsconfig.json b/lambdas/libs/runner-providers/tsconfig.json new file mode 100644 index 0000000000..eae83f2000 --- /dev/null +++ b/lambdas/libs/runner-providers/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["*.ts", "core/**/*", "aws/**/*"], + "exclude": ["aws/**/*.test.ts"] +} diff --git a/lambdas/libs/runner-providers/vitest.config.ts b/lambdas/libs/runner-providers/vitest.config.ts new file mode 100644 index 0000000000..8be97d837d --- /dev/null +++ b/lambdas/libs/runner-providers/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + setupFiles: '../../aws-vitest-setup.ts', + }, +}); diff --git a/lambdas/libs/runner-providers/webhook.ts b/lambdas/libs/runner-providers/webhook.ts new file mode 100644 index 0000000000..9de5aa4a69 --- /dev/null +++ b/lambdas/libs/runner-providers/webhook.ts @@ -0,0 +1,13 @@ +import { createRunnerProviderRegistry } from './core'; + +import { ec2WebhookPlugin } from './aws/ec2/webhook'; +import type { WebhookProviderCapabilities } from './contracts'; +import { enabledRunnerProviderTypes } from './providers.config'; + +const installedWebhookProviders = { + ec2: ec2WebhookPlugin, +}; + +export const webhookProviderRegistry = createRunnerProviderRegistry( + enabledRunnerProviderTypes.map((type) => installedWebhookProviders[type]), +); diff --git a/lambdas/yarn.lock b/lambdas/yarn.lock index 46f50d677b..075f533467 100644 --- a/lambdas/yarn.lock +++ b/lambdas/yarn.lock @@ -147,7 +147,7 @@ __metadata: dependencies: "@aws-github-runner/aws-powertools-util": "npm:*" "@aws-github-runner/aws-ssm-util": "npm:*" - "@aws-github-runner/runner-provider": "npm:*" + "@aws-github-runner/runner-providers": "npm:*" "@aws-lambda-powertools/parameters": "npm:^2.31.0" "@aws-sdk/client-ec2": "npm:^3.1009.0" "@aws-sdk/client-sqs": "npm:^3.1009.0" @@ -193,9 +193,19 @@ __metadata: languageName: unknown linkType: soft -"@aws-github-runner/runner-provider@npm:*, @aws-github-runner/runner-provider@workspace:libs/runner-provider": +"@aws-github-runner/runner-providers@npm:*, @aws-github-runner/runner-providers@workspace:libs/runner-providers": version: 0.0.0-use.local - resolution: "@aws-github-runner/runner-provider@workspace:libs/runner-provider" + resolution: "@aws-github-runner/runner-providers@workspace:libs/runner-providers" + dependencies: + "@aws-github-runner/aws-powertools-util": "npm:*" + "@aws-github-runner/aws-ssm-util": "npm:*" + "@aws-sdk/client-ec2": "npm:^3.1009.0" + "@octokit/rest": "npm:22.0.1" + aws-sdk-client-mock: "npm:^4.1.0" + aws-sdk-client-mock-jest: "npm:^4.1.0" + moment: "npm:2.29.4" + nock: "npm:^14.0.10" + yn: "npm:3.1.1" languageName: unknown linkType: soft @@ -228,7 +238,7 @@ __metadata: dependencies: "@aws-github-runner/aws-powertools-util": "npm:*" "@aws-github-runner/aws-ssm-util": "npm:*" - "@aws-github-runner/runner-provider": "npm:*" + "@aws-github-runner/runner-providers": "npm:*" "@aws-sdk/client-eventbridge": "npm:^3.1009.0" "@aws-sdk/client-sqs": "npm:^3.1009.0" "@middy/core": "npm:^6.4.5" @@ -8885,7 +8895,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:^2.29.4": +"moment@npm:2.29.4, moment@npm:^2.29.4": version: 2.29.4 resolution: "moment@npm:2.29.4" checksum: 10c0/844c6f3ce42862ac9467c8ca4f5e48a00750078682cc5bda1bc0e50cc7ca88e2115a0f932d65a06e4a90e26cb78892be9b3ca3dd6546ca2c4d994cebb787fc2b From 599393afd8b82c991fa9f9aa67933dc8390de70c Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 17:11:52 +0200 Subject: [PATCH 02/21] test(control-plane): add runner provider contracts --- .../src/pool/pool-contract.test.ts | 74 +++++++++++++++ .../scale-runners/scale-down-contract.test.ts | 34 +++++++ .../scale-runners/scale-up-contract.test.ts | 84 +++++++++++++++++ .../test-framework/runner-provider/pool.ts | 80 ++++++++++++++++ .../runner-provider/provider-types.ts | 3 + .../runner-provider/scale-down.ts | 69 ++++++++++++++ .../runner-provider/scale-up.ts | 93 +++++++++++++++++++ 7 files changed, 437 insertions(+) create mode 100644 lambdas/functions/control-plane/src/pool/pool-contract.test.ts create mode 100644 lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts create mode 100644 lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts create mode 100644 lambdas/functions/control-plane/src/test-framework/runner-provider/pool.ts create mode 100644 lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts create mode 100644 lambdas/functions/control-plane/src/test-framework/runner-provider/scale-down.ts create mode 100644 lambdas/functions/control-plane/src/test-framework/runner-provider/scale-up.ts diff --git a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts new file mode 100644 index 0000000000..13be80714d --- /dev/null +++ b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts @@ -0,0 +1,74 @@ +import type { Octokit } from '@octokit/rest'; +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import { beforeEach, vi } from 'vitest'; + +import { definePoolContractTests } from '../test-framework/runner-provider/pool'; +import { providerTypes } from '../test-framework/runner-provider/provider-types'; +import * as ghAuth from '../github/auth'; +import { controlPlaneProviderRegistry } from '../provider-registry'; +import * as githubRunner from '../scale-runners/github-runner'; +import { adjust } from './pool'; +import type { PoolRunnerProvider } from './pool-provider'; + +vi.mock('../github/auth', () => ({ + createGithubAppAuth: vi.fn(), + createGithubInstallationAuth: vi.fn(), + createOctokitClient: vi.fn(), +})); + +vi.mock('../scale-runners/github-runner', () => ({ + createStartRunnerConfig: vi.fn(), + getGitHubEnterpriseApiUrl: vi.fn(), + validateSsmParameterStoreTags: vi.fn(), +})); + +const mockedAppAuth = vi.mocked(ghAuth.createGithubAppAuth); +const mockedInstallationAuth = vi.mocked(ghAuth.createGithubInstallationAuth); +const mockedCreateClient = vi.mocked(ghAuth.createOctokitClient); +const mockedResolveCapability = vi.spyOn(controlPlaneProviderRegistry, 'capability'); + +const githubClient = { + actions: { listSelfHostedRunnersForOrg: vi.fn() }, + apps: { getOrgInstallation: vi.fn() }, + paginate: vi.fn(), +} as unknown as Octokit; + +const cleanEnv = process.env; + +const lanes = providerTypes.map((type) => ({ + provider: { + type, + listRunners: vi.fn(), + countAvailableRunners: vi.fn(), + createRunners: vi.fn(), + } satisfies PoolRunnerProvider, +})); + +beforeEach(() => { + vi.clearAllMocks(); + process.env = { ...cleanEnv }; + + mockedAppAuth.mockResolvedValue({ type: 'app', token: 'app-token', appId: 1, expiresAt: 'some-date' }); + mockedInstallationAuth.mockResolvedValue({ + type: 'token', + tokenType: 'installation', + token: 'installation-token', + createdAt: 'some-date', + expiresAt: 'some-date', + permissions: {}, + repositorySelection: 'selected', + installationId: 2, + }); + mockedCreateClient.mockResolvedValue(githubClient); + vi.mocked(githubRunner.getGitHubEnterpriseApiUrl).mockReturnValue({ ghesApiUrl: '', ghesBaseUrl: '' }); + vi.mocked(githubRunner.validateSsmParameterStoreTags).mockReturnValue([]); + vi.mocked(githubClient.apps.getOrgInstallation).mockResolvedValue({ data: { id: 2 } } as never); + vi.mocked(githubClient.paginate).mockResolvedValue([]); +}); + +definePoolContractTests({ + adjust, + githubInstallationClient: githubClient, + lanes, + resolveCapability: mockedResolveCapability, +}); diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts new file mode 100644 index 0000000000..7e60fe7140 --- /dev/null +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts @@ -0,0 +1,34 @@ +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import { beforeEach, vi } from 'vitest'; + +import { providerTypes } from '../test-framework/runner-provider/provider-types'; +import { defineScaleDownContractTests } from '../test-framework/runner-provider/scale-down'; +import { controlPlaneProviderRegistry } from '../provider-registry'; +import { scaleDown } from './scale-down'; +import type { ScaleDownRunnerProvider } from './scale-down-provider'; + +const mockedResolveCapability = vi.spyOn(controlPlaneProviderRegistry, 'capability'); + +const cleanEnv = process.env; + +const lanes = providerTypes.map((type) => ({ + provider: { + type, + list: vi.fn(), + bootTimeExceeded: vi.fn(), + markOrphan: vi.fn(), + unmarkOrphan: vi.fn(), + terminate: vi.fn(), + } satisfies ScaleDownRunnerProvider, +})); + +beforeEach(() => { + vi.clearAllMocks(); + process.env = { ...cleanEnv }; +}); + +defineScaleDownContractTests({ + lanes, + resolveCapability: mockedResolveCapability, + scaleDown, +}); diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts new file mode 100644 index 0000000000..f0136752e7 --- /dev/null +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts @@ -0,0 +1,84 @@ +import type { Octokit } from '@octokit/rest'; +import { beforeEach, vi } from 'vitest'; + +import { providerTypes } from '../test-framework/runner-provider/provider-types'; +import { defineScaleUpContractTests } from '../test-framework/runner-provider/scale-up'; +import * as ghAuth from '../github/auth'; +import { controlPlaneProviderRegistry } from '../provider-registry'; +import * as githubRunner from './github-runner'; +import { scaleUp } from './scale-up'; +import type { ScaleUpRunnerProvider } from './scale-up-provider'; +import type { ActionRequestMessageSQS } from './types'; + +vi.mock('../github/auth', () => ({ + createGithubAppAuth: vi.fn(), + createGithubInstallationAuth: vi.fn(), + createOctokitClient: vi.fn(), +})); + +vi.mock('./github-runner', async (importOriginal) => ({ + ...(await importOriginal()), + getGitHubEnterpriseApiUrl: vi.fn(), + getInstallationId: vi.fn(), + isJobQueued: vi.fn(), +})); + +const mockedAppAuth = vi.mocked(ghAuth.createGithubAppAuth); +const mockedInstallationAuth = vi.mocked(ghAuth.createGithubInstallationAuth); +const mockedCreateClient = vi.mocked(ghAuth.createOctokitClient); +const mockedResolveCapability = vi.spyOn(controlPlaneProviderRegistry, 'capability'); + +const githubClient = {} as Octokit; + +const payloads: ActionRequestMessageSQS[] = [ + { + id: 1, + eventType: 'workflow_job', + repositoryName: 'hello-world', + repositoryOwner: 'Codertocat', + installationId: 2, + repoOwnerType: 'Organization', + messageId: 'foobar', + }, +]; + +const cleanEnv = process.env; + +const lanes = providerTypes.map((type) => ({ + provider: { + type, + prepareGroup: vi.fn(), + getCurrentRunners: vi.fn(), + createRunners: vi.fn(), + } satisfies ScaleUpRunnerProvider, + state: { lane: type }, +})); + +beforeEach(() => { + vi.clearAllMocks(); + process.env = { ...cleanEnv }; + + mockedAppAuth.mockResolvedValue({ type: 'app', token: 'app-token', appId: 1, expiresAt: 'some-date' }); + mockedInstallationAuth.mockResolvedValue({ + type: 'token', + tokenType: 'installation', + token: 'installation-token', + createdAt: 'some-date', + expiresAt: 'some-date', + permissions: {}, + repositorySelection: 'selected', + installationId: 2, + }); + mockedCreateClient.mockResolvedValue(githubClient); + vi.mocked(githubRunner.getGitHubEnterpriseApiUrl).mockReturnValue({ ghesApiUrl: '', ghesBaseUrl: '' }); + vi.mocked(githubRunner.getInstallationId).mockResolvedValue(2); + vi.mocked(githubRunner.isJobQueued).mockResolvedValue(true); +}); + +defineScaleUpContractTests({ + createPayloads: () => structuredClone(payloads), + githubInstallationClient: githubClient, + lanes, + resolveCapability: mockedResolveCapability, + scaleUp, +}); diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/pool.ts b/lambdas/functions/control-plane/src/test-framework/runner-provider/pool.ts new file mode 100644 index 0000000000..9997e51aba --- /dev/null +++ b/lambdas/functions/control-plane/src/test-framework/runner-provider/pool.ts @@ -0,0 +1,80 @@ +import type { Octokit } from '@octokit/rest'; +import { beforeEach, describe, expect, it, type MockInstance, vi } from 'vitest'; + +import type { PoolEvent } from '../../pool/pool'; +import type { PoolRunnerProvider } from '../../pool/pool-provider'; + +type TestPoolProvider = Omit & { type: TType }; + +export interface PoolContractLane { + provider: TestPoolProvider; +} + +interface PoolContractOptions { + adjust: (event: PoolEvent) => Promise; + githubInstallationClient: Octokit; + lanes: readonly PoolContractLane[]; + resolveCapability: MockInstance<(type: TType, capability: 'pool') => () => Omit, 'type'>>; +} + +export function definePoolContractTests({ + adjust, + githubInstallationClient, + lanes, + resolveCapability, +}: PoolContractOptions): void { + describe.each(lanes.map((lane) => [lane.provider.type, lane] as const))( + '%s pool orchestration contract', + (_, { provider }) => { + beforeEach(() => { + process.env.ENVIRONMENT = 'test-environment'; + process.env.RUNNER_OWNER = 'test-owner'; + process.env.RUNNERS_MAXIMUM_COUNT = '-1'; + + resolveCapability.mockReturnValue(() => provider); + vi.mocked(provider.listRunners).mockResolvedValue([{}, {}]); + vi.mocked(provider.countAvailableRunners).mockReturnValue(2); + vi.mocked(provider.createRunners).mockResolvedValue([]); + }); + + it('creates only the runners required to reach the requested pool size', async () => { + await adjust({ poolSize: 5, type: provider.type }); + + expect(resolveCapability).toHaveBeenCalledWith(provider.type, 'pool'); + expect(provider.listRunners).toHaveBeenCalledWith({ + environment: 'test-environment', + runnerOwner: 'test-owner', + runnerType: 'Org', + }); + expect(provider.createRunners).toHaveBeenCalledWith( + expect.objectContaining({ + githubInstallationClient, + numberOfRunners: 3, + }), + ); + }); + + it('does not create runners when the requested pool size is already available', async () => { + await adjust({ poolSize: 2, type: provider.type }); + + expect(provider.createRunners).not.toHaveBeenCalled(); + }); + + it('caps pool creation at the remaining maximum-runner headroom', async () => { + process.env.RUNNERS_MAXIMUM_COUNT = '4'; + + await adjust({ poolSize: 5, type: provider.type }); + + expect(provider.createRunners).toHaveBeenCalledWith(expect.objectContaining({ numberOfRunners: 2 })); + }); + + it('forwards whether busy runners count toward the pool', async () => { + process.env.INCLUDE_BUSY_RUNNERS = 'true'; + + await adjust({ poolSize: 2, type: provider.type }); + + expect(provider.countAvailableRunners).toHaveBeenCalledWith(expect.any(Array), expect.any(Map), true); + }); + }, + ); +} diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts b/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts new file mode 100644 index 0000000000..a11a6449fb --- /dev/null +++ b/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts @@ -0,0 +1,3 @@ +import { runnerProviderTypes } from '../../provider-registry'; + +export const providerTypes = runnerProviderTypes; diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-down.ts b/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-down.ts new file mode 100644 index 0000000000..01a5ba3cca --- /dev/null +++ b/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-down.ts @@ -0,0 +1,69 @@ +import { beforeEach, describe, expect, it, type MockInstance, vi } from 'vitest'; + +import type { ScaleDownRunnerProvider } from '../../scale-runners/scale-down-provider'; + +type TestScaleDownProvider = Omit & { type: TType }; + +export interface ScaleDownContractLane { + provider: TestScaleDownProvider; +} + +interface ScaleDownContractOptions { + lanes: readonly ScaleDownContractLane[]; + resolveCapability: MockInstance< + (type: TType, capability: 'scaleDown') => () => Omit, 'type'> + >; + scaleDown: () => Promise; +} + +export function defineScaleDownContractTests({ + lanes, + resolveCapability, + scaleDown, +}: ScaleDownContractOptions): void { + describe.each(lanes.map((lane) => [lane.provider.type, lane] as const))( + '%s scale-down orchestration contract', + (_, { provider }) => { + beforeEach(() => { + process.env.ENVIRONMENT = 'test-environment'; + process.env.RUNNER_PROVIDER_TYPE = provider.type; + process.env.SCALE_DOWN_CONFIG = '[]'; + + resolveCapability.mockReturnValue(() => provider); + vi.mocked(provider.list).mockResolvedValue([]); + vi.mocked(provider.bootTimeExceeded).mockReturnValue(false); + vi.mocked(provider.markOrphan).mockResolvedValue(); + vi.mocked(provider.unmarkOrphan).mockResolvedValue(); + vi.mocked(provider.terminate).mockResolvedValue(); + }); + + it('uses the provider to inspect orphan and active runners', async () => { + await scaleDown(); + + expect(resolveCapability).toHaveBeenCalledWith(provider.type, 'scaleDown'); + expect(provider.list).toHaveBeenNthCalledWith(1, 'test-environment', true); + expect(provider.list).toHaveBeenNthCalledWith(2, 'test-environment'); + }); + + it('terminates an orphan that has no GitHub runner identity', async () => { + vi.mocked(provider.list) + .mockResolvedValueOnce([{ id: 'orphan-runner', orphan: true }]) + .mockResolvedValue([]); + + await scaleDown(); + + expect(provider.terminate).toHaveBeenCalledWith('orphan-runner'); + }); + + it('does not terminate an orphan with bypass removal enabled', async () => { + vi.mocked(provider.list) + .mockResolvedValueOnce([{ id: 'protected-runner', orphan: true, bypassRemoval: true }]) + .mockResolvedValue([]); + + await scaleDown(); + + expect(provider.terminate).not.toHaveBeenCalled(); + }); + }, + ); +} diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-up.ts b/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-up.ts new file mode 100644 index 0000000000..dddccbcf01 --- /dev/null +++ b/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-up.ts @@ -0,0 +1,93 @@ +import type { Octokit } from '@octokit/rest'; +import { beforeEach, describe, expect, it, type MockInstance, vi } from 'vitest'; + +import type { ScaleUpRunnerProvider } from '../../scale-runners/scale-up-provider'; +import type { ActionRequestMessageSQS } from '../../scale-runners/types'; + +type TestScaleUpProvider = Omit & { type: TType }; + +export interface ScaleUpContractLane { + provider: TestScaleUpProvider; + state: unknown; +} + +interface ScaleUpContractOptions { + createPayloads: () => ActionRequestMessageSQS[]; + githubInstallationClient: Octokit; + lanes: readonly ScaleUpContractLane[]; + resolveCapability: MockInstance< + (type: TType, capability: 'scaleUp') => () => Omit, 'type'> + >; + scaleUp: (payloads: ActionRequestMessageSQS[]) => Promise; +} + +const createResult = { + instances: ['runner-1'], + retryableErrorCount: 0, + nonRetryableErrorCount: 0, +}; + +export function defineScaleUpContractTests({ + createPayloads, + githubInstallationClient, + lanes, + resolveCapability, + scaleUp, +}: ScaleUpContractOptions): void { + describe.each(lanes.map((lane) => [lane.provider.type, lane] as const))( + '%s scale-up orchestration contract', + (_, { provider, state }) => { + beforeEach(() => { + process.env.ENABLE_ORGANIZATION_RUNNERS = 'true'; + process.env.RUNNERS_MAXIMUM_COUNT = '3'; + process.env.RUNNER_PROVIDER_TYPE = provider.type; + + resolveCapability.mockReturnValue(() => provider); + vi.mocked(provider.prepareGroup).mockResolvedValue({ runnerLabels: [], state }); + vi.mocked(provider.getCurrentRunners).mockResolvedValue(0); + vi.mocked(provider.createRunners).mockResolvedValue(createResult); + }); + + it('forwards the prepared lane state through runner lookup and creation', async () => { + const payloads = createPayloads(); + payloads[0].labels = ['lane-label']; + + await scaleUp(payloads); + + expect(resolveCapability).toHaveBeenCalledWith(provider.type, 'scaleUp'); + expect(provider.prepareGroup).toHaveBeenCalledWith(['lane-label']); + expect(provider.getCurrentRunners).toHaveBeenCalledWith(state, { + runnerOwner: payloads[0].repositoryOwner, + runnerType: 'Org', + }); + expect(provider.createRunners).toHaveBeenCalledWith( + expect.objectContaining({ + githubInstallationClient, + numberOfRunners: 1, + state, + }), + ); + }); + + it('does not query current runners when the lane has unlimited capacity', async () => { + process.env.RUNNERS_MAXIMUM_COUNT = '-1'; + const payloads = createPayloads(); + payloads.push({ ...payloads[0], id: 2, messageId: 'message-2' }); + + await scaleUp(payloads); + + expect(provider.getCurrentRunners).not.toHaveBeenCalled(); + expect(provider.createRunners).toHaveBeenCalledWith(expect.objectContaining({ numberOfRunners: 2 })); + }); + + it('does not create runners when the lane has reached maximum capacity', async () => { + process.env.RUNNERS_MAXIMUM_COUNT = '1'; + vi.mocked(provider.getCurrentRunners).mockResolvedValue(1); + + await scaleUp(createPayloads()); + + expect(provider.createRunners).not.toHaveBeenCalled(); + }); + }, + ); +} From 222e9bb4df242ed14a4ba2e7f52d5f590baa9d05 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 17:18:30 +0200 Subject: [PATCH 03/21] refactor(runners): centralize provider registration --- .../runner-providers/aws/ec2/control-plane.ts | 4 +++- lambdas/libs/runner-providers/aws/ec2/index.ts | 14 ++++++++++++-- .../libs/runner-providers/aws/ec2/webhook.ts | 2 +- lambdas/libs/runner-providers/contracts.ts | 18 ++++++++++++++++-- lambdas/libs/runner-providers/control-plane.ts | 9 ++------- lambdas/libs/runner-providers/core/index.ts | 13 +++++-------- .../libs/runner-providers/providers.config.ts | 11 ++++++++--- lambdas/libs/runner-providers/webhook.ts | 9 ++------- 8 files changed, 49 insertions(+), 31 deletions(-) diff --git a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts index 8b4cc7edf0..aca992e116 100644 --- a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts +++ b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts @@ -1,3 +1,5 @@ +/// + import type { CreateStartRunnerConfig, RunnerProviderPlugin } from '../../core'; import type { ControlPlaneProviderCapabilities } from '../../contracts'; @@ -7,7 +9,7 @@ import { createEc2ScaleUpProvider } from './src/control-plane/scale-up'; export function createEc2ControlPlanePlugin( createStartRunnerConfig: CreateStartRunnerConfig, -): RunnerProviderPlugin { +): RunnerProviderPlugin { return { type: 'ec2', capabilities: { diff --git a/lambdas/libs/runner-providers/aws/ec2/index.ts b/lambdas/libs/runner-providers/aws/ec2/index.ts index 7f7ec3cccd..092d23302c 100644 --- a/lambdas/libs/runner-providers/aws/ec2/index.ts +++ b/lambdas/libs/runner-providers/aws/ec2/index.ts @@ -1,5 +1,15 @@ -export { createEc2ControlPlanePlugin } from './control-plane'; -export { ec2WebhookPlugin } from './webhook'; +import type { RunnerProviderModule } from '../../contracts'; + +import { createEc2ControlPlanePlugin } from './control-plane'; +import { ec2WebhookPlugin } from './webhook'; + +export const ec2Provider = { + type: 'ec2', + createControlPlanePlugin: createEc2ControlPlanePlugin, + webhookPlugin: ec2WebhookPlugin, +} satisfies RunnerProviderModule; + +export { createEc2ControlPlanePlugin, ec2WebhookPlugin }; export * from './src/control-plane/dynamic-labels'; export * from './src/control-plane/pool'; diff --git a/lambdas/libs/runner-providers/aws/ec2/webhook.ts b/lambdas/libs/runner-providers/aws/ec2/webhook.ts index e1418e2d08..e791b74247 100644 --- a/lambdas/libs/runner-providers/aws/ec2/webhook.ts +++ b/lambdas/libs/runner-providers/aws/ec2/webhook.ts @@ -3,7 +3,7 @@ import type { RunnerProviderPlugin } from '../../core'; import type { WebhookProviderCapabilities } from '../../contracts'; import { ec2DynamicLabelProvider } from './src/webhook/dynamic-labels'; -export const ec2WebhookPlugin: RunnerProviderPlugin = { +export const ec2WebhookPlugin: RunnerProviderPlugin = { type: 'ec2', capabilities: { dynamicLabels: ec2DynamicLabelProvider }, }; diff --git a/lambdas/libs/runner-providers/contracts.ts b/lambdas/libs/runner-providers/contracts.ts index c9ca8d2d01..d02aa670a1 100644 --- a/lambdas/libs/runner-providers/contracts.ts +++ b/lambdas/libs/runner-providers/contracts.ts @@ -1,4 +1,10 @@ -import type { PoolRunnerProvider, RunnerProviderType, ScaleDownRunnerProvider, ScaleUpRunnerProvider } from './core'; +import type { + CreateStartRunnerConfig, + PoolRunnerProvider, + RunnerProviderPlugin, + ScaleDownRunnerProvider, + ScaleUpRunnerProvider, +} from './core'; export interface AwsDynamicLabelsValueRule { allowed?: string[]; @@ -14,7 +20,7 @@ export interface AwsDynamicLabelsPolicy { export interface AwsRunnerMatcherConfig { id: string; arn: string; - runnerProvider?: RunnerProviderType; + runnerProvider?: string; matcherConfig: { labelMatchers: string[][]; exactMatch: boolean; @@ -47,3 +53,11 @@ export interface ControlPlaneProviderCapabilities { export interface WebhookProviderCapabilities { dynamicLabels: DynamicLabelProvider; } + +export interface RunnerProviderModule { + type: TType; + createControlPlanePlugin( + createStartRunnerConfig: CreateStartRunnerConfig, + ): RunnerProviderPlugin; + webhookPlugin: RunnerProviderPlugin; +} diff --git a/lambdas/libs/runner-providers/control-plane.ts b/lambdas/libs/runner-providers/control-plane.ts index f8b3b5a4f2..fc6d49fffc 100644 --- a/lambdas/libs/runner-providers/control-plane.ts +++ b/lambdas/libs/runner-providers/control-plane.ts @@ -1,16 +1,11 @@ import type { CreateStartRunnerConfig } from './core'; import { createRunnerProviderRegistry } from './core'; -import { createEc2ControlPlanePlugin } from './aws/ec2/control-plane'; import type { ControlPlaneProviderCapabilities } from './contracts'; -import { enabledRunnerProviderTypes } from './providers.config'; - -const installedControlPlaneProviders = { - ec2: createEc2ControlPlanePlugin, -}; +import { enabledRunnerProviders } from './providers.config'; export function createControlPlaneProviderRegistry(createStartRunnerConfig: CreateStartRunnerConfig) { return createRunnerProviderRegistry( - enabledRunnerProviderTypes.map((type) => installedControlPlaneProviders[type](createStartRunnerConfig)), + enabledRunnerProviders.map((provider) => provider.createControlPlanePlugin(createStartRunnerConfig)), ); } diff --git a/lambdas/libs/runner-providers/core/index.ts b/lambdas/libs/runner-providers/core/index.ts index ebb3fb77b2..02f282d40c 100644 --- a/lambdas/libs/runner-providers/core/index.ts +++ b/lambdas/libs/runner-providers/core/index.ts @@ -2,19 +2,16 @@ import type { Octokit } from '@octokit/rest'; import { enabledRunnerProviderTypes } from '../providers.config'; -export const runnerProviderTypes = enabledRunnerProviderTypes; -export type RunnerProviderType = (typeof runnerProviderTypes)[number]; - -const defaultRunnerProvider: RunnerProviderType = 'ec2'; +export type RunnerProviderType = (typeof import('../providers.config').enabledRunnerProviders)[number]['type']; export function normalizeRunnerProviderType(type: unknown): RunnerProviderType | undefined { - if (type === undefined) return defaultRunnerProvider; + if (type === undefined) return enabledRunnerProviderTypes[0]; if (typeof type !== 'string') return undefined; const normalizedType = type.trim().toLowerCase(); - if (!normalizedType) return defaultRunnerProvider; + if (!normalizedType) return enabledRunnerProviderTypes[0]; - return runnerProviderTypes.find((runnerProviderType) => runnerProviderType === normalizedType); + return enabledRunnerProviderTypes.find((runnerProviderType) => runnerProviderType === normalizedType); } export function resolveRunnerProviderType(type: unknown): RunnerProviderType { @@ -146,7 +143,7 @@ export interface PoolRunnerProvider extends RunnerProvider { createRunners(input: CreatePoolRunnersInput): Promise; } -export interface RunnerProviderPlugin { +export interface RunnerProviderPlugin { type: TType; capabilities: TCapabilities; } diff --git a/lambdas/libs/runner-providers/providers.config.ts b/lambdas/libs/runner-providers/providers.config.ts index 3d42f9cbda..e8ed4df2c7 100644 --- a/lambdas/libs/runner-providers/providers.config.ts +++ b/lambdas/libs/runner-providers/providers.config.ts @@ -1,5 +1,10 @@ +import { ec2Provider } from './aws/ec2'; +import type { RunnerProviderModule } from './contracts'; + /** - * Provider types enabled in webhook and control-plane bundles. - * Installing a provider does not activate it; add its type to this list explicitly. + * Provider plugins enabled in webhook and control-plane bundles. + * Installing and enabling a provider requires adding it only to this list. */ -export const enabledRunnerProviderTypes = ['ec2'] as const; +export const enabledRunnerProviders = [ec2Provider] satisfies readonly RunnerProviderModule[]; + +export const enabledRunnerProviderTypes = enabledRunnerProviders.map(({ type }) => type); diff --git a/lambdas/libs/runner-providers/webhook.ts b/lambdas/libs/runner-providers/webhook.ts index 9de5aa4a69..db3d2ce820 100644 --- a/lambdas/libs/runner-providers/webhook.ts +++ b/lambdas/libs/runner-providers/webhook.ts @@ -1,13 +1,8 @@ import { createRunnerProviderRegistry } from './core'; -import { ec2WebhookPlugin } from './aws/ec2/webhook'; import type { WebhookProviderCapabilities } from './contracts'; -import { enabledRunnerProviderTypes } from './providers.config'; - -const installedWebhookProviders = { - ec2: ec2WebhookPlugin, -}; +import { enabledRunnerProviders } from './providers.config'; export const webhookProviderRegistry = createRunnerProviderRegistry( - enabledRunnerProviderTypes.map((type) => installedWebhookProviders[type]), + enabledRunnerProviders.map((provider) => provider.webhookPlugin), ); From 7d3a4c19e84877791bc46cdad2a340bbdccb9ac4 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 17:21:33 +0200 Subject: [PATCH 04/21] docs(runners): add provider plugin template --- .../templates/provider/README.md | 20 +++++ .../templates/provider/control-plane.ts | 85 +++++++++++++++++++ .../templates/provider/index.ts | 14 +++ .../templates/provider/provider.test.ts | 31 +++++++ .../templates/provider/webhook.ts | 16 ++++ 5 files changed, 166 insertions(+) create mode 100644 lambdas/libs/runner-providers/templates/provider/README.md create mode 100644 lambdas/libs/runner-providers/templates/provider/control-plane.ts create mode 100644 lambdas/libs/runner-providers/templates/provider/index.ts create mode 100644 lambdas/libs/runner-providers/templates/provider/provider.test.ts create mode 100644 lambdas/libs/runner-providers/templates/provider/webhook.ts diff --git a/lambdas/libs/runner-providers/templates/provider/README.md b/lambdas/libs/runner-providers/templates/provider/README.md new file mode 100644 index 0000000000..4a14505ab9 --- /dev/null +++ b/lambdas/libs/runner-providers/templates/provider/README.md @@ -0,0 +1,20 @@ +# Runner provider template + +Copy this directory to the appropriate provider namespace, for example +`aws/codebuild`, and replace `template` with the new lane type. + +The template is compile-checked but intentionally not registered. To enable a +completed provider, import its module and add it once to +`providers.config.ts`. The webhook and control-plane registries will then use +the corresponding plugin automatically, and the generic control-plane +contracts will include the new lane type. + +Implement every capability before registering the provider: + +- `pool`: list managed runners, count available runners, and create runners. +- `scaleUp`: prepare lane state, count current runners, and create runners. +- `scaleDown`: list, inspect, mark, unmark, and terminate runners. +- `dynamicLabels`: select a webhook dispatch target for supported labels. + +Provider-specific tests should remain beside the provider implementation. The +generic orchestration contracts remain owned by the control-plane package. diff --git a/lambdas/libs/runner-providers/templates/provider/control-plane.ts b/lambdas/libs/runner-providers/templates/provider/control-plane.ts new file mode 100644 index 0000000000..9d0fb8e3f8 --- /dev/null +++ b/lambdas/libs/runner-providers/templates/provider/control-plane.ts @@ -0,0 +1,85 @@ +import type { + CreateStartRunnerConfig, + PoolRunnerProvider, + RunnerProviderPlugin, + ScaleDownRunnerProvider, + ScaleUpRunnerProvider, +} from '../../core'; + +import type { ControlPlaneProviderCapabilities } from '../../contracts'; + +export interface TemplateScaleUpState { + resourceGroupId: string; +} + +function notImplemented(operation: string): never { + throw new Error(`Template runner provider must implement ${operation}`); +} + +export function createTemplatePoolProvider( + createStartRunnerConfig: CreateStartRunnerConfig, +): Omit { + return { + listRunners: async () => notImplemented('pool.listRunners'), + countAvailableRunners: () => notImplemented('pool.countAvailableRunners'), + createRunners: async (input) => { + // Use this dependency after provisioning runner IDs to create their GitHub configuration. + void createStartRunnerConfig; + void input; + return notImplemented('pool.createRunners'); + }, + }; +} + +export function createTemplateScaleUpProvider( + createStartRunnerConfig: CreateStartRunnerConfig, +): Omit { + return { + prepareGroup: async (messageLabels) => { + void messageLabels; + return notImplemented('scaleUp.prepareGroup'); + }, + getCurrentRunners: async (state, input) => { + const templateState = state as TemplateScaleUpState; + void templateState; + void input; + return notImplemented('scaleUp.getCurrentRunners'); + }, + createRunners: async (input) => { + // Use this dependency after provisioning runner IDs to create their GitHub configuration. + void createStartRunnerConfig; + void input; + return notImplemented('scaleUp.createRunners'); + }, + }; +} + +export function createTemplateScaleDownProvider(): Omit { + return { + list: async (environment, orphan) => { + void environment; + void orphan; + return notImplemented('scaleDown.list'); + }, + bootTimeExceeded: (runner) => { + void runner; + return notImplemented('scaleDown.bootTimeExceeded'); + }, + markOrphan: async (id) => notImplemented(`scaleDown.markOrphan(${id})`), + unmarkOrphan: async (id) => notImplemented(`scaleDown.unmarkOrphan(${id})`), + terminate: async (id) => notImplemented(`scaleDown.terminate(${id})`), + }; +} + +export function createTemplateControlPlanePlugin( + createStartRunnerConfig: CreateStartRunnerConfig, +): RunnerProviderPlugin { + return { + type: 'template', + capabilities: { + pool: () => createTemplatePoolProvider(createStartRunnerConfig), + scaleUp: () => createTemplateScaleUpProvider(createStartRunnerConfig), + scaleDown: createTemplateScaleDownProvider, + }, + }; +} diff --git a/lambdas/libs/runner-providers/templates/provider/index.ts b/lambdas/libs/runner-providers/templates/provider/index.ts new file mode 100644 index 0000000000..59277cd3ca --- /dev/null +++ b/lambdas/libs/runner-providers/templates/provider/index.ts @@ -0,0 +1,14 @@ +import type { RunnerProviderModule } from '../../contracts'; + +import { createTemplateControlPlanePlugin } from './control-plane'; +import { templateWebhookPlugin } from './webhook'; + +/** + * Copy this module for a new provider and register the completed module once in + * providers.config.ts. Do not register this template itself. + */ +export const templateProvider = { + type: 'template', + createControlPlanePlugin: createTemplateControlPlanePlugin, + webhookPlugin: templateWebhookPlugin, +} satisfies RunnerProviderModule<'template'>; diff --git a/lambdas/libs/runner-providers/templates/provider/provider.test.ts b/lambdas/libs/runner-providers/templates/provider/provider.test.ts new file mode 100644 index 0000000000..2508135932 --- /dev/null +++ b/lambdas/libs/runner-providers/templates/provider/provider.test.ts @@ -0,0 +1,31 @@ +import { expect, it, vi } from 'vitest'; + +import { templateProvider } from './index'; + +it('exposes every runner provider capability from one module', () => { + const controlPlanePlugin = templateProvider.createControlPlanePlugin(vi.fn(async () => [])); + const pool = controlPlanePlugin.capabilities.pool(); + const scaleUp = controlPlanePlugin.capabilities.scaleUp(); + const scaleDown = controlPlanePlugin.capabilities.scaleDown(); + + expect(controlPlanePlugin.type).toBe(templateProvider.type); + expect(pool).toEqual({ + listRunners: expect.any(Function), + countAvailableRunners: expect.any(Function), + createRunners: expect.any(Function), + }); + expect(scaleUp).toEqual({ + prepareGroup: expect.any(Function), + getCurrentRunners: expect.any(Function), + createRunners: expect.any(Function), + }); + expect(scaleDown).toEqual({ + list: expect.any(Function), + bootTimeExceeded: expect.any(Function), + markOrphan: expect.any(Function), + unmarkOrphan: expect.any(Function), + terminate: expect.any(Function), + }); + expect(templateProvider.webhookPlugin.type).toBe(templateProvider.type); + expect(templateProvider.webhookPlugin.capabilities.dynamicLabels.selectQueue).toEqual(expect.any(Function)); +}); diff --git a/lambdas/libs/runner-providers/templates/provider/webhook.ts b/lambdas/libs/runner-providers/templates/provider/webhook.ts new file mode 100644 index 0000000000..706121f1e7 --- /dev/null +++ b/lambdas/libs/runner-providers/templates/provider/webhook.ts @@ -0,0 +1,16 @@ +import type { RunnerProviderPlugin } from '../../core'; + +import type { DynamicLabelProvider, WebhookProviderCapabilities } from '../../contracts'; + +export const templateDynamicLabelProvider: DynamicLabelProvider = { + selectQueue: (input) => { + void input; + // Return a dispatch target when this provider accepts the requested dynamic labels. + return undefined; + }, +}; + +export const templateWebhookPlugin: RunnerProviderPlugin = { + type: 'template', + capabilities: { dynamicLabels: templateDynamicLabelProvider }, +}; From 711dcd1377df6ec77b992eab4940a9cbe9b84b71 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 19:50:23 +0200 Subject: [PATCH 05/21] refactor(runners): merge provider registries --- lambdas/functions/control-plane/src/provider-registry.ts | 2 +- .../functions/webhook/src/runners/aws-dynamic-labels.ts | 2 +- lambdas/libs/runner-providers/package.json | 3 +-- lambdas/libs/runner-providers/registry.test.ts | 3 +-- .../runner-providers/{control-plane.ts => registry.ts} | 6 +++++- lambdas/libs/runner-providers/webhook.ts | 8 -------- 6 files changed, 9 insertions(+), 15 deletions(-) rename lambdas/libs/runner-providers/{control-plane.ts => registry.ts} (63%) delete mode 100644 lambdas/libs/runner-providers/webhook.ts diff --git a/lambdas/functions/control-plane/src/provider-registry.ts b/lambdas/functions/control-plane/src/provider-registry.ts index 383f872a03..18b012ba6d 100644 --- a/lambdas/functions/control-plane/src/provider-registry.ts +++ b/lambdas/functions/control-plane/src/provider-registry.ts @@ -1,4 +1,4 @@ -import { createControlPlaneProviderRegistry } from '@aws-github-runner/runner-providers/control-plane'; +import { createControlPlaneProviderRegistry } from '@aws-github-runner/runner-providers/registry'; import { createStartRunnerConfig } from './scale-runners/github-runner'; diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts index 0bf3729164..1fa034018c 100644 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts +++ b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts @@ -1,7 +1,7 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; import type { DynamicLabelDispatchTarget } from '@aws-github-runner/runner-providers'; import { normalizeRunnerProviderType } from '@aws-github-runner/runner-providers/core'; -import { webhookProviderRegistry } from '@aws-github-runner/runner-providers/webhook'; +import { webhookProviderRegistry } from '@aws-github-runner/runner-providers/registry'; import type { RunnerMatcherConfig } from '../sqs'; diff --git a/lambdas/libs/runner-providers/package.json b/lambdas/libs/runner-providers/package.json index 27f9594a0d..156b08d90b 100644 --- a/lambdas/libs/runner-providers/package.json +++ b/lambdas/libs/runner-providers/package.json @@ -4,9 +4,8 @@ "main": "contracts.ts", "exports": { ".": "./contracts.ts", - "./control-plane": "./control-plane.ts", "./core": "./core/index.ts", - "./webhook": "./webhook.ts", + "./registry": "./registry.ts", "./aws/ec2": "./aws/ec2/index.ts", "./aws/ec2/control-plane/runners": "./aws/ec2/src/control-plane/runners.ts", "./aws/ec2/control-plane/runner-config": "./aws/ec2/src/control-plane/runner-config.ts" diff --git a/lambdas/libs/runner-providers/registry.test.ts b/lambdas/libs/runner-providers/registry.test.ts index df9b2f81d0..44ebb44bc6 100644 --- a/lambdas/libs/runner-providers/registry.test.ts +++ b/lambdas/libs/runner-providers/registry.test.ts @@ -1,8 +1,7 @@ import { expect, it, vi } from 'vitest'; -import { createControlPlaneProviderRegistry } from './control-plane'; import { enabledRunnerProviderTypes } from './providers.config'; -import { webhookProviderRegistry } from './webhook'; +import { createControlPlaneProviderRegistry, webhookProviderRegistry } from './registry'; it('enables every configured provider in both runtime registries', () => { const createStartRunnerConfig = vi.fn(async () => []); diff --git a/lambdas/libs/runner-providers/control-plane.ts b/lambdas/libs/runner-providers/registry.ts similarity index 63% rename from lambdas/libs/runner-providers/control-plane.ts rename to lambdas/libs/runner-providers/registry.ts index fc6d49fffc..a9326a7508 100644 --- a/lambdas/libs/runner-providers/control-plane.ts +++ b/lambdas/libs/runner-providers/registry.ts @@ -1,7 +1,7 @@ import type { CreateStartRunnerConfig } from './core'; import { createRunnerProviderRegistry } from './core'; -import type { ControlPlaneProviderCapabilities } from './contracts'; +import type { ControlPlaneProviderCapabilities, WebhookProviderCapabilities } from './contracts'; import { enabledRunnerProviders } from './providers.config'; export function createControlPlaneProviderRegistry(createStartRunnerConfig: CreateStartRunnerConfig) { @@ -9,3 +9,7 @@ export function createControlPlaneProviderRegistry(createStartRunnerConfig: Crea enabledRunnerProviders.map((provider) => provider.createControlPlanePlugin(createStartRunnerConfig)), ); } + +export const webhookProviderRegistry = createRunnerProviderRegistry( + enabledRunnerProviders.map((provider) => provider.webhookPlugin), +); diff --git a/lambdas/libs/runner-providers/webhook.ts b/lambdas/libs/runner-providers/webhook.ts deleted file mode 100644 index db3d2ce820..0000000000 --- a/lambdas/libs/runner-providers/webhook.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createRunnerProviderRegistry } from './core'; - -import type { WebhookProviderCapabilities } from './contracts'; -import { enabledRunnerProviders } from './providers.config'; - -export const webhookProviderRegistry = createRunnerProviderRegistry( - enabledRunnerProviders.map((provider) => provider.webhookPlugin), -); From ab7985362f5f28366fd350df20ba528aabad8d56 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 19:54:24 +0200 Subject: [PATCH 06/21] refactor(runners): expose provider registry facade --- .../control-plane/src/provider-registry.ts | 6 +++--- .../webhook/src/runners/aws-dynamic-labels.ts | 4 ++-- lambdas/libs/runner-providers/registry.test.ts | 9 +++++---- lambdas/libs/runner-providers/registry.ts | 17 ++++++++++------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lambdas/functions/control-plane/src/provider-registry.ts b/lambdas/functions/control-plane/src/provider-registry.ts index 18b012ba6d..48ece5bbe2 100644 --- a/lambdas/functions/control-plane/src/provider-registry.ts +++ b/lambdas/functions/control-plane/src/provider-registry.ts @@ -1,7 +1,7 @@ -import { createControlPlaneProviderRegistry } from '@aws-github-runner/runner-providers/registry'; +import { providerRegistry } from '@aws-github-runner/runner-providers/registry'; import { createStartRunnerConfig } from './scale-runners/github-runner'; -export const controlPlaneProviderRegistry = createControlPlaneProviderRegistry(createStartRunnerConfig); +export const controlPlaneProviderRegistry = providerRegistry.controlPlane(createStartRunnerConfig); -export const runnerProviderTypes = controlPlaneProviderRegistry.types; +export const runnerProviderTypes = providerRegistry.types; diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts index 1fa034018c..ad44b57170 100644 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts +++ b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts @@ -1,7 +1,7 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; import type { DynamicLabelDispatchTarget } from '@aws-github-runner/runner-providers'; import { normalizeRunnerProviderType } from '@aws-github-runner/runner-providers/core'; -import { webhookProviderRegistry } from '@aws-github-runner/runner-providers/registry'; +import { providerRegistry } from '@aws-github-runner/runner-providers/registry'; import type { RunnerMatcherConfig } from '../sqs'; @@ -14,7 +14,7 @@ export function selectAwsDynamicLabelQueue( ): DynamicLabelDispatchTarget | undefined { for (const queue of matches) { const provider = normalizeRunnerProviderType(queue.runnerProvider); - const dynamicLabels = provider ? webhookProviderRegistry.capability(provider, 'dynamicLabels') : undefined; + const dynamicLabels = provider ? providerRegistry.webhook.capability(provider, 'dynamicLabels') : undefined; if (!dynamicLabels) { logger.warn(`Queue ${queue.id} has unsupported runner provider '${provider ?? String(queue.runnerProvider)}'`); diff --git a/lambdas/libs/runner-providers/registry.test.ts b/lambdas/libs/runner-providers/registry.test.ts index 44ebb44bc6..35fdaf4d6f 100644 --- a/lambdas/libs/runner-providers/registry.test.ts +++ b/lambdas/libs/runner-providers/registry.test.ts @@ -1,20 +1,21 @@ import { expect, it, vi } from 'vitest'; import { enabledRunnerProviderTypes } from './providers.config'; -import { createControlPlaneProviderRegistry, webhookProviderRegistry } from './registry'; +import { providerRegistry } from './registry'; it('enables every configured provider in both runtime registries', () => { const createStartRunnerConfig = vi.fn(async () => []); - const controlPlaneRegistry = createControlPlaneProviderRegistry(createStartRunnerConfig); + const controlPlaneRegistry = providerRegistry.controlPlane(createStartRunnerConfig); const enabledTypes = enabledRunnerProviderTypes; + expect(providerRegistry.types).toEqual(enabledTypes); expect(controlPlaneRegistry.types).toEqual(enabledTypes); - expect(webhookProviderRegistry.types).toEqual(enabledTypes); + expect(providerRegistry.webhook.types).toEqual(enabledTypes); for (const type of enabledTypes) { expect(controlPlaneRegistry.capability(type, 'pool')).toEqual(expect.any(Function)); expect(controlPlaneRegistry.capability(type, 'scaleUp')).toEqual(expect.any(Function)); expect(controlPlaneRegistry.capability(type, 'scaleDown')).toEqual(expect.any(Function)); - expect(webhookProviderRegistry.capability(type, 'dynamicLabels').selectQueue).toEqual(expect.any(Function)); + expect(providerRegistry.webhook.capability(type, 'dynamicLabels').selectQueue).toEqual(expect.any(Function)); } }); diff --git a/lambdas/libs/runner-providers/registry.ts b/lambdas/libs/runner-providers/registry.ts index a9326a7508..39d177e050 100644 --- a/lambdas/libs/runner-providers/registry.ts +++ b/lambdas/libs/runner-providers/registry.ts @@ -4,12 +4,15 @@ import { createRunnerProviderRegistry } from './core'; import type { ControlPlaneProviderCapabilities, WebhookProviderCapabilities } from './contracts'; import { enabledRunnerProviders } from './providers.config'; -export function createControlPlaneProviderRegistry(createStartRunnerConfig: CreateStartRunnerConfig) { - return createRunnerProviderRegistry( - enabledRunnerProviders.map((provider) => provider.createControlPlanePlugin(createStartRunnerConfig)), - ); -} - -export const webhookProviderRegistry = createRunnerProviderRegistry( +const webhook = createRunnerProviderRegistry( enabledRunnerProviders.map((provider) => provider.webhookPlugin), ); + +export const providerRegistry = { + types: webhook.types, + webhook, + controlPlane: (createStartRunnerConfig: CreateStartRunnerConfig) => + createRunnerProviderRegistry( + enabledRunnerProviders.map((provider) => provider.createControlPlanePlugin(createStartRunnerConfig)), + ), +}; From 94d9094d00263f1367131fca0d2ca76541937159 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 20:01:50 +0200 Subject: [PATCH 07/21] refactor(runners): use plugin factories consistently --- .../runner-providers/aws/ec2/control-plane.ts | 3 +- .../libs/runner-providers/aws/ec2/index.ts | 6 ++-- .../aws/ec2/src/environment.d.ts | 36 ++++++++++--------- .../libs/runner-providers/aws/ec2/webhook.ts | 10 +++--- lambdas/libs/runner-providers/contracts.ts | 2 +- lambdas/libs/runner-providers/registry.ts | 2 +- .../templates/provider/index.ts | 4 +-- .../templates/provider/provider.test.ts | 5 +-- .../templates/provider/webhook.ts | 10 +++--- 9 files changed, 43 insertions(+), 35 deletions(-) diff --git a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts index aca992e116..e3ffe4587f 100644 --- a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts +++ b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts @@ -1,8 +1,7 @@ -/// - import type { CreateStartRunnerConfig, RunnerProviderPlugin } from '../../core'; import type { ControlPlaneProviderCapabilities } from '../../contracts'; +import type {} from './src/environment'; import { createEc2PoolProvider } from './src/control-plane/pool'; import { createEc2ScaleDownProvider } from './src/control-plane/scale-down'; import { createEc2ScaleUpProvider } from './src/control-plane/scale-up'; diff --git a/lambdas/libs/runner-providers/aws/ec2/index.ts b/lambdas/libs/runner-providers/aws/ec2/index.ts index 092d23302c..d439394ed8 100644 --- a/lambdas/libs/runner-providers/aws/ec2/index.ts +++ b/lambdas/libs/runner-providers/aws/ec2/index.ts @@ -1,15 +1,15 @@ import type { RunnerProviderModule } from '../../contracts'; import { createEc2ControlPlanePlugin } from './control-plane'; -import { ec2WebhookPlugin } from './webhook'; +import { createEc2WebhookPlugin } from './webhook'; export const ec2Provider = { type: 'ec2', createControlPlanePlugin: createEc2ControlPlanePlugin, - webhookPlugin: ec2WebhookPlugin, + createWebhookPlugin: createEc2WebhookPlugin, } satisfies RunnerProviderModule; -export { createEc2ControlPlanePlugin, ec2WebhookPlugin }; +export { createEc2ControlPlanePlugin, createEc2WebhookPlugin }; export * from './src/control-plane/dynamic-labels'; export * from './src/control-plane/pool'; diff --git a/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts b/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts index 1cd1c61108..71ee01ff2f 100644 --- a/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/environment.d.ts @@ -1,18 +1,22 @@ -declare namespace NodeJS { - export interface ProcessEnv { - ENVIRONMENT: string; - LAUNCH_TEMPLATE_NAME: string; - SUBNET_IDS: string; - INSTANCE_TYPES: string; - INSTANCE_TARGET_CAPACITY_TYPE: 'on-demand' | 'spot'; - INSTANCE_MAX_SPOT_PRICE: string | undefined; - INSTANCE_ALLOCATION_STRATEGY: - | 'lowest-price' - | 'price-capacity-optimized' - | 'diversified' - | 'capacity-optimized' - | 'capacity-optimized-prioritized' - | 'prioritized'; - SCALE_ERRORS: string; +export {}; + +declare global { + namespace NodeJS { + interface ProcessEnv { + ENVIRONMENT: string; + LAUNCH_TEMPLATE_NAME: string; + SUBNET_IDS: string; + INSTANCE_TYPES: string; + INSTANCE_TARGET_CAPACITY_TYPE: 'on-demand' | 'spot'; + INSTANCE_MAX_SPOT_PRICE: string | undefined; + INSTANCE_ALLOCATION_STRATEGY: + | 'lowest-price' + | 'price-capacity-optimized' + | 'diversified' + | 'capacity-optimized' + | 'capacity-optimized-prioritized' + | 'prioritized'; + SCALE_ERRORS: string; + } } } diff --git a/lambdas/libs/runner-providers/aws/ec2/webhook.ts b/lambdas/libs/runner-providers/aws/ec2/webhook.ts index e791b74247..8a8f2fde16 100644 --- a/lambdas/libs/runner-providers/aws/ec2/webhook.ts +++ b/lambdas/libs/runner-providers/aws/ec2/webhook.ts @@ -3,7 +3,9 @@ import type { RunnerProviderPlugin } from '../../core'; import type { WebhookProviderCapabilities } from '../../contracts'; import { ec2DynamicLabelProvider } from './src/webhook/dynamic-labels'; -export const ec2WebhookPlugin: RunnerProviderPlugin = { - type: 'ec2', - capabilities: { dynamicLabels: ec2DynamicLabelProvider }, -}; +export function createEc2WebhookPlugin(): RunnerProviderPlugin { + return { + type: 'ec2', + capabilities: { dynamicLabels: ec2DynamicLabelProvider }, + }; +} diff --git a/lambdas/libs/runner-providers/contracts.ts b/lambdas/libs/runner-providers/contracts.ts index d02aa670a1..a9f7953294 100644 --- a/lambdas/libs/runner-providers/contracts.ts +++ b/lambdas/libs/runner-providers/contracts.ts @@ -59,5 +59,5 @@ export interface RunnerProviderModule { createControlPlanePlugin( createStartRunnerConfig: CreateStartRunnerConfig, ): RunnerProviderPlugin; - webhookPlugin: RunnerProviderPlugin; + createWebhookPlugin(): RunnerProviderPlugin; } diff --git a/lambdas/libs/runner-providers/registry.ts b/lambdas/libs/runner-providers/registry.ts index 39d177e050..1e2580f26e 100644 --- a/lambdas/libs/runner-providers/registry.ts +++ b/lambdas/libs/runner-providers/registry.ts @@ -5,7 +5,7 @@ import type { ControlPlaneProviderCapabilities, WebhookProviderCapabilities } fr import { enabledRunnerProviders } from './providers.config'; const webhook = createRunnerProviderRegistry( - enabledRunnerProviders.map((provider) => provider.webhookPlugin), + enabledRunnerProviders.map((provider) => provider.createWebhookPlugin()), ); export const providerRegistry = { diff --git a/lambdas/libs/runner-providers/templates/provider/index.ts b/lambdas/libs/runner-providers/templates/provider/index.ts index 59277cd3ca..faaf1e8303 100644 --- a/lambdas/libs/runner-providers/templates/provider/index.ts +++ b/lambdas/libs/runner-providers/templates/provider/index.ts @@ -1,7 +1,7 @@ import type { RunnerProviderModule } from '../../contracts'; import { createTemplateControlPlanePlugin } from './control-plane'; -import { templateWebhookPlugin } from './webhook'; +import { createTemplateWebhookPlugin } from './webhook'; /** * Copy this module for a new provider and register the completed module once in @@ -10,5 +10,5 @@ import { templateWebhookPlugin } from './webhook'; export const templateProvider = { type: 'template', createControlPlanePlugin: createTemplateControlPlanePlugin, - webhookPlugin: templateWebhookPlugin, + createWebhookPlugin: createTemplateWebhookPlugin, } satisfies RunnerProviderModule<'template'>; diff --git a/lambdas/libs/runner-providers/templates/provider/provider.test.ts b/lambdas/libs/runner-providers/templates/provider/provider.test.ts index 2508135932..7b8a025dd5 100644 --- a/lambdas/libs/runner-providers/templates/provider/provider.test.ts +++ b/lambdas/libs/runner-providers/templates/provider/provider.test.ts @@ -7,6 +7,7 @@ it('exposes every runner provider capability from one module', () => { const pool = controlPlanePlugin.capabilities.pool(); const scaleUp = controlPlanePlugin.capabilities.scaleUp(); const scaleDown = controlPlanePlugin.capabilities.scaleDown(); + const webhookPlugin = templateProvider.createWebhookPlugin(); expect(controlPlanePlugin.type).toBe(templateProvider.type); expect(pool).toEqual({ @@ -26,6 +27,6 @@ it('exposes every runner provider capability from one module', () => { unmarkOrphan: expect.any(Function), terminate: expect.any(Function), }); - expect(templateProvider.webhookPlugin.type).toBe(templateProvider.type); - expect(templateProvider.webhookPlugin.capabilities.dynamicLabels.selectQueue).toEqual(expect.any(Function)); + expect(webhookPlugin.type).toBe(templateProvider.type); + expect(webhookPlugin.capabilities.dynamicLabels.selectQueue).toEqual(expect.any(Function)); }); diff --git a/lambdas/libs/runner-providers/templates/provider/webhook.ts b/lambdas/libs/runner-providers/templates/provider/webhook.ts index 706121f1e7..0517af8bcf 100644 --- a/lambdas/libs/runner-providers/templates/provider/webhook.ts +++ b/lambdas/libs/runner-providers/templates/provider/webhook.ts @@ -10,7 +10,9 @@ export const templateDynamicLabelProvider: DynamicLabelProvider = { }, }; -export const templateWebhookPlugin: RunnerProviderPlugin = { - type: 'template', - capabilities: { dynamicLabels: templateDynamicLabelProvider }, -}; +export function createTemplateWebhookPlugin(): RunnerProviderPlugin { + return { + type: 'template', + capabilities: { dynamicLabels: templateDynamicLabelProvider }, + }; +} From 54659f214e9a975fb67738a5c8a4ad9370b72ad9 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 20:07:03 +0200 Subject: [PATCH 08/21] fix(runners): source provider types from configuration --- lambdas/libs/runner-providers/registry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambdas/libs/runner-providers/registry.ts b/lambdas/libs/runner-providers/registry.ts index 1e2580f26e..18f18ac282 100644 --- a/lambdas/libs/runner-providers/registry.ts +++ b/lambdas/libs/runner-providers/registry.ts @@ -2,14 +2,14 @@ import type { CreateStartRunnerConfig } from './core'; import { createRunnerProviderRegistry } from './core'; import type { ControlPlaneProviderCapabilities, WebhookProviderCapabilities } from './contracts'; -import { enabledRunnerProviders } from './providers.config'; +import { enabledRunnerProviders, enabledRunnerProviderTypes } from './providers.config'; const webhook = createRunnerProviderRegistry( enabledRunnerProviders.map((provider) => provider.createWebhookPlugin()), ); export const providerRegistry = { - types: webhook.types, + types: enabledRunnerProviderTypes, webhook, controlPlane: (createStartRunnerConfig: CreateStartRunnerConfig) => createRunnerProviderRegistry( From 3989a95cad06c349d5332cb6bc44a051b9e97efc Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 20:09:56 +0200 Subject: [PATCH 09/21] refactor(runners): derive provider types from modules --- lambdas/libs/runner-providers/core/index.ts | 10 ++++++---- lambdas/libs/runner-providers/providers.config.ts | 2 -- lambdas/libs/runner-providers/registry.test.ts | 4 ++-- lambdas/libs/runner-providers/registry.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lambdas/libs/runner-providers/core/index.ts b/lambdas/libs/runner-providers/core/index.ts index 02f282d40c..9e9f7d388e 100644 --- a/lambdas/libs/runner-providers/core/index.ts +++ b/lambdas/libs/runner-providers/core/index.ts @@ -1,17 +1,19 @@ import type { Octokit } from '@octokit/rest'; -import { enabledRunnerProviderTypes } from '../providers.config'; +import { enabledRunnerProviders } from '../providers.config'; + +const providerTypes = enabledRunnerProviders.map(({ type }) => type); export type RunnerProviderType = (typeof import('../providers.config').enabledRunnerProviders)[number]['type']; export function normalizeRunnerProviderType(type: unknown): RunnerProviderType | undefined { - if (type === undefined) return enabledRunnerProviderTypes[0]; + if (type === undefined) return providerTypes[0]; if (typeof type !== 'string') return undefined; const normalizedType = type.trim().toLowerCase(); - if (!normalizedType) return enabledRunnerProviderTypes[0]; + if (!normalizedType) return providerTypes[0]; - return enabledRunnerProviderTypes.find((runnerProviderType) => runnerProviderType === normalizedType); + return providerTypes.find((runnerProviderType) => runnerProviderType === normalizedType); } export function resolveRunnerProviderType(type: unknown): RunnerProviderType { diff --git a/lambdas/libs/runner-providers/providers.config.ts b/lambdas/libs/runner-providers/providers.config.ts index e8ed4df2c7..a48843f2ed 100644 --- a/lambdas/libs/runner-providers/providers.config.ts +++ b/lambdas/libs/runner-providers/providers.config.ts @@ -6,5 +6,3 @@ import type { RunnerProviderModule } from './contracts'; * Installing and enabling a provider requires adding it only to this list. */ export const enabledRunnerProviders = [ec2Provider] satisfies readonly RunnerProviderModule[]; - -export const enabledRunnerProviderTypes = enabledRunnerProviders.map(({ type }) => type); diff --git a/lambdas/libs/runner-providers/registry.test.ts b/lambdas/libs/runner-providers/registry.test.ts index 35fdaf4d6f..24789cf627 100644 --- a/lambdas/libs/runner-providers/registry.test.ts +++ b/lambdas/libs/runner-providers/registry.test.ts @@ -1,12 +1,12 @@ import { expect, it, vi } from 'vitest'; -import { enabledRunnerProviderTypes } from './providers.config'; +import { enabledRunnerProviders } from './providers.config'; import { providerRegistry } from './registry'; it('enables every configured provider in both runtime registries', () => { const createStartRunnerConfig = vi.fn(async () => []); const controlPlaneRegistry = providerRegistry.controlPlane(createStartRunnerConfig); - const enabledTypes = enabledRunnerProviderTypes; + const enabledTypes = enabledRunnerProviders.map(({ type }) => type); expect(providerRegistry.types).toEqual(enabledTypes); expect(controlPlaneRegistry.types).toEqual(enabledTypes); diff --git a/lambdas/libs/runner-providers/registry.ts b/lambdas/libs/runner-providers/registry.ts index 18f18ac282..55bc65eb32 100644 --- a/lambdas/libs/runner-providers/registry.ts +++ b/lambdas/libs/runner-providers/registry.ts @@ -2,14 +2,14 @@ import type { CreateStartRunnerConfig } from './core'; import { createRunnerProviderRegistry } from './core'; import type { ControlPlaneProviderCapabilities, WebhookProviderCapabilities } from './contracts'; -import { enabledRunnerProviders, enabledRunnerProviderTypes } from './providers.config'; +import { enabledRunnerProviders } from './providers.config'; const webhook = createRunnerProviderRegistry( enabledRunnerProviders.map((provider) => provider.createWebhookPlugin()), ); export const providerRegistry = { - types: enabledRunnerProviderTypes, + types: enabledRunnerProviders.map(({ type }) => type), webhook, controlPlane: (createStartRunnerConfig: CreateStartRunnerConfig) => createRunnerProviderRegistry( From 602e6b23bfe71a68430b0ee9f222cef49ef6bf3b Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 20:29:32 +0200 Subject: [PATCH 10/21] refactor(control-plane): clarify provider composition name --- .../src/{provider-registry.ts => control-plane-providers.ts} | 0 lambdas/functions/control-plane/src/pool/pool-contract.test.ts | 2 +- lambdas/functions/control-plane/src/pool/pool.ts | 2 +- .../control-plane/src/scale-runners/scale-down-contract.test.ts | 2 +- lambdas/functions/control-plane/src/scale-runners/scale-down.ts | 2 +- .../control-plane/src/scale-runners/scale-up-contract.test.ts | 2 +- lambdas/functions/control-plane/src/scale-runners/scale-up.ts | 2 +- .../src/test-framework/runner-provider/provider-types.ts | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename lambdas/functions/control-plane/src/{provider-registry.ts => control-plane-providers.ts} (100%) diff --git a/lambdas/functions/control-plane/src/provider-registry.ts b/lambdas/functions/control-plane/src/control-plane-providers.ts similarity index 100% rename from lambdas/functions/control-plane/src/provider-registry.ts rename to lambdas/functions/control-plane/src/control-plane-providers.ts diff --git a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts index 13be80714d..e250f3dec2 100644 --- a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts +++ b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts @@ -5,7 +5,7 @@ import { beforeEach, vi } from 'vitest'; import { definePoolContractTests } from '../test-framework/runner-provider/pool'; import { providerTypes } from '../test-framework/runner-provider/provider-types'; import * as ghAuth from '../github/auth'; -import { controlPlaneProviderRegistry } from '../provider-registry'; +import { controlPlaneProviderRegistry } from '../control-plane-providers'; import * as githubRunner from '../scale-runners/github-runner'; import { adjust } from './pool'; import type { PoolRunnerProvider } from './pool-provider'; diff --git a/lambdas/functions/control-plane/src/pool/pool.ts b/lambdas/functions/control-plane/src/pool/pool.ts index 8577918e62..708f0cffd5 100644 --- a/lambdas/functions/control-plane/src/pool/pool.ts +++ b/lambdas/functions/control-plane/src/pool/pool.ts @@ -4,7 +4,7 @@ import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/c import yn from 'yn'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; -import { controlPlaneProviderRegistry } from '../provider-registry'; +import { controlPlaneProviderRegistry } from '../control-plane-providers'; import { getGitHubEnterpriseApiUrl, validateSsmParameterStoreTags } from '../scale-runners/github-runner'; import type { RunnerStatus } from './pool-provider'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts index 7e60fe7140..f45187c5a6 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts @@ -3,7 +3,7 @@ import { beforeEach, vi } from 'vitest'; import { providerTypes } from '../test-framework/runner-provider/provider-types'; import { defineScaleDownContractTests } from '../test-framework/runner-provider/scale-down'; -import { controlPlaneProviderRegistry } from '../provider-registry'; +import { controlPlaneProviderRegistry } from '../control-plane-providers'; import { scaleDown } from './scale-down'; import type { ScaleDownRunnerProvider } from './scale-down-provider'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down.ts index 027b8ef121..f4304936b1 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down.ts @@ -6,7 +6,7 @@ import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/c import moment from 'moment'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; -import { controlPlaneProviderRegistry } from '../provider-registry'; +import { controlPlaneProviderRegistry } from '../control-plane-providers'; import { GhRunners, githubCache } from './cache'; import { ScalingDownConfigList, getEvictionStrategy, getIdleRunnerCount } from './scale-down-config'; import { metricGitHubAppRateLimit } from '../github/rate-limit'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts index f0136752e7..6c29ba9324 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts @@ -4,7 +4,7 @@ import { beforeEach, vi } from 'vitest'; import { providerTypes } from '../test-framework/runner-provider/provider-types'; import { defineScaleUpContractTests } from '../test-framework/runner-provider/scale-up'; import * as ghAuth from '../github/auth'; -import { controlPlaneProviderRegistry } from '../provider-registry'; +import { controlPlaneProviderRegistry } from '../control-plane-providers'; import * as githubRunner from './github-runner'; import { scaleUp } from './scale-up'; import type { ScaleUpRunnerProvider } from './scale-up-provider'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up.ts index d2eb743c84..1ab94b29bf 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-up.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up.ts @@ -4,7 +4,7 @@ import { Octokit } from '@octokit/rest'; import yn from 'yn'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; -import { controlPlaneProviderRegistry } from '../provider-registry'; +import { controlPlaneProviderRegistry } from '../control-plane-providers'; import { getGitHubEnterpriseApiUrl, getInstallationId, diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts b/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts index a11a6449fb..6a97a2cc2b 100644 --- a/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts +++ b/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts @@ -1,3 +1,3 @@ -import { runnerProviderTypes } from '../../provider-registry'; +import { runnerProviderTypes } from '../../control-plane-providers'; export const providerTypes = runnerProviderTypes; From aaefd63763a6e34fc8f4186de9ec2bad95625514 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 20:40:07 +0200 Subject: [PATCH 11/21] test(control-plane): clarify provider contract location --- .../functions/control-plane/src/pool/pool-contract.test.ts | 4 ++-- .../src/scale-runners/scale-down-contract.test.ts | 4 ++-- .../control-plane/src/scale-runners/scale-up-contract.test.ts | 4 ++-- .../runner-provider-contracts}/pool.ts | 0 .../runner-provider-contracts}/provider-types.ts | 0 .../runner-provider-contracts}/scale-down.ts | 0 .../runner-provider-contracts}/scale-up.ts | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename lambdas/functions/control-plane/src/{test-framework/runner-provider => test/runner-provider-contracts}/pool.ts (100%) rename lambdas/functions/control-plane/src/{test-framework/runner-provider => test/runner-provider-contracts}/provider-types.ts (100%) rename lambdas/functions/control-plane/src/{test-framework/runner-provider => test/runner-provider-contracts}/scale-down.ts (100%) rename lambdas/functions/control-plane/src/{test-framework/runner-provider => test/runner-provider-contracts}/scale-up.ts (100%) diff --git a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts index e250f3dec2..686e62dd4c 100644 --- a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts +++ b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts @@ -2,8 +2,8 @@ import type { Octokit } from '@octokit/rest'; import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; import { beforeEach, vi } from 'vitest'; -import { definePoolContractTests } from '../test-framework/runner-provider/pool'; -import { providerTypes } from '../test-framework/runner-provider/provider-types'; +import { definePoolContractTests } from '../test/runner-provider-contracts/pool'; +import { providerTypes } from '../test/runner-provider-contracts/provider-types'; import * as ghAuth from '../github/auth'; import { controlPlaneProviderRegistry } from '../control-plane-providers'; import * as githubRunner from '../scale-runners/github-runner'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts index f45187c5a6..1c7ef7fa80 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts @@ -1,8 +1,8 @@ import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; import { beforeEach, vi } from 'vitest'; -import { providerTypes } from '../test-framework/runner-provider/provider-types'; -import { defineScaleDownContractTests } from '../test-framework/runner-provider/scale-down'; +import { providerTypes } from '../test/runner-provider-contracts/provider-types'; +import { defineScaleDownContractTests } from '../test/runner-provider-contracts/scale-down'; import { controlPlaneProviderRegistry } from '../control-plane-providers'; import { scaleDown } from './scale-down'; import type { ScaleDownRunnerProvider } from './scale-down-provider'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts index 6c29ba9324..bad5ff475f 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up-contract.test.ts @@ -1,8 +1,8 @@ import type { Octokit } from '@octokit/rest'; import { beforeEach, vi } from 'vitest'; -import { providerTypes } from '../test-framework/runner-provider/provider-types'; -import { defineScaleUpContractTests } from '../test-framework/runner-provider/scale-up'; +import { providerTypes } from '../test/runner-provider-contracts/provider-types'; +import { defineScaleUpContractTests } from '../test/runner-provider-contracts/scale-up'; import * as ghAuth from '../github/auth'; import { controlPlaneProviderRegistry } from '../control-plane-providers'; import * as githubRunner from './github-runner'; diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/pool.ts b/lambdas/functions/control-plane/src/test/runner-provider-contracts/pool.ts similarity index 100% rename from lambdas/functions/control-plane/src/test-framework/runner-provider/pool.ts rename to lambdas/functions/control-plane/src/test/runner-provider-contracts/pool.ts diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts b/lambdas/functions/control-plane/src/test/runner-provider-contracts/provider-types.ts similarity index 100% rename from lambdas/functions/control-plane/src/test-framework/runner-provider/provider-types.ts rename to lambdas/functions/control-plane/src/test/runner-provider-contracts/provider-types.ts diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-down.ts b/lambdas/functions/control-plane/src/test/runner-provider-contracts/scale-down.ts similarity index 100% rename from lambdas/functions/control-plane/src/test-framework/runner-provider/scale-down.ts rename to lambdas/functions/control-plane/src/test/runner-provider-contracts/scale-down.ts diff --git a/lambdas/functions/control-plane/src/test-framework/runner-provider/scale-up.ts b/lambdas/functions/control-plane/src/test/runner-provider-contracts/scale-up.ts similarity index 100% rename from lambdas/functions/control-plane/src/test-framework/runner-provider/scale-up.ts rename to lambdas/functions/control-plane/src/test/runner-provider-contracts/scale-up.ts From 8945075080b42828a77ebc37ae97fe641793b12a Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 20:56:40 +0200 Subject: [PATCH 12/21] refactor(runners): expose provider types once --- lambdas/libs/runner-providers/core/index.test.ts | 4 ---- lambdas/libs/runner-providers/core/index.ts | 1 - lambdas/libs/runner-providers/registry.test.ts | 4 +--- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lambdas/libs/runner-providers/core/index.test.ts b/lambdas/libs/runner-providers/core/index.test.ts index e8dcfa92bc..1cf30e4263 100644 --- a/lambdas/libs/runner-providers/core/index.test.ts +++ b/lambdas/libs/runner-providers/core/index.test.ts @@ -42,10 +42,6 @@ describe('runner provider registry', () => { }; const registry = createRunnerProviderRegistry([plugin]); - it('lists the registered provider types', () => { - expect(registry.types).toEqual(['ec2']); - }); - it('resolves capabilities dynamically', () => { expect(registry.capability('ec2', 'scaleUp')()).toBe('scale-up'); expect(registry.capability('ec2', 'pool')()).toBe('pool'); diff --git a/lambdas/libs/runner-providers/core/index.ts b/lambdas/libs/runner-providers/core/index.ts index 9e9f7d388e..6f350c1d67 100644 --- a/lambdas/libs/runner-providers/core/index.ts +++ b/lambdas/libs/runner-providers/core/index.ts @@ -162,7 +162,6 @@ export function createRunnerProviderRegistry type), get, capability: (type: TType, capability: TKey): TCapabilities[TKey] => get(type).capabilities[capability], diff --git a/lambdas/libs/runner-providers/registry.test.ts b/lambdas/libs/runner-providers/registry.test.ts index 24789cf627..bd1cbcc5f9 100644 --- a/lambdas/libs/runner-providers/registry.test.ts +++ b/lambdas/libs/runner-providers/registry.test.ts @@ -3,14 +3,12 @@ import { expect, it, vi } from 'vitest'; import { enabledRunnerProviders } from './providers.config'; import { providerRegistry } from './registry'; -it('enables every configured provider in both runtime registries', () => { +it('exposes every configured provider through both capability registries', () => { const createStartRunnerConfig = vi.fn(async () => []); const controlPlaneRegistry = providerRegistry.controlPlane(createStartRunnerConfig); const enabledTypes = enabledRunnerProviders.map(({ type }) => type); expect(providerRegistry.types).toEqual(enabledTypes); - expect(controlPlaneRegistry.types).toEqual(enabledTypes); - expect(providerRegistry.webhook.types).toEqual(enabledTypes); for (const type of enabledTypes) { expect(controlPlaneRegistry.capability(type, 'pool')).toEqual(expect.any(Function)); From c023528f6c6484d6b634fff3eed315cc7d9376a6 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 21:14:21 +0200 Subject: [PATCH 13/21] refactor(runners): standardize provider exports --- lambdas/libs/runner-providers/aws/ec2/index.ts | 2 +- lambdas/libs/runner-providers/providers.config.ts | 4 ++-- .../libs/runner-providers/templates/provider/README.md | 7 +++++++ .../libs/runner-providers/templates/provider/index.ts | 2 +- .../templates/provider/provider.test.ts | 10 +++++----- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lambdas/libs/runner-providers/aws/ec2/index.ts b/lambdas/libs/runner-providers/aws/ec2/index.ts index d439394ed8..7b32653d16 100644 --- a/lambdas/libs/runner-providers/aws/ec2/index.ts +++ b/lambdas/libs/runner-providers/aws/ec2/index.ts @@ -3,7 +3,7 @@ import type { RunnerProviderModule } from '../../contracts'; import { createEc2ControlPlanePlugin } from './control-plane'; import { createEc2WebhookPlugin } from './webhook'; -export const ec2Provider = { +export const provider = { type: 'ec2', createControlPlanePlugin: createEc2ControlPlanePlugin, createWebhookPlugin: createEc2WebhookPlugin, diff --git a/lambdas/libs/runner-providers/providers.config.ts b/lambdas/libs/runner-providers/providers.config.ts index a48843f2ed..d62578bad7 100644 --- a/lambdas/libs/runner-providers/providers.config.ts +++ b/lambdas/libs/runner-providers/providers.config.ts @@ -1,8 +1,8 @@ -import { ec2Provider } from './aws/ec2'; +import { provider as ec2 } from './aws/ec2'; import type { RunnerProviderModule } from './contracts'; /** * Provider plugins enabled in webhook and control-plane bundles. * Installing and enabling a provider requires adding it only to this list. */ -export const enabledRunnerProviders = [ec2Provider] satisfies readonly RunnerProviderModule[]; +export const enabledRunnerProviders = [ec2] as const satisfies readonly RunnerProviderModule[]; diff --git a/lambdas/libs/runner-providers/templates/provider/README.md b/lambdas/libs/runner-providers/templates/provider/README.md index 4a14505ab9..932edcb7d3 100644 --- a/lambdas/libs/runner-providers/templates/provider/README.md +++ b/lambdas/libs/runner-providers/templates/provider/README.md @@ -9,6 +9,13 @@ completed provider, import its module and add it once to the corresponding plugin automatically, and the generic control-plane contracts will include the new lane type. +Every provider entry point exports its module as `provider`. Alias that export +to the lane name when enabling it, for example: + +```ts +import { provider as codebuild } from './aws/codebuild'; +``` + Implement every capability before registering the provider: - `pool`: list managed runners, count available runners, and create runners. diff --git a/lambdas/libs/runner-providers/templates/provider/index.ts b/lambdas/libs/runner-providers/templates/provider/index.ts index faaf1e8303..8d46aedaca 100644 --- a/lambdas/libs/runner-providers/templates/provider/index.ts +++ b/lambdas/libs/runner-providers/templates/provider/index.ts @@ -7,7 +7,7 @@ import { createTemplateWebhookPlugin } from './webhook'; * Copy this module for a new provider and register the completed module once in * providers.config.ts. Do not register this template itself. */ -export const templateProvider = { +export const provider = { type: 'template', createControlPlanePlugin: createTemplateControlPlanePlugin, createWebhookPlugin: createTemplateWebhookPlugin, diff --git a/lambdas/libs/runner-providers/templates/provider/provider.test.ts b/lambdas/libs/runner-providers/templates/provider/provider.test.ts index 7b8a025dd5..0b345f8693 100644 --- a/lambdas/libs/runner-providers/templates/provider/provider.test.ts +++ b/lambdas/libs/runner-providers/templates/provider/provider.test.ts @@ -1,15 +1,15 @@ import { expect, it, vi } from 'vitest'; -import { templateProvider } from './index'; +import { provider } from './index'; it('exposes every runner provider capability from one module', () => { - const controlPlanePlugin = templateProvider.createControlPlanePlugin(vi.fn(async () => [])); + const controlPlanePlugin = provider.createControlPlanePlugin(vi.fn(async () => [])); const pool = controlPlanePlugin.capabilities.pool(); const scaleUp = controlPlanePlugin.capabilities.scaleUp(); const scaleDown = controlPlanePlugin.capabilities.scaleDown(); - const webhookPlugin = templateProvider.createWebhookPlugin(); + const webhookPlugin = provider.createWebhookPlugin(); - expect(controlPlanePlugin.type).toBe(templateProvider.type); + expect(controlPlanePlugin.type).toBe(provider.type); expect(pool).toEqual({ listRunners: expect.any(Function), countAvailableRunners: expect.any(Function), @@ -27,6 +27,6 @@ it('exposes every runner provider capability from one module', () => { unmarkOrphan: expect.any(Function), terminate: expect.any(Function), }); - expect(webhookPlugin.type).toBe(templateProvider.type); + expect(webhookPlugin.type).toBe(provider.type); expect(webhookPlugin.capabilities.dynamicLabels.selectQueue).toEqual(expect.any(Function)); }); From e8612ab5618cd2abc4a512a7ff71179f13582c22 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 21:22:43 +0200 Subject: [PATCH 14/21] test(control-plane): exclude contract helpers from coverage --- lambdas/functions/control-plane/tsconfig.json | 10 +++------- lambdas/functions/control-plane/vitest.config.ts | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lambdas/functions/control-plane/tsconfig.json b/lambdas/functions/control-plane/tsconfig.json index 30cbbee83e..3b30c17b7e 100644 --- a/lambdas/functions/control-plane/tsconfig.json +++ b/lambdas/functions/control-plane/tsconfig.json @@ -1,9 +1,5 @@ { - "extends" : "../../tsconfig.json", - "include": [ - "src/**/*" - ], - "exclude": [ - "src/**/*.test.ts" - ] + "extends": "../../tsconfig.json", + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts", "src/test/**/*"] } diff --git a/lambdas/functions/control-plane/vitest.config.ts b/lambdas/functions/control-plane/vitest.config.ts index 7abf8c629d..26601c12e1 100644 --- a/lambdas/functions/control-plane/vitest.config.ts +++ b/lambdas/functions/control-plane/vitest.config.ts @@ -8,7 +8,7 @@ export default mergeConfig(defaultConfig, { setupFiles: [resolve(__dirname, '../../aws-vitest-setup.ts')], coverage: { include: ['src/**/*.ts'], - exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'], + exclude: ['src/**/*.test.ts', 'src/test/**/*', 'src/**/*.d.ts'], thresholds: { statements: 96.64, branches: 96.43, From 11f9742a6d8587dd661a73c831e47410bfd5a30b Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 21:27:46 +0200 Subject: [PATCH 15/21] test(control-plane): format pool provider mock --- lambdas/functions/control-plane/src/pool/pool.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lambdas/functions/control-plane/src/pool/pool.test.ts b/lambdas/functions/control-plane/src/pool/pool.test.ts index fe820d3533..3aa194bbd4 100644 --- a/lambdas/functions/control-plane/src/pool/pool.test.ts +++ b/lambdas/functions/control-plane/src/pool/pool.test.ts @@ -38,9 +38,7 @@ vi.mock('./../github/auth', async () => ({ })); vi.mock('@aws-github-runner/runner-providers/aws/ec2/control-plane/runner-config', async (importOriginal) => ({ - ...(await importOriginal< - typeof import('@aws-github-runner/runner-providers/aws/ec2/control-plane/runner-config') - >()), + ...(await importOriginal()), createRunners: vi.fn(), })); From 4f0f8ab9298bf8fa5916f997f2b8abface0fcb99 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 21:51:21 +0200 Subject: [PATCH 16/21] fix(runners): prevent recursive provider tests --- lambdas/libs/runner-providers/package.json | 8 ++++++++ lambdas/libs/runner-providers/vitest.config.ts | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lambdas/libs/runner-providers/package.json b/lambdas/libs/runner-providers/package.json index 156b08d90b..d7e9069664 100644 --- a/lambdas/libs/runner-providers/package.json +++ b/lambdas/libs/runner-providers/package.json @@ -32,5 +32,13 @@ "aws-sdk-client-mock": "^4.1.0", "aws-sdk-client-mock-jest": "^4.1.0", "nock": "^14.0.10" + }, + "nx": { + "includedScripts": [ + "format", + "format-check", + "lint", + "all" + ] } } diff --git a/lambdas/libs/runner-providers/vitest.config.ts b/lambdas/libs/runner-providers/vitest.config.ts index 8be97d837d..4efec45830 100644 --- a/lambdas/libs/runner-providers/vitest.config.ts +++ b/lambdas/libs/runner-providers/vitest.config.ts @@ -1,8 +1,11 @@ -import { defineConfig } from 'vitest/config'; +import { resolve } from 'path'; -export default defineConfig({ +import { mergeConfig } from 'vitest/config'; +import defaultConfig from '../../vitest.base.config'; + +export default mergeConfig(defaultConfig, { test: { globals: true, - setupFiles: '../../aws-vitest-setup.ts', + setupFiles: [resolve(__dirname, '../../aws-vitest-setup.ts')], }, }); From 6284f470d9a6c7703047a52440ab72ddc9663e5c Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 21:54:10 +0200 Subject: [PATCH 17/21] test(runners): configure provider coverage --- lambdas/libs/runner-providers/vitest.config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lambdas/libs/runner-providers/vitest.config.ts b/lambdas/libs/runner-providers/vitest.config.ts index 4efec45830..250baf306c 100644 --- a/lambdas/libs/runner-providers/vitest.config.ts +++ b/lambdas/libs/runner-providers/vitest.config.ts @@ -7,5 +7,15 @@ export default mergeConfig(defaultConfig, { test: { globals: true, setupFiles: [resolve(__dirname, '../../aws-vitest-setup.ts')], + coverage: { + include: ['contracts.ts', 'providers.config.ts', 'registry.ts', 'core/**/*.ts', 'aws/**/*.ts'], + exclude: ['**/*.test.ts', '**/*.d.ts'], + thresholds: { + statements: 96.16, + branches: 95.32, + functions: 93.06, + lines: 96.53, + }, + }, }, }); From fb1ef7b3a27962f0ef9dbef5d1dfa85b64ce5af0 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Fri, 31 Jul 2026 21:58:39 +0200 Subject: [PATCH 18/21] test(runners): inherit shared vitest defaults --- lambdas/libs/runner-providers/vitest.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lambdas/libs/runner-providers/vitest.config.ts b/lambdas/libs/runner-providers/vitest.config.ts index 250baf306c..62d5095f1f 100644 --- a/lambdas/libs/runner-providers/vitest.config.ts +++ b/lambdas/libs/runner-providers/vitest.config.ts @@ -5,7 +5,6 @@ import defaultConfig from '../../vitest.base.config'; export default mergeConfig(defaultConfig, { test: { - globals: true, setupFiles: [resolve(__dirname, '../../aws-vitest-setup.ts')], coverage: { include: ['contracts.ts', 'providers.config.ts', 'registry.ts', 'core/**/*.ts', 'aws/**/*.ts'], From e42eab24ac0c1fe4977320d904e6b70a3d769c1a Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Mon, 3 Aug 2026 16:14:09 +0200 Subject: [PATCH 19/21] refactor(providers): split lambda provider registries --- .../src/control-plane-providers.ts | 7 ++-- .../functions/control-plane/src/pool/pool.ts | 2 +- .../src/scale-runners/scale-down.ts | 2 +- .../src/scale-runners/scale-up.ts | 2 +- .../webhook/src/runners/aws-dynamic-labels.ts | 6 +-- lambdas/functions/webhook/src/sqs/index.ts | 23 +---------- .../runner-providers/aws/ec2/control-plane.ts | 7 +++- .../libs/runner-providers/aws/ec2/index.ts | 23 ----------- .../aws/ec2/src/webhook/types.ts | 2 +- .../libs/runner-providers/aws/ec2/webhook.ts | 7 +++- lambdas/libs/runner-providers/contracts.ts | 41 ++++++++++++------- .../libs/runner-providers/control-plane.ts | 11 +++++ lambdas/libs/runner-providers/core/index.ts | 25 +---------- lambdas/libs/runner-providers/package.json | 7 +++- .../libs/runner-providers/provider-types.ts | 24 +++++++++++ .../providers.config.control-plane.ts | 5 +++ .../libs/runner-providers/providers.config.ts | 8 ---- .../providers.config.webhook.ts | 5 +++ lambdas/libs/runner-providers/registry.ts | 18 -------- lambdas/libs/runner-providers/webhook.ts | 8 ++++ 20 files changed, 110 insertions(+), 123 deletions(-) delete mode 100644 lambdas/libs/runner-providers/aws/ec2/index.ts create mode 100644 lambdas/libs/runner-providers/control-plane.ts create mode 100644 lambdas/libs/runner-providers/provider-types.ts create mode 100644 lambdas/libs/runner-providers/providers.config.control-plane.ts delete mode 100644 lambdas/libs/runner-providers/providers.config.ts create mode 100644 lambdas/libs/runner-providers/providers.config.webhook.ts delete mode 100644 lambdas/libs/runner-providers/registry.ts create mode 100644 lambdas/libs/runner-providers/webhook.ts diff --git a/lambdas/functions/control-plane/src/control-plane-providers.ts b/lambdas/functions/control-plane/src/control-plane-providers.ts index 48ece5bbe2..d3dfbeeeee 100644 --- a/lambdas/functions/control-plane/src/control-plane-providers.ts +++ b/lambdas/functions/control-plane/src/control-plane-providers.ts @@ -1,7 +1,8 @@ -import { providerRegistry } from '@aws-github-runner/runner-providers/registry'; +import { createControlPlaneProviderRegistry } from '@aws-github-runner/runner-providers/control-plane'; +import { runnerProviderTypes } from '@aws-github-runner/runner-providers/provider-types'; import { createStartRunnerConfig } from './scale-runners/github-runner'; -export const controlPlaneProviderRegistry = providerRegistry.controlPlane(createStartRunnerConfig); +export const controlPlaneProviderRegistry = createControlPlaneProviderRegistry(createStartRunnerConfig); -export const runnerProviderTypes = providerRegistry.types; +export { runnerProviderTypes }; diff --git a/lambdas/functions/control-plane/src/pool/pool.ts b/lambdas/functions/control-plane/src/pool/pool.ts index 708f0cffd5..3a6ed45be9 100644 --- a/lambdas/functions/control-plane/src/pool/pool.ts +++ b/lambdas/functions/control-plane/src/pool/pool.ts @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; import yn from 'yn'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down.ts index f4304936b1..fb2acef72e 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down.ts @@ -2,7 +2,7 @@ import { Octokit } from '@octokit/rest'; import { Endpoints } from '@octokit/types'; import { RequestError } from '@octokit/request-error'; import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; import moment from 'moment'; import { createGithubAppAuth, createGithubInstallationAuth, createOctokitClient } from '../github/auth'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-up.ts b/lambdas/functions/control-plane/src/scale-runners/scale-up.ts index 1ab94b29bf..cdb8b1bb4a 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-up.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-up.ts @@ -1,5 +1,5 @@ import { addPersistentContextToChildLogger, createChildLogger } from '@aws-github-runner/aws-powertools-util'; -import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import { resolveRunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; import { Octokit } from '@octokit/rest'; import yn from 'yn'; diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts index ad44b57170..7a05cd8cc8 100644 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts +++ b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts @@ -1,7 +1,7 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; import type { DynamicLabelDispatchTarget } from '@aws-github-runner/runner-providers'; -import { normalizeRunnerProviderType } from '@aws-github-runner/runner-providers/core'; -import { providerRegistry } from '@aws-github-runner/runner-providers/registry'; +import { normalizeRunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; +import { webhookProviderRegistry } from '@aws-github-runner/runner-providers/webhook'; import type { RunnerMatcherConfig } from '../sqs'; @@ -14,7 +14,7 @@ export function selectAwsDynamicLabelQueue( ): DynamicLabelDispatchTarget | undefined { for (const queue of matches) { const provider = normalizeRunnerProviderType(queue.runnerProvider); - const dynamicLabels = provider ? providerRegistry.webhook.capability(provider, 'dynamicLabels') : undefined; + const dynamicLabels = provider ? webhookProviderRegistry.capability(provider, 'dynamicLabels') : undefined; if (!dynamicLabels) { logger.warn(`Queue ${queue.id} has unsupported runner provider '${provider ?? String(queue.runnerProvider)}'`); diff --git a/lambdas/functions/webhook/src/sqs/index.ts b/lambdas/functions/webhook/src/sqs/index.ts index 0d21f81427..40fe9f1e75 100644 --- a/lambdas/functions/webhook/src/sqs/index.ts +++ b/lambdas/functions/webhook/src/sqs/index.ts @@ -1,9 +1,8 @@ import { SQS, SendMessageCommandInput } from '@aws-sdk/client-sqs'; import { WorkflowJobEvent } from '@octokit/webhooks-types'; import { createChildLogger, getTracedAWSV3Client } from '@aws-github-runner/aws-powertools-util'; -import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; -import type { AwsDynamicLabelsPolicy } from '../runners/aws-dynamic-labels-policy'; +export type { MatcherConfig, RunnerConfig, RunnerMatcherConfig } from '@aws-github-runner/runner-providers'; const logger = createChildLogger('sqs'); @@ -20,26 +19,6 @@ export interface ActionRequestMessage { labels?: string[]; } -export interface MatcherConfig { - labelMatchers: string[][]; - exactMatch: boolean; - bidirectionalLabelMatch?: boolean; - enableDynamicLabels?: boolean; - awsDynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; - // TODO: Remove this legacy compatibility field and fallback in the next release. - /** @deprecated Use awsDynamicLabelsPolicy. Retained while existing SSM configurations migrate. */ - ec2DynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; -} - -export type RunnerConfig = RunnerMatcherConfig[]; - -export interface RunnerMatcherConfig { - matcherConfig: MatcherConfig; - runnerProvider?: RunnerProviderType; - id: string; - arn: string; -} - export interface GithubWorkflowEvent { workflowJobEvent: WorkflowJobEvent; } diff --git a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts index e3ffe4587f..ef07fec861 100644 --- a/lambdas/libs/runner-providers/aws/ec2/control-plane.ts +++ b/lambdas/libs/runner-providers/aws/ec2/control-plane.ts @@ -1,6 +1,6 @@ import type { CreateStartRunnerConfig, RunnerProviderPlugin } from '../../core'; -import type { ControlPlaneProviderCapabilities } from '../../contracts'; +import type { ControlPlaneProviderCapabilities, ControlPlaneProviderModule } from '../../contracts'; import type {} from './src/environment'; import { createEc2PoolProvider } from './src/control-plane/pool'; import { createEc2ScaleDownProvider } from './src/control-plane/scale-down'; @@ -18,3 +18,8 @@ export function createEc2ControlPlanePlugin( }, }; } + +export const provider = { + type: 'ec2', + createPlugin: createEc2ControlPlanePlugin, +} satisfies ControlPlaneProviderModule<'ec2'>; diff --git a/lambdas/libs/runner-providers/aws/ec2/index.ts b/lambdas/libs/runner-providers/aws/ec2/index.ts deleted file mode 100644 index 7b32653d16..0000000000 --- a/lambdas/libs/runner-providers/aws/ec2/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { RunnerProviderModule } from '../../contracts'; - -import { createEc2ControlPlanePlugin } from './control-plane'; -import { createEc2WebhookPlugin } from './webhook'; - -export const provider = { - type: 'ec2', - createControlPlanePlugin: createEc2ControlPlanePlugin, - createWebhookPlugin: createEc2WebhookPlugin, -} satisfies RunnerProviderModule; - -export { createEc2ControlPlanePlugin, createEc2WebhookPlugin }; - -export * from './src/control-plane/dynamic-labels'; -export * from './src/control-plane/pool'; -export * from './src/control-plane/runner-config'; -export * from './src/control-plane/runners'; -export type * from './src/control-plane/runners.d'; -export * from './src/control-plane/scale-down'; -export * from './src/control-plane/scale-up'; -export * from './src/webhook/dynamic-labels'; -export * from './src/webhook/dynamic-labels-policy'; -export type * from './src/webhook/types'; diff --git a/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts index 16edb804d6..d8fa8822f4 100644 --- a/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts @@ -1,7 +1,7 @@ export type { AwsDynamicLabelsPolicy, AwsDynamicLabelsValueRule, - AwsRunnerMatcherConfig as Ec2RunnerMatcherConfig, DynamicLabelDispatchTarget, DynamicLabelProvider, + RunnerMatcherConfig as Ec2RunnerMatcherConfig, } from '../../../../contracts'; diff --git a/lambdas/libs/runner-providers/aws/ec2/webhook.ts b/lambdas/libs/runner-providers/aws/ec2/webhook.ts index 8a8f2fde16..1ad62e00b4 100644 --- a/lambdas/libs/runner-providers/aws/ec2/webhook.ts +++ b/lambdas/libs/runner-providers/aws/ec2/webhook.ts @@ -1,6 +1,6 @@ import type { RunnerProviderPlugin } from '../../core'; -import type { WebhookProviderCapabilities } from '../../contracts'; +import type { WebhookProviderCapabilities, WebhookProviderModule } from '../../contracts'; import { ec2DynamicLabelProvider } from './src/webhook/dynamic-labels'; export function createEc2WebhookPlugin(): RunnerProviderPlugin { @@ -9,3 +9,8 @@ export function createEc2WebhookPlugin(): RunnerProviderPlugin; diff --git a/lambdas/libs/runner-providers/contracts.ts b/lambdas/libs/runner-providers/contracts.ts index a9f7953294..1d1fe01b85 100644 --- a/lambdas/libs/runner-providers/contracts.ts +++ b/lambdas/libs/runner-providers/contracts.ts @@ -5,6 +5,7 @@ import type { ScaleDownRunnerProvider, ScaleUpRunnerProvider, } from './core'; +import type { RunnerProviderType } from './provider-types'; export interface AwsDynamicLabelsValueRule { allowed?: string[]; @@ -17,28 +18,34 @@ export interface AwsDynamicLabelsPolicy { restricted_keys?: Record; } -export interface AwsRunnerMatcherConfig { +export interface MatcherConfig { + labelMatchers: string[][]; + exactMatch: boolean; + bidirectionalLabelMatch?: boolean; + enableDynamicLabels?: boolean; + awsDynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; + // TODO: Remove this legacy compatibility field and fallback in the next release. + /** @deprecated Use awsDynamicLabelsPolicy. Retained while existing SSM configurations migrate. */ + ec2DynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; +} + +export interface RunnerMatcherConfig { id: string; arn: string; - runnerProvider?: string; - matcherConfig: { - labelMatchers: string[][]; - exactMatch: boolean; - bidirectionalLabelMatch?: boolean; - enableDynamicLabels?: boolean; - awsDynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; - ec2DynamicLabelsPolicy?: AwsDynamicLabelsPolicy | null; - }; + runnerProvider?: RunnerProviderType; + matcherConfig: MatcherConfig; } +export type RunnerConfig = RunnerMatcherConfig[]; + export interface DynamicLabelDispatchTarget { - queue: AwsRunnerMatcherConfig; + queue: RunnerMatcherConfig; labels: string[]; } export interface DynamicLabelProvider { selectQueue(input: { - queue: AwsRunnerMatcherConfig; + queue: RunnerMatcherConfig; nonGhrLabels: string[]; sanitizedGhrLabels: string[]; }): DynamicLabelDispatchTarget | undefined; @@ -54,10 +61,14 @@ export interface WebhookProviderCapabilities { dynamicLabels: DynamicLabelProvider; } -export interface RunnerProviderModule { +export interface ControlPlaneProviderModule { type: TType; - createControlPlanePlugin( + createPlugin( createStartRunnerConfig: CreateStartRunnerConfig, ): RunnerProviderPlugin; - createWebhookPlugin(): RunnerProviderPlugin; +} + +export interface WebhookProviderModule { + type: TType; + createPlugin(): RunnerProviderPlugin; } diff --git a/lambdas/libs/runner-providers/control-plane.ts b/lambdas/libs/runner-providers/control-plane.ts new file mode 100644 index 0000000000..282949fe7e --- /dev/null +++ b/lambdas/libs/runner-providers/control-plane.ts @@ -0,0 +1,11 @@ +import type { CreateStartRunnerConfig } from './core'; +import { createRunnerProviderRegistry } from './core'; + +import type { ControlPlaneProviderCapabilities } from './contracts'; +import { enabledControlPlaneProviders } from './providers.config.control-plane'; + +export function createControlPlaneProviderRegistry(createStartRunnerConfig: CreateStartRunnerConfig) { + return createRunnerProviderRegistry( + enabledControlPlaneProviders.map((provider) => provider.createPlugin(createStartRunnerConfig)), + ); +} diff --git a/lambdas/libs/runner-providers/core/index.ts b/lambdas/libs/runner-providers/core/index.ts index 6f350c1d67..d55ca72fba 100644 --- a/lambdas/libs/runner-providers/core/index.ts +++ b/lambdas/libs/runner-providers/core/index.ts @@ -1,29 +1,8 @@ import type { Octokit } from '@octokit/rest'; -import { enabledRunnerProviders } from '../providers.config'; +import type { RunnerProviderType } from '../provider-types'; -const providerTypes = enabledRunnerProviders.map(({ type }) => type); - -export type RunnerProviderType = (typeof import('../providers.config').enabledRunnerProviders)[number]['type']; - -export function normalizeRunnerProviderType(type: unknown): RunnerProviderType | undefined { - if (type === undefined) return providerTypes[0]; - if (typeof type !== 'string') return undefined; - - const normalizedType = type.trim().toLowerCase(); - if (!normalizedType) return providerTypes[0]; - - return providerTypes.find((runnerProviderType) => runnerProviderType === normalizedType); -} - -export function resolveRunnerProviderType(type: unknown): RunnerProviderType { - const normalizedType = normalizeRunnerProviderType(type); - if (!normalizedType) { - throw new Error(`Unsupported runner provider type '${String(type)}'`); - } - - return normalizedType; -} +export type { RunnerProviderType } from '../provider-types'; export interface RunnerProvider { type: RunnerProviderType; diff --git a/lambdas/libs/runner-providers/package.json b/lambdas/libs/runner-providers/package.json index d7e9069664..77c9ea71d2 100644 --- a/lambdas/libs/runner-providers/package.json +++ b/lambdas/libs/runner-providers/package.json @@ -5,8 +5,11 @@ "exports": { ".": "./contracts.ts", "./core": "./core/index.ts", - "./registry": "./registry.ts", - "./aws/ec2": "./aws/ec2/index.ts", + "./provider-types": "./provider-types.ts", + "./webhook": "./webhook.ts", + "./control-plane": "./control-plane.ts", + "./aws/ec2/webhook": "./aws/ec2/webhook.ts", + "./aws/ec2/control-plane": "./aws/ec2/control-plane.ts", "./aws/ec2/control-plane/runners": "./aws/ec2/src/control-plane/runners.ts", "./aws/ec2/control-plane/runner-config": "./aws/ec2/src/control-plane/runner-config.ts" }, diff --git a/lambdas/libs/runner-providers/provider-types.ts b/lambdas/libs/runner-providers/provider-types.ts new file mode 100644 index 0000000000..236a750544 --- /dev/null +++ b/lambdas/libs/runner-providers/provider-types.ts @@ -0,0 +1,24 @@ +export const runnerProviderTypes = ['ec2'] as const; + +export type RunnerProviderType = (typeof runnerProviderTypes)[number]; + +export const defaultRunnerProvider = 'ec2' satisfies RunnerProviderType; + +export function normalizeRunnerProviderType(type: unknown): RunnerProviderType | undefined { + if (type === undefined) return defaultRunnerProvider; + if (typeof type !== 'string') return undefined; + + const normalizedType = type.trim().toLowerCase(); + if (!normalizedType) return defaultRunnerProvider; + + return runnerProviderTypes.find((runnerProviderType) => runnerProviderType === normalizedType); +} + +export function resolveRunnerProviderType(type: unknown): RunnerProviderType { + const normalizedType = normalizeRunnerProviderType(type); + if (!normalizedType) { + throw new Error(`Unsupported runner provider type '${String(type)}'`); + } + + return normalizedType; +} diff --git a/lambdas/libs/runner-providers/providers.config.control-plane.ts b/lambdas/libs/runner-providers/providers.config.control-plane.ts new file mode 100644 index 0000000000..55ebaca95e --- /dev/null +++ b/lambdas/libs/runner-providers/providers.config.control-plane.ts @@ -0,0 +1,5 @@ +import { provider as ec2 } from './aws/ec2/control-plane'; +import type { ControlPlaneProviderModule } from './contracts'; + +/** Provider plugins included in the control-plane bundle. */ +export const enabledControlPlaneProviders = [ec2] as const satisfies readonly ControlPlaneProviderModule[]; diff --git a/lambdas/libs/runner-providers/providers.config.ts b/lambdas/libs/runner-providers/providers.config.ts deleted file mode 100644 index d62578bad7..0000000000 --- a/lambdas/libs/runner-providers/providers.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { provider as ec2 } from './aws/ec2'; -import type { RunnerProviderModule } from './contracts'; - -/** - * Provider plugins enabled in webhook and control-plane bundles. - * Installing and enabling a provider requires adding it only to this list. - */ -export const enabledRunnerProviders = [ec2] as const satisfies readonly RunnerProviderModule[]; diff --git a/lambdas/libs/runner-providers/providers.config.webhook.ts b/lambdas/libs/runner-providers/providers.config.webhook.ts new file mode 100644 index 0000000000..19c92734da --- /dev/null +++ b/lambdas/libs/runner-providers/providers.config.webhook.ts @@ -0,0 +1,5 @@ +import { provider as ec2 } from './aws/ec2/webhook'; +import type { WebhookProviderModule } from './contracts'; + +/** Provider plugins included in the webhook bundle. */ +export const enabledWebhookProviders = [ec2] as const satisfies readonly WebhookProviderModule[]; diff --git a/lambdas/libs/runner-providers/registry.ts b/lambdas/libs/runner-providers/registry.ts deleted file mode 100644 index 55bc65eb32..0000000000 --- a/lambdas/libs/runner-providers/registry.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CreateStartRunnerConfig } from './core'; -import { createRunnerProviderRegistry } from './core'; - -import type { ControlPlaneProviderCapabilities, WebhookProviderCapabilities } from './contracts'; -import { enabledRunnerProviders } from './providers.config'; - -const webhook = createRunnerProviderRegistry( - enabledRunnerProviders.map((provider) => provider.createWebhookPlugin()), -); - -export const providerRegistry = { - types: enabledRunnerProviders.map(({ type }) => type), - webhook, - controlPlane: (createStartRunnerConfig: CreateStartRunnerConfig) => - createRunnerProviderRegistry( - enabledRunnerProviders.map((provider) => provider.createControlPlanePlugin(createStartRunnerConfig)), - ), -}; diff --git a/lambdas/libs/runner-providers/webhook.ts b/lambdas/libs/runner-providers/webhook.ts new file mode 100644 index 0000000000..e79e4624f9 --- /dev/null +++ b/lambdas/libs/runner-providers/webhook.ts @@ -0,0 +1,8 @@ +import { createRunnerProviderRegistry } from './core'; + +import type { WebhookProviderCapabilities } from './contracts'; +import { enabledWebhookProviders } from './providers.config.webhook'; + +export const webhookProviderRegistry = createRunnerProviderRegistry( + enabledWebhookProviders.map((provider) => provider.createPlugin()), +); From 1a76132bf13a5829979c094dbca8f8087b537141 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Mon, 3 Aug 2026 16:14:35 +0200 Subject: [PATCH 20/21] test(providers): validate split provider entry points --- .../src/pool/pool-contract.test.ts | 2 +- .../scale-runners/scale-down-contract.test.ts | 2 +- .../src/runners/aws-dynamic-labels.test.ts | 2 +- .../libs/runner-providers/core/index.test.ts | 32 +------------- .../runner-providers/provider-types.test.ts | 44 +++++++++++++++++++ .../libs/runner-providers/registry.test.ts | 39 +++++++++++----- .../templates/provider/README.md | 18 ++++---- .../templates/provider/control-plane.ts | 7 ++- .../templates/provider/index.ts | 14 ------ .../templates/provider/provider.test.ts | 13 +++--- .../templates/provider/webhook.ts | 7 ++- lambdas/libs/runner-providers/tsconfig.json | 2 +- .../libs/runner-providers/vitest.config.ts | 12 ++++- 13 files changed, 117 insertions(+), 77 deletions(-) create mode 100644 lambdas/libs/runner-providers/provider-types.test.ts delete mode 100644 lambdas/libs/runner-providers/templates/provider/index.ts diff --git a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts index 686e62dd4c..e7c5eee21a 100644 --- a/lambdas/functions/control-plane/src/pool/pool-contract.test.ts +++ b/lambdas/functions/control-plane/src/pool/pool-contract.test.ts @@ -1,5 +1,5 @@ import type { Octokit } from '@octokit/rest'; -import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; import { beforeEach, vi } from 'vitest'; import { definePoolContractTests } from '../test/runner-provider-contracts/pool'; diff --git a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts index 1c7ef7fa80..8e808aff17 100644 --- a/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts +++ b/lambdas/functions/control-plane/src/scale-runners/scale-down-contract.test.ts @@ -1,4 +1,4 @@ -import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; import { beforeEach, vi } from 'vitest'; import { providerTypes } from '../test/runner-provider-contracts/provider-types'; diff --git a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts index fc9ad189d6..870aae8f65 100644 --- a/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts +++ b/lambdas/functions/webhook/src/runners/aws-dynamic-labels.test.ts @@ -1,4 +1,4 @@ -import type { RunnerProviderType } from '@aws-github-runner/runner-providers/core'; +import type { RunnerProviderType } from '@aws-github-runner/runner-providers/provider-types'; import { describe, expect, it } from 'vitest'; import type { RunnerMatcherConfig } from '../sqs'; diff --git a/lambdas/libs/runner-providers/core/index.test.ts b/lambdas/libs/runner-providers/core/index.test.ts index 1cf30e4263..b910a9f8ff 100644 --- a/lambdas/libs/runner-providers/core/index.test.ts +++ b/lambdas/libs/runner-providers/core/index.test.ts @@ -1,36 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { createRunnerProviderRegistry, normalizeRunnerProviderType, resolveRunnerProviderType } from './index'; - -describe('runner provider normalization', () => { - it.each([ - [undefined, 'ec2'], - ['', 'ec2'], - [' ', 'ec2'], - [' EC2 ', 'ec2'], - ])('normalizes provider type %j to %j', (type, expected) => { - expect(normalizeRunnerProviderType(type)).toBe(expected); - }); - - it.each([[' Unknown '], ['microvm'], [null], [1]])('returns undefined for unsupported provider type %j', (type) => { - expect(normalizeRunnerProviderType(type)).toBeUndefined(); - }); -}); - -describe('runner provider resolution', () => { - it.each([ - [undefined, 'ec2'], - ['', 'ec2'], - [' ', 'ec2'], - [' EC2 ', 'ec2'], - ])('resolves provider type %j to %j', (type, expected) => { - expect(resolveRunnerProviderType(type)).toBe(expected); - }); - - it.each([[' Unknown '], ['microvm'], [null], [1]])('rejects unsupported provider type %j', (type) => { - expect(() => resolveRunnerProviderType(type)).toThrow(`Unsupported runner provider type '${String(type)}'`); - }); -}); +import { createRunnerProviderRegistry } from './index'; describe('runner provider registry', () => { const plugin = { diff --git a/lambdas/libs/runner-providers/provider-types.test.ts b/lambdas/libs/runner-providers/provider-types.test.ts new file mode 100644 index 0000000000..d87a689716 --- /dev/null +++ b/lambdas/libs/runner-providers/provider-types.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; + +import { + defaultRunnerProvider, + normalizeRunnerProviderType, + resolveRunnerProviderType, + runnerProviderTypes, +} from './provider-types'; + +describe('runner provider configuration', () => { + it('defines an explicit default provider', () => { + expect(runnerProviderTypes).toContain(defaultRunnerProvider); + }); +}); + +describe('runner provider normalization', () => { + it.each([ + [undefined, 'ec2'], + ['', 'ec2'], + [' ', 'ec2'], + [' EC2 ', 'ec2'], + ])('normalizes provider type %j to %j', (type, expected) => { + expect(normalizeRunnerProviderType(type)).toBe(expected); + }); + + it.each([[' Unknown '], ['microvm'], [null], [1]])('returns undefined for unsupported provider type %j', (type) => { + expect(normalizeRunnerProviderType(type)).toBeUndefined(); + }); +}); + +describe('runner provider resolution', () => { + it.each([ + [undefined, 'ec2'], + ['', 'ec2'], + [' ', 'ec2'], + [' EC2 ', 'ec2'], + ])('resolves provider type %j to %j', (type, expected) => { + expect(resolveRunnerProviderType(type)).toBe(expected); + }); + + it.each([[' Unknown '], ['microvm'], [null], [1]])('rejects unsupported provider type %j', (type) => { + expect(() => resolveRunnerProviderType(type)).toThrow(`Unsupported runner provider type '${String(type)}'`); + }); +}); diff --git a/lambdas/libs/runner-providers/registry.test.ts b/lambdas/libs/runner-providers/registry.test.ts index bd1cbcc5f9..28a35a75f5 100644 --- a/lambdas/libs/runner-providers/registry.test.ts +++ b/lambdas/libs/runner-providers/registry.test.ts @@ -1,19 +1,38 @@ import { expect, it, vi } from 'vitest'; -import { enabledRunnerProviders } from './providers.config'; -import { providerRegistry } from './registry'; +import { createControlPlaneProviderRegistry } from './control-plane'; +import { runnerProviderTypes } from './provider-types'; +import { enabledControlPlaneProviders } from './providers.config.control-plane'; +import { enabledWebhookProviders } from './providers.config.webhook'; +import { webhookProviderRegistry } from './webhook'; it('exposes every configured provider through both capability registries', () => { const createStartRunnerConfig = vi.fn(async () => []); - const controlPlaneRegistry = providerRegistry.controlPlane(createStartRunnerConfig); - const enabledTypes = enabledRunnerProviders.map(({ type }) => type); + const controlPlaneRegistry = createControlPlaneProviderRegistry(createStartRunnerConfig); + const controlPlaneTypes = enabledControlPlaneProviders.map(({ type }) => type); + const webhookTypes = enabledWebhookProviders.map(({ type }) => type); - expect(providerRegistry.types).toEqual(enabledTypes); + expect(controlPlaneTypes).toEqual(runnerProviderTypes); + expect(webhookTypes).toEqual(runnerProviderTypes); - for (const type of enabledTypes) { - expect(controlPlaneRegistry.capability(type, 'pool')).toEqual(expect.any(Function)); - expect(controlPlaneRegistry.capability(type, 'scaleUp')).toEqual(expect.any(Function)); - expect(controlPlaneRegistry.capability(type, 'scaleDown')).toEqual(expect.any(Function)); - expect(providerRegistry.webhook.capability(type, 'dynamicLabels').selectQueue).toEqual(expect.any(Function)); + for (const type of runnerProviderTypes) { + expect(controlPlaneRegistry.capability(type, 'pool')()).toEqual({ + listRunners: expect.any(Function), + countAvailableRunners: expect.any(Function), + createRunners: expect.any(Function), + }); + expect(controlPlaneRegistry.capability(type, 'scaleUp')()).toEqual({ + prepareGroup: expect.any(Function), + getCurrentRunners: expect.any(Function), + createRunners: expect.any(Function), + }); + expect(controlPlaneRegistry.capability(type, 'scaleDown')()).toEqual({ + list: expect.any(Function), + bootTimeExceeded: expect.any(Function), + markOrphan: expect.any(Function), + unmarkOrphan: expect.any(Function), + terminate: expect.any(Function), + }); + expect(webhookProviderRegistry.capability(type, 'dynamicLabels').selectQueue).toEqual(expect.any(Function)); } }); diff --git a/lambdas/libs/runner-providers/templates/provider/README.md b/lambdas/libs/runner-providers/templates/provider/README.md index 932edcb7d3..27fbc09086 100644 --- a/lambdas/libs/runner-providers/templates/provider/README.md +++ b/lambdas/libs/runner-providers/templates/provider/README.md @@ -3,17 +3,19 @@ Copy this directory to the appropriate provider namespace, for example `aws/codebuild`, and replace `template` with the new lane type. -The template is compile-checked but intentionally not registered. To enable a -completed provider, import its module and add it once to -`providers.config.ts`. The webhook and control-plane registries will then use -the corresponding plugin automatically, and the generic control-plane -contracts will include the new lane type. +The template is compile-checked but intentionally not registered. A provider +has separate webhook and control-plane entry points so each Lambda bundles only +the code it uses. To enable a completed provider, add its lane type to +`provider-types.ts`, then register each entry point in its matching file: -Every provider entry point exports its module as `provider`. Alias that export -to the lane name when enabling it, for example: +- `providers.config.webhook.ts` +- `providers.config.control-plane.ts` + +Each entry point exports its module as `provider`. Alias that export to the lane +name when enabling it, for example: ```ts -import { provider as codebuild } from './aws/codebuild'; +import { provider as codebuild } from './aws/codebuild/webhook'; ``` Implement every capability before registering the provider: diff --git a/lambdas/libs/runner-providers/templates/provider/control-plane.ts b/lambdas/libs/runner-providers/templates/provider/control-plane.ts index 9d0fb8e3f8..61f6d5f482 100644 --- a/lambdas/libs/runner-providers/templates/provider/control-plane.ts +++ b/lambdas/libs/runner-providers/templates/provider/control-plane.ts @@ -6,7 +6,7 @@ import type { ScaleUpRunnerProvider, } from '../../core'; -import type { ControlPlaneProviderCapabilities } from '../../contracts'; +import type { ControlPlaneProviderCapabilities, ControlPlaneProviderModule } from '../../contracts'; export interface TemplateScaleUpState { resourceGroupId: string; @@ -83,3 +83,8 @@ export function createTemplateControlPlanePlugin( }, }; } + +export const provider = { + type: 'template', + createPlugin: createTemplateControlPlanePlugin, +} satisfies ControlPlaneProviderModule<'template'>; diff --git a/lambdas/libs/runner-providers/templates/provider/index.ts b/lambdas/libs/runner-providers/templates/provider/index.ts deleted file mode 100644 index 8d46aedaca..0000000000 --- a/lambdas/libs/runner-providers/templates/provider/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { RunnerProviderModule } from '../../contracts'; - -import { createTemplateControlPlanePlugin } from './control-plane'; -import { createTemplateWebhookPlugin } from './webhook'; - -/** - * Copy this module for a new provider and register the completed module once in - * providers.config.ts. Do not register this template itself. - */ -export const provider = { - type: 'template', - createControlPlanePlugin: createTemplateControlPlanePlugin, - createWebhookPlugin: createTemplateWebhookPlugin, -} satisfies RunnerProviderModule<'template'>; diff --git a/lambdas/libs/runner-providers/templates/provider/provider.test.ts b/lambdas/libs/runner-providers/templates/provider/provider.test.ts index 0b345f8693..2d05fb3478 100644 --- a/lambdas/libs/runner-providers/templates/provider/provider.test.ts +++ b/lambdas/libs/runner-providers/templates/provider/provider.test.ts @@ -1,15 +1,16 @@ import { expect, it, vi } from 'vitest'; -import { provider } from './index'; +import { provider as controlPlaneProvider } from './control-plane'; +import { provider as webhookProvider } from './webhook'; -it('exposes every runner provider capability from one module', () => { - const controlPlanePlugin = provider.createControlPlanePlugin(vi.fn(async () => [])); +it('exposes every runner provider capability from its lane entry point', () => { + const controlPlanePlugin = controlPlaneProvider.createPlugin(vi.fn(async () => [])); const pool = controlPlanePlugin.capabilities.pool(); const scaleUp = controlPlanePlugin.capabilities.scaleUp(); const scaleDown = controlPlanePlugin.capabilities.scaleDown(); - const webhookPlugin = provider.createWebhookPlugin(); + const webhookPlugin = webhookProvider.createPlugin(); - expect(controlPlanePlugin.type).toBe(provider.type); + expect(controlPlanePlugin.type).toBe(controlPlaneProvider.type); expect(pool).toEqual({ listRunners: expect.any(Function), countAvailableRunners: expect.any(Function), @@ -27,6 +28,6 @@ it('exposes every runner provider capability from one module', () => { unmarkOrphan: expect.any(Function), terminate: expect.any(Function), }); - expect(webhookPlugin.type).toBe(provider.type); + expect(webhookPlugin.type).toBe(webhookProvider.type); expect(webhookPlugin.capabilities.dynamicLabels.selectQueue).toEqual(expect.any(Function)); }); diff --git a/lambdas/libs/runner-providers/templates/provider/webhook.ts b/lambdas/libs/runner-providers/templates/provider/webhook.ts index 0517af8bcf..a782ea8477 100644 --- a/lambdas/libs/runner-providers/templates/provider/webhook.ts +++ b/lambdas/libs/runner-providers/templates/provider/webhook.ts @@ -1,6 +1,6 @@ import type { RunnerProviderPlugin } from '../../core'; -import type { DynamicLabelProvider, WebhookProviderCapabilities } from '../../contracts'; +import type { DynamicLabelProvider, WebhookProviderCapabilities, WebhookProviderModule } from '../../contracts'; export const templateDynamicLabelProvider: DynamicLabelProvider = { selectQueue: (input) => { @@ -16,3 +16,8 @@ export function createTemplateWebhookPlugin(): RunnerProviderPlugin; diff --git a/lambdas/libs/runner-providers/tsconfig.json b/lambdas/libs/runner-providers/tsconfig.json index eae83f2000..52d55867fe 100644 --- a/lambdas/libs/runner-providers/tsconfig.json +++ b/lambdas/libs/runner-providers/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["*.ts", "core/**/*", "aws/**/*"], + "include": ["*.ts", "core/**/*", "aws/**/*", "templates/**/*"], "exclude": ["aws/**/*.test.ts"] } diff --git a/lambdas/libs/runner-providers/vitest.config.ts b/lambdas/libs/runner-providers/vitest.config.ts index 62d5095f1f..fd62b358c0 100644 --- a/lambdas/libs/runner-providers/vitest.config.ts +++ b/lambdas/libs/runner-providers/vitest.config.ts @@ -7,8 +7,16 @@ export default mergeConfig(defaultConfig, { test: { setupFiles: [resolve(__dirname, '../../aws-vitest-setup.ts')], coverage: { - include: ['contracts.ts', 'providers.config.ts', 'registry.ts', 'core/**/*.ts', 'aws/**/*.ts'], - exclude: ['**/*.test.ts', '**/*.d.ts'], + include: [ + 'contracts.ts', + 'provider-types.ts', + 'providers.config.*.ts', + 'control-plane.ts', + 'webhook.ts', + 'core/**/*.ts', + 'aws/**/*.ts', + ], + exclude: ['**/*.test.ts', '**/*.d.ts', 'templates/**/*'], thresholds: { statements: 96.16, branches: 95.32, From fb734f9896e039292a63b693a2e4558659b82ef0 Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Mon, 3 Aug 2026 17:06:13 +0200 Subject: [PATCH 21/21] refactor(providers): remove redundant type facades --- .../aws/ec2/src/webhook/dynamic-labels-policy.ts | 2 +- .../aws/ec2/src/webhook/dynamic-labels.test.ts | 4 ++-- .../runner-providers/aws/ec2/src/webhook/dynamic-labels.ts | 6 +++--- lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts | 7 ------- lambdas/libs/runner-providers/core/index.ts | 2 -- 5 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts diff --git a/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts index 426c78028c..a9b919c7bd 100644 --- a/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels-policy.ts @@ -1,4 +1,4 @@ -import type { AwsDynamicLabelsPolicy, AwsDynamicLabelsValueRule } from './types'; +import type { AwsDynamicLabelsPolicy, AwsDynamicLabelsValueRule } from '../../../../contracts'; export type Ec2DynamicLabelsValueRule = AwsDynamicLabelsValueRule; diff --git a/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts index db478968f8..d201f4f607 100644 --- a/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; +import type { RunnerMatcherConfig } from '../../../../contracts'; import { selectEc2DynamicLabelQueue } from './dynamic-labels'; -import type { Ec2RunnerMatcherConfig } from './types'; describe('selectEc2DynamicLabelQueue', () => { it('enforces a legacy EC2 dynamic labels policy when the new key is absent', () => { @@ -43,7 +43,7 @@ describe('selectEc2DynamicLabelQueue', () => { }); }); -function runnerQueue(id: string): Ec2RunnerMatcherConfig { +function runnerQueue(id: string): RunnerMatcherConfig { return { id, arn: `arn:${id}`, diff --git a/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts index 5053f703dd..6ddf5b8fbb 100644 --- a/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts +++ b/lambdas/libs/runner-providers/aws/ec2/src/webhook/dynamic-labels.ts @@ -1,13 +1,13 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util'; +import type { DynamicLabelDispatchTarget, DynamicLabelProvider, RunnerMatcherConfig } from '../../../../contracts'; import { violationsAgainstPolicy } from './dynamic-labels-policy'; -import type { DynamicLabelDispatchTarget, DynamicLabelProvider, Ec2RunnerMatcherConfig } from './types'; const logger = createChildLogger('handler'); export type Ec2DynamicLabelDispatchTarget = DynamicLabelDispatchTarget; -function resolveEc2DynamicLabelsPolicy(queue: Ec2RunnerMatcherConfig) { +function resolveEc2DynamicLabelsPolicy(queue: RunnerMatcherConfig) { const hasLegacyEc2DynamicLabelsPolicy = Object.prototype.hasOwnProperty.call( queue.matcherConfig, 'ec2DynamicLabelsPolicy', @@ -24,7 +24,7 @@ function resolveEc2DynamicLabelsPolicy(queue: Ec2RunnerMatcherConfig) { } export function selectEc2DynamicLabelQueue( - matches: Ec2RunnerMatcherConfig[], + matches: RunnerMatcherConfig[], nonGhrLabels: string[], sanitizedGhrLabels: string[], ): Ec2DynamicLabelDispatchTarget | undefined { diff --git a/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts b/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts deleted file mode 100644 index d8fa8822f4..0000000000 --- a/lambdas/libs/runner-providers/aws/ec2/src/webhook/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type { - AwsDynamicLabelsPolicy, - AwsDynamicLabelsValueRule, - DynamicLabelDispatchTarget, - DynamicLabelProvider, - RunnerMatcherConfig as Ec2RunnerMatcherConfig, -} from '../../../../contracts'; diff --git a/lambdas/libs/runner-providers/core/index.ts b/lambdas/libs/runner-providers/core/index.ts index d55ca72fba..e0e387acdf 100644 --- a/lambdas/libs/runner-providers/core/index.ts +++ b/lambdas/libs/runner-providers/core/index.ts @@ -2,8 +2,6 @@ import type { Octokit } from '@octokit/rest'; import type { RunnerProviderType } from '../provider-types'; -export type { RunnerProviderType } from '../provider-types'; - export interface RunnerProvider { type: RunnerProviderType; }