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

Commit ac158f6

Browse files
authored
fix(syncer): replaced aws-sdk v2 by aws-sdk v3 (#3075)
* fix: update aws sdk v3 * increase coverage thresholds * remove tslog
1 parent 418c74b commit ac158f6

File tree

5 files changed

+1364
-299
lines changed

5 files changed

+1364
-299
lines changed

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ module.exports = {
55
collectCoverageFrom: ['src/**/*.{ts,js,jsx}', '!src/**/*local*.ts', '!src/**/*.d.ts'],
66
coverageThreshold: {
77
global: {
8-
branches: 85,
9-
functions: 78,
10-
lines: 93,
11-
statements: 93
8+
branches: 87,
9+
functions: 92,
10+
lines: 98,
11+
statements: 98
1212
}
1313
}
1414
};

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"@typescript-eslint/eslint-plugin": "^5.54.1",
2727
"@typescript-eslint/parser": "^5.54.1",
2828
"@vercel/ncc": "^0.36.1",
29+
"aws-sdk-client-mock": "^2.1.1",
30+
"aws-sdk-client-mock-jest": "^2.1.1",
2931
"eslint": "^8.35.0",
3032
"eslint-plugin-prettier": "4.2.1",
3133
"jest": "^29.5",
@@ -37,8 +39,8 @@
3739
},
3840
"dependencies": {
3941
"@aws-lambda-powertools/logger": "^1.6.0",
40-
"aws-sdk": "^2.1312.0",
41-
"axios": "^1.3.4",
42-
"tslog": "^3.3.4"
42+
"@aws-sdk/client-s3": "^3.294.0",
43+
"@aws-sdk/lib-storage": "^3.294.0",
44+
"axios": "^1.3.4"
4345
}
4446
}

Diff for: modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/src/syncer/syncer.test.ts

+57-53
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { GetObjectTaggingCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
2+
import { mockClient } from 'aws-sdk-client-mock';
3+
import 'aws-sdk-client-mock-jest';
14
import axios from 'axios';
25
import { PassThrough } from 'stream';
36

@@ -21,20 +24,13 @@ mockStream.push(mockResponse);
2124
mockStream.end();
2225

2326
jest.mock('axios');
24-
const mockedAxios = axios as jest.Mocked<typeof axios>;
25-
mockedAxios.request.mockResolvedValue({
27+
const mockAxios = axios as jest.Mocked<typeof axios>;
28+
mockAxios.get.mockResolvedValue({
2629
data: mockStream,
2730
});
2831

29-
const mockS3 = {
30-
getObjectTagging: jest.fn(),
31-
upload: jest.fn().mockImplementation(() => {
32-
return { promise: jest.fn(() => Promise.resolve()) };
33-
}),
34-
};
35-
jest.mock('aws-sdk', () => ({
36-
S3: jest.fn().mockImplementation(() => mockS3),
37-
}));
32+
process.env.AWS_REGION = 'us-east-1';
33+
const mockS3client = mockClient(S3Client);
3834

3935
const bucketName = 'my-bucket';
4036
const objectExtension: Record<string, string> = {
@@ -54,14 +50,14 @@ const latestRelease = '2.296.2';
5450

5551
beforeEach(() => {
5652
jest.clearAllMocks();
53+
mockS3client.reset();
5754
});
5855

5956
jest.setTimeout(60 * 1000);
6057

6158
describe('Synchronize action distribution (no S3 tags).', () => {
6259
beforeEach(() => {
6360
process.env.S3_BUCKET_NAME = bucketName;
64-
6561
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
6662
data: mockDataLatestRelease,
6763
}));
@@ -70,25 +66,19 @@ describe('Synchronize action distribution (no S3 tags).', () => {
7066
test.each(runnerOs)('%p Distribution is S3 has no tags.', async (os) => {
7167
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
7268
process.env.GITHUB_RUNNER_OS = os;
73-
mockS3.getObjectTagging.mockImplementation(() => {
74-
return {
75-
promise() {
76-
return Promise.resolve({
77-
TagSet: undefined,
78-
});
79-
},
80-
};
69+
mockS3client.on(GetObjectTaggingCommand).resolves({
70+
TagSet: undefined,
8171
});
8272

8373
await sync();
84-
expect(mockS3.upload).toBeCalledTimes(1);
74+
expect(mockS3client).toHaveReceivedCommandTimes(PutObjectCommand, 1);
8575
});
8676
});
8777

88-
describe('Synchronize action distribution (up-to-date).', () => {
78+
describe('Synchronize action distribution.', () => {
8979
beforeEach(() => {
9080
process.env.S3_BUCKET_NAME = bucketName;
91-
81+
mockS3client.reset();
9282
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
9383
data: mockDataLatestRelease,
9484
}));
@@ -97,67 +87,81 @@ describe('Synchronize action distribution (up-to-date).', () => {
9787
test.each(runnerOs)('%p Distribution is up-to-date with latest release.', async (os) => {
9888
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
9989
process.env.GITHUB_RUNNER_OS = os;
100-
mockS3.getObjectTagging.mockImplementation(() => {
101-
return {
102-
promise() {
103-
return Promise.resolve({
104-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}` }],
105-
});
106-
},
107-
};
90+
mockS3client.on(GetObjectTaggingCommand).resolves({
91+
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}` }],
10892
});
10993

11094
await sync();
11195
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
112-
expect(mockS3.getObjectTagging).toBeCalledWith({
96+
expect(mockS3client).toHaveReceivedNthCommandWith(1, GetObjectTaggingCommand, {
11397
Bucket: bucketName,
11498
Key: bucketObjectKey(os),
11599
});
116-
expect(mockS3.upload).toBeCalledTimes(0);
100+
101+
expect(mockS3client).toHaveReceivedCommandTimes(PutObjectCommand, 0);
117102
});
118103

119104
test.each(runnerOs)('%p Distribution should update to release.', async (os) => {
120105
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
121106
process.env.GITHUB_RUNNER_OS = os;
122-
mockS3.getObjectTagging.mockImplementation(() => {
123-
return {
124-
promise() {
125-
return Promise.resolve({
126-
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
127-
});
128-
},
129-
};
107+
108+
mockS3client.on(GetObjectTaggingCommand).resolves({
109+
TagSet: [{ Key: 'name', Value: `actions-runner-${os}-x64-0${objectExtension[os]}` }],
130110
});
131111

132112
await sync();
133113
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
134-
expect(mockS3.getObjectTagging).toBeCalledWith({
114+
expect(mockS3client).toHaveReceivedNthCommandWith(1, GetObjectTaggingCommand, {
135115
Bucket: bucketName,
136116
Key: bucketObjectKey(os),
137117
});
138-
expect(mockS3.upload).toBeCalledTimes(1);
139-
const s3JsonBody = mockS3.upload.mock.calls[0][0];
140-
expect(s3JsonBody['Tagging']).toEqual(`name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`);
118+
119+
expect(mockS3client).toHaveReceivedNthSpecificCommandWith(1, PutObjectCommand, {
120+
Bucket: bucketName,
121+
Key: bucketObjectKey(os),
122+
Tagging: `name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`,
123+
});
124+
});
125+
126+
test.each(runnerOs)('%p Distribution should update to release (tags look-up errored)', async (os) => {
127+
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
128+
process.env.GITHUB_RUNNER_OS = os;
129+
130+
mockS3client.on(GetObjectTaggingCommand).rejects(new Error('No tags'));
131+
132+
await sync();
133+
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
134+
expect(mockS3client).toHaveReceivedNthCommandWith(1, GetObjectTaggingCommand, {
135+
Bucket: bucketName,
136+
Key: bucketObjectKey(os),
137+
});
138+
139+
expect(mockS3client).toHaveReceivedNthSpecificCommandWith(1, PutObjectCommand, {
140+
Bucket: bucketName,
141+
Key: bucketObjectKey(os),
142+
Tagging: `name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`,
143+
});
141144
});
142145

143146
test.each(runnerOs)('%p Tags, but no version, distribution should update.', async (os) => {
144147
process.env.S3_OBJECT_KEY = bucketObjectKey(os);
145148
process.env.GITHUB_RUNNER_OS = os;
146-
mockS3.getObjectTagging.mockImplementation(() => {
147-
return {
148-
promise() {
149-
return Promise.resolve({ TagSet: [{ Key: 'someKey', Value: 'someValue' }] });
150-
},
151-
};
149+
mockS3client.on(GetObjectTaggingCommand).resolves({
150+
TagSet: [{ Key: 'someKey', Value: `someValue` }],
152151
});
153152

154153
await sync();
155154
expect(mockOctokit.repos.getLatestRelease).toBeCalledTimes(1);
156-
expect(mockS3.getObjectTagging).toBeCalledWith({
155+
expect(mockS3client).toHaveReceivedNthCommandWith(1, GetObjectTaggingCommand, {
156+
Bucket: bucketName,
157+
Key: bucketObjectKey(os),
158+
});
159+
160+
expect(mockS3client).toHaveReceivedNthSpecificCommandWith(1, PutObjectCommand, {
157161
Bucket: bucketName,
158162
Key: bucketObjectKey(os),
163+
Tagging: `name=actions-runner-${os}-x64-${latestRelease}${objectExtension[os]}`,
159164
});
160-
expect(mockS3.upload).toBeCalledTimes(1);
161165
});
162166
});
163167

Diff for: modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/src/syncer/syncer.ts

+36-45
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { GetObjectTaggingCommand, S3Client, Tag } from '@aws-sdk/client-s3';
2+
import { Upload } from '@aws-sdk/lib-storage';
13
import { Octokit } from '@octokit/rest';
2-
import { S3 } from 'aws-sdk';
3-
import AWS from 'aws-sdk';
44
import axios from 'axios';
5-
import { PassThrough } from 'stream';
5+
import { Stream } from 'stream';
66

77
import { createChildLogger } from '../logger';
88

@@ -15,16 +15,16 @@ interface CacheObject {
1515
key: string;
1616
}
1717

18-
async function getCachedVersion(s3: S3, cacheObject: CacheObject): Promise<string | undefined> {
18+
async function getCachedVersion(s3Client: S3Client, cacheObject: CacheObject): Promise<string | undefined> {
19+
const command = new GetObjectTaggingCommand({
20+
Bucket: cacheObject.bucket,
21+
Key: cacheObject.key,
22+
});
23+
1924
try {
20-
const objectTagging = await s3
21-
.getObjectTagging({
22-
Bucket: cacheObject.bucket,
23-
Key: cacheObject.key,
24-
})
25-
.promise();
26-
const versions = objectTagging.TagSet?.filter((t: S3.Tag) => t.Key === versionKey);
27-
return versions.length === 1 ? versions[0].Value : undefined;
25+
const objectTagging = await s3Client.send(command);
26+
const versions = objectTagging.TagSet?.filter((t: Tag) => t.Key === versionKey);
27+
return versions?.length === 1 ? versions[0].Value : undefined;
2828
} catch (e) {
2929
logger.debug('No tags found');
3030
return undefined;
@@ -53,48 +53,39 @@ async function getReleaseAsset(runnerOs = 'linux', runnerArch = 'x64'): Promise<
5353
return assets?.length === 1 ? { name: assets[0].name, downloadUrl: assets[0].browser_download_url } : undefined;
5454
}
5555

56-
async function uploadToS3(s3: S3, cacheObject: CacheObject, actionRunnerReleaseAsset: ReleaseAsset): Promise<void> {
57-
const writeStream = new PassThrough();
58-
const writePromise = s3
59-
.upload({
56+
async function uploadToS3(
57+
s3Client: S3Client,
58+
cacheObject: CacheObject,
59+
actionRunnerReleaseAsset: ReleaseAsset,
60+
): Promise<void> {
61+
const response = await axios.get(actionRunnerReleaseAsset.downloadUrl, {
62+
responseType: 'stream',
63+
});
64+
65+
const passThrough = new Stream.PassThrough();
66+
response.data.pipe(passThrough);
67+
68+
const upload = new Upload({
69+
client: s3Client,
70+
params: {
6071
Bucket: cacheObject.bucket,
6172
Key: cacheObject.key,
6273
Tagging: versionKey + '=' + actionRunnerReleaseAsset.name,
63-
Body: writeStream,
74+
Body: passThrough,
6475
ServerSideEncryption: process.env.S3_SSE_ALGORITHM,
65-
SSEKMSKeyId: process.env.S3_SSE_KMS_KEY_ID,
66-
})
67-
.promise();
68-
69-
logger.debug(`Start downloading ${actionRunnerReleaseAsset.name} and uploading to S3.`);
70-
71-
const readPromise = new Promise<void>((resolve, reject) => {
72-
axios
73-
.request<NodeJS.ReadableStream>({
74-
method: 'get',
75-
url: actionRunnerReleaseAsset.downloadUrl,
76-
responseType: 'stream',
77-
})
78-
.then((res) => {
79-
res.data
80-
.pipe(writeStream)
81-
82-
.on('finish', () => resolve())
83-
.on('error', (error) => reject(error));
84-
})
85-
.catch((error) => reject(error));
76+
},
8677
});
8778

88-
await Promise.all([readPromise, writePromise])
89-
.then(() => logger.info(`The new distribution is uploaded to S3.`))
90-
.catch((error) => {
91-
logger.error('Uploading of the new distribution to S3 failed.', error);
92-
throw error;
93-
});
79+
upload.on('httpUploadProgress', () => logger.debug(`Downloading ${actionRunnerReleaseAsset.name} in progress`));
80+
logger.debug(`Start downloading ${actionRunnerReleaseAsset.name} and uploading to S3.`);
81+
await upload
82+
.done()
83+
.then(() => logger.info(`The new distribution ${actionRunnerReleaseAsset.name} is uploaded to S3.`))
84+
.catch((e) => logger.error(`Error uploading ${actionRunnerReleaseAsset.name} to S3`, e));
9485
}
9586

9687
export async function sync(): Promise<void> {
97-
const s3 = new AWS.S3();
88+
const s3 = new S3Client({});
9889

9990
const runnerOs = process.env.GITHUB_RUNNER_OS || 'linux';
10091
const runnerArch = process.env.GITHUB_RUNNER_ARCHITECTURE || 'x64';

0 commit comments

Comments
 (0)