mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-27 14:49:25 +00:00
Add ability to push to remote device UUID
Change-type: minor Signed-off-by: Josh Bowling <josh@balena.io>
This commit is contained in:
parent
cc60e86507
commit
1de95a5f6c
@ -2913,12 +2913,14 @@ Examples:
|
||||
|
||||
$ balena push 23c73a1.local --system
|
||||
$ balena push 23c73a1.local --system --service my-service
|
||||
|
||||
$ balena push 9f0cc5e4-0707-487c-ba84-edd0c36ff1c9
|
||||
|
||||
### Arguments
|
||||
|
||||
#### FLEETORDEVICE
|
||||
|
||||
fleet name or slug, or local device IP address or ".local" hostname
|
||||
fleet name or slug, device UUID, or local device IP address or ".local" hostname
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -112,13 +112,15 @@ export default class PushCmd extends Command {
|
||||
'',
|
||||
'$ balena push 23c73a1.local --system',
|
||||
'$ balena push 23c73a1.local --system --service my-service',
|
||||
'',
|
||||
'$ balena push 9f0cc5e4-0707-487c-ba84-edd0c36ff1c9',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'fleetOrDevice',
|
||||
description:
|
||||
'fleet name or slug, or local device IP address or ".local" hostname',
|
||||
'fleet name or slug, device UUID, or local device IP address or ".local" hostname',
|
||||
required: true,
|
||||
parse: lowercaseIfSlug,
|
||||
},
|
||||
@ -315,7 +317,7 @@ export default class PushCmd extends Command {
|
||||
break;
|
||||
|
||||
case BuildTarget.Device:
|
||||
logger.logDebug(`Pushing to local device: ${params.fleetOrDevice}`);
|
||||
logger.logDebug(`Pushing to device: ${params.fleetOrDevice}`);
|
||||
await this.pushToDevice(
|
||||
params.fleetOrDevice,
|
||||
options,
|
||||
@ -442,9 +444,9 @@ export default class PushCmd extends Command {
|
||||
}
|
||||
|
||||
protected async getBuildTarget(appOrDevice: string): Promise<BuildTarget> {
|
||||
const { validateLocalHostnameOrIp } = await import('../utils/validation');
|
||||
const { validateDeviceAddress } = await import('../utils/validation');
|
||||
|
||||
return validateLocalHostnameOrIp(appOrDevice)
|
||||
return validateDeviceAddress(appOrDevice)
|
||||
? BuildTarget.Device
|
||||
: BuildTarget.Cloud;
|
||||
}
|
||||
|
@ -40,7 +40,10 @@ import { DeviceAPI, DeviceInfo } from './api';
|
||||
import * as LocalPushErrors from './errors';
|
||||
import LivepushManager from './live';
|
||||
import { displayBuildLog } from './logs';
|
||||
import { stripIndent } from '../lazy';
|
||||
import { getBalenaSdk, stripIndent } from '../lazy';
|
||||
import { validateIPAddress } from '../validation';
|
||||
import { Server, Socket } from 'net';
|
||||
import { BalenaSDK, Device } from 'balena-sdk';
|
||||
|
||||
const LOCAL_APPNAME = 'localapp';
|
||||
const LOCAL_RELEASEHASH = 'localrelease';
|
||||
@ -121,7 +124,110 @@ async function environmentFromInput(
|
||||
return ret;
|
||||
}
|
||||
|
||||
const logConnection = (
|
||||
logger: Logger,
|
||||
fromHost: string,
|
||||
fromPort: number,
|
||||
localAddress: string,
|
||||
localPort: number,
|
||||
deviceAddress: string,
|
||||
devicePort: number,
|
||||
err?: Error,
|
||||
) => {
|
||||
const logMessage = `${fromHost}:${fromPort} => ${localAddress}:${localPort} ===> ${deviceAddress}:${devicePort}`;
|
||||
|
||||
if (err) {
|
||||
logger.logError(`${logMessage} :: ${err.message}`);
|
||||
} else {
|
||||
logger.logLogs(logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
async function openTunnel(
|
||||
logger: Logger,
|
||||
device: Device,
|
||||
sdk: BalenaSDK,
|
||||
port: number,
|
||||
): Promise<any> {
|
||||
const localhost = 'localhost';
|
||||
try {
|
||||
const { tunnelConnectionToDevice } = await import('../tunnel');
|
||||
const handler = await tunnelConnectionToDevice(device.uuid, port, sdk);
|
||||
|
||||
const { createServer } = await import('net');
|
||||
const server = createServer(async (client: Socket) => {
|
||||
try {
|
||||
await handler(client);
|
||||
logConnection(
|
||||
logger,
|
||||
client.remoteAddress || '',
|
||||
client.remotePort || 0,
|
||||
client.localAddress,
|
||||
client.localPort,
|
||||
device.vpn_address || '',
|
||||
port,
|
||||
);
|
||||
} catch (err: any) {
|
||||
logConnection(
|
||||
logger,
|
||||
client.remoteAddress || '',
|
||||
client.remotePort || 0,
|
||||
client.localAddress,
|
||||
client.localPort,
|
||||
device.vpn_address || '',
|
||||
port,
|
||||
err,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<Server>((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
server.listen(port, localhost, () => {
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
|
||||
logger.logInfo(
|
||||
` - tunnelling ${localhost}:${port} to ${device.uuid}:${port}`,
|
||||
);
|
||||
|
||||
// opts.deviceHost = localhost;
|
||||
} catch (err: any) {
|
||||
logger.logWarn(
|
||||
` - tunnel failed ${localhost}:${port} to ${
|
||||
device.uuid
|
||||
}:${port}, failed ${JSON.stringify(err.message)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
// Can only communicate with device using IP if local
|
||||
const isLocal =
|
||||
opts.deviceHost.includes('.local') || validateIPAddress(opts.deviceHost)
|
||||
? true
|
||||
: false;
|
||||
if (!isLocal) {
|
||||
// 1. Open tunnel from remote device to localhost
|
||||
// 2. Deploy to localhost
|
||||
const logger = Logger.getLogger();
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
// Ascertain device uuid
|
||||
const { getOnlineTargetDeviceUuid } = await import('../patterns');
|
||||
const uuid = await getOnlineTargetDeviceUuid(sdk, opts.deviceHost);
|
||||
const device = await sdk.models.device.get(uuid);
|
||||
logger.logInfo(`Opening a tunnel to ${device.uuid}...`);
|
||||
|
||||
await openTunnel(logger, device, sdk, 48484);
|
||||
await openTunnel(logger, device, sdk, 2375);
|
||||
|
||||
logger.logInfo('Opened tunnels to supervisor and docker...');
|
||||
|
||||
opts.deviceHost = 'localhost';
|
||||
}
|
||||
|
||||
// Resolve .local addresses to IP to avoid
|
||||
// issue with Windows and rapid repeat lookups.
|
||||
// see: https://github.com/balena-io/balena-cli/issues/1518
|
||||
@ -145,7 +251,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
throw new ExpectedError(stripIndent`
|
||||
Could not communicate with device supervisor at address ${opts.deviceHost}:${port}.
|
||||
Device may not have local mode enabled. Check with:
|
||||
balena device local-mode <device-uuid>
|
||||
balena device local-mode <device-uuid>
|
||||
`);
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,10 @@ export function validateDotLocalUrl(input: string): boolean {
|
||||
return DOTLOCAL_REGEX.test(input);
|
||||
}
|
||||
|
||||
export function validateDeviceAddress(input: string): boolean {
|
||||
return validateLocalHostnameOrIp(input) || validateUuid(input);
|
||||
}
|
||||
|
||||
export function validateLocalHostnameOrIp(input: string): boolean {
|
||||
return validateIPAddress(input) || validateDotLocalUrl(input);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user