Skip to content

Commit

Permalink
feat: support multiple instance types (#898)
Browse files Browse the repository at this point in the history
* fix(scale): Refactor Runner Type and Owner

* `environment` should not be optional

* feat(runners): Support Multiple Instance Types

* Correcting failed launch logic

* Updating tests

* Test for all launch templates failing

* Marking `instance_type` as deprecated
  • Loading branch information
mcaulifn authored and npalm committed Jun 17, 2021
1 parent 83dd263 commit c996f73
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 159 deletions.
1 change: 1 addition & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ module "runners" {
s3_location_runner_binaries = local.s3_action_runner_url

instance_type = var.instance_type
instance_types = var.instance_types
market_options = var.market_options
block_device_mappings = var.block_device_mappings

Expand Down
23 changes: 11 additions & 12 deletions modules/runners/lambdas/runners/src/scale-runners/runners.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ jest.mock('aws-sdk', () => ({
SSM: jest.fn().mockImplementation(() => mockSSM),
}));

const LAUNCH_TEMPLATE = 'lt-1';
const ORG_NAME = 'SomeAwesomeCoder';
const REPO_NAME = `${ORG_NAME}/some-amazing-library`;
const ENVIRONMENT = 'unit-test-environment';
Expand Down Expand Up @@ -115,22 +116,20 @@ describe('create runner', () => {
],
});
mockSSM.putParameter.mockImplementation(() => mockPutParameter);
process.env.LAUNCH_TEMPLATE_NAME = 'launch-template-name';
process.env.LAUNCH_TEMPLATE_VERSION = '1';
process.env.SUBNET_IDS = 'sub-1234';
});

it('calls run instances with the correct config for repo', async () => {
await createRunner({
runnerConfig: 'bla',
runnerServiceConfig: 'bla',
environment: ENVIRONMENT,
runnerType: 'Repo',
runnerOwner: REPO_NAME
});
}, LAUNCH_TEMPLATE);
expect(mockEC2.runInstances).toBeCalledWith({
MaxCount: 1,
MinCount: 1,
LaunchTemplate: { LaunchTemplateName: 'launch-template-name', Version: '1' },
LaunchTemplate: { LaunchTemplateName: LAUNCH_TEMPLATE, Version: '$Default' },
SubnetId: 'sub-1234',
TagSpecifications: [
{
Expand All @@ -146,15 +145,15 @@ describe('create runner', () => {

it('calls run instances with the correct config for org', async () => {
await createRunner({
runnerConfig: 'bla',
runnerServiceConfig: 'bla',
environment: ENVIRONMENT,
runnerType: 'Org',
runnerOwner: ORG_NAME,
});
}, LAUNCH_TEMPLATE);
expect(mockEC2.runInstances).toBeCalledWith({
MaxCount: 1,
MinCount: 1,
LaunchTemplate: { LaunchTemplateName: 'launch-template-name', Version: '1' },
LaunchTemplate: { LaunchTemplateName: LAUNCH_TEMPLATE, Version: '$Default' },
SubnetId: 'sub-1234',
TagSpecifications: [
{
Expand All @@ -170,11 +169,11 @@ describe('create runner', () => {

it('creates ssm parameters for each created instance', async () => {
await createRunner({
runnerConfig: 'bla',
runnerServiceConfig: 'bla',
environment: ENVIRONMENT,
runnerType: 'Org',
runnerOwner: ORG_NAME,
});
}, LAUNCH_TEMPLATE);
expect(mockSSM.putParameter).toBeCalledWith({
Name: `${ENVIRONMENT}-i-1234`,
Value: 'bla',
Expand All @@ -187,11 +186,11 @@ describe('create runner', () => {
Instances: [],
});
await createRunner({
runnerConfig: 'bla',
runnerServiceConfig: 'bla',
environment: ENVIRONMENT,
runnerType: 'Org',
runnerOwner: ORG_NAME,
});
}, LAUNCH_TEMPLATE);
expect(mockSSM.putParameter).not.toBeCalled();
});
});
78 changes: 42 additions & 36 deletions modules/runners/lambdas/runners/src/scale-runners/runners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export interface ListRunnerFilters {
environment: string | undefined;
}

export interface RunnerInputParameters {
runnerServiceConfig: string;
environment: string;
runnerType: 'Org' | 'Repo';
runnerOwner: string;
}

export async function listRunners(filters: ListRunnerFilters | undefined = undefined): Promise<RunnerInfo[]> {
const ec2 = new EC2();
const ec2Filters = [
Expand Down Expand Up @@ -46,13 +53,6 @@ export async function listRunners(filters: ListRunnerFilters | undefined = undef
return runners;
}

export interface RunnerInputParameters {
runnerConfig: string;
environment: string;
runnerType: 'Org' | 'Repo';
runnerOwner: string;
}

export async function terminateRunner(runner: RunnerInfo): Promise<void> {
const ec2 = new EC2();
await ec2
Expand All @@ -63,47 +63,53 @@ export async function terminateRunner(runner: RunnerInfo): Promise<void> {
console.debug('Runner terminated.' + runner.instanceId);
}

export async function createRunner(runnerParameters: RunnerInputParameters): Promise<void> {
const launchTemplateName = process.env.LAUNCH_TEMPLATE_NAME as string;
const launchTemplateVersion = process.env.LAUNCH_TEMPLATE_VERSION as string;

const subnets = (process.env.SUBNET_IDS as string).split(',');
const randomSubnet = subnets[Math.floor(Math.random() * subnets.length)];
export async function createRunner(runnerParameters: RunnerInputParameters, launchTemplateName: string): Promise<void> {
console.debug('Runner configuration: ' + JSON.stringify(runnerParameters));
const ec2 = new EC2();
const runInstancesResponse = await ec2
.runInstances({
MaxCount: 1,
MinCount: 1,
LaunchTemplate: {
LaunchTemplateName: launchTemplateName,
Version: launchTemplateVersion,
},
SubnetId: randomSubnet,
TagSpecifications: [
{
ResourceType: 'instance',
Tags: [
{ Key: 'Application', Value: 'github-action-runner' },
{
Key: runnerParameters.runnerType,
Value: runnerParameters.runnerOwner
},
],
},
],
})
.runInstances(getInstanceParams(launchTemplateName, runnerParameters))
.promise();
console.info('Created instance(s): ', runInstancesResponse.Instances?.map((i) => i.InstanceId).join(','));

const ssm = new SSM();
runInstancesResponse.Instances?.forEach(async (i: EC2.Instance) => {
await ssm
.putParameter({
Name: runnerParameters.environment + '-' + (i.InstanceId as string),
Value: runnerParameters.runnerConfig,
Value: runnerParameters.runnerServiceConfig,
Type: 'SecureString',
})
.promise();
});
}

function getInstanceParams(
launchTemplateName: string,
runnerParameters: RunnerInputParameters
): EC2.RunInstancesRequest {
return {
MaxCount: 1,
MinCount: 1,
LaunchTemplate: {
LaunchTemplateName: launchTemplateName,
Version: '$Default',
},
SubnetId: getSubnet(),
TagSpecifications: [
{
ResourceType: 'instance',
Tags: [
{ Key: 'Application', Value: 'github-action-runner' },
{
Key: runnerParameters.runnerType,
Value: runnerParameters.runnerOwner
},
],
},
],
};
}

function getSubnet(): string {
const subnets = (process.env.SUBNET_IDS as string).split(',');
return subnets[Math.floor(Math.random() * subnets.length)];
}
Loading

0 comments on commit c996f73

Please sign in to comment.