Skip to content

Feat/windows support #1476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 76 commits into from
Dec 9, 2021
Merged
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
736a854
fix(scale): Refactor Runner Type and Owner (#871)
mcaulifn Jun 15, 2021
6e35845
feat: support multiple instance types (#898)
mcaulifn Jun 16, 2021
a768687
docs: fix lambda_security_group_ids incorrect description #738 (#902)
npalm Jun 17, 2021
5b5ee3b
fix: scale down runners (#905)
npalm Jun 17, 2021
8746249
chore: group upgrade lambda dependencies (#906)
npalm Jun 17, 2021
95a3ab6
chore: Bump aws-sdk (#752) (#909)
dependabot[bot] Jun 18, 2021
c1e7bf3
chore: Bump aws-sdk (#752) (#908)
dependabot[bot] Jun 18, 2021
b860437
chore: Bump aws-sdk (#752) (#887)
dependabot[bot] Jun 18, 2021
cd88e3d
chore: Bump aws-sdk (#752) (#885)
dependabot[bot] Jun 18, 2021
fae4839
chore: Bump aws-sdk (#752) (#889)
dependabot[bot] Jun 18, 2021
b62b63e
chore: Bump aws-sdk (#752) (#892)
dependabot[bot] Jun 18, 2021
5ae4a4d
chore: Bump aws-sdk (#752) (#907)
dependabot[bot] Jun 18, 2021
744d977
chore: Bump aws-sdk (#752) (#864)
dependabot[bot] Jun 18, 2021
118c5d3
feat: support windows binary in cache lambda
RichiCoder1 Apr 10, 2021
ecff591
add variable to control runner os
RichiCoder1 Apr 13, 2021
40f334c
update docs
RichiCoder1 Apr 16, 2021
6a41c42
feat: add windows instance support
npalm Jun 20, 2021
f39dcfd
docs
RichiCoder1 Apr 16, 2021
e21476a
update docs
npalm Jun 20, 2021
0a1f706
fix zip name to include correct os name
RichiCoder1 Apr 18, 2021
8d46612
add powershell closing tag for userdata
RichiCoder1 Apr 18, 2021
8a061b1
add transcript
RichiCoder1 Apr 18, 2021
210b251
fix alias and add more verbose logging
RichiCoder1 Apr 18, 2021
50112c3
tweak install cmdlets
RichiCoder1 Apr 18, 2021
822eacc
add some robustness to userdata
RichiCoder1 Apr 18, 2021
890e26c
fix a number of typos and bugs and restart
RichiCoder1 Apr 19, 2021
f632dd3
try and make run better in weird environment
RichiCoder1 Apr 19, 2021
289d90e
use containers image and kill docker install
RichiCoder1 Apr 19, 2021
7697b00
fix cloudagent log paths
RichiCoder1 Apr 19, 2021
47644c3
update default
RichiCoder1 Apr 19, 2021
83a1c42
give windows runners more time to spin up
RichiCoder1 Apr 19, 2021
9d528e9
fix trigger syntax
RichiCoder1 Apr 19, 2021
df38f3b
move restart before transcript stop
RichiCoder1 Apr 19, 2021
b740451
skip restart
RichiCoder1 Apr 19, 2021
20dbe40
fix container ami filter
RichiCoder1 Apr 19, 2021
dce7292
address pr feedback, add example, and fix ami_filter
npalm Jun 20, 2021
98e8324
fix issue with cloudwatch
RichiCoder1 Apr 28, 2021
e8d889e
remove syntax error highlights in template
RichiCoder1 Apr 28, 2021
093d620
fix win log path
RichiCoder1 May 4, 2021
5d3912b
run as administrator
npalm May 21, 2021
0f49f9b
Merge branch 'develop' into feat/windows-support
ScottGuymer Dec 1, 2021
e47e726
Get the example working OOTB
ScottGuymer Dec 1, 2021
d9ebb4c
Fixes from the merge
ScottGuymer Dec 2, 2021
713b902
Fix readme
ScottGuymer Dec 2, 2021
2a54b31
Fixes to align with develop
ScottGuymer Dec 2, 2021
6b01760
Make the example work for testing
ScottGuymer Dec 2, 2021
a6342ab
Fix the passthrough of the boot time to the runner module
ScottGuymer Dec 2, 2021
323efc9
Extend the boot time so a machine has time to boot.
ScottGuymer Dec 2, 2021
b1d6981
Remove the default AMI from the main module.
ScottGuymer Dec 2, 2021
61f0caf
Added more documentation to the windows example
ScottGuymer Dec 2, 2021
9a7b10a
Format typescript
ScottGuymer Dec 2, 2021
fb61a51
Fix formatting
ScottGuymer Dec 2, 2021
febcad9
Reset default example
ScottGuymer Dec 2, 2021
33ea24b
Improve readability
ScottGuymer Dec 2, 2021
0c5cdb9
Add some validation for the runner_os variable
ScottGuymer Dec 2, 2021
5872b09
Merge branch 'develop' into feat/windows-support
ScottGuymer Dec 3, 2021
ae61125
Added logging change
ScottGuymer Dec 3, 2021
0a77a00
Some further refactoring
ScottGuymer Dec 6, 2021
64b348a
Add a windows start-runner.ps1
ScottGuymer Dec 6, 2021
5a68873
Add more validation
ScottGuymer Dec 6, 2021
a165182
Upgrade the provider
ScottGuymer Dec 6, 2021
add16c2
Add time since boot to script
ScottGuymer Dec 6, 2021
6a5e083
Merge remote-tracking branch 'upstream/develop' into feat/windows-sup…
ScottGuymer Dec 7, 2021
84042c6
Refactoring to use tag/parameter based config
ScottGuymer Dec 7, 2021
648131d
Tweaks from testing
ScottGuymer Dec 7, 2021
72c1a09
Runners only have PS5 where get-uptime might not be available
ScottGuymer Dec 8, 2021
8cd699a
Some more small refactoring.
ScottGuymer Dec 8, 2021
d930490
Fix config access and remove packages that are no longer needed.
ScottGuymer Dec 8, 2021
1078dfc
Set the value of runner_log_files as null so it can be defaulted.
ScottGuymer Dec 8, 2021
86767e7
Merge remote-tracking branch 'upstream/develop' into feat/windows-sup…
ScottGuymer Dec 8, 2021
582ee83
Fix failing tests
ScottGuymer Dec 8, 2021
177bf53
Update modules/runners/variables.tf
ScottGuymer Dec 9, 2021
d6e2926
Updates the github secrets to use hex values rather than strings.
ScottGuymer Dec 9, 2021
37b3984
Merge remote-tracking branch 'origin/feat/windows-support' into feat/…
ScottGuymer Dec 9, 2021
923b681
Updates based on PR feedback
ScottGuymer Dec 9, 2021
1f698f2
Updated byte length to 20
ScottGuymer Dec 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -266,6 +266,8 @@ idle_config = [{
}]
```

_**Note**_: When using Windows runners it's recommended to keep a few runners warmed up due to the minutes-long cold start time.

### Prebuilt Images

This module also allows you to run agents from a prebuilt AMI to gain faster startup times. You can find more information in [the image README.md](/images/README.md)
@@ -294,7 +296,9 @@ Examples are located in the [examples](./examples) directory. The following exam

- _[Default](examples/default/README.md)_: The default example of the module
- _[Permissions boundary](examples/permissions-boundary/README.md)_: Example usages of permissions boundaries.
- _[Ubuntu](examples/ubuntu/README.md)_: Example usage of creating a runner using Ubuntu AMIs.
- _[Prebuilt Images](examples/prebuilt/README.md)_: Example usages of deploying runners with a custom prebuilt image.
- _[Windows](examples/windows/README.md)_: Example usage of creating a runner using Windows as the OS.

## Sub modules

6 changes: 3 additions & 3 deletions examples/default/main.tf
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ locals {
aws_region = "eu-west-1"
}

resource "random_password" "random" {
length = 28
resource "random_id" "random" {
byte_length = 20
}


@@ -27,7 +27,7 @@ module "runners" {
github_app = {
key_base64 = var.github_app_key_base64
id = var.github_app_id
webhook_secret = random_password.random.result
webhook_secret = random_id.random.hex
}

webhook_lambda_zip = "lambdas-download/webhook.zip"
2 changes: 1 addition & 1 deletion examples/default/outputs.tf
Original file line number Diff line number Diff line change
@@ -10,6 +10,6 @@ output "webhook_endpoint" {

output "webhook_secret" {
sensitive = true
value = random_password.random.result
value = random_id.random.hex
}

6 changes: 3 additions & 3 deletions examples/permissions-boundary/main.tf
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ locals {
aws_region = "eu-west-1"
}

resource "random_password" "random" {
length = 32
resource "random_id" "random" {
byte_length = 20
}

data "terraform_remote_state" "iam" {
@@ -46,7 +46,7 @@ module "runners" {
id = var.github_app_id
client_id = var.github_app_client_id
client_secret = var.github_app_client_secret
webhook_secret = random_password.random.result
webhook_secret = random_id.random.hex
}

webhook_lambda_zip = "lambdas-download/webhook.zip"
2 changes: 1 addition & 1 deletion examples/permissions-boundary/outputs.tf
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ output "runners" {

output "webhook" {
value = {
secret = random_password.random.result
secret = random_id.random.hex
endpoint = module.runners.webhook.endpoint
}
}
6 changes: 3 additions & 3 deletions examples/prebuilt/main.tf
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ locals {
aws_region = "eu-west-1"
}

resource "random_password" "random" {
length = 28
resource "random_id" "random" {
byte_length = 20
}

data "aws_caller_identity" "current" {}
@@ -21,7 +21,7 @@ module "runners" {
github_app = {
key_base64 = var.github_app_key_base64
id = var.github_app_id
webhook_secret = random_password.random.result
webhook_secret = random_id.random.hex
}

webhook_lambda_zip = "../../lambda_output/webhook.zip"
2 changes: 1 addition & 1 deletion examples/prebuilt/outputs.tf
Original file line number Diff line number Diff line change
@@ -10,6 +10,6 @@ output "webhook_endpoint" {

output "webhook_secret" {
sensitive = true
value = random_password.random.result
value = random_id.random.hex
}

6 changes: 3 additions & 3 deletions examples/ubuntu/main.tf
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ locals {
aws_region = "eu-west-1"
}

resource "random_password" "random" {
length = 28
resource "random_id" "random" {
byte_length = 20
}

module "runners" {
@@ -22,7 +22,7 @@ module "runners" {
github_app = {
key_base64 = var.github_app_key_base64
id = var.github_app_id
webhook_secret = random_password.random.result
webhook_secret = random_id.random.hex
}

# webhook_lambda_zip = "lambdas-download/webhook.zip"
2 changes: 1 addition & 1 deletion examples/ubuntu/outputs.tf
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ output "runners" {

output "webhook" {
value = {
secret = random_password.random.result
secret = random_id.random.hex
endpoint = module.runners.webhook.endpoint
}
}
39 changes: 39 additions & 0 deletions examples/windows/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions examples/windows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Action runners deployment windows example

This module shows how to create GitHub action runners using an Windows Runners. Lambda release will be downloaded from GitHub.

## Usages

Steps for the full setup, such as creating a GitHub app can be found in the root module's [README](../../README.md). First, download the Lambda releases from GitHub. Alternatively you can build the lambdas locally with Node or Docker, for which there is a build script available at `<root>/.ci/build.sh`. In the `main.tf` you can remove the location of the lambda zip files, the default location will work in this case.

> Ensure you have set the version in `lambdas-download/main.tf` for running the example. The version needs to be set to a GitHub release version, see <https://github.com/philips-labs/terraform-aws-github-runner/releases>

```pwsh
cd lambdas-download
terraform init
terraform apply
cd ..
```

Before running Terraform, ensure the GitHub app is configured.

```bash
terraform init
terraform apply
```

_**Note**_: It can take upwards of ten minutes for a runner to start processing jobs, and about as long for logs to start showing up. It's recommend that scale the runners via a warm-up job and then keep them idled.
25 changes: 25 additions & 0 deletions examples/windows/lambdas-download/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
locals {
version = "<REPLACE_BY_GITHUB_RELEASE_VERSION>"
}

module "lambdas" {
source = "../../../modules/download-lambda"
lambdas = [
{
name = "webhook"
tag = local.version
},
{
name = "runners"
tag = local.version
},
{
name = "runner-binaries-syncer"
tag = local.version
}
]
}

output "files" {
value = module.lambdas.files
}
48 changes: 48 additions & 0 deletions examples/windows/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
locals {
environment = "windows"
aws_region = "eu-west-1"
}

resource "random_id" "random" {
byte_length = 20
}

module "runners" {
source = "../../"

aws_region = local.aws_region
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
environment = local.environment

github_app = {
key_base64 = var.github_app_key_base64
id = var.github_app_id
webhook_secret = random_id.random.hex
}

# Grab the lambda packages from local directory. Must run /.ci/build.sh first
webhook_lambda_zip = "../../lambda_output/webhook.zip"
runner_binaries_syncer_lambda_zip = "../../lambda_output/runner-binaries-syncer.zip"
runners_lambda_zip = "../../lambda_output/runners.zip"

enable_organization_runners = false
# no need to add extra windows tag here as it is automatically added by GitHub
runner_extra_labels = "default,example"

# Set the OS to Windows
runner_os = "win"
# we need to give the runner time to start because this is windows.
runner_boot_time_in_minutes = 20

# enable access to the runners via SSM
enable_ssm_on_runners = true

instance_types = ["m5.large", "c5.large"]

# override delay of events in seconds for testing
delay_webhook_event = 5

# override scaling down for testing
scale_down_schedule_expression = "cron(* * * * ? *)"
}
15 changes: 15 additions & 0 deletions examples/windows/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
output "runners" {
value = {
lambda_syncer_name = module.runners.binaries_syncer.lambda.function_name
}
}

output "webhook_endpoint" {
value = module.runners.webhook.endpoint
}

output "webhook_secret" {
sensitive = true
value = random_id.random.hex
}

3 changes: 3 additions & 0 deletions examples/windows/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
provider "aws" {
region = local.aws_region
}
4 changes: 4 additions & 0 deletions examples/windows/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

variable "github_app_key_base64" {}

variable "github_app_id" {}
7 changes: 7 additions & 0 deletions examples/windows/vpc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module "vpc" {
source = "git::https://github.com/philips-software/terraform-aws-vpc.git?ref=2.2.0"

environment = local.environment
aws_region = local.aws_region
create_private_hosted_zone = false
}
8 changes: 4 additions & 4 deletions main.tf
Original file line number Diff line number Diff line change
@@ -6,9 +6,6 @@ locals {

s3_action_runner_url = "s3://${module.runner_binaries.bucket.id}/${module.runner_binaries.runner_distribution_object_key}"
runner_architecture = substr(var.instance_type, 0, 2) == "a1" || substr(var.instance_type, 1, 2) == "6g" ? "arm64" : "x64"

ami_filter = length(var.ami_filter) > 0 ? var.ami_filter : local.runner_architecture == "arm64" ? { name = ["amzn2-ami-hvm-2*-arm64-gp2"] } : { name = ["amzn2-ami-hvm-2.*-x86_64-ebs"] }

github_app_parameters = {
id = module.ssm.parameters.github_app_id
key_base64 = module.ssm.parameters.github_app_key_base64
@@ -82,20 +79,22 @@ module "runners" {
s3_bucket_runner_binaries = module.runner_binaries.bucket
s3_location_runner_binaries = local.s3_action_runner_url

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

runner_architecture = local.runner_architecture
ami_filter = local.ami_filter
ami_filter = var.ami_filter
ami_owners = var.ami_owners

sqs_build_queue = aws_sqs_queue.queued_builds
github_app_parameters = local.github_app_parameters
enable_organization_runners = var.enable_organization_runners
scale_down_schedule_expression = var.scale_down_schedule_expression
minimum_running_time_in_minutes = var.minimum_running_time_in_minutes
runner_boot_time_in_minutes = var.runner_boot_time_in_minutes
runner_extra_labels = var.runner_extra_labels
runner_as_root = var.runner_as_root
runners_maximum_count = var.runners_maximum_count
@@ -155,6 +154,7 @@ module "runner_binaries" {

distribution_bucket_name = "${var.environment}-dist-${random_string.random.result}"

runner_os = var.runner_os
runner_architecture = local.runner_architecture
runner_allow_prerelease_binaries = var.runner_allow_prerelease_binaries

Original file line number Diff line number Diff line change
@@ -310,3 +310,23 @@ describe('Synchronize action distribution for arm64.', () => {
await expect(sync()).rejects.toThrow(errorMessage);
});
});

describe('Synchronize action distribution for windows.', () => {
const errorMessage = 'Cannot find GitHub release asset.';
beforeEach(() => {
process.env.S3_BUCKET_NAME = bucketName;
process.env.S3_OBJECT_KEY = bucketObjectKey;
process.env.GITHUB_RUNNER_OS = 'win';
});

it('No win asset.', async () => {
mockOctokit.repos.listReleases.mockImplementation(() => ({
data: listReleases.map((release) => ({
...release,
assets: release.assets.filter((asset) => !asset.name.includes('win')),
})),
}));

await expect(sync()).rejects.toThrow(errorMessage);
});
});
Original file line number Diff line number Diff line change
@@ -34,7 +34,8 @@ interface ReleaseAsset {
downloadUrl: string;
}

async function getLinuxReleaseAsset(
async function getReleaseAsset(
runnerOs = 'linux',
runnerArch = 'x64',
fetchPrereleaseBinaries = false,
): Promise<ReleaseAsset | undefined> {
@@ -58,11 +59,11 @@ async function getLinuxReleaseAsset(
} else {
return undefined;
}
const linuxAssets = asset.assets?.filter((a) => a.name?.includes(`actions-runner-linux-${runnerArch}-`));
const assets = asset.assets?.filter((a: { name?: string }) =>
a.name?.includes(`actions-runner-${runnerOs}-${runnerArch}-`),
);

return linuxAssets?.length === 1
? { name: linuxAssets[0].name, downloadUrl: linuxAssets[0].browser_download_url }
: undefined;
return assets?.length === 1 ? { name: assets[0].name, downloadUrl: assets[0].browser_download_url } : undefined;
}

async function uploadToS3(s3: S3, cacheObject: CacheObject, actionRunnerReleaseAsset: ReleaseAsset): Promise<void> {
@@ -106,6 +107,7 @@ async function uploadToS3(s3: S3, cacheObject: CacheObject, actionRunnerReleaseA
export async function sync(): Promise<void> {
const s3 = new AWS.S3();

const runnerOs = process.env.GITHUB_RUNNER_OS || 'linux';
const runnerArch = process.env.GITHUB_RUNNER_ARCHITECTURE || 'x64';
const fetchPrereleaseBinaries = JSON.parse(process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES || 'false');

@@ -116,8 +118,7 @@ export async function sync(): Promise<void> {
if (!cacheObject.bucket || !cacheObject.key) {
throw Error('Please check all mandatory variables are set.');
}

const actionRunnerReleaseAsset = await getLinuxReleaseAsset(runnerArch, fetchPrereleaseBinaries);
const actionRunnerReleaseAsset = await getReleaseAsset(runnerOs, runnerArch, fetchPrereleaseBinaries);
if (actionRunnerReleaseAsset === undefined) {
throw Error('Cannot find GitHub release asset.');
}
Original file line number Diff line number Diff line change
@@ -3365,6 +3365,7 @@ tr46@^2.1.0:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"

tr46@~0.0.3:
2 changes: 1 addition & 1 deletion modules/runner-binaries-syncer/main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
locals {
action_runner_distribution_object_key = "actions-runner-linux.tar.gz"
action_runner_distribution_object_key = "actions-runner-${var.runner_os}.${var.runner_os == "linux" ? "tar.gz" : "zip"}"
}

resource "aws_s3_bucket" "action_dist" {
3 changes: 2 additions & 1 deletion modules/runner-binaries-syncer/runner-binaries-syncer.tf
Original file line number Diff line number Diff line change
@@ -18,8 +18,9 @@ resource "aws_lambda_function" "syncer" {

environment {
variables = {
GITHUB_RUNNER_ARCHITECTURE = var.runner_architecture
GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = var.runner_allow_prerelease_binaries
GITHUB_RUNNER_ARCHITECTURE = var.runner_architecture
GITHUB_RUNNER_OS = var.runner_os
LOG_LEVEL = var.log_level
LOG_TYPE = var.log_type
S3_BUCKET_NAME = aws_s3_bucket.action_dist.id
6 changes: 6 additions & 0 deletions modules/runner-binaries-syncer/variables.tf
Original file line number Diff line number Diff line change
@@ -54,6 +54,12 @@ variable "role_path" {
default = null
}

variable "runner_os" {
description = "The operating system for the runner instance (linux, win), defaults to 'linux'"
type = string
default = "linux"
}

variable "runner_architecture" {
description = "The platform architecture for the runner instance (x64, arm64), defaults to 'x64'"
type = string
32 changes: 31 additions & 1 deletion modules/runners/logging.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
locals {
logfiles = var.enable_cloudwatch_agent ? [for l in var.runner_log_files : {
runner_log_files = (
var.runner_log_files != null
? var.runner_log_files
: [
{
"prefix_log_group" : true,
"file_path" : "/var/log/messages",
"log_group_name" : "messages",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "user_data",
"prefix_log_group" : true,
"file_path" : var.runner_os == "win" ? "C:/UserData.log" : "/var/log/user-data.log",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "runner",
"prefix_log_group" : true,
"file_path" : var.runner_os == "win" ? "C:/actions-runner/_diag/Runner_*.log" : "/home/runners/actions-runner/_diag/Runner_**.log",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "runner-startup",
"prefix_log_group" : true,
"file_path" : var.runner_os == "win" ? "C:/runner-startup.log" : "/var/log/runner-startup.log",
"log_stream_name" : "{instance_id}"
}
]
)
logfiles = var.enable_cloudwatch_agent ? [for l in local.runner_log_files : {
"log_group_name" : l.prefix_log_group ? "/github-self-hosted-runners/${var.environment}/${l.log_group_name}" : "/${l.log_group_name}"
"log_stream_name" : l.log_stream_name
"file_path" : l.file_path
50 changes: 34 additions & 16 deletions modules/runners/main.tf
Original file line number Diff line number Diff line change
@@ -6,26 +6,44 @@ locals {
var.tags,
)

name_sg = var.overrides["name_sg"] == "" ? local.tags["Name"] : var.overrides["name_sg"]
name_runner = var.overrides["name_runner"] == "" ? local.tags["Name"] : var.overrides["name_runner"]
role_path = var.role_path == null ? "/${var.environment}/" : var.role_path
instance_profile_path = var.instance_profile_path == null ? "/${var.environment}/" : var.instance_profile_path
lambda_zip = var.lambda_zip == null ? "${path.module}/lambdas/runners/runners.zip" : var.lambda_zip
userdata_template = var.userdata_template == null ? "${path.module}/templates/user-data.sh" : var.userdata_template
userdata_arm_patch = "${path.module}/templates/arm-runner-patch.tpl"
userdata_install_runner = "${path.module}/templates/install-runner.sh"
userdata_start_runner = "${path.module}/templates/start-runner.sh"

instance_types = distinct(var.instance_types == null ? [var.instance_type] : var.instance_types)

kms_key_arn = var.kms_key_arn != null ? var.kms_key_arn : ""
name_sg = var.overrides["name_sg"] == "" ? local.tags["Name"] : var.overrides["name_sg"]
name_runner = var.overrides["name_runner"] == "" ? local.tags["Name"] : var.overrides["name_runner"]
role_path = var.role_path == null ? "/${var.environment}/" : var.role_path
instance_profile_path = var.instance_profile_path == null ? "/${var.environment}/" : var.instance_profile_path
lambda_zip = var.lambda_zip == null ? "${path.module}/lambdas/runners/runners.zip" : var.lambda_zip
userdata_template = var.userdata_template == null ? local.default_userdata_template[var.runner_os] : var.userdata_template
userdata_arm_patch = "${path.module}/templates/arm-runner-patch.tpl"
instance_types = distinct(var.instance_types == null ? [var.instance_type] : var.instance_types)
kms_key_arn = var.kms_key_arn != null ? var.kms_key_arn : ""

default_ami = {
"win" = { name = ["Windows_Server-20H2-English-Core-ContainersLatest-*"] }
"linux" = var.runner_architecture == "arm64" ? { name = ["amzn2-ami-hvm-2*-arm64-gp2"] } : { name = ["amzn2-ami-hvm-2.*-x86_64-ebs"] }
}

default_userdata_template = {
"win" = "${path.module}/templates/user-data.ps1"
"linux" = "${path.module}/templates/user-data.sh"
}

userdata_install_runner = {
"win" = "${path.module}/templates/install-runner.ps1"
"linux" = "${path.module}/templates/install-runner.sh"
}

userdata_start_runner = {
"win" = "${path.module}/templates/start-runner.ps1"
"linux" = "${path.module}/templates/start-runner.sh"
}

ami_filter = coalesce(var.ami_filter, local.default_ami[var.runner_os])
}

data "aws_ami" "runner" {
most_recent = "true"

dynamic "filter" {
for_each = var.ami_filter
for_each = local.ami_filter
content {
name = filter.key
values = filter.value
@@ -112,12 +130,12 @@ resource "aws_launch_template" "runner" {

user_data = var.enabled_userdata ? base64encode(templatefile(local.userdata_template, {
pre_install = var.userdata_pre_install
install_runner = templatefile(local.userdata_install_runner, {
install_runner = templatefile(local.userdata_install_runner[var.runner_os], {
S3_LOCATION_RUNNER_DISTRIBUTION = var.s3_location_runner_binaries
ARM_PATCH = var.runner_architecture == "arm64" ? templatefile(local.userdata_arm_patch, {}) : ""
})
post_install = var.userdata_post_install
start_runner = templatefile(local.userdata_start_runner, {})
start_runner = templatefile(local.userdata_start_runner[var.runner_os], {})
ghes_url = var.ghes_url
ghes_ssl_verify = var.ghes_ssl_verify
## retain these for backwards compatibility
4 changes: 2 additions & 2 deletions modules/runners/policies-runner.tf
Original file line number Diff line number Diff line change
@@ -26,8 +26,8 @@ resource "aws_iam_role_policy" "ssm_parameters" {
role = aws_iam_role.runner.name
policy = templatefile("${path.module}/policies/instance-ssm-parameters-policy.json",
{
arn_ssm_parameters_prefix= "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.environment}-*"
arn_ssm_parameters_path ="arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.environment}/*"
arn_ssm_parameters_prefix = "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.environment}-*"
arn_ssm_parameters_path = "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.environment}/*"
}
)
}
9 changes: 8 additions & 1 deletion modules/runners/scale-down.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
locals {
# Windows Runners can take their sweet time to do anything
min_runtime_defaults = {
"win" = 15
"linux" = 5
}
}
resource "aws_lambda_function" "scale_down" {
s3_bucket = var.lambda_s3_bucket != null ? var.lambda_s3_bucket : null
s3_key = var.runners_lambda_s3_key != null ? var.runners_lambda_s3_key : null
@@ -18,7 +25,7 @@ resource "aws_lambda_function" "scale_down" {
GHES_URL = var.ghes_url
LOG_LEVEL = var.log_level
LOG_TYPE = var.log_type
MINIMUM_RUNNING_TIME_IN_MINUTES = var.minimum_running_time_in_minutes
MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.minimum_running_time_in_minutes, local.min_runtime_defaults[var.runner_os])
NODE_TLS_REJECT_UNAUTHORIZED = var.ghes_url != null && !var.ghes_ssl_verify ? 0 : 1
PARAMETER_GITHUB_APP_ID_NAME = var.github_app_parameters.id.name
PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.github_app_parameters.key_base64.name
14 changes: 14 additions & 0 deletions modules/runners/templates/install-runner.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## install the runner

Write-Host "Creating actions-runner directory for the GH Action installtion"
New-Item -ItemType Directory -Path C:\actions-runner ; Set-Location C:\actions-runner

Write-Host "Downloading the GH Action runner from s3 bucket $s3_location"
aws s3 cp ${S3_LOCATION_RUNNER_DISTRIBUTION} actions-runner.zip

Write-Host "Un-zip action runner"
Expand-Archive -Path actions-runner.zip -DestinationPath .

Write-Host "Delete zip file"
Remove-Item actions-runner.zip

97 changes: 97 additions & 0 deletions modules/runners/templates/start-runner.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

## Retrieve instance metadata

Write-Host "Retrieving TOKEN from AWS API"
$token=Invoke-RestMethod -Method PUT -Uri "http://169.254.169.254/latest/api/token" -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "180"}

$metadata=Invoke-RestMethod -Uri "http://169.254.169.254/latest/dynamic/instance-identity/document" -Headers @{"X-aws-ec2-metadata-token" = $token}

$Region = $metadata.region
Write-Host "Reteieved REGION from AWS API ($Region)"

$InstanceId = $metadata.instanceId
Write-Host "Reteieved InstanceId from AWS API ($InstanceId)"

$tags=aws ec2 describe-tags --region "$Region" --filters "Name=resource-id,Values=$InstanceId" | ConvertFrom-Json
Write-Host "Retrieved tags from AWS API"

$environment=$tags.Tags.where( {$_.Key -eq 'ghr:environment'}).value
Write-Host "Reteieved ghr:environment tag - ($environment)"

$parameters=$(aws ssm get-parameters-by-path --path "/$environment/runner" --region "$Region" --query "Parameters[*].{Name:Name,Value:Value}") | ConvertFrom-Json
Write-Host "Retrieved parameters from AWS SSM"

$run_as=$parameters.where( {$_.Name -eq "/$environment/runner/run-as"}).value
Write-Host "Retrieved /$environment/runner/run-as parameter - ($run_as)"

$enable_cloudwatch_agent=$parameters.where( {$_.Name -eq "/$environment/runner/enable-cloudwatch"}).value
Write-Host "Retrieved /$environment/runner/enable-cloudwatch parameter - ($enable_cloudwatch_agent)"

$agent_mode=$parameters.where( {$_.Name -eq "/$environment/runner/agent-mode"}).value
Write-Host "Retrieved /$environment/runner/agent-mode parameter - ($agent_mode)"

if ($enable_cloudwatch_agent)
{
Write-Host "Enabling CloudWatch Agent"
& 'C:\Program Files\Amazon\AmazonCloudWatchAgent\amazon-cloudwatch-agent-ctl.ps1' -a fetch-config -m ec2 -s -c "ssm:$environment-cloudwatch_agent_config_runner"
}

## Configure the runner

Write-Host "Get GH Runner config from AWS SSM"
$config = $null
$i = 0
do {
$config = (aws ssm get-parameters --names "$environment-$InstanceId" --with-decryption --region $Region --query "Parameters[*].{Name:Name,Value:Value}" | ConvertFrom-Json)[0].value
Write-Host "Waiting for GH Runner config to become available in AWS SSM ($i/30)"
Start-Sleep 1
$i++
} while (($null -eq $config) -and ($i -lt 30))

Write-Host "Delete GH Runner token from AWS SSM"
aws ssm delete-parameter --name "$environment-$InstanceId" --region $Region

# Create or update user
if (-not($run_as)) {
Write-Host "No user specified, using default ec2-user account"
$run_as="ec2-user"
}
Add-Type -AssemblyName "System.Web"
$password = [System.Web.Security.Membership]::GeneratePassword(24, 4)
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$username = $run_as
if (!(Get-LocalUser -Name $username -ErrorAction Ignore)) {
New-LocalUser -Name $username -Password $securePassword
Write-Host "Created new user ($username)"
}
else {
Set-LocalUser -Name $username -Password $securePassword
Write-Host "Changed password for user ($username)"
}
# Add user to groups
foreach ($group in @("Administrators", "docker-users")) {
if ((Get-LocalGroup -Name "$group" -ErrorAction Ignore) -and
!(Get-LocalGroupMember -Group "$group" -Member $username -ErrorAction Ignore)) {
Add-LocalGroupMember -Group "$group" -Member $username
Write-Host "Added $username to $group group"
}
}

# Disable User Access Control (UAC)
# TODO investigate if this is needed or if its overkill - https://github.com/philips-labs/terraform-aws-github-runner/issues/1505
Set-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -Name ConsentPromptBehaviorAdmin -Value 0 -Force
Write-Host "Disabled User Access Control (UAC)"

$configCmd = ".\config.cmd --unattended --name $InstanceId --work `"_work`" $config"
Write-Host "Configure GH Runner as user $run_as"
Invoke-Expression $configCmd

Write-Host "Starting the runner as user $run_as"

Write-Host "Installing the runner as a service"

$action = New-ScheduledTaskAction -WorkingDirectory "$pwd" -Execute "run.cmd"
$trigger = Get-CimClass "MSFT_TaskRegistrationTrigger" -Namespace "Root/Microsoft/Windows/TaskScheduler"
Register-ScheduledTask -TaskName "runnertask" -Action $action -Trigger $trigger -User $username -Password $password -RunLevel Highest -Force
Write-Host "Starting the runner in persistent mode"
Write-Host "Starting runner after $(((get-date) - (gcim Win32_OperatingSystem).LastBootUpTime).tostring("hh':'mm':'ss''"))"
5 changes: 3 additions & 2 deletions modules/runners/templates/start-runner.sh
Original file line number Diff line number Diff line change
@@ -37,11 +37,11 @@ fi

## Configure the runner

echo "Get GH Runner token from AWS SSM"
echo "Get GH Runner config from AWS SSM"
config=$(aws ssm get-parameters --names "$environment"-"$instance_id" --with-decryption --region "$region" | jq -r ".Parameters | .[0] | .Value")

while [[ -z "$config" ]]; do
echo "Waiting for GH Runner token to become available in AWS SSM"
echo "Waiting for GH Runner config to become available in AWS SSM"
sleep 1
config=$(aws ssm get-parameters --names "$environment"-"$instance_id" --with-decryption --region "$region" | jq -r ".Parameters | .[0] | .Value")
done
@@ -50,6 +50,7 @@ echo "Delete GH Runner token from AWS SSM"
aws ssm delete-parameter --name "$environment"-"$instance_id" --region "$region"

if [ -z "$run_as" ]; then
echo "No user specified, using default ec2-user account"
run_as="ec2-user"
fi

47 changes: 47 additions & 0 deletions modules/runners/templates/user-data.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<powershell>
$ErrorActionPreference = "Continue"
$VerbosePreference = "Continue"
Start-Transcript -Path "C:\UserData.log" -Append

${pre_install}

# Install Chocolatey
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$env:chocolateyUseWindowsCompression = 'true'
Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression

# Add Chocolatey to powershell profile
$ChocoProfileValue = @'
$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
if (Test-Path($ChocolateyProfile)) {
Import-Module "$ChocolateyProfile"
}
refreshenv
'@
# Write it to the $profile location
Set-Content -Path "$PsHome\Microsoft.PowerShell_profile.ps1" -Value $ChocoProfileValue -Force
# Source it
. "$PsHome\Microsoft.PowerShell_profile.ps1"


refreshenv

Write-Host "Installing cloudwatch agent..."
Invoke-WebRequest -Uri https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi -OutFile C:\amazon-cloudwatch-agent.msi
$cloudwatchParams = '/i', 'C:\amazon-cloudwatch-agent.msi', '/qn', '/L*v', 'C:\CloudwatchInstall.log'
Start-Process "msiexec.exe" $cloudwatchParams -Wait -NoNewWindow
Remove-Item C:\amazon-cloudwatch-agent.msi


# Install dependent tools
Write-Host "Installing additional development tools"
choco install git awscli -y
refreshenv

${install_runner}
${post_install}
${start_runner}

Stop-Transcript
</powershell>
49 changes: 16 additions & 33 deletions modules/runners/variables.tf
Original file line number Diff line number Diff line change
@@ -57,25 +57,33 @@ variable "market_options" {
default = "spot"
}

variable "runner_os" {
description = "The EC2 Operating System type to use for action runner instances (linux,win)."
type = string
default = "linux"

validation {
condition = contains(["linux", "win"], var.runner_os)
error_message = "Valid values for runner_os are (linux, win)."
}
}

variable "instance_type" {
description = "[DEPRECATED] See instance_types."
type = string
default = "m5.large"
}

variable "instance_types" {
description = "List of instance types for the action runner."
description = "List of instance types for the action runner. Defaults are based on runner_os (amzn2 for linux and Windows Server Core for win)."
type = list(string)
default = null
}

variable "ami_filter" {
description = "Map of lists used to create the AMI filter for the action runner AMI."
type = map(list(string))

default = {
name = ["amzn2-ami-hvm-2.*-x86_64-ebs"]
}
default = null
}

variable "ami_owners" {
@@ -134,9 +142,9 @@ variable "scale_down_schedule_expression" {
}

variable "minimum_running_time_in_minutes" {
description = "The time an ec2 action runner should be running at minimum before terminated if non busy."
description = "The time an ec2 action runner should be running at minimum before terminated if non busy. If not set the default is calculated based on the OS."
type = number
default = 5
default = null
}

variable "runner_boot_time_in_minutes" {
@@ -285,32 +293,7 @@ variable "runner_log_files" {
file_path = string
log_stream_name = string
}))
default = [
{
"log_group_name" : "messages",
"prefix_log_group" : true,
"file_path" : "/var/log/messages",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "user_data",
"prefix_log_group" : true,
"file_path" : "/var/log/user-data.log",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "runner",
"prefix_log_group" : true,
"file_path" : "/home/ec2-user/actions-runner/_diag/Runner_**.log",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "runner-startup",
"prefix_log_group" : true,
"file_path" : "/var/log/runner-startup.log",
"log_stream_name" : "{instance_id}"
},
]
default = null
}

variable "ghes_url" {
45 changes: 15 additions & 30 deletions variables.tf
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ variable "scale_down_schedule_expression" {
variable "minimum_running_time_in_minutes" {
description = "The time an ec2 action runner should be running at minimum before terminated if not busy."
type = number
default = 5
default = null
}

variable "runner_boot_time_in_minutes" {
@@ -226,8 +226,7 @@ variable "block_device_mappings" {
variable "ami_filter" {
description = "List of maps used to create the AMI filter for the action runner AMI. By default amazon linux 2 is used."
type = map(list(string))

default = {}
default = null
}
variable "ami_owners" {
description = "The list of owners used to select the AMI of action runner instances."
@@ -301,32 +300,7 @@ variable "runner_log_files" {
file_path = string
log_stream_name = string
}))
default = [
{
"log_group_name" : "messages",
"prefix_log_group" : true,
"file_path" : "/var/log/messages",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "user_data",
"prefix_log_group" : true,
"file_path" : "/var/log/user-data.log",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "runner",
"prefix_log_group" : true,
"file_path" : "/home/ec2-user/actions-runner/_diag/Runner_**.log",
"log_stream_name" : "{instance_id}"
},
{
"log_group_name" : "runner-startup",
"prefix_log_group" : true,
"file_path" : "/var/log/runner-startup.log",
"log_stream_name" : "{instance_id}"
},
]
default = null
}

variable "ghes_url" {
@@ -378,7 +352,7 @@ variable "volume_size" {
}

variable "instance_types" {
description = "List of instance types for the action runner."
description = "List of instance types for the action runner. Defaults are based on runner_os (amzn2 for linux and Windows Server Core for win)."
type = list(string)
default = null
}
@@ -479,3 +453,14 @@ variable "runner_metadata_options" {
}

}

variable "runner_os" {
description = "The Operating System to use for GitHub Actions Runners (linux,win)"
type = string
default = "linux"

validation {
condition = contains(["linux", "win"], var.runner_os)
error_message = "Valid values for runner_os are (linux, win)."
}
}