-
Notifications
You must be signed in to change notification settings - Fork 722
Expand file tree
/
Copy pathdispatch.ts
More file actions
117 lines (107 loc) · 4.54 KB
/
dispatch.ts
File metadata and controls
117 lines (107 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
import { WorkflowJobEvent } from '@octokit/webhooks-types';
import { Response } from '../lambda';
import { RunnerMatcherConfig, sendActionRequest } from '../sqs';
import ValidationError from '../ValidationError';
import { ConfigDispatcher, ConfigWebhook } from '../ConfigLoader';
const logger = createChildLogger('handler');
export async function dispatch(
event: WorkflowJobEvent,
eventType: string,
config: ConfigDispatcher | ConfigWebhook,
): Promise<Response> {
validateRepoInAllowList(event, config);
return await handleWorkflowJob(event, eventType, config.matcherConfig!);
}
function validateRepoInAllowList(event: WorkflowJobEvent, config: ConfigDispatcher) {
if (config.repositoryAllowList.length > 0 && !config.repositoryAllowList.includes(event.repository.full_name)) {
logger.info(`Received event from unauthorized repository ${event.repository.full_name}`);
throw new ValidationError(403, `Received event from unauthorized repository ${event.repository.full_name}`);
}
}
async function handleWorkflowJob(
body: WorkflowJobEvent,
githubEvent: string,
matcherConfig: Array<RunnerMatcherConfig>,
): Promise<Response> {
if (body.action !== 'queued') {
return {
statusCode: 201,
body: `Workflow job not queued, not dispatching to queue.`,
};
}
logger.debug(
`Processing workflow job event - Repository: ${body.repository.full_name}, ` +
`Job ID: ${body.workflow_job.id}, Job Name: ${body.workflow_job.name}, ` +
`Run ID: ${body.workflow_job.run_id}, Labels: ${JSON.stringify(body.workflow_job.labels)}`,
);
// sort the queuesConfig by order of matcher config exact/bidirectional match, with all true matches lined up ahead.
matcherConfig.sort((a, b) => {
const aStrict = a.matcherConfig.bidirectionalLabelMatch || a.matcherConfig.exactMatch;
const bStrict = b.matcherConfig.bidirectionalLabelMatch || b.matcherConfig.exactMatch;
return aStrict === bStrict ? 0 : aStrict ? -1 : 1;
});
for (const queue of matcherConfig) {
if (
canRunJob(
body.workflow_job.labels,
queue.matcherConfig.labelMatchers,
queue.matcherConfig.exactMatch,
queue.matcherConfig.bidirectionalLabelMatch,
)
) {
await sendActionRequest({
id: body.workflow_job.id,
repositoryName: body.repository.name,
repositoryOwner: body.repository.owner.login,
eventType: githubEvent,
installationId: body.installation?.id ?? 0,
queueId: queue.id,
repoOwnerType: body.repository.owner.type,
});
logger.info(
`Successfully dispatched job for ${body.repository.full_name} to the queue ${queue.id} - ` +
`Job ID: ${body.workflow_job.id}, Job Name: ${body.workflow_job.name}, Run ID: ${body.workflow_job.run_id}`,
);
return {
statusCode: 201,
body: `Successfully queued job for ${body.repository.full_name} to the queue ${queue.id}`,
};
}
}
const notAcceptedErrorMsg = `Received event contains runner labels '${body.workflow_job.labels}' from '${
body.repository.full_name
}' that are not accepted.`;
logger.warn(
`${notAcceptedErrorMsg} - Job ID: ${body.workflow_job.id}, Job Name: ${body.workflow_job.name}, Run ID: ${body.workflow_job.run_id}`,
);
return { statusCode: 202, body: notAcceptedErrorMsg };
}
export function canRunJob(
workflowJobLabels: string[],
runnerLabelsMatchers: string[][],
workflowLabelCheckAll: boolean,
bidirectionalLabelMatch = false,
): boolean {
runnerLabelsMatchers = runnerLabelsMatchers.map((runnerLabel) => {
return runnerLabel.map((label) => label.toLowerCase());
});
let match: boolean;
if (bidirectionalLabelMatch) {
const workflowLabelsLower = workflowJobLabels.map((wl) => wl.toLowerCase());
match = runnerLabelsMatchers.some(
(rl) => workflowLabelsLower.every((wl) => rl.includes(wl)) && rl.every((r) => workflowLabelsLower.includes(r)),
);
} else {
const matchLabels = workflowLabelCheckAll
? runnerLabelsMatchers.some((rl) => workflowJobLabels.every((wl) => rl.includes(wl.toLowerCase())))
: runnerLabelsMatchers.some((rl) => workflowJobLabels.some((wl) => rl.includes(wl.toLowerCase())));
match = workflowJobLabels.length === 0 ? !matchLabels : matchLabels;
}
logger.debug(
`Received workflow job event with labels: '${JSON.stringify(workflowJobLabels)}'. The event does ${
match ? '' : 'NOT '
}match the runner labels: '${Array.from(runnerLabelsMatchers).join(',')}'`,
);
return match;
}