balena-cli
Version:
The official balena Command Line Interface
157 lines (152 loc) • 7.23 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const errors_1 = require("../../errors");
const lazy_1 = require("../../utils/lazy");
const normalization_1 = require("../../utils/normalization");
class DeviceTunnelCmd extends core_1.Command {
async run() {
const { args: params, flags: options } = await this.parse(DeviceTunnelCmd);
const Logger = await Promise.resolve().then(() => require('../../utils/logger'));
const logger = Logger.getLogger();
const sdk = (0, lazy_1.getBalenaSdk)();
const logConnection = (fromHost, fromPort, localAddress, localPort, deviceAddress, devicePort, err) => {
const logMessage = `${fromHost}:${fromPort} => ${localAddress}:${localPort} ===> ${deviceAddress}:${devicePort}`;
if (err) {
logger.logError(`${logMessage} :: ${err.message}`);
}
else {
logger.logLogs(logMessage);
}
};
if (options.port === undefined) {
throw new errors_1.NoPortsDefinedError();
}
const { getOnlineTargetDeviceUuid } = await Promise.resolve().then(() => require('../../utils/patterns'));
const uuid = await getOnlineTargetDeviceUuid(sdk, params.deviceOrFleet);
logger.logInfo(`Opening a tunnel to ${uuid}...`);
const _ = await Promise.resolve().then(() => require('lodash'));
const localListeners = _.chain(options.port)
.map((mapping) => {
return this.parsePortMapping(mapping);
})
.map(async ({ localPort, localAddress, remotePort }) => {
try {
const { tunnelConnectionToDevice } = await Promise.resolve().then(() => require('../../utils/tunnel'));
const handler = await tunnelConnectionToDevice(uuid, remotePort, sdk);
const { createServer } = await Promise.resolve().then(() => require('net'));
const server = createServer(async (client) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
try {
await handler(client);
logConnection((_a = client.remoteAddress) !== null && _a !== void 0 ? _a : '', (_b = client.remotePort) !== null && _b !== void 0 ? _b : 0, (_c = client.localAddress) !== null && _c !== void 0 ? _c : '', (_d = client.localPort) !== null && _d !== void 0 ? _d : 0, uuid, remotePort);
}
catch (err) {
logConnection((_e = client.remoteAddress) !== null && _e !== void 0 ? _e : '', (_f = client.remotePort) !== null && _f !== void 0 ? _f : 0, (_g = client.localAddress) !== null && _g !== void 0 ? _g : '', (_h = client.localPort) !== null && _h !== void 0 ? _h : 0, uuid, remotePort, err);
}
});
await new Promise((resolve, reject) => {
server.on('error', reject);
server.listen(localPort, localAddress, () => {
resolve(server);
});
});
logger.logInfo(` - tunnelling ${localAddress}:${localPort} to ${uuid}:${remotePort}`);
return true;
}
catch (err) {
logger.logWarn(` - not tunnelling ${localAddress}:${localPort} to ${uuid}:${remotePort}, failed ${JSON.stringify(err.message)}`);
return false;
}
})
.value();
const results = await Promise.all(localListeners);
if (!results.includes(true)) {
throw new errors_1.ExpectedError('No ports are valid for tunnelling');
}
logger.logInfo('Waiting for connections...');
}
parsePortMapping(portMapping) {
const mappingElements = portMapping.split(':');
let localAddress = 'localhost';
const remotePort = parseInt(mappingElements[0], undefined);
let localPort = remotePort;
if (mappingElements.length === 2) {
if (/^\d+$/.test(mappingElements[1])) {
localPort = parseInt(mappingElements[1], undefined);
}
else {
localAddress = mappingElements[1];
}
}
else if (mappingElements.length === 3) {
localAddress = mappingElements[1];
localPort = parseInt(mappingElements[2], undefined);
}
else if (mappingElements.length > 3) {
throw new errors_1.InvalidPortMappingError(portMapping);
}
if (!this.isValidPort(remotePort) || !this.isValidPort(localPort)) {
throw new errors_1.InvalidPortMappingError(portMapping);
}
return { remotePort, localAddress, localPort };
}
isValidPort(port) {
const MAX_PORT_VALUE = Math.pow(2, 16) - 1;
return port > 0 && port <= MAX_PORT_VALUE;
}
}
DeviceTunnelCmd.aliases = ['tunnel'];
DeviceTunnelCmd.deprecateAliases = true;
DeviceTunnelCmd.description = (0, lazy_1.stripIndent) `
Tunnel local ports to your balenaOS device.
Use this command to open local TCP ports that tunnel to listening sockets in a
balenaOS device.
For example, this command could be used to expose the ssh server of a balenaOS
device (port number 22222) on the local machine, or to expose a web server
running on the device. The port numbers do not have be the same between the
device and the local machine, and multiple ports may be tunneled in a single
command line.
Port mappings are specified in the format: <remotePort>[:[localIP:]localPort]
localIP defaults to 'localhost', and localPort defaults to the specified
remotePort value.
Note: the -p (--port) flag must be provided at the end of the command line,
as per examples.
In the case of openBalena, the tunnel command in CLI v12.38.5 or later requires
openBalena v3.1.2 or later. Older CLI versions work with older openBalena
versions.
`;
DeviceTunnelCmd.examples = [
'# map remote port 22222 to localhost:22222',
'$ balena device tunnel myFleet -p 22222',
'',
'# map remote port 22222 to localhost:222',
'$ balena device tunnel 2ead211 -p 22222:222',
'',
'# map remote port 22222 to any address on your host machine, port 22222',
'$ balena device tunnel 1546690 -p 22222:0.0.0.0',
'',
'# map remote port 22222 to any address on your host machine, port 222',
'$ balena device tunnel myFleet -p 22222:0.0.0.0:222',
'',
'# multiple port tunnels can be specified at any one time',
'$ balena device tunnel myFleet -p 8080:3000 -p 8081:9000',
];
DeviceTunnelCmd.args = {
deviceOrFleet: core_1.Args.string({
description: 'device UUID or fleet name/slug',
required: true,
parse: normalization_1.lowercaseIfSlug,
}),
};
DeviceTunnelCmd.flags = {
port: core_1.Flags.string({
description: 'port mapping in the format <remotePort>[:[localIP:]localPort]',
char: 'p',
multiple: true,
}),
};
DeviceTunnelCmd.primary = true;
DeviceTunnelCmd.authenticated = true;
exports.default = DeviceTunnelCmd;
//# sourceMappingURL=tunnel.js.map
;