Skip to content

Commit

Permalink
feat: add windows runner instances
Browse files Browse the repository at this point in the history
* change RunnerType to include platform
* refactor asg-runner-stack to build for both platform types macOS and
windows
* add windows-runner-user-data.yaml with PowerShell script to set up
  runner
* update runner-config.json to include Windows runners
* TODO: add licenses for Windows
* TODO: update cdk.context.json once licenses added

Signed-off-by: Gavin Inglis <[email protected]>
  • Loading branch information
ginglis13 committed Sep 20, 2023
1 parent 3c7f98c commit f8134d2
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 53 deletions.
101 changes: 78 additions & 23 deletions config/runner-config.json
Original file line number Diff line number Diff line change
@@ -1,152 +1,207 @@
{
"runnerBeta": {
"licenseArn": "arn:aws:license-manager:us-west-2:820462304213:license-configuration:lic-7ac3e249bd13f71b44ac34963a8c3353",
"macLicenseArn": "arn:aws:license-manager:us-west-2:820462304213:license-configuration:lic-7ac3e249bd13f71b44ac34963a8c3353",
"windowsLicenseArn": "<TODO>",
"runnerTypes": [
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "arm",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"platform": "windows",
"version": "2022",
"arch": "x86",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"platform": "windows",
"version": "2022",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
}
]
},
"runnerProd": {
"licenseArn": "arn:aws:license-manager:us-west-2:090529234398:license-configuration:lic-c0c3e2458f6d2a45b0bcc8d66fbc072e",
"macLicenseArn": "arn:aws:license-manager:us-west-2:090529234398:license-configuration:lic-c0c3e2458f6d2a45b0bcc8d66fbc072e",
"windowsLicenseArn": "<TODO>",
"runnerTypes": [
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "arm",
"repo": "finch",
"desiredInstances": 2,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "x86",
"repo": "finch",
"desiredInstances": 2,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "12.6",
"platform": "macOS",
"version": "12.6",
"arch": "arm",
"repo": "finch",
"desiredInstances": 2,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "12.6",
"platform": "macOS",
"version": "12.6",
"arch": "x86",
"repo": "finch",
"desiredInstances": 2,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "arm",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "12.6",
"platform": "macOS",
"version": "12.6",
"arch": "arm",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"macOSVersion": "12.6",
"platform": "macOS",
"version": "12.6",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"platform": "windows",
"version": "2022",
"arch": "x86",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
},
{
"platform": "windows",
"version": "2022",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-west-2a", "us-west-2b", "us-west-2c", "us-west-2d"]
}
]
},
"runnerRelease": {
"licenseArn": "arn:aws:license-manager:us-east-2:019528120233:license-configuration:lic-94c3e244cb74a70c90c82f432e9e0d91",
"macLicenseArn": "arn:aws:license-manager:us-east-2:019528120233:license-configuration:lic-94c3e244cb74a70c90c82f432e9e0d91",
"windowsLicenseArn": "<TODO>",
"runnerTypes": [
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "arm",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "x86",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "12.6",
"platform": "macOS",
"version": "12.6",
"arch": "arm",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "12.6",
"platform": "macOS",
"version": "12.6",
"arch": "x86",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "11.7",
"platform": "macOS",
"version": "11.7",
"arch": "arm",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "11.7",
"platform": "macOS",
"version": "11.7",
"arch": "x86",
"repo": "finch",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "arm",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "13.2",
"platform": "macOS",
"version": "13.2",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "11.7",
"platform": "macOS",
"version": "11.7",
"arch": "arm",
"repo": "finch-core",
"desiredInstances": 1,
"availabilityZones": ["us-east-2a", "us-east-2b", "us-east-2c"]
},
{
"macOSVersion": "11.7",
"platform": "macOS",
"version": "11.7",
"arch": "x86",
"repo": "finch-core",
"desiredInstances": 1,
Expand Down
6 changes: 4 additions & 2 deletions config/runner-config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import config from './runner-config.json';

export interface RunnerProps {
licenseArn: string;
macLicenseArn: string;
windowsLicenseArn: string;
runnerTypes: Array<RunnerType>;
}

export interface RunnerType {
macOSVersion: string;
platform: string; // windows or macOS
version: string; // e.g. 13.2 if platform == macOS, 2022 if platform == windows
arch: string;
repo: string;
desiredInstances: number;
Expand Down
67 changes: 41 additions & 26 deletions lib/asg-runner-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,25 @@ interface ASGRunnerStackProps extends cdk.StackProps {
export class ASGRunnerStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: ASGRunnerStackProps) {
super(scope, id, props);
const arch = props.type.arch === 'arm' ? 'arm64_mac' : 'x86_64_mac';
const instanceType = props.type.arch === 'arm' ? 'mac2.metal' : 'mac1.metal';

const platform = props.type.platform === 'windows' ? 'windows': 'mac';
const arch = props.type.arch === 'arm' ? `arm64_${platform}` : `x86_64_${platform}`;
const instanceType = platform === 'windows' ? 'm5zn.metal' : (props.type.arch === 'arm' ? 'mac2.metal' : 'mac1.metal');
const version = props.type.version;

if (props.env == undefined) {
throw new Error('Runner environment is undefined!');
}

const vpc = cdk.aws_ec2.Vpc.fromLookup(this, 'VPC', { isDefault: true });

const securityGroup = new ec2.SecurityGroup(this, 'MacEC2SecurityGroup', {
const securityGroup = new ec2.SecurityGroup(this, 'EC2SecurityGroup', {
vpc,
description: 'Allow only outbound traffic',
allowAllOutbound: true
});

const role = new iam.Role(this, 'MacEC2Role', {
const role = new iam.Role(this, 'EC2Role', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
});

Expand All @@ -66,7 +69,7 @@ export class ASGRunnerStack extends cdk.Stack {

// Create a custom name for this as names for resource groups cannot be repeated
const resourceGroupName =
props.type.repo + '-' + 'Mac' + '-' + props.type.macOSVersion.split('.')[0] + '-' + props.type.arch + 'HostGroup';
`${props.type.repo}-${platform}-${version.split('.')[0]}-${props.type.arch}HostGroup`;

const resourceGroup = new resourcegroups.CfnGroup(this, resourceGroupName, {
name: resourceGroupName,
Expand Down Expand Up @@ -105,30 +108,42 @@ export class ASGRunnerStack extends cdk.Stack {
]
});

const macOSVersion = props.type.macOSVersion;
const amiSearchString = `amzn-ec2-macos-${macOSVersion}*`;
const macImage = new ec2.LookupMachineImage({
name: amiSearchString,
filters: {
'virtualization-type': ['hvm'],
'root-device-type': ['ebs'],
architecture: [arch],
'owner-alias': ['amazon']
}
});

const userData =
`#!/bin/bash
LABEL_STAGE=${props.stage === ENVIRONMENT_STAGE.Release ? 'release' : 'test'}
REPO=${props.type.repo}
REGION=${props.env?.region}
` + readFileSync('./scripts/setup-runner.sh', 'utf8');
const amiSearchString = `amzn-ec2-macos-${version}*`;
const machineImage = platform === 'windows' ?
ec2.MachineImage.latestWindows(ec2.WindowsVersion.WINDOWS_SERVER_2022_ENGLISH_FULL_BASE)
:
new ec2.LookupMachineImage({
name: amiSearchString,
filters: {
'virtualization-type': ['hvm'],
'root-device-type': ['ebs'],
architecture: [arch],
'owner-alias': ['amazon']
}
})

var userData = '';
if (platform === 'windows') {
// We need to provide user data as a yaml file to specify runAs: admin
// Maintain that file as yaml and source here to ensure formatting.
userData = readFileSync('./scripts/windows-runner-user-data.yaml', 'utf8')
.replace("<STAGE>",props.stage === ENVIRONMENT_STAGE.Release ? 'release' : 'test')
.replace("<REPO>", props.type.repo)
.replace("<REGION>", props.env?.region || '');
} else if (platform === 'mac') {
userData =
`#!/bin/bash
LABEL_STAGE=${props.stage === ENVIRONMENT_STAGE.Release ? 'release' : 'test'}
REPO=${props.type.repo}
REGION=${props.env?.region}
` + readFileSync(`./scripts/setup-runner.sh`, 'utf8');
}

const lt = new ec2.LaunchTemplate(this, 'MacASGLaunchTemplate', {
const lt = new ec2.LaunchTemplate(this, `${platform}ASGLaunchTemplate`, {
requireImdsv2: true,
instanceType: new ec2.InstanceType(instanceType),
keyName: 'runner-key',
machineImage: macImage,
machineImage: machineImage,
role: role,
securityGroup: securityGroup,
userData: ec2.UserData.custom(userData)
Expand Down Expand Up @@ -161,7 +176,7 @@ export class ASGRunnerStack extends cdk.Stack {
]
};

const asg = new autoscaling.AutoScalingGroup(this, 'MacASG', {
const asg = new autoscaling.AutoScalingGroup(this, `${platform}ASG`, {
vpc,
desiredCapacity: props.type.desiredInstances,
maxCapacity: props.type.desiredInstances + 2,
Expand Down
5 changes: 3 additions & 2 deletions lib/finch-pipeline-app-stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ export class FinchPipelineAppStage extends cdk.Stage {
constructor(scope: Construct, id: string, props: FinchPipelineAppStageProps) {
super(scope, id, props);
props.runnerConfig.runnerTypes.forEach((runnerType) => {
const ASGStackName = 'ASG' + '-' + runnerType.repo + '-' + runnerType.macOSVersion.split('.')[0] + '-' + runnerType.arch + 'Stack'
const ASGStackName = `ASG-${runnerType.platform}-${runnerType.repo}-${runnerType.version.split('.')[0]}-${runnerType.arch}Stack`;
const licenseArn = runnerType.platform === 'windows' ? props.runnerConfig.windowsLicenseArn : props.runnerConfig.macLicenseArn;
new ASGRunnerStack(this, ASGStackName , {
env: props.env,
stage: props.environmentStage,
licenseArn: props.runnerConfig.licenseArn,
licenseArn: licenseArn,
type: runnerType
});
});
Expand Down
Loading

0 comments on commit f8134d2

Please sign in to comment.