Skip to content

Commit e5c5a42

Browse files
npalmedersonbrilhante
authored andcommitted
feat: Add feature to enable dynamic instance types via workflow labels
1 parent 021354d commit e5c5a42

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
@@ -29,6 +29,7 @@ export interface ActionRequestMessage {
2929
installationId: number;
3030
repoOwnerType: string;
3131
retryCounter?: number;
32+
labels?: string[];
3233
}
3334

3435
export interface ActionRequestMessageSQS extends ActionRequestMessage {
@@ -216,6 +217,7 @@ export async function createRunners(
216217
ghClient: Octokit,
217218
): Promise<string[]> {
218219
const instances = await createRunner({
220+
environment: ec2RunnerConfig.environment,
219221
runnerType: githubRunnerConfig.runnerType,
220222
runnerOwner: githubRunnerConfig.runnerOwner,
221223
numberOfRunners,
@@ -233,9 +235,30 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
233235
n_requests: payloads.length,
234236
});
235237

238+
const dynamicEc2TypesEnabled = yn(process.env.ENABLE_DYNAMIC_EC2_CONFIGURATION, { default: false });
239+
const requestedInstanceType = payload.labels?.find(label => label.startsWith('ghr-ec2-'))?.replace('ghr-ec2-', '');
240+
241+
if (dynamicEc2TypesEnabled && requestedInstanceType) {
242+
logger.info(`Dynamic EC2 instance type requested: ${requestedInstanceType}`);
243+
}
244+
245+
// Store the requested instance type for use in createRunners
246+
const ec2Config = {
247+
...payload,
248+
requestedInstanceType: dynamicEc2TypesEnabled ? requestedInstanceType : undefined,
249+
};
250+
236251
const enableOrgLevel = yn(process.env.ENABLE_ORGANIZATION_RUNNERS, { default: true });
237252
const maximumRunners = parseInt(process.env.RUNNERS_MAXIMUM_COUNT || '3');
238-
const runnerLabels = process.env.RUNNER_LABELS || '';
253+
254+
// Combine configured runner labels with dynamic EC2 instance type label if present
255+
let runnerLabels = process.env.RUNNER_LABELS || '';
256+
if (dynamicEc2TypesEnabled && requestedInstanceType) {
257+
const ec2Label = `ghr-ec2-${requestedInstanceType}`;
258+
runnerLabels = runnerLabels ? `${runnerLabels},${ec2Label}` : ec2Label;
259+
logger.debug(`Added dynamic EC2 instance type label: ${ec2Label} to runner config.`);
260+
}
261+
239262
const runnerGroup = process.env.RUNNER_GROUP_NAME || 'Default';
240263
const environment = process.env.ENVIRONMENT;
241264
const ssmTokenPath = process.env.SSM_TOKEN_PATH;
@@ -275,6 +298,7 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
275298
githubInstallationClient: Octokit;
276299
};
277300

301+
<<<<<<< HEAD
278302
const validMessages = new Map<string, MessagesWithClient>();
279303
const invalidMessages: string[] = [];
280304
for (const payload of payloads) {
@@ -283,6 +307,41 @@ export async function scaleUp(payloads: ActionRequestMessageSQS[]): Promise<stri
283307
logger.warn(
284308
'Event is not supported in combination with ephemeral runners. Please ensure you have enabled workflow_job events.',
285309
{ eventType, messageId },
310+
=======
311+
if (scaleUp) {
312+
logger.info(`Attempting to launch a new runner`);
313+
314+
await createRunners(
315+
{
316+
ephemeral,
317+
enableJitConfig,
318+
ghesBaseUrl,
319+
runnerLabels,
320+
runnerGroup,
321+
runnerNamePrefix,
322+
runnerOwner,
323+
runnerType,
324+
disableAutoUpdate,
325+
ssmTokenPath,
326+
ssmConfigPath,
327+
},
328+
{
329+
ec2instanceCriteria: {
330+
instanceTypes,
331+
targetCapacityType: instanceTargetCapacityType,
332+
maxSpotPrice: instanceMaxSpotPrice,
333+
instanceAllocationStrategy: instanceAllocationStrategy,
334+
},
335+
environment,
336+
launchTemplateName,
337+
subnets,
338+
amiIdSsmParameterName,
339+
tracingEnabled,
340+
onDemandFailoverOnError,
341+
},
342+
githubInstallationClient,
343+
ec2Config.requestedInstanceType,
344+
>>>>>>> 44737379 (feat: Add feature to enable dynamic instance types via workflow labels)
286345
);
287346

288347
invalidMessages.push(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
@@ -184,8 +184,9 @@ module "runners" {
184184
github_app_parameters = local.github_app_parameters
185185
enable_organization_runners = var.enable_organization_runners
186186
enable_ephemeral_runners = var.enable_ephemeral_runners
187-
enable_jit_config = var.enable_jit_config
187+
enable_dynamic_ec2_configuration = var.enable_dynamic_ec2_configuration
188188
enable_job_queued_check = var.enable_job_queued_check
189+
enable_jit_config = var.enable_jit_config
189190
enable_on_demand_failover_for_errors = var.enable_runner_on_demand_failover_for_errors
190191
scale_errors = var.scale_errors
191192
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), [
@@ -206,7 +207,8 @@ variable "multi_runner_config" {
206207
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/)"
207208
ebs_optimized: "The EC2 EBS optimized configuration."
208209
enable_ephemeral_runners: "Enable ephemeral runners, runners will only be used once."
209-
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."
210+
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')."
211+
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."
210212
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."
211213
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"
212214
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
@@ -520,6 +520,12 @@ variable "enable_ephemeral_runners" {
520520
default = false
521521
}
522522

523+
variable "enable_dynamic_ec2_configuration" {
524+
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."
525+
type = bool
526+
default = false
527+
}
528+
523529
variable "enable_job_queued_check" {
524530
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."
525531
type = bool

variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,12 @@ variable "enable_ephemeral_runners" {
661661
default = false
662662
}
663663

664+
variable "enable_dynamic_ec2_configuration" {
665+
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')."
666+
type = bool
667+
default = false
668+
}
669+
664670
variable "enable_job_queued_check" {
665671
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."
666672
type = bool

0 commit comments

Comments
 (0)