Merge pull request #1180 from balena-io/tunnel-short-uuid

tunnel: allow using partial device uuids
This commit is contained in:
Will Boyce 2019-04-24 11:15:05 +01:00 committed by GitHub
commit 12615cd0dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -20,6 +20,7 @@ import * as _ from 'lodash';
import { createServer, Server, Socket } from 'net'; import { createServer, Server, Socket } from 'net';
import { isArray } from 'util'; import { isArray } from 'util';
import { inferOrSelectDevice } from '../utils/patterns';
import { tunnelConnectionToDevice } from '../utils/tunnel'; import { tunnelConnectionToDevice } from '../utils/tunnel';
interface Args { interface Args {
@ -128,116 +129,117 @@ export const tunnel: CommandDefinition<Args, Options> = {
? (options.port as string[]) ? (options.port as string[])
: [options.port as string]; : [options.port as string];
return Bluebird.try(() => return inferOrSelectDevice(params.uuid)
sdk.models.device .then(sdk.models.device.get)
.get(params.uuid) .then(device => {
.then(device => { if (!device.is_online) {
if (!device.is_online) { throw new DeviceIsOfflineError(device.uuid);
throw new DeviceIsOfflineError(params.uuid); }
}
const localListeners = _.chain(ports) const localListeners = _.chain(ports)
.map(mapping => { .map(mapping => {
const regexResult = /^([0-9]+)(?:$|\:(?:([\w\:\.]+)\:|)([0-9]+))$/.exec( const regexResult = /^([0-9]+)(?:$|\:(?:([\w\:\.]+)\:|)([0-9]+))$/.exec(
mapping, mapping,
); );
if (regexResult === null) { if (regexResult === null) {
throw new InvalidPortMappingError(mapping); throw new InvalidPortMappingError(mapping);
} }
// grab the groups // grab the groups
// tslint:disable-next-line:prefer-const // tslint:disable-next-line:prefer-const
let [, remotePort, localAddress, localPort] = regexResult; let [, remotePort, localAddress, localPort] = regexResult;
if ( if (
!isValidPort(parseInt(localPort, undefined)) || !isValidPort(parseInt(localPort, undefined)) ||
!isValidPort(parseInt(remotePort, undefined)) !isValidPort(parseInt(remotePort, undefined))
) { ) {
throw new InvalidPortMappingError(mapping); throw new InvalidPortMappingError(mapping);
} }
// default bind to localAddress // default bind to localAddress
if (localAddress == null) { if (localAddress == null) {
localAddress = 'localhost'; localAddress = 'localhost';
} }
// default use same port number locally as remote // default use same port number locally as remote
if (localPort == null) { if (localPort == null) {
localPort = remotePort; localPort = remotePort;
} }
return { return {
localPort: parseInt(localPort, undefined), localPort: parseInt(localPort, undefined),
localAddress, localAddress,
remotePort: parseInt(remotePort, undefined), remotePort: parseInt(remotePort, undefined),
}; };
}) })
.map(({ localPort, localAddress, remotePort }) => { .map(({ localPort, localAddress, remotePort }) => {
return tunnelConnectionToDevice(params.uuid, remotePort, sdk) return tunnelConnectionToDevice(device.uuid, remotePort, sdk)
.then(handler => .then(handler =>
createServer((client: Socket) => { createServer((client: Socket) => {
return handler(client) return handler(client)
.then(() => { .then(() => {
logConnection( logConnection(
client.remoteAddress, client.remoteAddress,
client.remotePort, client.remotePort,
client.localAddress, client.localAddress,
client.localPort, client.localPort,
device.vpn_address || '', device.vpn_address || '',
remotePort, remotePort,
);
})
.catch(err =>
logConnection(
client.remoteAddress,
client.remotePort,
client.localAddress,
client.localPort,
device.vpn_address || '',
remotePort,
err,
),
); );
})
.catch(err =>
logConnection(
client.remoteAddress,
client.remotePort,
client.localAddress,
client.localPort,
device.vpn_address || '',
remotePort,
err,
),
);
}),
)
.then(
server =>
new Bluebird.Promise<Server>((resolve, reject) => {
server.on('error', reject);
server.listen(localPort, localAddress, () => {
resolve(server);
});
}), }),
) )
.then( .then(() => {
server => logger.logInfo(
new Bluebird.Promise<Server>((resolve, reject) => { ` - tunnelling ${localAddress}:${localPort} to ${
server.on('error', reject); device.uuid
server.listen(localPort, localAddress, () => { }:${remotePort}`,
resolve(server); );
});
}),
)
.then(() => {
logger.logInfo(
` - tunnelling ${localAddress}:${localPort} to device:${remotePort}`,
);
return true; return true;
}) })
.catch((err: Error) => { .catch((err: Error) => {
logger.logWarn( logger.logWarn(
` - not tunnelling ${localAddress}:${localPort} to device:${remotePort}, failed ${JSON.stringify( ` - not tunnelling ${localAddress}:${localPort} to ${
err.message, device.uuid
)}`, }:${remotePort}, failed ${JSON.stringify(err.message)}`,
); );
return false; return false;
}); });
}) })
.value(); .value();
return Bluebird.all(localListeners); return Bluebird.all(localListeners);
}) })
.then(results => { .then(results => {
if (!results.includes(true)) { if (!results.includes(true)) {
throw new Error('No ports are valid for tunnelling'); throw new Error('No ports are valid for tunnelling');
} }
logger.logInfo('Waiting for connections...'); logger.logInfo('Waiting for connections...');
}), })
).nodeify(done); .nodeify(done);
}, },
}; };