mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-20 06:07:55 +00:00
Convert command scan
to TypeScript, migrate to oclif
Change-type: patch Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
e3672bc655
commit
a2b761ec4b
@ -82,7 +82,7 @@ const capitanoDoc = {
|
|||||||
{
|
{
|
||||||
title: 'Network',
|
title: 'Network',
|
||||||
files: [
|
files: [
|
||||||
'build/actions/scan.js',
|
'build/actions-oclif/scan.js',
|
||||||
'build/actions/ssh.js',
|
'build/actions/ssh.js',
|
||||||
'build/actions/tunnel.js',
|
'build/actions/tunnel.js',
|
||||||
],
|
],
|
||||||
|
@ -1182,6 +1182,7 @@ Only show system logs. This can be used in combination with --service.
|
|||||||
|
|
||||||
## scan
|
## scan
|
||||||
|
|
||||||
|
Scan for balenaOS devices on your local network.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@ -1191,13 +1192,13 @@ Examples:
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
#### --verbose, -v
|
#### -v, --verbose
|
||||||
|
|
||||||
Display full info
|
display full info
|
||||||
|
|
||||||
#### --timeout, -t <timeout>
|
#### -t, --timeout TIMEOUT
|
||||||
|
|
||||||
Scan timeout in seconds
|
scan timeout in seconds
|
||||||
|
|
||||||
## ssh <applicationOrDevice> [serviceName]
|
## ssh <applicationOrDevice> [serviceName]
|
||||||
|
|
||||||
|
174
lib/actions-oclif/scan.ts
Normal file
174
lib/actions-oclif/scan.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2020 Balena Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { flags } from '@oclif/command';
|
||||||
|
import { LocalBalenaOsDevice } from 'balena-sync';
|
||||||
|
import { stripIndent } from 'common-tags';
|
||||||
|
import Command from '../command';
|
||||||
|
import * as cf from '../utils/common-flags';
|
||||||
|
import { getVisuals } from '../utils/lazy';
|
||||||
|
|
||||||
|
interface FlagsDef {
|
||||||
|
verbose: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
help: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ScanCmd extends Command {
|
||||||
|
public static description = stripIndent`
|
||||||
|
Scan for balenaOS devices on your local network.
|
||||||
|
|
||||||
|
Scan for balenaOS devices on your local network.
|
||||||
|
`;
|
||||||
|
|
||||||
|
public static examples = [
|
||||||
|
'$ balena scan',
|
||||||
|
'$ balena scan --timeout 120',
|
||||||
|
'$ balena scan --verbose',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static usage = 'scan';
|
||||||
|
|
||||||
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
|
verbose: flags.boolean({
|
||||||
|
char: 'v',
|
||||||
|
default: false,
|
||||||
|
description: 'display full info',
|
||||||
|
}),
|
||||||
|
timeout: flags.integer({
|
||||||
|
char: 't',
|
||||||
|
description: 'scan timeout in seconds',
|
||||||
|
}),
|
||||||
|
help: cf.help,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static primary = true;
|
||||||
|
public static root = true;
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const Bluebird = await import('bluebird');
|
||||||
|
const _ = await import('lodash');
|
||||||
|
const { SpinnerPromise } = getVisuals();
|
||||||
|
const { discover } = await import('balena-sync');
|
||||||
|
const prettyjson = await import('prettyjson');
|
||||||
|
const { ExpectedError } = await import('../errors');
|
||||||
|
const { dockerPort, dockerTimeout } = await import(
|
||||||
|
'../actions/local/common'
|
||||||
|
);
|
||||||
|
const dockerUtils = await import('../utils/docker');
|
||||||
|
|
||||||
|
const { flags: options } = this.parse<FlagsDef, {}>(ScanCmd);
|
||||||
|
|
||||||
|
const discoverTimeout =
|
||||||
|
options.timeout != null ? options.timeout * 1000 : undefined;
|
||||||
|
|
||||||
|
// Find active local devices
|
||||||
|
const activeLocalDevices: LocalBalenaOsDevice[] = await new SpinnerPromise({
|
||||||
|
promise: discover.discoverLocalBalenaOsDevices(discoverTimeout),
|
||||||
|
startMessage: 'Scanning for local balenaOS devices..',
|
||||||
|
stopMessage: 'Reporting scan results',
|
||||||
|
}).filter(async ({ address }: { address: string }) => {
|
||||||
|
const docker = dockerUtils.createClient({
|
||||||
|
host: address,
|
||||||
|
port: dockerPort,
|
||||||
|
timeout: dockerTimeout,
|
||||||
|
}) as any;
|
||||||
|
try {
|
||||||
|
await docker.pingAsync();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exit with message if no devices found
|
||||||
|
if (_.isEmpty(activeLocalDevices)) {
|
||||||
|
// TODO: Consider whether this should really be an error
|
||||||
|
throw new ExpectedError(
|
||||||
|
process.platform === 'win32'
|
||||||
|
? ScanCmd.noDevicesFoundMessage + ScanCmd.windowsTipMessage
|
||||||
|
: ScanCmd.noDevicesFoundMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query devices for info
|
||||||
|
const devicesInfo = await Bluebird.map(
|
||||||
|
activeLocalDevices,
|
||||||
|
({ host, address }) => {
|
||||||
|
const docker = dockerUtils.createClient({
|
||||||
|
host: address,
|
||||||
|
port: dockerPort,
|
||||||
|
timeout: dockerTimeout,
|
||||||
|
}) as any;
|
||||||
|
return Bluebird.props({
|
||||||
|
host,
|
||||||
|
address,
|
||||||
|
dockerInfo: docker
|
||||||
|
.infoAsync()
|
||||||
|
.catchReturn('Could not get Docker info'),
|
||||||
|
dockerVersion: docker
|
||||||
|
.versionAsync()
|
||||||
|
.catchReturn('Could not get Docker version'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reduce properties if not --verbose
|
||||||
|
if (!options.verbose) {
|
||||||
|
devicesInfo.forEach((d: any) => {
|
||||||
|
d.dockerInfo = _.isObject(d.dockerInfo)
|
||||||
|
? _.pick(d.dockerInfo, ScanCmd.dockerInfoProperties)
|
||||||
|
: d.dockerInfo;
|
||||||
|
d.dockerVersion = _.isObject(d.dockerVersion)
|
||||||
|
? _.pick(d.dockerVersion, ScanCmd.dockerVersionProperties)
|
||||||
|
: d.dockerVersion;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output results
|
||||||
|
console.log(prettyjson.render(devicesInfo, { noColor: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static dockerInfoProperties = [
|
||||||
|
'Containers',
|
||||||
|
'ContainersRunning',
|
||||||
|
'ContainersPaused',
|
||||||
|
'ContainersStopped',
|
||||||
|
'Images',
|
||||||
|
'Driver',
|
||||||
|
'SystemTime',
|
||||||
|
'KernelVersion',
|
||||||
|
'OperatingSystem',
|
||||||
|
'Architecture',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static dockerVersionProperties = ['Version', 'ApiVersion'];
|
||||||
|
|
||||||
|
protected static noDevicesFoundMessage =
|
||||||
|
'Could not find any balenaOS devices on the local network.';
|
||||||
|
|
||||||
|
protected static windowsTipMessage = `
|
||||||
|
|
||||||
|
Note for Windows users:
|
||||||
|
The 'scan' command relies on the Bonjour service. Check whether Bonjour is
|
||||||
|
installed (Control Panel > Programs and Features). If not, you can download
|
||||||
|
Bonjour for Windows (included with Bonjour Print Services) from here:
|
||||||
|
https://support.apple.com/kb/DL999
|
||||||
|
|
||||||
|
After installing Bonjour, restart your PC and run the 'balena scan' command
|
||||||
|
again.`;
|
||||||
|
}
|
@ -22,7 +22,6 @@ module.exports =
|
|||||||
tags: require('./tags')
|
tags: require('./tags')
|
||||||
logs: require('./logs')
|
logs: require('./logs')
|
||||||
local: require('./local')
|
local: require('./local')
|
||||||
scan: require('./scan')
|
|
||||||
help: require('./help')
|
help: require('./help')
|
||||||
os: require('./os')
|
os: require('./os')
|
||||||
config: require('./config')
|
config: require('./config')
|
||||||
|
19
lib/actions/local/common.d.ts
vendored
Normal file
19
lib/actions/local/common.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2020 Balena Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const dockerPort: number;
|
||||||
|
export const dockerTimeout: number;
|
@ -1,115 +0,0 @@
|
|||||||
###
|
|
||||||
Copyright 2017 Balena
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
###
|
|
||||||
|
|
||||||
{ getVisuals } = require('../utils/lazy')
|
|
||||||
|
|
||||||
dockerInfoProperties = [
|
|
||||||
'Containers'
|
|
||||||
'ContainersRunning'
|
|
||||||
'ContainersPaused'
|
|
||||||
'ContainersStopped'
|
|
||||||
'Images'
|
|
||||||
'Driver'
|
|
||||||
'SystemTime'
|
|
||||||
'KernelVersion'
|
|
||||||
'OperatingSystem'
|
|
||||||
'Architecture'
|
|
||||||
]
|
|
||||||
|
|
||||||
dockerVersionProperties = [
|
|
||||||
'Version'
|
|
||||||
'ApiVersion'
|
|
||||||
]
|
|
||||||
|
|
||||||
scanErrorMessage = 'Could not find any balenaOS devices in the local network.'
|
|
||||||
|
|
||||||
winScanErrorMessage = scanErrorMessage + """
|
|
||||||
\n
|
|
||||||
Note for Windows users:
|
|
||||||
The 'scan' command relies on the Bonjour service. Check whether Bonjour is
|
|
||||||
installed (Control Panel > Programs and Features). If not, you can download
|
|
||||||
Bonjour for Windows (included with Bonjour Print Services) from here:
|
|
||||||
https://support.apple.com/kb/DL999
|
|
||||||
|
|
||||||
After installing Bonjour, restart your PC and run the 'balena scan' command
|
|
||||||
again.
|
|
||||||
"""
|
|
||||||
|
|
||||||
module.exports =
|
|
||||||
signature: 'scan'
|
|
||||||
description: 'Scan for balenaOS devices in your local network'
|
|
||||||
help: '''
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
$ balena scan
|
|
||||||
$ balena scan --timeout 120
|
|
||||||
$ balena scan --verbose
|
|
||||||
'''
|
|
||||||
options: [
|
|
||||||
signature: 'verbose'
|
|
||||||
boolean: true
|
|
||||||
description: 'Display full info'
|
|
||||||
alias: 'v'
|
|
||||||
,
|
|
||||||
signature: 'timeout'
|
|
||||||
parameter: 'timeout'
|
|
||||||
description: 'Scan timeout in seconds'
|
|
||||||
alias: 't'
|
|
||||||
]
|
|
||||||
primary: true
|
|
||||||
root: true
|
|
||||||
action: (params, options) ->
|
|
||||||
Promise = require('bluebird')
|
|
||||||
_ = require('lodash')
|
|
||||||
prettyjson = require('prettyjson')
|
|
||||||
{ discover } = require('balena-sync')
|
|
||||||
{ SpinnerPromise } = getVisuals()
|
|
||||||
{ dockerPort, dockerTimeout } = require('./local/common')
|
|
||||||
dockerUtils = require('../utils/docker')
|
|
||||||
{ exitWithExpectedError } = require('../utils/patterns')
|
|
||||||
|
|
||||||
if options.timeout?
|
|
||||||
options.timeout *= 1000
|
|
||||||
|
|
||||||
Promise.try ->
|
|
||||||
new SpinnerPromise
|
|
||||||
promise: discover.discoverLocalBalenaOsDevices(options.timeout)
|
|
||||||
startMessage: 'Scanning for local balenaOS devices..'
|
|
||||||
stopMessage: 'Reporting scan results'
|
|
||||||
.filter ({ address }) ->
|
|
||||||
Promise.try ->
|
|
||||||
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
|
|
||||||
docker.pingAsync()
|
|
||||||
.return(true)
|
|
||||||
.catchReturn(false)
|
|
||||||
.tap (devices) ->
|
|
||||||
if _.isEmpty(devices)
|
|
||||||
exitWithExpectedError(if process.platform == 'win32' then winScanErrorMessage else scanErrorMessage)
|
|
||||||
.map ({ host, address }) ->
|
|
||||||
docker = dockerUtils.createClient(host: address, port: dockerPort, timeout: dockerTimeout)
|
|
||||||
Promise.props
|
|
||||||
dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info')
|
|
||||||
dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version')
|
|
||||||
.then ({ dockerInfo, dockerVersion }) ->
|
|
||||||
|
|
||||||
if not options.verbose
|
|
||||||
dockerInfo = _.pick(dockerInfo, dockerInfoProperties) if _.isObject(dockerInfo)
|
|
||||||
dockerVersion = _.pick(dockerVersion, dockerVersionProperties) if _.isObject(dockerVersion)
|
|
||||||
|
|
||||||
return { host, address, dockerInfo, dockerVersion }
|
|
||||||
.then (devicesInfo) ->
|
|
||||||
console.log(prettyjson.render(devicesInfo, noColor: true))
|
|
@ -107,7 +107,6 @@ capitano.command(actions.ssh.ssh)
|
|||||||
# ---------- Local balenaOS Module ----------
|
# ---------- Local balenaOS Module ----------
|
||||||
capitano.command(actions.local.configure)
|
capitano.command(actions.local.configure)
|
||||||
capitano.command(actions.local.flash)
|
capitano.command(actions.local.flash)
|
||||||
capitano.command(actions.scan)
|
|
||||||
|
|
||||||
# ---------- Public utils ----------
|
# ---------- Public utils ----------
|
||||||
capitano.command(actions.util.availableDrives)
|
capitano.command(actions.util.availableDrives)
|
||||||
@ -135,7 +134,7 @@ exports.run = (argv) ->
|
|||||||
# cmdSignature is literally a string like, for example:
|
# cmdSignature is literally a string like, for example:
|
||||||
# "push <applicationOrDevice>"
|
# "push <applicationOrDevice>"
|
||||||
# ("applicationOrDevice" is NOT replaced with its actual value)
|
# ("applicationOrDevice" is NOT replaced with its actual value)
|
||||||
# In case of failures like an inexistent or invalid command,
|
# In case of failures like an nonexistent or invalid command,
|
||||||
# command.signature.toString() returns '*'
|
# command.signature.toString() returns '*'
|
||||||
cmdSignature = command.signature.toString()
|
cmdSignature = command.signature.toString()
|
||||||
events.trackCommand(cmdSignature)
|
events.trackCommand(cmdSignature)
|
||||||
|
@ -143,6 +143,7 @@ export const convertedCommands = [
|
|||||||
'os:configure',
|
'os:configure',
|
||||||
'settings',
|
'settings',
|
||||||
'version',
|
'version',
|
||||||
|
'scan',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
8
lib/utils/docker-coffee.d.ts
vendored
8
lib/utils/docker-coffee.d.ts
vendored
@ -30,8 +30,14 @@ export interface BuildDockerOptions {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DockerToolbeltOpts {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function getDocker(
|
export function getDocker(
|
||||||
options: BuildDockerOptions,
|
options: BuildDockerOptions,
|
||||||
): Bluebird<DockerToolbelt>;
|
): Bluebird<DockerToolbelt>;
|
||||||
|
|
||||||
export function createClient(options: BuildDockerOptions): DockerToolbelt;
|
export function createClient(opts: DockerToolbeltOpts): DockerToolbelt;
|
||||||
|
@ -27,7 +27,7 @@ Primary commands:
|
|||||||
deploy <appName> [image] Deploy a single image or a multicontainer project to a balena application
|
deploy <appName> [image] Deploy a single image or a multicontainer project to a balena application
|
||||||
join [deviceiporhostname] move a local device to an application on another balena server
|
join [deviceiporhostname] move a local device to an application on another balena server
|
||||||
leave [deviceiporhostname] remove a local device from its balena application
|
leave [deviceiporhostname] remove a local device from its balena application
|
||||||
scan Scan for balenaOS devices in your local network
|
scan scan for balenaOS devices on your local network
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -92,9 +92,20 @@ const GLOBAL_OPTIONS = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
describe('balena help', function() {
|
describe('balena help', function() {
|
||||||
it('should print simple help text', async () => {
|
it('should list primary command summaries', async () => {
|
||||||
const { out, err } = await runCommand('help');
|
const { out, err } = await runCommand('help');
|
||||||
|
|
||||||
|
console.log('ONE');
|
||||||
|
console.log(cleanOutput(out));
|
||||||
|
console.log(
|
||||||
|
cleanOutput([
|
||||||
|
SIMPLE_HELP,
|
||||||
|
'Run `balena help --verbose` to list additional commands',
|
||||||
|
GLOBAL_OPTIONS,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
console.log();
|
||||||
|
|
||||||
expect(cleanOutput(out)).to.deep.equal(
|
expect(cleanOutput(out)).to.deep.equal(
|
||||||
cleanOutput([
|
cleanOutput([
|
||||||
SIMPLE_HELP,
|
SIMPLE_HELP,
|
||||||
@ -106,7 +117,7 @@ describe('balena help', function() {
|
|||||||
expect(err.join('')).to.equal('');
|
expect(err.join('')).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print additional commands with the -v flag', async () => {
|
it('should list all command summaries with the -v flag', async () => {
|
||||||
const { out, err } = await runCommand('help -v');
|
const { out, err } = await runCommand('help -v');
|
||||||
|
|
||||||
expect(cleanOutput(out)).to.deep.equal(
|
expect(cleanOutput(out)).to.deep.equal(
|
||||||
@ -118,7 +129,7 @@ describe('balena help', function() {
|
|||||||
expect(err.join('')).to.equal('');
|
expect(err.join('')).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print simple help text when no arguments present', async () => {
|
it('should list primary command summaries', async () => {
|
||||||
const { out, err } = await runCommand('');
|
const { out, err } = await runCommand('');
|
||||||
|
|
||||||
expect(cleanOutput(out)).to.deep.equal(
|
expect(cleanOutput(out)).to.deep.equal(
|
||||||
|
12
typings/balena-sync/index.d.ts
vendored
12
typings/balena-sync/index.d.ts
vendored
@ -20,9 +20,21 @@ declare module 'balena-sync' {
|
|||||||
|
|
||||||
export function capitano(tool: 'balena-cli'): CommandDefinition;
|
export function capitano(tool: 'balena-cli'): CommandDefinition;
|
||||||
|
|
||||||
|
export interface LocalBalenaOsDevice {
|
||||||
|
address: string;
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
declare namespace forms {
|
declare namespace forms {
|
||||||
export function selectLocalBalenaOsDevice(
|
export function selectLocalBalenaOsDevice(
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
): Promise<string>;
|
): Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare namespace discover {
|
||||||
|
export function discoverLocalBalenaOsDevices(
|
||||||
|
timeout?: number,
|
||||||
|
): Promise<LocalBalenaOsDevice[]>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user