Skip to content

Commit 5c23012

Browse files
npalmedersonbrilhante
authored andcommitted
feat: Add feature to enable dynamic instance types via workflow labels
1 parent e4b12ae commit 5c23012

8 files changed

Lines changed: 85 additions & 6 deletions

File tree

lambdas/functions/control-plane/src/scale-runners/scale-up.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface ActionRequestMessage {
3030
installationId: number;
3131
repoOwnerType: string;
3232
retryCounter?: number;
33+
labels?: string[];
3334
}
3435

3536
export interface ActionRequestMessageSQS extends ActionRequestMessage {
@@ -250,6 +251,7 @@ export async function createRunners(
250251
ghClient: Octokit,
251252
): Promise<string[]> {
252253
const instances = await createRunner({
254+
environment: ec2RunnerConfig.environment,
253255
runnerType: githubRunnerConfig.runnerType,
254256
runnerOwner: githubRunnerConfig.runnerOwner,
255257
numberOfRunners,
@@ -289,9 +291,30 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
289291
n_requests: payloads.length,
290292
});
291293

294+
const dynamicEc2TypesEnabled = yn(process.env.ENABLE_DYNAMIC_EC2_CONFIGURATION, { default: false });
295+
const requestedInstanceType = payload.labels?.find(label => label.startsWith('ghr-ec2-'))?.replace('ghr-ec2-', '');
296+
297+
if (dynamicEc2TypesEnabled && requestedInstanceType) {
298+
logger.info(`Dynamic EC2 instance type requested: ${requestedInstanceType}`);
299+
}
300+
301+
// Store the requested instance type for use in createRunners
302+
const ec2Config = {
303+
...payload,
304+
requestedInstanceType: dynamicEc2TypesEnabled ? requestedInstanceType : undefined,
305+
};
306+
292307
const enableOrgLevel = yn(process.env.ENABLE_ORGANIZATION_RUNNERS, { default: true });
293308
const maximumRunners = parseInt(process.env.RUNNERS_MAXIMUM_COUNT || '3');
294-
const runnerLabels = process.env.RUNNER_LABELS || '';
309+
310+
// Combine configured runner labels with dynamic EC2 instance type label if present
311+
let runnerLabels = process.env.RUNNER_LABELS || '';
312+
if (dynamicEc2TypesEnabled && requestedInstanceType) {
313+
const ec2Label = `ghr-ec2-${requestedInstanceType}`;
314+
runnerLabels = runnerLabels ? `${runnerLabels},${ec2Label}` : ec2Label;
315+
logger.debug(`Added dynamic EC2 instance type label: ${ec2Label} to runner config.`);
316+
}
317+
295318
const runnerGroup = process.env.RUNNER_GROUP_NAME || 'Default';
296319
const environment = process.env.ENVIRONMENT;
297320
const ssmTokenPath = process.env.SSM_TOKEN_PATH;
@@ -335,6 +358,7 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
335358
githubInstallationClient: Octokit;
336359
};
337360

361+
<<<<<<< HEAD
338362
const validMessages = new Map<string, MessagesWithClient>();
339363
const rejectedMessageIds = new Set<string>();
340364
for (const payload of payloads) {
@@ -343,6 +367,41 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
343367
logger.warn(
344368
'Event is not supported in combination with ephemeral runners. Please ensure you have enabled workflow_job events.',
345369
{ eventType, messageId },
370+
=======
371+
if (scaleUp) {
372+
logger.info(`Attempting to launch a new runner`);
373+
374+
await createRunners(
375+
{
376+
ephemeral,
377+
enableJitConfig,
378+
ghesBaseUrl,
379+
runnerLabels,
380+
runnerGroup,
381+
runnerNamePrefix,
382+
runnerOwner,
383+
runnerType,
384+
disableAutoUpdate,
385+
ssmTokenPath,
386+
ssmConfigPath,
387+
},
388+
{
389+
ec2instanceCriteria: {
390+
instanceTypes,
391+
targetCapacityType: instanceTargetCapacityType,
392+
maxSpotPrice: instanceMaxSpotPrice,
393+
instanceAllocationStrategy: instanceAllocationStrategy,
394+
},
395+
environment,
396+
launchTemplateName,
397+
subnets,
398+
amiIdSsmParameterName,
399+
tracingEnabled,
400+
onDemandFailoverOnError,
401+
},
402+
githubInstallationClient,
403+
ec2Config.requestedInstanceType,
404+
>>>>>>> 44737379 (feat: Add feature to enable dynamic instance types via workflow labels)
346405
);
347406

348407
rejectedMessageIds.add(messageId);

lambdas/functions/webhook/src/runners/dispatch.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,16 @@ export function canRunJob(
8181
runnerLabelsMatchers: string[][],
8282
workflowLabelCheckAll: boolean,
8383
): boolean {
84+
// Filter out ghr-ec2- labels as they are handled by the dynamic EC2 instance type feature
85+
const filteredLabels = workflowJobLabels.filter(label => !label.startsWith('ghr-ec2-'));
86+
8487
runnerLabelsMatchers = runnerLabelsMatchers.map((runnerLabel) => {
8588
return runnerLabel.map((label) => label.toLowerCase());
8689
});
8790
const matchLabels = workflowLabelCheckAll
88-
? runnerLabelsMatchers.some((rl) => workflowJobLabels.every((wl) => rl.includes(wl.toLowerCase())))
89-
: runnerLabelsMatchers.some((rl) => workflowJobLabels.some((wl) => rl.includes(wl.toLowerCase())));
90-
const match = workflowJobLabels.length === 0 ? !matchLabels : matchLabels;
91+
? runnerLabelsMatchers.some((rl) => filteredLabels.every((wl) => rl.includes(wl.toLowerCase())))
92+
: runnerLabelsMatchers.some((rl) => filteredLabels.some((wl) => rl.includes(wl.toLowerCase())));
93+
const match = filteredLabels.length === 0 ? !matchLabels : matchLabels;
9194

9295
logger.debug(
9396
`Received workflow job event with labels: '${JSON.stringify(workflowJobLabels)}'. The event does ${

lambdas/functions/webhook/src/sqs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface ActionRequestMessage {
1212
installationId: number;
1313
queueId: string;
1414
repoOwnerType: string;
15+
labels?: string[];
1516
}
1617

1718
export interface MatcherConfig {

main.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,9 @@ module "runners" {
185185
github_app_parameters = local.github_app_parameters
186186
enable_organization_runners = var.enable_organization_runners
187187
enable_ephemeral_runners = var.enable_ephemeral_runners
188-
enable_jit_config = var.enable_jit_config
188+
enable_dynamic_ec2_configuration = var.enable_dynamic_ec2_configuration
189189
enable_job_queued_check = var.enable_job_queued_check
190+
enable_jit_config = var.enable_jit_config
190191
enable_on_demand_failover_for_errors = var.enable_runner_on_demand_failover_for_errors
191192
scale_errors = var.scale_errors
192193
disable_runner_autoupdate = var.disable_runner_autoupdate

modules/multi-runner/variables.tf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ variable "multi_runner_config" {
7777
disable_runner_autoupdate = optional(bool, false)
7878
ebs_optimized = optional(bool, false)
7979
enable_ephemeral_runners = optional(bool, false)
80+
enable_dynamic_ec2_configuration = optional(bool, false)
8081
enable_job_queued_check = optional(bool, null)
8182
enable_on_demand_failover_for_errors = optional(list(string), [])
8283
scale_errors = optional(list(string), [
@@ -207,7 +208,8 @@ variable "multi_runner_config" {
207208
disable_runner_autoupdate: "Disable the auto update of the github runner agent. Be aware there is a grace period of 30 days, see also the [GitHub article](https://github.blog/changelog/2022-02-01-github-actions-self-hosted-runners-can-now-disable-automatic-updates/)"
208209
ebs_optimized: "The EC2 EBS optimized configuration."
209210
enable_ephemeral_runners: "Enable ephemeral runners, runners will only be used once."
210-
enable_job_queued_check: "Enables JIT configuration for creating runners instead of registration token based registraton. JIT configuration will only be applied for ephemeral runners. By default JIT configuration is enabled for ephemeral runners an can be disabled via this override. When running on GHES without support for JIT configuration this variable should be set to true for ephemeral runners."
211+
enable_dynamic_ec2_configuration: "Enable dynamic EC2 configs based on workflow job labels. When enabled, jobs can request specific configs via the 'gh-ec2-<config type key>:<config type value>' label (e.g., 'gh-ec2-instance-type:t3.large')."
212+
enable_job_queued_check: "(Optional) Only scale if the job event received by the scale up lambda is is in the state queued. By default enabled for non ephemeral runners and disabled for ephemeral. Set this variable to overwrite the default behavior."
211213
enable_on_demand_failover_for_errors: "Enable on-demand failover. For example to fall back to on demand when no spot capacity is available the variable can be set to `InsufficientInstanceCapacity`. When not defined the default behavior is to retry later."
212214
scale_errors: "List of aws error codes that should trigger retry during scale up. This list will replace the default errors defined in the variable `defaultScaleErrors` in https://github.com/github-aws-runners/terraform-aws-github-runner/blob/main/lambdas/functions/control-plane/src/aws/runners.ts"
213215
enable_organization_runners: "Register runners to organization, instead of repo level"

modules/runners/scale-up.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ resource "aws_lambda_function" "scale_up" {
2828
AMI_ID_SSM_PARAMETER_NAME = local.ami_id_ssm_parameter_name
2929
DISABLE_RUNNER_AUTOUPDATE = var.disable_runner_autoupdate
3030
ENABLE_EPHEMERAL_RUNNERS = var.enable_ephemeral_runners
31+
ENABLE_DYNAMIC_EC2_CONFIGURATION = var.enable_dynamic_ec2_configuration
3132
ENABLE_JIT_CONFIG = var.enable_jit_config
3233
ENABLE_JOB_QUEUED_CHECK = local.enable_job_queued_check
3334
ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.metrics.enable && var.metrics.metric.enable_github_app_rate_limit

modules/runners/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,12 @@ variable "enable_ephemeral_runners" {
532532
default = false
533533
}
534534

535+
variable "enable_dynamic_ec2_configuration" {
536+
description = "Enable dynamic EC2 instance types based on workflow job labels. When enabled, jobs can request specific instance types via the 'gh:ec2:instance-type' label."
537+
type = bool
538+
default = false
539+
}
540+
535541
variable "enable_job_queued_check" {
536542
description = "Only scale if the job event received by the scale up lambda is is in the state queued. By default enabled for non ephemeral runners and disabled for ephemeral. Set this variable to overwrite the default behavior."
537543
type = bool

variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,12 @@ variable "enable_ephemeral_runners" {
673673
default = false
674674
}
675675

676+
variable "enable_dynamic_ec2_configuration" {
677+
description = "Enable dynamic EC2 configs based on workflow job labels. When enabled, jobs can request specific configs via the 'gh-ec2-<config type key>:<config type value>' label (e.g., 'gh-ec2-instance-type:t3.large')."
678+
type = bool
679+
default = false
680+
}
681+
676682
variable "enable_job_queued_check" {
677683
description = "Only scale if the job event received by the scale up lambda is in the queued state. By default enabled for non ephemeral runners and disabled for ephemeral. Set this variable to overwrite the default behavior."
678684
type = bool

0 commit comments

Comments
 (0)