Skip to content
This repository was archived by the owner on Jan 16, 2025. It is now read-only.

Commit e75e092

Browse files
authored
feat: Download runner release via latest release API (#2455)
* test * feat: Download latest release via latest release API - Use release API instead of listing releases - Drop support draft release * revert examples
1 parent 87bad10 commit e75e092

18 files changed

+1878
-10171
lines changed

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ In case the setup does not work as intended follow the trace of events:
448448
| <a name="input_role_path"></a> [role\_path](#input\_role\_path) | The path that will be added to role path for created roles, if not set the environment name will be used. | `string` | `null` | no |
449449
| <a name="input_role_permissions_boundary"></a> [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | Permissions boundary that will be added to the created roles. | `string` | `null` | no |
450450
| <a name="input_runner_additional_security_group_ids"></a> [runner\_additional\_security\_group\_ids](#input\_runner\_additional\_security\_group\_ids) | (optional) List of additional security groups IDs to apply to the runner | `list(string)` | `[]` | no |
451-
| <a name="input_runner_allow_prerelease_binaries"></a> [runner\_allow\_prerelease\_binaries](#input\_runner\_allow\_prerelease\_binaries) | Allow the runners to update to prerelease binaries. | `bool` | `false` | no |
451+
| <a name="input_runner_allow_prerelease_binaries"></a> [runner\_allow\_prerelease\_binaries](#input\_runner\_allow\_prerelease\_binaries) | (Deprecated, no longer used), allow the runners to update to prerelease binaries. | `bool` | `null` | no |
452452
| <a name="input_runner_architecture"></a> [runner\_architecture](#input\_runner\_architecture) | The platform architecture of the runner instance\_type. | `string` | `"x64"` | no |
453453
| <a name="input_runner_as_root"></a> [runner\_as\_root](#input\_runner\_as\_root) | Run the action runner under the root user. Variable `runner_run_as` will be ignored. | `bool` | `false` | no |
454454
| <a name="input_runner_binaries_s3_sse_configuration"></a> [runner\_binaries\_s3\_sse\_configuration](#input\_runner\_binaries\_s3\_sse\_configuration) | Map containing server-side encryption configuration for runner-binaries S3 bucket. | `any` | `{}` | no |

Diff for: examples/ubuntu/main.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ module "runners" {
9393
# Uncomment to enable ephemeral runners
9494
# delay_webhook_event = 0
9595
# enable_ephemeral_runners = true
96-
# enabled_userdata = false
96+
# enabled_userdata = true
9797

9898
# Uncommet idle config to have idle runners from 9 to 5 in time zone Amsterdam
9999
# idle_config = [{

Diff for: modules/runner-binaries-syncer/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ No modules.
100100
| <a name="input_prefix"></a> [prefix](#input\_prefix) | The prefix used for naming resources | `string` | `"github-actions"` | no |
101101
| <a name="input_role_path"></a> [role\_path](#input\_role\_path) | The path that will be added to the role, if not set the environment name will be used. | `string` | `null` | no |
102102
| <a name="input_role_permissions_boundary"></a> [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | Permissions boundary that will be added to the created role for the lambda. | `string` | `null` | no |
103-
| <a name="input_runner_allow_prerelease_binaries"></a> [runner\_allow\_prerelease\_binaries](#input\_runner\_allow\_prerelease\_binaries) | Allow the runners to update to prerelease binaries. | `bool` | `false` | no |
103+
| <a name="input_runner_allow_prerelease_binaries"></a> [runner\_allow\_prerelease\_binaries](#input\_runner\_allow\_prerelease\_binaries) | (Deprecated, no longer used), allow the runners to update to prerelease binaries. | `bool` | `null` | no |
104104
| <a name="input_runner_architecture"></a> [runner\_architecture](#input\_runner\_architecture) | The platform architecture of the runner instance\_type. | `string` | `"x64"` | no |
105105
| <a name="input_runner_os"></a> [runner\_os](#input\_runner\_os) | The EC2 Operating System type to use for action runner instances (linux,windows). | `string` | `"linux"` | no |
106106
| <a name="input_server_side_encryption_configuration"></a> [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no |

Diff for: modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/jest.config.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ module.exports = {
22
preset: 'ts-jest',
33
testEnvironment: 'node',
44
collectCoverage: true,
5-
collectCoverageFrom: ['src/**/*.{ts,js,jsx}', '!src/**/*local*.ts'],
5+
collectCoverageFrom: ['src/**/*.{ts,js,jsx}', '!src/**/*local*.ts', '!src/**/*.d.ts'],
66
coverageThreshold: {
77
global: {
8-
branches: 80,
9-
functions: 80,
10-
lines: 80,
11-
statements: 80
8+
branches: 85,
9+
functions: 78,
10+
lines: 93,
11+
statements: 93
1212
}
1313
}
1414
};

Diff for: modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/response.json

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { S3 } from 'aws-sdk';
21
import axios from 'axios';
3-
import { request } from 'http';
4-
import { EventEmitter, PassThrough, Readable } from 'stream';
2+
import { PassThrough } from 'stream';
53

6-
import listReleasesEmpty from '../../test/resources/github-list-releases-empty-assets.json';
7-
import listReleasesNoArm64 from '../../test/resources/github-list-releases-no-arm64.json';
8-
import listReleasesNoLinux from '../../test/resources/github-list-releases-no-linux.json';
9-
import listReleases from '../../test/resources/github-list-releases.json';
4+
import mockDataLatestRelease from '../../test/resources/github-latest-release.json';
5+
import noX64Assets from '../../test/resources/github-releases-no-x64.json';
106
import { sync } from './syncer';
117

128
const mockOctokit = {
139
repos: {
14-
listReleases: jest.fn(),
10+
getLatestRelease: jest.fn(),
1511
},
1612
};
1713
jest.mock('@octokit/rest', () => ({
@@ -54,191 +50,75 @@ const bucketObjectKey = (os: string) => bucketObjectNames[os];
5450

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

57-
const latestRelease = '2.287.0';
58-
const latestPreRelease = '2.287.1';
53+
const latestRelease = '2.296.2';
5954

6055
beforeEach(() => {
6156
jest.clearAllMocks();
6257
});
6358

6459
jest.setTimeout(60 * 1000);
6560

66-
describe('Synchronize action distribution.', () => {
61+
describe('Synchronize action distribution (no S3 tags).', () => {
6762
beforeEach(() => {
6863
process.env.S3_BUCKET_NAME = bucketName;
69-
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'false';
7064

71-
mockOctokit.repos.listReleases.mockImplementation(() => ({
72-
data: listReleases,
65+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
66+
data: mockDataLatestRelease,
7367
}));
7468
});
7569

76-
test.each(runnerOs)('%p Distribution is up-to-date with latest release.', async (os) => {
70+
test.each(runnerOs)('%p Distribution is S3 has no tags.', async (os) => {
7771
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
7872
process.env.GITHUB_RUNNER_OS = os;
7973
mockS3.getObjectTagging.mockImplementation(() => {
8074
return {
8175
promise() {
8276
return Promise.resolve({
83-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}` }],
77+
TagSet: undefined,
8478
});
8579
},
8680
};
8781
});
8882

8983
await sync();
90-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
91-
expect(mockS3.getObjectTagging).toBeCalledWith({
92-
Bucket: bucketName,
93-
Key: bucketObjectKey(os),
94-
});
95-
expect(mockS3.upload).toBeCalledTimes(0);
96-
});
97-
98-
test.each(runnerOs)(
99-
'%p Distribution is up-to-date with latest release when there are no prereleases.',
100-
async (os) => {
101-
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
102-
process.env.GITHUB_RUNNER_OS = os;
103-
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
104-
const releases = listReleases.slice(1);
105-
106-
mockOctokit.repos.listReleases.mockImplementation(() => ({
107-
data: releases,
108-
}));
109-
mockS3.getObjectTagging.mockImplementation(() => {
110-
return {
111-
promise() {
112-
return Promise.resolve({
113-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}` }],
114-
});
115-
},
116-
};
117-
});
118-
119-
await sync();
120-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
121-
expect(mockS3.getObjectTagging).toBeCalledWith({
122-
Bucket: bucketName,
123-
Key: bucketObjectKey(os),
124-
});
125-
expect(mockS3.upload).toBeCalledTimes(0);
126-
},
127-
);
128-
129-
test.each(runnerOs)('%p Distribution is up-to-date with latest prerelease.', async (os) => {
130-
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
131-
process.env.GITHUB_RUNNER_OS = os;
132-
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
133-
mockS3.getObjectTagging.mockImplementation(() => {
134-
return {
135-
promise() {
136-
return Promise.resolve({
137-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-${latestPreRelease}${objectExtension[os]}` }],
138-
});
139-
},
140-
};
141-
});
142-
143-
await sync();
144-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
145-
expect(mockS3.getObjectTagging).toBeCalledWith({
146-
Bucket: bucketName,
147-
Key: bucketObjectKey(os),
148-
});
149-
expect(mockS3.upload).toBeCalledTimes(0);
150-
});
151-
152-
test.each(runnerOs)('%p Distribution should update to release.', async (os) => {
153-
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
154-
process.env.GITHUB_RUNNER_OS = os;
155-
mockS3.getObjectTagging.mockImplementation(() => {
156-
return {
157-
promise() {
158-
return Promise.resolve({
159-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
160-
});
161-
},
162-
};
163-
});
164-
165-
await sync();
166-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
167-
expect(mockS3.getObjectTagging).toBeCalledWith({
168-
Bucket: bucketName,
169-
Key: bucketObjectKey(os),
170-
});
17184
expect(mockS3.upload).toBeCalledTimes(1);
172-
const s3JsonBody = mockS3.upload.mock.calls[0][0];
173-
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`);
17485
});
86+
});
17587

176-
test.each(runnerOs)('%p Distribution should update to release if there are no pre-releases.', async (os) => {
177-
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
178-
process.env.GITHUB_RUNNER_OS = os;
179-
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
180-
const releases = listReleases.slice(1);
88+
describe('Synchronize action distribution (up-to-date).', () => {
89+
beforeEach(() => {
90+
process.env.S3_BUCKET_NAME = bucketName;
18191

182-
mockOctokit.repos.listReleases.mockImplementation(() => ({
183-
data: releases,
92+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
93+
data: mockDataLatestRelease,
18494
}));
185-
mockS3.getObjectTagging.mockImplementation(() => {
186-
return {
187-
promise() {
188-
return Promise.resolve({
189-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
190-
});
191-
},
192-
};
193-
});
194-
195-
await sync();
196-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
197-
expect(mockS3.getObjectTagging).toBeCalledWith({
198-
Bucket: bucketName,
199-
Key: bucketObjectKey(os),
200-
});
201-
expect(mockS3.upload).toBeCalledTimes(1);
202-
const s3JsonBody = mockS3.upload.mock.calls[0][0];
203-
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`);
20495
});
20596

206-
test.each(runnerOs)('%p Distribution should update to prerelease.', async (os) => {
97+
test.each(runnerOs)('%p Distribution is up-to-date with latest release.', async (os) => {
20798
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
20899
process.env.GITHUB_RUNNER_OS = os;
209-
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
210100
mockS3.getObjectTagging.mockImplementation(() => {
211101
return {
212102
promise() {
213103
return Promise.resolve({
214-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
104+
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}` }],
215105
});
216106
},
217107
};
218108
});
219109

220110
await sync();
221-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
111+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
222112
expect(mockS3.getObjectTagging).toBeCalledWith({
223113
Bucket: bucketName,
224114
Key: bucketObjectKey(os),
225115
});
226-
expect(mockS3.upload).toBeCalledTimes(1);
227-
const s3JsonBody = mockS3.upload.mock.calls[0][0];
228-
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-${latestPreRelease}${objectExtension[os]}`);
116+
expect(mockS3.upload).toBeCalledTimes(0);
229117
});
230118

231-
test.each(runnerOs)('%p Distribution should not update to prerelease if there is a newer release.', async (os) => {
119+
test.each(runnerOs)('%p Distribution should update to release.', async (os) => {
232120
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
233121
process.env.GITHUB_RUNNER_OS = os;
234-
process.env.GITHUB_RUNNER_ALLOW_PRERELEASE_BINARIES = 'true';
235-
const releases = listReleases;
236-
releases[0].prerelease = false;
237-
releases[1].prerelease = true;
238-
239-
mockOctokit.repos.listReleases.mockImplementation(() => ({
240-
data: releases,
241-
}));
242122
mockS3.getObjectTagging.mockImplementation(() => {
243123
return {
244124
promise() {
@@ -250,34 +130,14 @@ describe('Synchronize action distribution.', () => {
250130
});
251131

252132
await sync();
253-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
133+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
254134
expect(mockS3.getObjectTagging).toBeCalledWith({
255135
Bucket: bucketName,
256136
Key: bucketObjectKey(os),
257137
});
258138
expect(mockS3.upload).toBeCalledTimes(1);
259139
const s3JsonBody = mockS3.upload.mock.calls[0][0];
260-
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-${latestPreRelease}${objectExtension[os]}`);
261-
});
262-
263-
test.each(runnerOs)('%p No tag in S3, distribution should update.', async (os) => {
264-
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
265-
process.env.GITHUB_RUNNER_OS = os;
266-
mockS3.getObjectTagging.mockImplementation(() => {
267-
return {
268-
promise() {
269-
throw new Error();
270-
},
271-
};
272-
});
273-
274-
await sync();
275-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
276-
expect(mockS3.getObjectTagging).toBeCalledWith({
277-
Bucket: bucketName,
278-
Key: bucketObjectKey(os),
279-
});
280-
expect(mockS3.upload).toBeCalledTimes(1);
140+
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`);
281141
});
282142

283143
test.each(runnerOs)('%p Tags, but no version, distribution should update.', async (os) => {
@@ -292,7 +152,7 @@ describe('Synchronize action distribution.', () => {
292152
});
293153

294154
await sync();
295-
expect(mockOctokit.repos.listReleases).toBeCalledTimes(1);
155+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
296156
expect(mockS3.getObjectTagging).toBeCalledWith({
297157
Bucket: bucketName,
298158
Key: bucketObjectKey(os),
@@ -308,9 +168,9 @@ describe('No release assets found.', () => {
308168
process.env.S3_OBJECT_KEY = bucketObjectKey('linux');
309169
});
310170

311-
it('Empty list of assets.', async () => {
312-
mockOctokit.repos.listReleases.mockImplementation(() => ({
313-
data: listReleasesEmpty,
171+
test('Empty result.', async () => {
172+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
173+
data: undefined,
314174
}));
315175

316176
await expect(sync()).rejects.toThrow(errorMessage);
@@ -319,55 +179,30 @@ describe('No release assets found.', () => {
319179
test.each(runnerOs)('No %p x64 asset.', async (os) => {
320180
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
321181
process.env.GITHUB_RUNNER_OS = os;
322-
mockOctokit.repos.listReleases.mockImplementation(() => ({
323-
data: [listReleasesNoLinux],
182+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
183+
data: noX64Assets,
324184
}));
325-
326-
await expect(sync()).rejects.toThrow(errorMessage);
327-
});
328-
329-
it('Empty asset list.', async () => {
330-
mockOctokit.repos.listReleases.mockImplementation(() => ({
331-
data: [],
332-
}));
333-
334185
await expect(sync()).rejects.toThrow(errorMessage);
335186
});
336187
});
337188

338189
describe('Invalid config', () => {
339190
const errorMessage = 'Please check all mandatory variables are set.';
340-
it('No bucket and object key.', async () => {
191+
test('No bucket and object key.', async () => {
341192
delete process.env.S3_OBJECT_KEY;
342193
delete process.env.S3_BUCKET_NAME;
343194
await expect(sync()).rejects.toThrow(errorMessage);
344195
});
345-
it('No bucket.', async () => {
196+
197+
test('No bucket.', async () => {
346198
delete process.env.S3_BUCKET_NAME;
347199
process.env.S3_OBJECT_KEY = bucketObjectKey('linux');
348200
await expect(sync()).rejects.toThrow(errorMessage);
349201
});
350-
it('No object key.', async () => {
351-
delete process.env.S3_OBJECT_KEY;
352-
process.env.S3_BUCKET_NAME = bucketName;
353-
await expect(sync()).rejects.toThrow(errorMessage);
354-
});
355-
});
356202

357-
describe('Synchronize action distribution for arm64.', () => {
358-
const errorMessage = 'Cannot find GitHub release asset.';
359-
beforeEach(() => {
203+
test('No object key.', async () => {
204+
delete process.env.S3_OBJECT_KEY;
360205
process.env.S3_BUCKET_NAME = bucketName;
361-
process.env.GITHUB_RUNNER_ARCHITECTURE = 'arm64';
362-
});
363-
364-
test.each(runnerOs)('No %p arm64 asset.', async (os) => {
365-
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
366-
process.env.GITHUB_RUNNER_OS = os;
367-
mockOctokit.repos.listReleases.mockImplementation(() => ({
368-
data: [listReleasesNoArm64],
369-
}));
370-
371206
await expect(sync()).rejects.toThrow(errorMessage);
372207
});
373208
});

0 commit comments

Comments
 (0)