@applitools/execution-grid-tunnel
Version:
Allows user to run tests with exection-grid and navigate to private hosts and ips
131 lines (106 loc) • 3.9 kB
JavaScript
const {
tunnelId,
stringifyConfig,
socks5ProxyPort,
stringifyPortRange,
configFileRootPath,
binPath,
binArgs = '-c',
logFilePath,
heartbeatTimeout = 15000,
startTunnelTimeoutThreshold = 10000,
stopProcessTimeout = 5000,
} = process.env
const os = require('os')
const path = require('path')
const {promises: fs, unlinkSync} = require('fs')
const {SubProcess} = require('teen_process')
const nodeCleanup = require('node-cleanup')
const {createIniFileFromJson, findFreePort} = require('../utils')
const TUNNEL_STATUS = require('./tunnel-status')
const {convertFrpcOutputToTunnelStatus} = require('./convert-frpc-output-to-tunnel-status.js')
let selfKillTimeoutId
let initTimeoutId
const _startTunnel = async () => {
const config = JSON.parse(stringifyConfig)
const {min, max} = JSON.parse(stringifyPortRange)
if (typeof socks5ProxyPort !== 'undefined') {
config[tunnelId].local_port = socks5ProxyPort
delete config[tunnelId].plugin
} else {
const localPort = await findFreePort(min, max)
config[tunnelId].local_port = localPort
}
const configFilePath = path.resolve(configFileRootPath, `${tunnelId}.ini`)
await createIniFileFromJson({path: configFilePath, content: config})
const proc = new SubProcess(binPath, [binArgs, configFilePath], {
stdio: 'pipe',
//shell: os.platform() !== 'win32', // TODO: understand why it causes to problems
detached: false,
windowsHide: true
})
proc.on('stream-line', (line) => {
if (!process.send) return
const status = convertFrpcOutputToTunnelStatus(line)
if (status) {
const data = {
status,
...(status === TUNNEL_STATUS.INIT_ERROR ? {error: line} : {}),
}
process.send(data)
}
// TODO: Run tunnels in many case!!!!!
if (status === TUNNEL_STATUS.RUNNING || status === TUNNEL_STATUS.INIT_ERROR) {
initTimeoutId && clearTimeout(initTimeoutId)
initTimeoutId = undefined
// set timeout to heartbeatTimeout after init
clearTimeout(selfKillTimeoutId)
startSelfKillTimeout(heartbeatTimeout)
}
logFilePath && fs.appendFile(logFilePath, `${line}\r\n`).catch(console.log)
})
proc.on('die', () => {
process.send && process.send({status: TUNNEL_STATUS.STOPPED})
initTimeoutId && clearTimeout(initTimeoutId)
initTimeoutId = undefined
process.exit()
})
// Closing frpc if doesn't get heart bit from parent
const startSelfKillTimeout = (timeout) => {
selfKillTimeoutId = setTimeout(async () => {
logFilePath &&
fs
.appendFile(logFilePath, `TunnelId ${tunnelId}: self-kill is called \r\n`)
.catch(console.log)
console.log(`TunnelId ${tunnelId}: self-kill is called`)
proc.isRunning && (await proc.stop())
process.exit(1)
}, timeout)
}
process.on('message', ({status}) => {
if (status !== 'ok') {
return
}
if (selfKillTimeoutId !== undefined) {
clearTimeout(selfKillTimeoutId)
}
startSelfKillTimeout(heartbeatTimeout)
})
// In some cases when frpc connection fails, it tries to reconnect forever.
// In that line we send error to frpc controller after startTunnelTimeoutThreshold
initTimeoutId = setTimeout(() => {
process.send({status: TUNNEL_STATUS.INIT_TIMEOUT_ERROR})
}, startTunnelTimeoutThreshold)
nodeCleanup((exitCode, signal) => {
process.send({status: TUNNEL_STATUS.STOPPED})
selfKillTimeoutId && clearTimeout(selfKillTimeoutId)
initTimeoutId && clearTimeout(initTimeoutId)
//TODO: should we check if proc.isRunning before run proc.stop
proc.stop(signal, stopProcessTimeout).catch(console.log)
unlinkSync(configFilePath)
})
await proc.start()
// set selfkill in case execution-grid-tunnel crashes before init
startSelfKillTimeout(parseInt(startTunnelTimeoutThreshold) + parseInt(heartbeatTimeout))
}
_startTunnel()