Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lambdas/functions/control-plane/src/pool/pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ describe('Test simple pool.', () => {
});

it('Rejects unsupported pool provider types.', async () => {
await expect(adjust({ poolSize: 10, type: 'microvm' })).rejects.toThrow(
"Unsupported compute provider type 'microvm'",
await expect(adjust({ poolSize: 10, type: 'unsupported-provider' })).rejects.toThrow(
"Unsupported compute provider type 'unsupported-provider'",
);
expect(mockListRunners).not.toHaveBeenCalled();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2157,9 +2157,11 @@ describe('compute provider selection', () => {
});

it('rejects unsupported scale-up provider types', async () => {
process.env.COMPUTE_PROVIDER_TYPE = 'microvm';
process.env.COMPUTE_PROVIDER_TYPE = 'unsupported-provider';

await expect(scaleUpModule.scaleUp(TEST_DATA)).rejects.toThrow("Unsupported compute provider type 'microvm'");
await expect(scaleUpModule.scaleUp(TEST_DATA)).rejects.toThrow(
"Unsupported compute provider type 'unsupported-provider'",
);
expect(mockedAppAuth).not.toHaveBeenCalled();
});
});
Expand Down

This file was deleted.

This file was deleted.

29 changes: 0 additions & 29 deletions lambdas/functions/webhook/src/runners/aws-dynamic-labels.ts

This file was deleted.

113 changes: 34 additions & 79 deletions lambdas/functions/webhook/src/runners/dispatch.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getParameter } from '@aws-github-runner/aws-ssm-util';
import { selectDynamicLabelQueue } from '@aws-github-runner/compute-providers/webhook';

import nock from 'nock';
import { WorkflowJobEvent } from '@octokit/webhooks-types';
Expand All @@ -14,6 +15,9 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';

vi.mock('../sqs');
vi.mock('@aws-github-runner/aws-ssm-util');
vi.mock('@aws-github-runner/compute-providers/webhook', () => ({
selectDynamicLabelQueue: vi.fn(),
}));

const GITHUB_APP_WEBHOOK_SECRET = 'TEST_SECRET';

Expand Down Expand Up @@ -246,7 +250,14 @@ describe('Dispatcher', () => {
describe('per-matcher dynamic labels handling', () => {
const baseRunner = runnerConfig[0];

it('strips invalid ghr- labels (too long, bad chars) before policy and dispatch', async () => {
beforeEach(() => {
vi.mocked(selectDynamicLabelQueue).mockImplementation((matches, nonGhrLabels, sanitizedGhrLabels) => ({
queue: matches[0],
labels: [...nonGhrLabels, ...sanitizedGhrLabels],
}));
});

it('strips invalid ghr- labels before provider selection and dispatch', async () => {
const longLabel = 'ghr-' + 'a'.repeat(125); // 129 chars
config = await createConfig(undefined, [
{
Expand Down Expand Up @@ -276,140 +287,84 @@ describe('Dispatcher', () => {
} as unknown as WorkflowJobEvent;
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(201);
expect(selectDynamicLabelQueue).toHaveBeenCalledWith(
[expect.objectContaining({ id: baseRunner.id })],
['self-hosted', 'linux'],
['ghr-valid:value', 'ghr-list:value;another'],
);
expect(sendActionRequest).toHaveBeenCalledWith(
expect.objectContaining({ labels: ['self-hosted', 'linux', 'ghr-valid:value', 'ghr-list:value;another'] }),
);
});

it('rejects the job (202) when the only matching runner has enableDynamicLabels=false', async () => {
it('rejects the job when no provider accepts the dynamic labels', async () => {
vi.mocked(selectDynamicLabelQueue).mockReturnValue(undefined);
config = await createConfig(undefined, [
{
...baseRunner,
matcherConfig: {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: false,
enableDynamicLabels: true,
},
},
]);
const event = {
...workFlowJobEvent,
workflow_job: {
...workFlowJobEvent.workflow_job,
labels: ['self-hosted', 'linux', 'ghr-ec2-instance-type:t3.large'],
labels: ['self-hosted', 'linux', 'ghr-provider-setting:value'],
},
} as unknown as WorkflowJobEvent;
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(202);
expect(sendActionRequest).not.toHaveBeenCalled();
});

it('keeps dynamic labels when the matched runner enables them and has no policy', async () => {
it('dispatches to the queue and labels returned by the provider selector', async () => {
config = await createConfig(undefined, [
{
...baseRunner,
id: 'first',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: true,
},
},
]);
const event = {
...workFlowJobEvent,
workflow_job: {
...workFlowJobEvent.workflow_job,
labels: ['self-hosted', 'linux', 'ghr-ec2-instance-type:t3.large'],
},
} as unknown as WorkflowJobEvent;
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toHaveBeenCalledWith(
expect.objectContaining({ labels: ['self-hosted', 'linux', 'ghr-ec2-instance-type:t3.large'] }),
);
});

it('skips a matching runner whose policy rejects the dynamic labels and uses the next compliant one', async () => {
config = await createConfig(undefined, [
{
...baseRunner,
id: 'strict',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: true,
awsDynamicLabelsPolicy: {
restricted_keys: {
'instance-type': { allowed: ['m5.*'] },
},
},
},
},
{
...baseRunner,
id: 'permissive',
id: 'selected',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: true,
},
},
]);

vi.mocked(selectDynamicLabelQueue).mockImplementation((matches) => ({
queue: matches[1],
labels: ['self-hosted', 'linux', 'ghr-provider-setting:normalized'],
}));

const event = {
...workFlowJobEvent,
workflow_job: {
...workFlowJobEvent.workflow_job,
labels: ['self-hosted', 'linux', 'ghr-ec2-instance-type:t3.large'],
labels: ['self-hosted', 'linux', 'ghr-provider-setting:requested'],
},
} as unknown as WorkflowJobEvent;
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toHaveBeenCalledWith(
expect.objectContaining({
queueId: 'permissive',
labels: ['self-hosted', 'linux', 'ghr-ec2-instance-type:t3.large'],
queueId: 'selected',
labels: ['self-hosted', 'linux', 'ghr-provider-setting:normalized'],
}),
);
});

it('rejects the job (202) when no runner accepts the policy', async () => {
config = await createConfig(undefined, [
{
...baseRunner,
id: 'first',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: true,
awsDynamicLabelsPolicy: {
restricted_keys: {
'instance-type': { allowed: ['m5.*'] },
},
},
},
},
{
...baseRunner,
id: 'second',
matcherConfig: {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: false,
},
},
]);
const event = {
...workFlowJobEvent,
workflow_job: {
...workFlowJobEvent.workflow_job,
labels: ['self-hosted', 'linux', 'ghr-ec2-instance-type:t3.large'],
},
} as unknown as WorkflowJobEvent;
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(202);
expect(sendActionRequest).not.toHaveBeenCalled();
});

it('forwards non-dynamic jobs as-is to the first match', async () => {
config = await createConfig(undefined, [
{
Expand All @@ -419,7 +374,6 @@ describe('Dispatcher', () => {
labelMatchers: [['self-hosted', 'linux']],
exactMatch: true,
enableDynamicLabels: true,
awsDynamicLabelsPolicy: {},
},
},
]);
Expand All @@ -435,6 +389,7 @@ describe('Dispatcher', () => {
expect(sendActionRequest).toHaveBeenCalledWith(
expect.objectContaining({ queueId: 'first', labels: ['self-hosted', 'linux'] }),
);
expect(selectDynamicLabelQueue).not.toHaveBeenCalled();
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions lambdas/functions/webhook/src/runners/dispatch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
import { selectDynamicLabelQueue } from '@aws-github-runner/compute-providers/webhook';
import { WorkflowJobEvent } from '@octokit/webhooks-types';

import { Response } from '../lambda';
import { RunnerMatcherConfig, sendActionRequest } from '../sqs';
import ValidationError from '../ValidationError';
import { ConfigDispatcher, ConfigWebhook, QueueSelectionStrategy } from '../ConfigLoader';
import { selectAwsDynamicLabelQueue } from './aws-dynamic-labels';
import { canRunJob, splitWorkflowJobLabels } from './labels';

const logger = createChildLogger('handler');
Expand Down Expand Up @@ -84,7 +84,7 @@ async function handleWorkflowJob(
// Dynamic labels present: prefer the first provider-compliant queue. The
// queue selection strategy applies to standard jobs only; dynamic-label jobs
// always use the first compliant queue.
const dynamicTarget = selectAwsDynamicLabelQueue(matches, nonGhrLabels, sanitizedGhrLabels);
const dynamicTarget = selectDynamicLabelQueue(matches, nonGhrLabels, sanitizedGhrLabels);

if (dynamicTarget) {
targets = [dynamicTarget.queue];
Expand Down
Loading