Skip to content

Commit 7fc6d5b

Browse files
authored
Merge pull request #4 from rchougule/self_hosted_fixes
Self hosted runner fixes and retries for starting binary
2 parents ec3ffe4 + 791ae32 commit 7fc6d5b

File tree

7 files changed

+294
-78
lines changed

7 files changed

+294
-78
lines changed

setup-local/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = {
1919
'prefer-destructuring': ['error', { object: true, array: false }],
2020
'no-restricted-syntax': 'off',
2121
'linebreak-style': 'off',
22+
'no-plusplus': 'off',
2223
},
2324
ignorePatterns: ['dist/index.js'],
2425
};

setup-local/config/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ module.exports = {
4949
},
5050
},
5151

52+
BINARY_MAX_TRIES: 3,
53+
RETRY_DELAY_BINARY: 5000,
54+
5255
RESTRICTED_LOCAL_ARGS: ['k', 'key', 'local-identifier', 'daemon', 'only-automate', 'verbose', 'log-file', 'ci-plugin'],
5356

5457
LOCAL_BINARY_FOLDER: 'LocalBinaryFolder',
5558
LOCAL_BINARY_NAME: 'BrowserStackLocal',
59+
LOCAL_BINARY_ZIP: 'binaryZip',
5660
LOCAL_LOG_FILE_PREFIX: 'BrowserStackLocal',
5761
};

setup-local/dist/index.js

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,8 +1080,11 @@ const {
10801080
LOCAL_BINARY_FOLDER,
10811081
PLATFORMS,
10821082
LOCAL_BINARY_NAME,
1083+
LOCAL_BINARY_ZIP,
10831084
LOCAL_LOG_FILE_PREFIX,
10841085
LOCAL_BINARY_TRIGGER,
1086+
RETRY_DELAY_BINARY,
1087+
BINARY_MAX_TRIES,
10851088
ALLOWED_INPUT_VALUES: {
10861089
LOCAL_TESTING,
10871090
},
@@ -1107,18 +1110,23 @@ class BinaryControl {
11071110
* platform and the architecture
11081111
*/
11091112
_decidePlatformAndBinary() {
1113+
this.binaryFolder = path.resolve(
1114+
process.env.GITHUB_WORKSPACE,
1115+
'..', '..', '..',
1116+
'_work',
1117+
'binary',
1118+
LOCAL_BINARY_FOLDER,
1119+
this.platform,
1120+
);
11101121
switch (this.platform) {
11111122
case PLATFORMS.DARWIN:
11121123
this.binaryLink = BINARY_LINKS.DARWIN;
1113-
this.binaryFolder = path.resolve(process.env.HOME, 'work', 'binary', LOCAL_BINARY_FOLDER, this.platform);
11141124
break;
11151125
case PLATFORMS.LINUX:
11161126
this.binaryLink = os.arch() === 'x32' ? BINARY_LINKS.LINUX_32 : BINARY_LINKS.LINUX_64;
1117-
this.binaryFolder = path.resolve(process.env.HOME, 'work', 'binary', LOCAL_BINARY_FOLDER, this.platform);
11181127
break;
11191128
case PLATFORMS.WIN32:
11201129
this.binaryLink = BINARY_LINKS.WINDOWS;
1121-
this.binaryFolder = path.resolve(process.env.GITHUB_WORKSPACE, '..', '..', 'work', 'binary', LOCAL_BINARY_FOLDER, this.platform);
11221130
break;
11231131
default:
11241132
throw Error(`Unsupported Platform: ${this.platform}. No BrowserStackLocal binary found.`);
@@ -1207,23 +1215,42 @@ class BinaryControl {
12071215
};
12081216
}
12091217

1218+
async _removeAnyStaleBinary() {
1219+
const binaryZip = path.resolve(this.binaryFolder, LOCAL_BINARY_ZIP);
1220+
const previousLocalBinary = path.resolve(
1221+
this.binaryFolder,
1222+
`${LOCAL_BINARY_NAME}${this.platform === PLATFORMS.WIN32 ? '.exe' : ''}`,
1223+
);
1224+
await Promise.all([io.rmRF(binaryZip), io.rmRF(previousLocalBinary)]);
1225+
}
1226+
12101227
/**
12111228
* Downloads the Local Binary, extracts it and adds it in the PATH variable
12121229
*/
12131230
async downloadBinary() {
1214-
if (Utils.checkToolInCache(LOCAL_BINARY_NAME)) {
1231+
const cachedBinaryPath = Utils.checkToolInCache(LOCAL_BINARY_NAME, '1.0.0');
1232+
if (cachedBinaryPath) {
12151233
core.info('BrowserStackLocal binary already exists in cache. Using that instead of downloading again...');
1234+
// A cached tool is persisted across runs. But the PATH is reset back to its original
1235+
// state between each run. Thus, adding the cached tool path back to PATH again.
1236+
core.addPath(cachedBinaryPath);
12161237
return;
12171238
}
1239+
12181240
try {
12191241
await this._makeDirectory();
1242+
core.debug('BrowserStackLocal binary not found in cache. Deleting any stale/existing binary before downloading...');
1243+
await this._removeAnyStaleBinary();
1244+
12201245
core.info('Downloading BrowserStackLocal binary...');
1221-
const downloadPath = await tc.downloadTool(this.binaryLink, path.resolve(this.binaryFolder, 'binaryZip'));
1246+
const downloadPath = await tc.downloadTool(
1247+
this.binaryLink,
1248+
path.resolve(this.binaryFolder, LOCAL_BINARY_ZIP),
1249+
);
12221250
const extractedPath = await tc.extractZip(downloadPath, this.binaryFolder);
12231251
core.info(`BrowserStackLocal binary downloaded & extracted successfuly at: ${extractedPath}`);
12241252
const cachedPath = await tc.cacheDir(extractedPath, LOCAL_BINARY_NAME, '1.0.0');
12251253
core.addPath(cachedPath);
1226-
this.binaryPath = extractedPath;
12271254
} catch (e) {
12281255
throw Error(`BrowserStackLocal binary could not be downloaded due to ${e.message}`);
12291256
}
@@ -1233,35 +1260,47 @@ class BinaryControl {
12331260
* Starts Local Binary using the args generated for this action
12341261
*/
12351262
async startBinary() {
1236-
try {
1237-
this._generateArgsForBinary();
1238-
let { localIdentifier } = this.stateForBinary;
1239-
localIdentifier = localIdentifier ? `with local-identifier=${localIdentifier}` : '';
1240-
core.info(`Starting local tunnel ${localIdentifier} in daemon mode...`);
1263+
this._generateArgsForBinary();
1264+
let { localIdentifier } = this.stateForBinary;
1265+
localIdentifier = localIdentifier ? `with local-identifier=${localIdentifier}` : '';
1266+
core.info(`Starting local tunnel ${localIdentifier} in daemon mode...`);
12411267

1242-
const { output, error } = await this._triggerBinary(LOCAL_TESTING.START);
1268+
let triesAvailable = BINARY_MAX_TRIES;
1269+
1270+
while (triesAvailable--) {
1271+
try {
1272+
// eslint-disable-next-line no-await-in-loop
1273+
const { output, error } = await this._triggerBinary(LOCAL_TESTING.START);
1274+
1275+
if (!error) {
1276+
const outputParsed = JSON.parse(output);
1277+
if (outputParsed.state === LOCAL_BINARY_TRIGGER.START.CONNECTED) {
1278+
core.info(`Local tunnel status: ${outputParsed.message}`);
1279+
return;
1280+
}
12431281

1244-
if (!error) {
1245-
const outputParsed = JSON.parse(output);
1246-
if (outputParsed.state === LOCAL_BINARY_TRIGGER.START.CONNECTED) {
1247-
core.info(`Local tunnel status: ${outputParsed.message}`);
1248-
} else {
12491282
throw Error(JSON.stringify(outputParsed.message));
1283+
} else {
1284+
throw Error(JSON.stringify(error));
1285+
}
1286+
} catch (e) {
1287+
if (triesAvailable) {
1288+
core.info(`Error in starting local tunnel: ${e.message}. Trying again in 5 seconds...`);
1289+
// eslint-disable-next-line no-await-in-loop
1290+
await Utils.sleepFor(RETRY_DELAY_BINARY);
1291+
} else {
1292+
throw Error(`Local tunnel could not be started. Error message from binary: ${e.message}`);
12501293
}
1251-
} else {
1252-
throw Error(JSON.stringify(error));
12531294
}
1254-
} catch (e) {
1255-
throw Error(`Local tunnel could not be started. Error message from binary: ${e.message}`);
12561295
}
12571296
}
12581297

12591298
/**
12601299
* Stops Local Binary using the args generated for this action
12611300
*/
12621301
async stopBinary() {
1302+
this._generateArgsForBinary();
12631303
try {
1264-
this._generateArgsForBinary();
12651304
let { localIdentifier } = this.stateForBinary;
12661305
localIdentifier = localIdentifier ? `with local-identifier=${localIdentifier}` : '';
12671306
core.info(`Stopping local tunnel ${localIdentifier} in daemon mode...`);
@@ -7230,9 +7269,15 @@ class Utils {
72307269
delete process.env[environmentVariable];
72317270
}
72327271

7233-
static checkToolInCache(toolName) {
7234-
const toolCache = tc.findAllVersions(toolName);
7235-
return toolCache.length !== 0;
7272+
static checkToolInCache(toolName, version) {
7273+
const toolCachePath = tc.find(toolName, version);
7274+
return toolCachePath;
7275+
}
7276+
7277+
static async sleepFor(ms) {
7278+
let parsedMilliseconds = parseFloat(ms);
7279+
parsedMilliseconds = parsedMilliseconds > 0 ? parsedMilliseconds : 0;
7280+
return new Promise((resolve) => setTimeout(resolve, parsedMilliseconds));
72367281
}
72377282
}
72387283

@@ -13444,10 +13489,14 @@ module.exports = {
1344413489
},
1344513490
},
1344613491

13492+
BINARY_MAX_TRIES: 3,
13493+
RETRY_DELAY_BINARY: 5000,
13494+
1344713495
RESTRICTED_LOCAL_ARGS: ['k', 'key', 'local-identifier', 'daemon', 'only-automate', 'verbose', 'log-file', 'ci-plugin'],
1344813496

1344913497
LOCAL_BINARY_FOLDER: 'LocalBinaryFolder',
1345013498
LOCAL_BINARY_NAME: 'BrowserStackLocal',
13499+
LOCAL_BINARY_ZIP: 'binaryZip',
1345113500
LOCAL_LOG_FILE_PREFIX: 'BrowserStackLocal',
1345213501
};
1345313502

setup-local/src/binaryControl.js

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ const {
1515
LOCAL_BINARY_FOLDER,
1616
PLATFORMS,
1717
LOCAL_BINARY_NAME,
18+
LOCAL_BINARY_ZIP,
1819
LOCAL_LOG_FILE_PREFIX,
1920
LOCAL_BINARY_TRIGGER,
21+
RETRY_DELAY_BINARY,
22+
BINARY_MAX_TRIES,
2023
ALLOWED_INPUT_VALUES: {
2124
LOCAL_TESTING,
2225
},
@@ -42,18 +45,23 @@ class BinaryControl {
4245
* platform and the architecture
4346
*/
4447
_decidePlatformAndBinary() {
48+
this.binaryFolder = path.resolve(
49+
process.env.GITHUB_WORKSPACE,
50+
'..', '..', '..',
51+
'_work',
52+
'binary',
53+
LOCAL_BINARY_FOLDER,
54+
this.platform,
55+
);
4556
switch (this.platform) {
4657
case PLATFORMS.DARWIN:
4758
this.binaryLink = BINARY_LINKS.DARWIN;
48-
this.binaryFolder = path.resolve(process.env.HOME, 'work', 'binary', LOCAL_BINARY_FOLDER, this.platform);
4959
break;
5060
case PLATFORMS.LINUX:
5161
this.binaryLink = os.arch() === 'x32' ? BINARY_LINKS.LINUX_32 : BINARY_LINKS.LINUX_64;
52-
this.binaryFolder = path.resolve(process.env.HOME, 'work', 'binary', LOCAL_BINARY_FOLDER, this.platform);
5362
break;
5463
case PLATFORMS.WIN32:
5564
this.binaryLink = BINARY_LINKS.WINDOWS;
56-
this.binaryFolder = path.resolve(process.env.GITHUB_WORKSPACE, '..', '..', 'work', 'binary', LOCAL_BINARY_FOLDER, this.platform);
5765
break;
5866
default:
5967
throw Error(`Unsupported Platform: ${this.platform}. No BrowserStackLocal binary found.`);
@@ -142,23 +150,42 @@ class BinaryControl {
142150
};
143151
}
144152

153+
async _removeAnyStaleBinary() {
154+
const binaryZip = path.resolve(this.binaryFolder, LOCAL_BINARY_ZIP);
155+
const previousLocalBinary = path.resolve(
156+
this.binaryFolder,
157+
`${LOCAL_BINARY_NAME}${this.platform === PLATFORMS.WIN32 ? '.exe' : ''}`,
158+
);
159+
await Promise.all([io.rmRF(binaryZip), io.rmRF(previousLocalBinary)]);
160+
}
161+
145162
/**
146163
* Downloads the Local Binary, extracts it and adds it in the PATH variable
147164
*/
148165
async downloadBinary() {
149-
if (Utils.checkToolInCache(LOCAL_BINARY_NAME)) {
166+
const cachedBinaryPath = Utils.checkToolInCache(LOCAL_BINARY_NAME, '1.0.0');
167+
if (cachedBinaryPath) {
150168
core.info('BrowserStackLocal binary already exists in cache. Using that instead of downloading again...');
169+
// A cached tool is persisted across runs. But the PATH is reset back to its original
170+
// state between each run. Thus, adding the cached tool path back to PATH again.
171+
core.addPath(cachedBinaryPath);
151172
return;
152173
}
174+
153175
try {
154176
await this._makeDirectory();
177+
core.debug('BrowserStackLocal binary not found in cache. Deleting any stale/existing binary before downloading...');
178+
await this._removeAnyStaleBinary();
179+
155180
core.info('Downloading BrowserStackLocal binary...');
156-
const downloadPath = await tc.downloadTool(this.binaryLink, path.resolve(this.binaryFolder, 'binaryZip'));
181+
const downloadPath = await tc.downloadTool(
182+
this.binaryLink,
183+
path.resolve(this.binaryFolder, LOCAL_BINARY_ZIP),
184+
);
157185
const extractedPath = await tc.extractZip(downloadPath, this.binaryFolder);
158186
core.info(`BrowserStackLocal binary downloaded & extracted successfuly at: ${extractedPath}`);
159187
const cachedPath = await tc.cacheDir(extractedPath, LOCAL_BINARY_NAME, '1.0.0');
160188
core.addPath(cachedPath);
161-
this.binaryPath = extractedPath;
162189
} catch (e) {
163190
throw Error(`BrowserStackLocal binary could not be downloaded due to ${e.message}`);
164191
}
@@ -168,35 +195,47 @@ class BinaryControl {
168195
* Starts Local Binary using the args generated for this action
169196
*/
170197
async startBinary() {
171-
try {
172-
this._generateArgsForBinary();
173-
let { localIdentifier } = this.stateForBinary;
174-
localIdentifier = localIdentifier ? `with local-identifier=${localIdentifier}` : '';
175-
core.info(`Starting local tunnel ${localIdentifier} in daemon mode...`);
198+
this._generateArgsForBinary();
199+
let { localIdentifier } = this.stateForBinary;
200+
localIdentifier = localIdentifier ? `with local-identifier=${localIdentifier}` : '';
201+
core.info(`Starting local tunnel ${localIdentifier} in daemon mode...`);
176202

177-
const { output, error } = await this._triggerBinary(LOCAL_TESTING.START);
203+
let triesAvailable = BINARY_MAX_TRIES;
204+
205+
while (triesAvailable--) {
206+
try {
207+
// eslint-disable-next-line no-await-in-loop
208+
const { output, error } = await this._triggerBinary(LOCAL_TESTING.START);
209+
210+
if (!error) {
211+
const outputParsed = JSON.parse(output);
212+
if (outputParsed.state === LOCAL_BINARY_TRIGGER.START.CONNECTED) {
213+
core.info(`Local tunnel status: ${outputParsed.message}`);
214+
return;
215+
}
178216

179-
if (!error) {
180-
const outputParsed = JSON.parse(output);
181-
if (outputParsed.state === LOCAL_BINARY_TRIGGER.START.CONNECTED) {
182-
core.info(`Local tunnel status: ${outputParsed.message}`);
183-
} else {
184217
throw Error(JSON.stringify(outputParsed.message));
218+
} else {
219+
throw Error(JSON.stringify(error));
220+
}
221+
} catch (e) {
222+
if (triesAvailable) {
223+
core.info(`Error in starting local tunnel: ${e.message}. Trying again in 5 seconds...`);
224+
// eslint-disable-next-line no-await-in-loop
225+
await Utils.sleepFor(RETRY_DELAY_BINARY);
226+
} else {
227+
throw Error(`Local tunnel could not be started. Error message from binary: ${e.message}`);
185228
}
186-
} else {
187-
throw Error(JSON.stringify(error));
188229
}
189-
} catch (e) {
190-
throw Error(`Local tunnel could not be started. Error message from binary: ${e.message}`);
191230
}
192231
}
193232

194233
/**
195234
* Stops Local Binary using the args generated for this action
196235
*/
197236
async stopBinary() {
237+
this._generateArgsForBinary();
198238
try {
199-
this._generateArgsForBinary();
200239
let { localIdentifier } = this.stateForBinary;
201240
localIdentifier = localIdentifier ? `with local-identifier=${localIdentifier}` : '';
202241
core.info(`Stopping local tunnel ${localIdentifier} in daemon mode...`);

setup-local/src/utils/index.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ class Utils {
77
delete process.env[environmentVariable];
88
}
99

10-
static checkToolInCache(toolName) {
11-
const toolCache = tc.findAllVersions(toolName);
12-
return toolCache.length !== 0;
10+
static checkToolInCache(toolName, version) {
11+
const toolCachePath = tc.find(toolName, version);
12+
return toolCachePath;
13+
}
14+
15+
static async sleepFor(ms) {
16+
let parsedMilliseconds = parseFloat(ms);
17+
parsedMilliseconds = parsedMilliseconds > 0 ? parsedMilliseconds : 0;
18+
return new Promise((resolve) => setTimeout(resolve, parsedMilliseconds));
1319
}
1420
}
1521

0 commit comments

Comments
 (0)