mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-23 01:58:51 +00:00
Update commands ssh, tunnel to support orgs
Change-type: patch Connects-to: #2119 Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
@ -19,11 +19,7 @@ import { flags } from '@oclif/command';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import {
|
||||
parseAsInteger,
|
||||
validateDotLocalUrl,
|
||||
validateIPAddress,
|
||||
} from '../utils/validation';
|
||||
import { parseAsInteger, validateLocalHostnameOrIp } from '../utils/validation';
|
||||
import * as BalenaSdk from 'balena-sdk';
|
||||
|
||||
interface FlagsDef {
|
||||
@ -39,14 +35,14 @@ interface ArgsDef {
|
||||
service?: string;
|
||||
}
|
||||
|
||||
export default class NoteCmd extends Command {
|
||||
export default class SshCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
SSH into the host or application container of a device.
|
||||
|
||||
Start a shell on a local or remote device. If a service name is not provided,
|
||||
a shell will be opened on the host OS.
|
||||
|
||||
If an application name is provided, an interactive menu will be presented
|
||||
If an application is provided, an interactive menu will be presented
|
||||
for the selection of an online device. A shell will then be opened for the
|
||||
host OS or service container of the chosen device.
|
||||
|
||||
@ -81,7 +77,8 @@ export default class NoteCmd extends Command {
|
||||
public static args = [
|
||||
{
|
||||
name: 'applicationOrDevice',
|
||||
description: 'application name, device uuid, or address of local device',
|
||||
description:
|
||||
'application name/slug/id, device uuid, or address of local device',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@ -104,17 +101,17 @@ export default class NoteCmd extends Command {
|
||||
tty: flags.boolean({
|
||||
default: false,
|
||||
description:
|
||||
'Force pseudo-terminal allocation (bypass TTY autodetection for stdin)',
|
||||
'force pseudo-terminal allocation (bypass TTY autodetection for stdin)',
|
||||
char: 't',
|
||||
}),
|
||||
verbose: flags.boolean({
|
||||
default: false,
|
||||
description: 'Increase verbosity',
|
||||
description: 'increase verbosity',
|
||||
char: 'v',
|
||||
}),
|
||||
noproxy: flags.boolean({
|
||||
default: false,
|
||||
description: 'Bypass global proxy configuration for the ssh connection',
|
||||
description: 'bypass global proxy configuration for the ssh connection',
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
@ -123,14 +120,11 @@ export default class NoteCmd extends Command {
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
NoteCmd,
|
||||
SshCmd,
|
||||
);
|
||||
|
||||
// if we're doing a direct SSH connection locally...
|
||||
if (
|
||||
validateDotLocalUrl(params.applicationOrDevice) ||
|
||||
validateIPAddress(params.applicationOrDevice)
|
||||
) {
|
||||
// Local connection
|
||||
if (validateLocalHostnameOrIp(params.applicationOrDevice)) {
|
||||
const { performLocalDeviceSSH } = await import('../utils/device/ssh');
|
||||
return await performLocalDeviceSSH({
|
||||
address: params.applicationOrDevice,
|
||||
@ -141,26 +135,27 @@ export default class NoteCmd extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
// Remote connection
|
||||
const { getProxyConfig, which } = await import('../utils/helpers');
|
||||
const { checkLoggedIn, getOnlineTargetUuid } = await import(
|
||||
'../utils/patterns'
|
||||
);
|
||||
const { getOnlineTargetDeviceUuid } = await import('../utils/patterns');
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
const proxyConfig = getProxyConfig();
|
||||
const useProxy = !!proxyConfig && !options.noproxy;
|
||||
|
||||
// this will be a tunnelled SSH connection...
|
||||
await checkLoggedIn();
|
||||
const uuid = await getOnlineTargetUuid(sdk, params.applicationOrDevice);
|
||||
let version: string | undefined;
|
||||
let id: number | undefined;
|
||||
await Command.checkLoggedIn();
|
||||
const deviceUuid = await getOnlineTargetDeviceUuid(
|
||||
sdk,
|
||||
params.applicationOrDevice,
|
||||
);
|
||||
|
||||
const device = await sdk.models.device.get(uuid, {
|
||||
const device = await sdk.models.device.get(deviceUuid, {
|
||||
$select: ['id', 'supervisor_version', 'is_online'],
|
||||
});
|
||||
id = device.id;
|
||||
version = device.supervisor_version;
|
||||
|
||||
const deviceId = device.id;
|
||||
const supervisorVersion = device.supervisor_version;
|
||||
|
||||
const [whichProxytunnel, username, proxyUrl] = await Promise.all([
|
||||
useProxy ? which('proxytunnel', false) : undefined,
|
||||
@ -207,20 +202,13 @@ export default class NoteCmd extends Command {
|
||||
|
||||
const proxyCommand = useProxy ? getSshProxyCommand() : undefined;
|
||||
|
||||
if (username == null) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(
|
||||
`Opening an SSH connection to a remote device requires you to be logged in.`,
|
||||
);
|
||||
}
|
||||
|
||||
// At this point, we have a long uuid with a device
|
||||
// At this point, we have a long uuid of a device
|
||||
// that we know exists and is accessible
|
||||
let containerId: string | undefined;
|
||||
if (params.service != null) {
|
||||
containerId = await this.getContainerId(
|
||||
sdk,
|
||||
uuid,
|
||||
deviceUuid,
|
||||
params.service,
|
||||
{
|
||||
port: options.port,
|
||||
@ -228,20 +216,20 @@ export default class NoteCmd extends Command {
|
||||
proxyUrl: proxyUrl || '',
|
||||
username: username!,
|
||||
},
|
||||
version,
|
||||
id,
|
||||
supervisorVersion,
|
||||
deviceId,
|
||||
);
|
||||
}
|
||||
|
||||
let accessCommand: string;
|
||||
if (containerId != null) {
|
||||
accessCommand = `enter ${uuid} ${containerId}`;
|
||||
accessCommand = `enter ${deviceUuid} ${containerId}`;
|
||||
} else {
|
||||
accessCommand = `host ${uuid}`;
|
||||
accessCommand = `host ${deviceUuid}`;
|
||||
}
|
||||
|
||||
const command = this.generateVpnSshCommand({
|
||||
uuid,
|
||||
uuid: deviceUuid,
|
||||
command: accessCommand,
|
||||
verbose: options.verbose,
|
||||
port: options.port,
|
||||
|
@ -24,11 +24,7 @@ import {
|
||||
} from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import { getOnlineTargetUuid } from '../utils/patterns';
|
||||
import * as _ from 'lodash';
|
||||
import { tunnelConnectionToDevice } from '../utils/tunnel';
|
||||
import { createServer, Server, Socket } from 'net';
|
||||
import { IArg } from '@oclif/parser/lib/args';
|
||||
import type { Server, Socket } from 'net';
|
||||
|
||||
interface FlagsDef {
|
||||
port: string[];
|
||||
@ -73,10 +69,10 @@ export default class TunnelCmd extends Command {
|
||||
'$ balena tunnel myApp -p 8080:3000 -p 8081:9000',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
public static args = [
|
||||
{
|
||||
name: 'deviceOrApplication',
|
||||
description: 'device uuid or application name/id',
|
||||
description: 'device uuid or application name/slug/id',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
@ -101,8 +97,7 @@ export default class TunnelCmd extends Command {
|
||||
TunnelCmd,
|
||||
);
|
||||
|
||||
const Logger = await import('../utils/logger');
|
||||
const logger = Logger.getLogger();
|
||||
const logger = await Command.getLogger();
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
const logConnection = (
|
||||
@ -127,23 +122,30 @@ export default class TunnelCmd extends Command {
|
||||
throw new NoPortsDefinedError();
|
||||
}
|
||||
|
||||
const uuid = await getOnlineTargetUuid(sdk, params.deviceOrApplication);
|
||||
// Ascertain device uuid
|
||||
const { getOnlineTargetDeviceUuid } = await import('../utils/patterns');
|
||||
const uuid = await getOnlineTargetDeviceUuid(
|
||||
sdk,
|
||||
params.deviceOrApplication,
|
||||
);
|
||||
const device = await sdk.models.device.get(uuid);
|
||||
|
||||
logger.logInfo(`Opening a tunnel to ${device.uuid}...`);
|
||||
|
||||
const _ = await import('lodash');
|
||||
const localListeners = _.chain(options.port)
|
||||
.map((mapping) => {
|
||||
return this.parsePortMapping(mapping);
|
||||
})
|
||||
.map(async ({ localPort, localAddress, remotePort }) => {
|
||||
try {
|
||||
const { tunnelConnectionToDevice } = await import('../utils/tunnel');
|
||||
const handler = await tunnelConnectionToDevice(
|
||||
device.uuid,
|
||||
remotePort,
|
||||
sdk,
|
||||
);
|
||||
|
||||
const { createServer } = await import('net');
|
||||
const server = createServer(async (client: Socket) => {
|
||||
try {
|
||||
await handler(client);
|
||||
|
Reference in New Issue
Block a user