Skip to content
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

fix(syncer): Fix for windows binaries in action runner syncer #1716

Merged
merged 10 commits into from
Feb 10, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,19 @@ jest.mock('aws-sdk', () => ({
}));

const bucketName = 'my-bucket';
const bucketObjectKey = 'actions-runner-linux.tar.gz';
const objectExtension: Record<string, string> = {
linux: '.tar.gz',
win: '.zip',
};
const bucketObjectNames: Record<string, string> = {
linux: `actions-runner-linux${objectExtension['linux']}`,
win: `actions-runner-win${objectExtension['win']}`,
};

const bucketObjectKey = (os: string) => bucketObjectNames[os];

const runnerOs = [['linux'], ['win']];

beforeEach(() => {
jest.clearAllMocks();
});
Expand All @@ -51,43 +63,22 @@ jest.setTimeout(60 * 1000);
describe('Synchronize action distribution.', () => {
beforeEach(() => {
process.env.S3_BUCKET_NAME = bucketName;
process.env.S3_OBJECT_KEY = bucketObjectKey;
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'false';

mockOctokit.repos.listReleases.mockImplementation(() => ({
data: listReleases,
}));
});

it('Distribution is up-to-date with latest release.', async () => {
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-2.285.1.tar.gz' }] });
},
};
});

await sync();
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
});
expect(mockS3.upload).toBeCalledTimes(0);
});

it('Distribution is up-to-date with latest release when there are no prereleases.', async () => {
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
const releases = listReleases.slice(1);

mockOctokit.repos.listReleases.mockImplementation(() => ({
data: releases,
}));
test.each(runnerOs)('%p Distribution is up-to-date with latest release.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-2.285.1.tar.gz' }] });
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-2.285.1${objectExtension[os]}` }],
});
},
};
});
Expand All @@ -96,17 +87,52 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(0);
});

it('Distribution is up-to-date with latest prerelease.', async () => {
test.each(runnerOs)(
'%p Distribution is up-to-date with latest release when there are no prereleases.',
async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
const releases = listReleases.slice(1);

mockOctokit.repos.listReleases.mockImplementation(() => ({
data: releases,
}));
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-2.285.1${objectExtension[os]}` }],
});
},
};
});

await sync();
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(0);
},
);

test.each(runnerOs)('%p Distribution is up-to-date with latest prerelease.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-2.286.0.tar.gz' }] });
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-2.286.0${objectExtension[os]}` }],
});
},
};
});
Expand All @@ -115,16 +141,20 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(0);
});

it('Distribution should update to release.', async () => {
test.each(runnerOs)('%p Distribution should update to release.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-0.tar.gz' }] });
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
});
},
};
});
Expand All @@ -133,14 +163,16 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(1);
const s3JsonBody = mockS3.upload.mock.calls[0][0];
expect(s3JsonBody['Tagging']).toEqual('name=actions-runner-linux-x64-2.285.1.tar.gz');
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-2.285.1${objectExtension[os]}`);
});

it('Distribution should update to release if there are no pre-releases.', async () => {
test.each(runnerOs)('%p Distribution should update to release if there are no pre-releases.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
const releases = listReleases.slice(1);

Expand All @@ -150,7 +182,9 @@ describe('Synchronize action distribution.', () => {
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-0.tar.gz' }] });
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
});
},
};
});
Expand All @@ -159,19 +193,23 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(1);
const s3JsonBody = mockS3.upload.mock.calls[0][0];
expect(s3JsonBody['Tagging']).toEqual('name=actions-runner-linux-x64-2.285.1.tar.gz');
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-2.285.1${objectExtension[os]}`);
});

it('Distribution should update to prerelease.', async () => {
test.each(runnerOs)('%p Distribution should update to prerelease.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-0.tar.gz' }] });
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
});
},
};
});
Expand All @@ -180,14 +218,16 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(1);
const s3JsonBody = mockS3.upload.mock.calls[0][0];
expect(s3JsonBody['Tagging']).toEqual('name=actions-runner-linux-x64-2.286.0.tar.gz');
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-2.286.0${objectExtension[os]}`);
});

it('Distribution should not update to prerelease if there is a newer release.', async () => {
test.each(runnerOs)('%p Distribution should not update to prerelease if there is a newer release.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
const releases = listReleases;
releases[0].prerelease = false;
Expand All @@ -199,7 +239,9 @@ describe('Synchronize action distribution.', () => {
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ TagSet: [{ Key: 'name', Value: 'actions-runner-linux-x64-0.tar.gz' }] });
return Promise.resolve({
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
});
},
};
});
Expand All @@ -208,14 +250,16 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(1);
const s3JsonBody = mockS3.upload.mock.calls[0][0];
expect(s3JsonBody['Tagging']).toEqual('name=actions-runner-linux-x64-2.286.0.tar.gz');
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-2.286.0${objectExtension[os]}`);
});

it('No tag in S3, distribution should update.', async () => {
test.each(runnerOs)('%p No tag in S3, distribution should update.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
Expand All @@ -228,12 +272,14 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(1);
});

it('Tags, but no version, distribution should update.', async () => {
test.each(runnerOs)('%p Tags, but no version, distribution should update.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
mockS3.getObjectTagging.mockImplementation(() => {
return {
promise() {
Expand All @@ -246,7 +292,7 @@ describe('Synchronize action distribution.', () => {
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
expect(mockS3.getObjectTagging).toBeCalledWith({
Bucket: bucketName,
Key: bucketObjectKey,
Key: bucketObjectKey(os),
});
expect(mockS3.upload).toBeCalledTimes(1);
});
Expand All @@ -256,7 +302,7 @@ describe('No release assets found.', () => {
const errorMessage = 'Cannot find GitHub release asset.';
beforeEach(() => {
process.env.S3_BUCKET_NAME = bucketName;
process.env.S3_OBJECT_KEY = bucketObjectKey;
process.env.S3_OBJECT_KEY = bucketObjectKey('linux');
});

it('Empty list of assets.', async () => {
Expand All @@ -267,7 +313,9 @@ describe('No release assets found.', () => {
await expect(sync()).rejects.toThrow(errorMessage);
});

it('No linux x64 asset.', async () => {
test.each(runnerOs)('No %p x64 asset.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
mockOctokit.repos.listReleases.mockImplementation(() => ({
data: [listReleasesNoLinux],
}));
Expand All @@ -293,7 +341,7 @@ describe('Invalid config', () => {
});
it('No bucket.', async () => {
delete process.env.S3_BUCKET_NAME;
process.env.S3_OBJECT_KEY = bucketObjectKey;
process.env.S3_OBJECT_KEY = bucketObjectKey('linux');
await expect(sync()).rejects.toThrow(errorMessage);
});
it('No object key.', async () => {
Expand All @@ -307,35 +355,16 @@ describe('Synchronize action distribution for arm64.', () => {
const errorMessage = 'Cannot find GitHub release asset.';
beforeEach(() => {
process.env.S3_BUCKET_NAME = bucketName;
process.env.S3_OBJECT_KEY = bucketObjectKey;
process.env.GITHUB_RUNNER_ARCHITECTURE = 'arm64';
});

it('No linux arm64 asset.', async () => {
test.each(runnerOs)('No %p arm64 asset.', async (os) => {
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
process.env.GITHUB_RUNNER_OS = os;
mockOctokit.repos.listReleases.mockImplementation(() => ({
data: [listReleasesNoArm64],
}));

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);
});
});
6 changes: 5 additions & 1 deletion modules/runner-binaries-syncer/runner-binaries-syncer.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
locals {
lambda_zip = var.lambda_zip == null ? "${path.module}/lambdas/runner-binaries-syncer/runner-binaries-syncer.zip" : var.lambda_zip
role_path = var.role_path == null ? "/${var.environment}/" : var.role_path
gh_binary_os_label = {
windows = "win",
linux = "linux"
}
}

resource "aws_lambda_function" "syncer" {
Expand All @@ -20,7 +24,7 @@ resource "aws_lambda_function" "syncer" {
variables = {
GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = var.runner_allow_prerelease_binaries
GITHUB_RUNNER_ARCHITECTURE = var.runner_architecture
GITHUB_RUNNER_OS = var.runner_os
GITHUB_RUNNER_OS = local.gh_binary_os_label[var.runner_os]
LOG_LEVEL = var.log_level
LOG_TYPE = var.log_type
S3_BUCKET_NAME = aws_s3_bucket.action_dist.id
Expand Down
7 changes: 6 additions & 1 deletion modules/runner-binaries-syncer/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,14 @@ variable "role_path" {
}

variable "runner_os" {
description = "The operating system for the runner instance (linux, win), defaults to 'linux'"
description = "The Operating System to use for GitHub Actions Runners (linux,win)"
ScottGuymer marked this conversation as resolved.
Show resolved Hide resolved
type = string
default = "linux"

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

variable "runner_architecture" {
Expand Down