Skip to content

Commit 15dbcf4

Browse files
feat(compute-providers): add MicroVM webhook routing
1 parent c08f4bc commit 15dbcf4

6 files changed

Lines changed: 165 additions & 1 deletion

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import type { RunnerMatcherConfig } from '../../../../contracts';
4+
import { microvmDynamicLabelProvider } from './dynamic-labels';
5+
6+
const imageArn = 'arn:aws:lambda:eu-west-1:123456789012:microvm-image:runner-large';
7+
const egressConnectorArn = 'arn:aws:lambda:eu-west-1:123456789012:network-connector:github-runner-private-egress';
8+
9+
describe('microvmDynamicLabelProvider', () => {
10+
it('accepts supported MicroVM overrides', () => {
11+
const queue = microvmQueue();
12+
const dynamicLabels = [
13+
`ghr-microvm-egress-network-connectors:${egressConnectorArn}`,
14+
`ghr-microvm-image-arn:${imageArn}`,
15+
'ghr-microvm-image-version:3.0',
16+
'ghr-microvm-maximum-duration-in-seconds:7200',
17+
];
18+
19+
expect(getViolations(queue, dynamicLabels)).toEqual([]);
20+
});
21+
22+
it('rejects unsupported MicroVM resource overrides', () => {
23+
expect(getViolations(microvmQueue(), ['ghr-microvm-memory:8192'])).toEqual([
24+
{
25+
label: 'ghr-microvm-memory:8192',
26+
reason: "key 'memory' is not a supported MicroVM override",
27+
},
28+
]);
29+
});
30+
31+
it('enforces the AWS dynamic-label policy', () => {
32+
const queue = microvmQueue();
33+
queue.matcherConfig.awsDynamicLabelsPolicy = {
34+
restricted_keys: { 'maximum-duration-in-seconds': { max: 3600 } },
35+
};
36+
37+
expect(getViolations(queue, ['ghr-microvm-maximum-duration-in-seconds:7200'])).toEqual([
38+
{
39+
label: 'ghr-microvm-maximum-duration-in-seconds:7200',
40+
reason: "value '7200' exceeds max '3600'",
41+
},
42+
]);
43+
});
44+
45+
it('applies allowed patterns to the complete image ARN', () => {
46+
const queue = microvmQueue();
47+
queue.matcherConfig.awsDynamicLabelsPolicy = {
48+
restricted_keys: {
49+
'image-arn': {
50+
allowed: ['arn:aws:lambda:eu-west-1:123456789012:microvm-image:approved-*'],
51+
},
52+
},
53+
};
54+
55+
expect(
56+
getViolations(queue, [
57+
'ghr-microvm-image-arn:arn:aws:lambda:eu-west-1:123456789012:microvm-image:approved-large',
58+
]),
59+
).toEqual([]);
60+
expect(
61+
getViolations(queue, ['ghr-microvm-image-arn:arn:aws:lambda:eu-west-1:123456789012:microvm-image:unapproved']),
62+
).toHaveLength(1);
63+
});
64+
65+
it('applies the policy to each egress connector label', () => {
66+
const queue = microvmQueue();
67+
queue.matcherConfig.awsDynamicLabelsPolicy = {
68+
restricted_keys: {
69+
'egress-network-connectors': {
70+
allowed: ['arn:aws:lambda:eu-west-1:123456789012:network-connector:approved-*'],
71+
},
72+
},
73+
};
74+
75+
expect(
76+
getViolations(queue, [
77+
'ghr-microvm-egress-network-connectors:arn:aws:lambda:eu-west-1:123456789012:network-connector:approved-private',
78+
]),
79+
).toEqual([]);
80+
expect(
81+
getViolations(queue, [
82+
'ghr-microvm-egress-network-connectors:arn:aws:lambda:eu-west-1:123456789012:network-connector:unapproved',
83+
]),
84+
).toHaveLength(1);
85+
});
86+
});
87+
88+
function getViolations(queue: RunnerMatcherConfig, labels: string[]) {
89+
return microvmDynamicLabelProvider.getViolations({ queue, labels });
90+
}
91+
92+
function microvmQueue(): RunnerMatcherConfig {
93+
return {
94+
id: 'microvm',
95+
arn: 'arn:aws:sqs:eu-west-1:123456789012:microvm',
96+
computeProvider: 'microvm',
97+
matcherConfig: {
98+
labelMatchers: [['self-hosted', 'linux', 'arm64', 'microvm']],
99+
exactMatch: false,
100+
enableDynamicLabels: true,
101+
},
102+
};
103+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { violationsAgainstAwsDynamicLabelsPolicy } from '../../../dynamic-labels-policy';
2+
import type { DynamicLabelProvider } from '../../../../contracts';
3+
import { MICROVM_DYNAMIC_LABEL_PREFIX, parseMicrovmDynamicLabels } from '../dynamic-labels';
4+
5+
export const microvmDynamicLabelProvider: DynamicLabelProvider = {
6+
getViolations: ({ queue, labels }) => {
7+
const parsedLabels = parseMicrovmDynamicLabels(labels);
8+
const policyViolations = violationsAgainstAwsDynamicLabelsPolicy(
9+
labels,
10+
queue.matcherConfig.awsDynamicLabelsPolicy,
11+
MICROVM_DYNAMIC_LABEL_PREFIX,
12+
);
13+
14+
return [...parsedLabels.violations, ...policyViolations];
15+
},
16+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { defineWebhookProviderContractTests } from '../../test/webhook-provider-contract';
2+
import { provider } from './webhook';
3+
4+
defineWebhookProviderContractTests({
5+
provider,
6+
acceptedDynamicLabels: ['ghr-microvm-image-version:3.0', 'ghr-microvm-maximum-duration-in-seconds:7200'],
7+
rejectingPolicies: [
8+
{
9+
name: 'blocked keys',
10+
apply: (queue) => {
11+
queue.matcherConfig.awsDynamicLabelsPolicy = {
12+
blocked_keys: ['image-version'],
13+
};
14+
},
15+
},
16+
{
17+
name: 'restricted keys',
18+
apply: (queue) => {
19+
queue.matcherConfig.awsDynamicLabelsPolicy = {
20+
restricted_keys: {
21+
'maximum-duration-in-seconds': { max: 3600 },
22+
},
23+
};
24+
},
25+
},
26+
],
27+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ComputeProviderPlugin } from '../../core';
2+
3+
import type { WebhookProviderCapabilities, WebhookProviderModule } from '../../contracts';
4+
import { microvmDynamicLabelProvider } from './src/webhook/dynamic-labels';
5+
6+
export function createMicrovmWebhookPlugin(): ComputeProviderPlugin<WebhookProviderCapabilities, 'microvm'> {
7+
return {
8+
type: 'microvm',
9+
capabilities: { dynamicLabels: microvmDynamicLabelProvider },
10+
};
11+
}
12+
13+
export const provider = {
14+
type: 'microvm',
15+
createPlugin: createMicrovmWebhookPlugin,
16+
} satisfies WebhookProviderModule<'microvm'>;

lambdas/libs/compute-providers/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"./aws/ec2/control-plane": "./aws/ec2/control-plane.ts",
1313
"./aws/ec2/control-plane/runners": "./aws/ec2/src/control-plane/runners.ts",
1414
"./aws/ec2/control-plane/runner-config": "./aws/ec2/src/control-plane/runner-config.ts",
15+
"./aws/microvm/webhook": "./aws/microvm/webhook.ts",
1516
"./aws/microvm/control-plane": "./aws/microvm/control-plane.ts"
1617
},
1718
"type": "module",
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { provider as ec2 } from './aws/ec2/webhook';
2+
import { provider as microvm } from './aws/microvm/webhook';
23
import type { WebhookProviderModule } from './contracts';
34

45
/** Provider plugins included in the webhook bundle. */
5-
export const enabledWebhookProviders = [ec2] as const satisfies readonly WebhookProviderModule[];
6+
export const enabledWebhookProviders = [ec2, microvm] as const satisfies readonly WebhookProviderModule[];

0 commit comments

Comments
 (0)