Rename applications to fleets (stage 1). See: https://git.io/JRuZr

- Add fleet(s) commands and -f, --fleet flags as aliases to the app(s)
  commands and -a, --app, --application flags.
- Conditionally rename column/row headers and JSON object properties
  from 'application' to 'fleet', with some variations.
- Print warning messages regarding the renaming, provided that stderr
  is attached to an interactive terminal.

Change-type: minor
Resolves: #2302
This commit is contained in:
Paulo Castro 2021-07-15 14:41:38 +01:00
parent c3406603db
commit 64a44e7a5f
69 changed files with 2117 additions and 1276 deletions

View File

@ -32,11 +32,11 @@ Please describe what actually happened instead:
Examples:
```
balena push myApp
balena push myFleet
balena push 192.168.0.12
balena deploy myApp
balena deploy myApp --build
balena build . -a myApp
balena deploy myFleet
balena deploy myFleet --build
balena build . -f myFleet
balena build . -A armv7hf -d raspberrypi3
```
@ -48,7 +48,7 @@ additional information. The `--logs` option reveals additional information for t
```
balena build . --logs
balena deploy myApp --build --logs
balena deploy myFleet --build --logs
```
# Steps to Reproduce the Problem

View File

@ -1,5 +1,4 @@
module.exports = {
spec: 'tests/commands/app/create.spec.ts',
reporter: 'spec',
require: 'ts-node/register/transpile-only',
file: './tests/config-tests',

View File

@ -57,7 +57,7 @@ guide](https://docs.docker.com/compose/completion/) for system setup instruction
## Logging in
Several CLI commands require access to your balenaCloud account, for example in order to push a
new release to your application. Those commands require creating a CLI login session by running:
new release to your fleet. Those commands require creating a CLI login session by running:
```sh
$ balena login

View File

@ -34,15 +34,22 @@ const capitanoDoc = {
files: ['build/commands/api-key/generate.js'],
},
{
title: 'Application',
title: 'Fleet',
files: [
'build/commands/apps.js',
'build/commands/fleets.js',
'build/commands/app/index.js',
'build/commands/fleet/index.js',
'build/commands/app/create.js',
'build/commands/fleet/create.js',
'build/commands/app/purge.js',
'build/commands/fleet/purge.js',
'build/commands/app/rename.js',
'build/commands/fleet/rename.js',
'build/commands/app/restart.js',
'build/commands/fleet/restart.js',
'build/commands/app/rm.js',
'build/commands/fleet/rm.js',
],
},
{

View File

@ -58,7 +58,7 @@ class FakeHelpCommand {
examples = [
'$ balena help',
'$ balena help apps',
'$ balena help login',
'$ balena help os download',
];

View File

@ -8,7 +8,7 @@ _balena() {
local context state line curcontext="$curcontext"
# Valid top-level completions
main_commands=( apps build deploy envs join keys leave login logout logs note orgs preload push scan settings ssh support tags tunnel version whoami api-key app app config device device devices env internal key key local os tag util )
main_commands=( apps build deploy envs fleets join keys leave login logout logs note orgs preload push scan settings ssh support tags tunnel version whoami api-key app app config device device devices env fleet fleet internal key key local os tag util )
# Sub-completions
api_key_cmds=( generate )
app_cmds=( create purge rename restart rm )
@ -16,6 +16,7 @@ _balena() {
device_cmds=( deactivate identify init local-mode move os-update public-url purge reboot register rename restart rm shutdown )
devices_cmds=( supported )
env_cmds=( add rename rm )
fleet_cmds=( create purge rename restart rm )
internal_cmds=( osinit )
key_cmds=( add rm )
local_cmds=( configure flash )
@ -57,6 +58,9 @@ _balena_sec_cmds() {
"env")
_describe -t env_cmds 'env_cmd' env_cmds "$@" && ret=0
;;
"fleet")
_describe -t fleet_cmds 'fleet_cmd' fleet_cmds "$@" && ret=0
;;
"internal")
_describe -t internal_cmds 'internal_cmd' internal_cmds "$@" && ret=0
;;

View File

@ -7,7 +7,7 @@ _balena_complete()
local cur prev
# Valid top-level completions
main_commands="apps build deploy envs join keys leave login logout logs note orgs preload push scan settings ssh support tags tunnel version whoami api-key app app config device device devices env internal key key local os tag util"
main_commands="apps build deploy envs fleets join keys leave login logout logs note orgs preload push scan settings ssh support tags tunnel version whoami api-key app app config device device devices env fleet fleet internal key key local os tag util"
# Sub-completions
api_key_cmds="generate"
app_cmds="create purge rename restart rm"
@ -15,6 +15,7 @@ _balena_complete()
device_cmds="deactivate identify init local-mode move os-update public-url purge reboot register rename restart rm shutdown"
devices_cmds="supported"
env_cmds="add rename rm"
fleet_cmds="create purge rename restart rm"
internal_cmds="osinit"
key_cmds="add rm"
local_cmds="configure flash"
@ -51,6 +52,9 @@ _balena_complete()
env)
COMPREPLY=( $(compgen -W "$env_cmds" -- $cur) )
;;
fleet)
COMPREPLY=( $(compgen -W "$fleet_cmds" -- $cur) )
;;
internal)
COMPREPLY=( $(compgen -W "$internal_cmds" -- $cur) )
;;

View File

@ -7,7 +7,7 @@ It requires collecting some preliminary information _once_.
The final command to provision the device looks like this:
```bash
balena device init --app APP_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
balena device init --fleet FLEET_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
```
@ -24,7 +24,7 @@ But before you can run it you need to collect the parameters and build the confi
```
and find the _slug_ for your target device type, like _raspberrypi3_.
1. `APP_ID`. Create an application (`balena app create APP_NAME --type DEVICE_TYPE`) or find an existing one (`balena apps`) and notice its ID.
1. `FLEET_ID`. Create a fleet (`balena fleet create FLEET_NAME --type DEVICE_TYPE`) or find an existing one (`balena fleets`) and notice its ID.
1. `OS_VERSION`. Run
```bash

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
* Copyright 2016-2021 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,14 @@
*/
import { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import type { Application } from 'balena-sdk';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import type { Application } from 'balena-sdk';
import { appToFleetCmdMsg, warnify } from '../../utils/messages';
interface FlagsDef {
organization?: string;
@ -32,18 +35,18 @@ interface ArgsDef {
name: string;
}
export default class AppCreateCmd extends Command {
export class FleetCreateCmd extends Command {
public static description = stripIndent`
Create an application.
Create a fleet.
Create a new balena application.
Create a new balena fleet.
You can specify the organization the application should belong to using
You can specify the organization the fleet should belong to using
the \`--organization\` option. The organization's handle, not its name,
should be provided. Organization handles can be listed with the
\`balena orgs\` command.
The application's default device type is specified with the \`--type\` option.
The fleet's default device type is specified with the \`--type\` option.
The \`balena devices supported\` command can be used to list the available
device types.
@ -55,41 +58,39 @@ export default class AppCreateCmd extends Command {
`;
public static examples = [
'$ balena app create MyApp',
'$ balena app create MyApp --organization mmyorg',
'$ balena app create MyApp -o myorg --type raspberry-pi',
'$ balena fleet create MyFleet',
'$ balena fleet create MyFleet --organization mmyorg',
'$ balena fleet create MyFleet -o myorg --type raspberry-pi',
];
public static args = [
{
name: 'name',
description: 'application name',
description: 'fleet name',
required: true,
},
];
public static usage = 'app create <name>';
public static usage = 'fleet create <name>';
public static flags: flags.Input<FlagsDef> = {
organization: flags.string({
char: 'o',
description:
'handle of the organization the application should belong to',
description: 'handle of the organization the fleet should belong to',
}),
type: flags.string({
char: 't',
description:
'application device type (Check available types with `balena devices supported`)',
'fleet device type (Check available types with `balena devices supported`)',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
AppCreateCmd,
);
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params, flags: options } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetCreateCmd);
// Ascertain device type
const deviceType =
@ -112,12 +113,12 @@ export default class AppCreateCmd extends Command {
if ((err.message || '').toLowerCase().includes('unique')) {
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
throw new ExpectedError(
`Error: application "${params.name}" already exists in organization "${organization}".`,
`Error: fleet "${params.name}" already exists in organization "${organization}".`,
);
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
// BalenaRequestError: Request error: Unauthorized
throw new ExpectedError(
`Error: You are not authorized to create applications in organization "${organization}".`,
`Error: You are not authorized to create fleets in organization "${organization}".`,
);
}
@ -128,8 +129,8 @@ export default class AppCreateCmd extends Command {
const { isV13 } = await import('../../utils/version');
console.log(
isV13()
? `Application created: slug "${application.slug}", device type "${deviceType}"`
: `Application created: ${application.slug} (${deviceType}, id ${application.id})`,
? `Fleet created: slug "${application.slug}", device type "${deviceType}"`
: `Fleet created: ${application.slug} (${deviceType}, id ${application.id})`,
);
}
@ -150,3 +151,31 @@ export default class AppCreateCmd extends Command {
}
}
}
export default class AppCreateCmd extends FleetCreateCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleet create' command
${appToFleetCmdMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleet create'
`;
public static examples = [];
public static usage = 'app create <name>';
public static args = FleetCreateCmd.args;
public static flags = FleetCreateCmd.flags;
public static authenticated = FleetCreateCmd.authenticated;
public static primary = FleetCreateCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppCreateCmd);
if (process.stderr.isTTY) {
console.error(warnify(appToFleetCmdMsg));
}
await super.run(parserOutput);
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
* Copyright 2016-2021 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,35 +15,44 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import type { Release } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import type { Release } from 'balena-sdk';
import {
applicationIdInfo,
appToFleetCmdMsg,
warnify,
} from '../../utils/messages';
interface FlagsDef {
help: void;
}
interface ArgsDef {
application: string;
fleet: string;
}
export default class AppCmd extends Command {
export class FleetCmd extends Command {
public static description = stripIndent`
Display information about a single application.
Display information about a single fleet.
Display detailed information about a single balena application.
Display detailed information about a single fleet.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = ['$ balena app MyApp', '$ balena app myorg/myapp'];
public static examples = [
'$ balena fleet MyFleet',
'$ balena fleet myorg/myfleet',
];
public static args = [ca.applicationRequired];
public static args = [ca.fleetRequired];
public static usage = 'app <nameOrSlug>';
public static usage = 'fleet <fleet>';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
@ -52,21 +61,18 @@ export default class AppCmd extends Command {
public static authenticated = true;
public static primary = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd);
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetCmd);
const { getApplication } = await import('../../utils/sdk');
const application = (await getApplication(
getBalenaSdk(),
params.application,
{
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
const application = (await getApplication(getBalenaSdk(), params.fleet, {
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
)) as ApplicationWithDeviceType & {
})) as ApplicationWithDeviceType & {
should_be_running__release: [Release?];
// For display purposes:
device_type: string;
@ -88,3 +94,31 @@ export default class AppCmd extends Command {
);
}
}
export default class AppCmd extends FleetCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleet' command
${appToFleetCmdMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleet'
`;
public static examples = [];
public static usage = 'app <fleet>';
public static args = FleetCmd.args;
public static flags = FleetCmd.flags;
public static authenticated = FleetCmd.authenticated;
public static primary = FleetCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppCmd);
if (process.stderr.isTTY) {
console.error(warnify(appToFleetCmdMsg));
}
await super.run(parserOutput);
}
}

View File

@ -15,39 +15,45 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetCmdMsg,
warnify,
} from '../../utils/messages';
interface FlagsDef {
help: void;
}
interface ArgsDef {
application: string;
fleet: string;
}
export default class AppPurgeCmd extends Command {
export class FleetPurgeCmd extends Command {
public static description = stripIndent`
Purge data from an application.
Purge data from a fleet.
Purge data from all devices belonging to an application.
This will clear the application's /data directory.
Purge data from all devices belonging to a fleet.
This will clear the fleet's '/data' directory.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena app purge MyApp',
'$ balena app purge myorg/myapp',
'$ balena fleet purge MyFleet',
'$ balena fleet purge myorg/myfleet',
];
public static args = [ca.applicationRequired];
public static args = [ca.fleetRequired];
public static usage = 'app purge <application>';
public static usage = 'fleet purge <fleet>';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
@ -55,8 +61,9 @@ export default class AppPurgeCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppPurgeCmd);
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetPurgeCmd);
const { getApplication } = await import('../../utils/sdk');
@ -64,7 +71,7 @@ export default class AppPurgeCmd extends Command {
// balena.models.application.purge only accepts a numeric id
// so we must first fetch the app to get it's id,
const application = await getApplication(balena, params.application);
const application = await getApplication(balena, params.fleet);
try {
await balena.models.application.purge(application.id);
@ -78,3 +85,31 @@ export default class AppPurgeCmd extends Command {
}
}
}
export default class AppPurgeCmd extends FleetPurgeCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleet purge' command
${appToFleetCmdMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleet purge'
`;
public static examples = [];
public static usage = 'app purge <fleet>';
public static args = FleetPurgeCmd.args;
public static flags = FleetPurgeCmd.flags;
public static authenticated = FleetPurgeCmd.authenticated;
public static primary = FleetPurgeCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppPurgeCmd);
if (process.stderr.isTTY) {
console.error(warnify(appToFleetCmdMsg));
}
await super.run(parserOutput);
}
}

View File

@ -15,28 +15,34 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import type { ApplicationType } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import type { ApplicationType } from 'balena-sdk';
import {
applicationIdInfo,
appToFleetCmdMsg,
warnify,
} from '../../utils/messages';
interface FlagsDef {
help: void;
}
interface ArgsDef {
application: string;
fleet: string;
newName?: string;
}
export default class AppRenameCmd extends Command {
export class FleetRenameCmd extends Command {
public static description = stripIndent`
Rename an application.
Rename a fleet.
Rename an application.
Rename a fleet.
Note, if the \`newName\` parameter is omitted, it will be
prompted for interactively.
@ -45,20 +51,20 @@ export default class AppRenameCmd extends Command {
`;
public static examples = [
'$ balena app rename OldName',
'$ balena app rename OldName NewName',
'$ balena app rename myorg/oldname NewName',
'$ balena fleet rename OldName',
'$ balena fleet rename OldName NewName',
'$ balena fleet rename myorg/oldname NewName',
];
public static args = [
ca.applicationRequired,
ca.fleetRequired,
{
name: 'newName',
description: 'the new name for the application',
description: 'the new name for the fleet',
},
];
public static usage = 'app rename <application> [newName]';
public static usage = 'fleet rename <fleet> [newName]';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
@ -66,8 +72,9 @@ export default class AppRenameCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppRenameCmd);
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetRenameCmd);
const { validateApplicationName } = await import('../../utils/validation');
const { ExpectedError } = await import('../../errors');
@ -76,7 +83,7 @@ export default class AppRenameCmd extends Command {
// Disambiguate target application (if params.params is a number, it could either be an ID or a numerical name)
const { getApplication } = await import('../../utils/sdk');
const application = await getApplication(balena, params.application, {
const application = await getApplication(balena, params.fleet, {
$expand: {
application_type: {
$select: ['is_legacy'],
@ -86,16 +93,14 @@ export default class AppRenameCmd extends Command {
// Check app exists
if (!application) {
throw new ExpectedError(
'Error: application ${params.nameOrSlug} not found.',
);
throw new ExpectedError(`Error: fleet ${params.fleet} not found.`);
}
// Check app supports renaming
const appType = (application.application_type as ApplicationType[])?.[0];
if (appType.is_legacy) {
throw new ExpectedError(
`Application ${params.application} is of 'legacy' type, and cannot be renamed.`,
`Fleet ${params.fleet} is of 'legacy' type, and cannot be renamed.`,
);
}
@ -103,7 +108,7 @@ export default class AppRenameCmd extends Command {
const newName =
params.newName ||
(await getCliForm().ask({
message: 'Please enter the new name for this application:',
message: 'Please enter the new name for this fleet:',
type: 'input',
validate: validateApplicationName,
})) ||
@ -115,9 +120,7 @@ export default class AppRenameCmd extends Command {
} catch (e) {
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
if ((e.message || '').toLowerCase().includes('unique')) {
throw new ExpectedError(
`Error: application ${params.application} already exists.`,
);
throw new ExpectedError(`Error: fleet ${params.fleet} already exists.`);
}
throw e;
}
@ -128,7 +131,7 @@ export default class AppRenameCmd extends Command {
);
// Output result
console.log(`Application renamed`);
console.log(`Fleet renamed`);
console.log('From:');
console.log(`\tname: ${application.app_name}`);
console.log(`\tslug: ${application.slug}`);
@ -137,3 +140,31 @@ export default class AppRenameCmd extends Command {
console.log(`\tslug: ${renamedApplication.slug}`);
}
}
export default class AppRenameCmd extends FleetRenameCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleet rename' command
${appToFleetCmdMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleet rename'
`;
public static examples = [];
public static usage = 'app rename <fleet> [newName]';
public static args = FleetRenameCmd.args;
public static flags = FleetRenameCmd.flags;
public static authenticated = FleetRenameCmd.authenticated;
public static primary = FleetRenameCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppRenameCmd);
if (process.stderr.isTTY) {
console.error(warnify(appToFleetCmdMsg));
}
await super.run(parserOutput);
}
}

View File

@ -15,38 +15,44 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetCmdMsg,
warnify,
} from '../../utils/messages';
interface FlagsDef {
help: void;
}
interface ArgsDef {
application: string;
fleet: string;
}
export default class AppRestartCmd extends Command {
export class FleetRestartCmd extends Command {
public static description = stripIndent`
Restart an application.
Restart a fleet.
Restart all devices belonging to an application.
Restart all devices belonging to a fleet.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena app restart MyApp',
'$ balena app restart myorg/myapp',
'$ balena fleet restart MyFleet',
'$ balena fleet restart myorg/myfleet',
];
public static args = [ca.applicationRequired];
public static args = [ca.fleetRequired];
public static usage = 'app restart <application>';
public static usage = 'fleet restart <fleet>';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
@ -54,16 +60,45 @@ export default class AppRestartCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppRestartCmd);
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetRestartCmd);
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk();
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
const application = await getApplication(balena, params.application);
const application = await getApplication(balena, params.fleet);
await balena.models.application.restart(application.id);
}
}
export default class AppRestartCmd extends FleetRestartCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleet restart' command
${appToFleetCmdMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleet restart'
`;
public static examples = [];
public static usage = 'app restart <fleet>';
public static args = FleetRestartCmd.args;
public static flags = FleetRestartCmd.flags;
public static authenticated = FleetRestartCmd.authenticated;
public static primary = FleetRestartCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppRestartCmd);
if (process.stderr.isTTY) {
console.error(warnify(appToFleetCmdMsg));
}
await super.run(parserOutput);
}
}

View File

@ -15,12 +15,18 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetCmdMsg,
warnify,
} from '../../utils/messages';
interface FlagsDef {
yes: boolean;
@ -28,14 +34,14 @@ interface FlagsDef {
}
interface ArgsDef {
application: string;
fleet: string;
}
export default class AppRmCmd extends Command {
export class FleetRmCmd extends Command {
public static description = stripIndent`
Remove an application.
Remove a fleet.
Permanently remove a balena application.
Permanently remove a fleet.
The --yes option may be used to avoid interactive confirmation.
@ -43,14 +49,14 @@ export default class AppRmCmd extends Command {
`;
public static examples = [
'$ balena app rm MyApp',
'$ balena app rm MyApp --yes',
'$ balena app rm myorg/myapp',
'$ balena fleet rm MyFleet',
'$ balena fleet rm MyFleet --yes',
'$ balena fleet rm myorg/myfleet',
];
public static args = [ca.applicationRequired];
public static args = [ca.fleetRequired];
public static usage = 'app rm <application>';
public static usage = 'fleet rm <fleet>';
public static flags: flags.Input<FlagsDef> = {
yes: cf.yes,
@ -59,10 +65,9 @@ export default class AppRmCmd extends Command {
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
AppRmCmd,
);
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
const { args: params, flags: options } =
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetRmCmd);
const { confirm } = await import('../../utils/patterns');
const { getApplication } = await import('../../utils/sdk');
@ -71,13 +76,41 @@ export default class AppRmCmd extends Command {
// Confirm
await confirm(
options.yes ?? false,
`Are you sure you want to delete application ${params.application}?`,
`Are you sure you want to delete fleet ${params.fleet}?`,
);
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
const application = await getApplication(balena, params.application);
const application = await getApplication(balena, params.fleet);
// Remove
await balena.models.application.remove(application.id);
}
}
export default class AppRmCmd extends FleetRmCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleet rm' command
${appToFleetCmdMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleet rm'
`;
public static examples = [];
public static usage = 'app rm <fleet>';
public static args = FleetRmCmd.args;
public static flags = FleetRmCmd.flags;
public static authenticated = FleetRmCmd.authenticated;
public static primary = FleetRmCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, ArgsDef>(AppRmCmd);
if (process.stderr.isTTY) {
console.error(warnify(appToFleetCmdMsg));
}
await super.run(parserOutput);
}
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
* Copyright 2016-2021 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,9 +16,12 @@
*/
import { flags } from '@oclif/command';
import type { Output as ParserOutput } from '@oclif/parser';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import { appToFleetCmdMsg, warnify } from '../utils/messages';
interface ExtendedApplication extends ApplicationWithDeviceType {
device_count?: number;
@ -27,22 +30,22 @@ interface ExtendedApplication extends ApplicationWithDeviceType {
interface FlagsDef {
help: void;
verbose: boolean;
verbose?: boolean;
}
export default class AppsCmd extends Command {
export class FleetsCmd extends Command {
public static description = stripIndent`
List all applications.
List all fleets.
list all your balena applications.
List all your balena fleets.
For detailed information on a particular application,
use \`balena app <application>\` instead.
For detailed information on a particular fleet, use
\`balena fleet <fleet>\`
`;
public static examples = ['$ balena apps'];
public static examples = ['$ balena fleets'];
public static usage = 'apps';
public static usage = 'fleets';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
@ -56,8 +59,10 @@ export default class AppsCmd extends Command {
public static authenticated = true;
public static primary = true;
public async run() {
this.parse<FlagsDef, {}>(AppsCmd);
protected useAppWord = false;
public async run(_parserOutput?: ParserOutput<FlagsDef, {}>) {
_parserOutput ||= this.parse<FlagsDef, {}>(FleetsCmd);
const balena = getBalenaSdk();
@ -85,7 +90,7 @@ export default class AppsCmd extends Command {
console.log(
getVisuals().table.horizontal(applications, [
'id',
'app_name',
this.useAppWord ? 'app_name' : 'app_name => NAME',
'slug',
'device_type',
'online_devices',
@ -94,3 +99,36 @@ export default class AppsCmd extends Command {
);
}
}
const appsToFleetsRenameMsg = appToFleetCmdMsg
.replace(/'app'/g, "'apps'")
.replace(/'fleet'/g, "'fleets'");
export default class AppsCmd extends FleetsCmd {
public static description = stripIndent`
DEPRECATED alias for the 'fleets' command
${appsToFleetsRenameMsg
.split('\n')
.map((l) => `\t\t${l}`)
.join('\n')}
For command usage, see 'balena help fleets'
`;
public static examples = [];
public static usage = 'apps';
public static args = FleetsCmd.args;
public static flags = FleetsCmd.flags;
public static authenticated = FleetsCmd.authenticated;
public static primary = FleetsCmd.primary;
public async run() {
// call this.parse() before deprecation message to parse '-h'
const parserOutput = this.parse<FlagsDef, {}>(AppsCmd);
if (process.stderr.isTTY) {
console.error(warnify(appsToFleetsRenameMsg));
}
this.useAppWord = true;
await super.run(parserOutput);
}
}

View File

@ -18,22 +18,27 @@
import { flags } from '@oclif/command';
import Command from '../command';
import { getBalenaSdk } from '../utils/lazy';
import * as cf from '../utils/common-flags';
import * as compose from '../utils/compose';
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
import {
appToFleetFlagMsg,
buildArgDeprecation,
dockerignoreHelp,
registrySecretsHelp,
warnify,
} from '../utils/messages';
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
import { buildProject, composeCliFlags } from '../utils/compose_ts';
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
import { dockerCliFlags } from '../utils/docker';
import { isV13 } from '../utils/version';
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
arch?: string;
deviceType?: string;
application?: string;
fleet?: string;
source?: string; // Not part of command profile - source param copied here.
help: void;
}
@ -51,7 +56,7 @@ the provided docker daemon in your development machine or balena device.
(See also the \`balena push\` command for the option of building images in the
balenaCloud build servers.)
You must provide either an application or a device-type/architecture pair.
You must specify either a fleet, or the device type and architecture.
This command will look into the given source directory (or the current working
directory if one isn't specified) for a docker-compose.yml file, and if found,
@ -65,12 +70,12 @@ ${registrySecretsHelp}
${dockerignoreHelp}
`;
public static examples = [
'$ balena build --application myApp',
'$ balena build ./source/ --application myApp',
'$ balena build --fleet myFleet',
'$ balena build ./source/ --fleet myorg/myfleet',
'$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated',
'$ balena build --docker /var/run/docker.sock --application myApp # Linux, Mac',
'$ balena build --docker //./pipe/docker_engine --application myApp # Windows',
'$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem -a myApp',
'$ balena build --docker /var/run/docker.sock --fleet myFleet # Linux, Mac',
'$ balena build --docker //./pipe/docker_engine --fleet myFleet # Windows',
'$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem -f myFleet',
];
public static args = [
@ -91,10 +96,8 @@ ${dockerignoreHelp}
description: 'the type of device this build is for',
char: 'd',
}),
application: flags.string({
description: 'name of the target balena application this build is for',
char: 'a',
}),
...(isV13() ? {} : { application: cf.application }),
fleet: cf.fleet,
...composeCliFlags,
...dockerCliFlags,
// NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags
@ -109,6 +112,11 @@ ${dockerignoreHelp}
BuildCmd,
);
if (options.application && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
await Command.checkLoggedInIf(!!options.application);
(await import('events')).defaultMaxListeners = 1000;
@ -160,7 +168,7 @@ ${dockerignoreHelp}
) {
const { ExpectedError } = await import('../errors');
throw new ExpectedError(
'You must specify either an application or an arch/deviceType pair to build for',
'You must specify either a fleet (-f), or the device type (-d) and architecture (-A)',
);
}
@ -240,7 +248,7 @@ ${dockerignoreHelp}
!appType.supports_multicontainer
) {
logger.logWarn(
'Target application does not support multiple containers.\n' +
'Target fleet does not support multiple containers.\n' +
'Continuing with build, but you will not be able to deploy.',
);
}

View File

@ -19,13 +19,19 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
import type { PineDeferred } from 'balena-sdk';
interface FlagsDef {
version: string; // OS version
application?: string;
app?: string; // application alias
fleet?: string;
device?: string;
deviceApiKey?: string;
deviceType?: string;
@ -43,16 +49,15 @@ export default class ConfigGenerateCmd extends Command {
public static description = stripIndent`
Generate a config.json file.
Generate a config.json file for a device or application.
Generate a config.json file for a device or fleet.
Calling this command with the exact version number of the targeted image is required.
The target balenaOS version must be specified with the --version option.
This command is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
To configure an image for a fleet of mixed device types, use the --fleet option
alongside the --deviceType option to specify the target device type.
In case that you want to configure an image for an application with mixed device types,
you can pass the --deviceType argument along with --application to specify the target device type.
To avoid interactive questions, specify a command line option for each question that
would otherwise be asked.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
@ -62,11 +67,11 @@ export default class ConfigGenerateCmd extends Command {
'$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
'$ balena config generate --app MyApp --version 2.12.7',
'$ balena config generate --app myorg/myapp --version 2.12.7',
'$ balena config generate --app MyApp --version 2.12.7 --deviceType fincm3',
'$ balena config generate --app MyApp --version 2.12.7 --output config.json',
'$ balena config generate --app MyApp --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1',
'$ balena config generate --fleet MyFleet --version 2.12.7',
'$ balena config generate --fleet myorg/myfleet --version 2.12.7',
'$ balena config generate --fleet MyFleet --version 2.12.7 --deviceType fincm3',
'$ balena config generate --fleet MyFleet --version 2.12.7 --output config.json',
'$ balena config generate --fleet MyFleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
];
public static usage = 'config generate';
@ -76,20 +81,28 @@ export default class ConfigGenerateCmd extends Command {
description: 'a balenaOS version',
required: true,
}),
application: { ...cf.application, exclusive: ['app', 'device'] },
app: { ...cf.app, exclusive: ['application', 'device'] },
device: flags.string({
description: 'device uuid',
char: 'd',
exclusive: ['application', 'app'],
}),
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'device'],
},
app: { ...cf.app, exclusive: ['application', 'fleet', 'device'] },
appUpdatePollInterval: flags.string({
description: 'DEPRECATED alias for --updatePollInterval',
}),
}),
fleet: { ...cf.fleet, exclusive: ['application', 'app', 'device'] },
device: { ...cf.device, exclusive: ['application', 'app', 'fleet'] },
deviceApiKey: flags.string({
description:
'custom device key - note that this is only supported on balenaOS 2.0.3+',
char: 'k',
}),
deviceType: flags.string({
description: 'device type slug',
description:
"device type slug (run 'balena devices supported' for possible values)",
}),
'generate-device-api-key': flags.boolean({
description: 'generate a fresh device key for the device',
@ -113,7 +126,7 @@ export default class ConfigGenerateCmd extends Command {
}),
appUpdatePollInterval: flags.string({
description:
'how frequently (in minutes) to poll for application updates',
'supervisor cloud polling interval in minutes (e.g. for variable updates)',
}),
help: cf.help,
};
@ -143,8 +156,8 @@ export default class ConfigGenerateCmd extends Command {
if (!rawDevice.belongs_to__application) {
const { ExpectedError } = await import('../../errors');
throw new ExpectedError(stripIndent`
Device ${options.device} does not appear to belong to an accessible application.
Try with a different device, or use '--application' instead of '--device'.`);
Device ${options.device} does not appear to belong to an accessible fleet.
Try with a different device, or use '--fleet' instead of '--device'.`);
}
device = rawDevice as DeviceWithDeviceType & {
belongs_to__application: PineDeferred;
@ -177,7 +190,7 @@ export default class ConfigGenerateCmd extends Command {
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
) {
throw new balena.errors.BalenaInvalidDeviceType(
`Device type ${options.deviceType} is incompatible with application ${options.application}`,
`Device type ${options.deviceType} is incompatible with fleet ${options.application}`,
);
}
}
@ -218,7 +231,7 @@ export default class ConfigGenerateCmd extends Command {
}
protected readonly missingDeviceOrAppMessage = stripIndent`
Either a device or an application must be specified.
Either a device or a fleet must be specified.
See the help page for examples:
@ -226,13 +239,16 @@ export default class ConfigGenerateCmd extends Command {
`;
protected readonly deviceTypeNotAllowedMessage =
'The --deviceType option can only be used alongside the --application option';
'The --deviceType option can only be used alongside the --fleet option';
protected async validateOptions(options: FlagsDef) {
const { ExpectedError } = await import('../../errors');
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
// Prefer options.application over options.app
options.application = options.application || options.app;
delete options.app;
if (options.device == null && options.application == null) {

View File

@ -26,6 +26,7 @@ import {
registrySecretsHelp,
buildArgDeprecation,
} from '../utils/messages';
import * as ca from '../utils/common-args';
import * as compose from '../utils/compose';
import type {
BuiltImage,
@ -62,18 +63,18 @@ interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
}
interface ArgsDef {
appName: string;
fleet: string;
image?: string;
}
export default class DeployCmd extends Command {
public static description = `\
Deploy a single image or a multicontainer project to a balena application.
Deploy a single image or a multicontainer project to a balena fleet.
Usage: \`deploy <appName> ([image] | --build [--source build-dir])\`
Usage: \`deploy <fleet> ([image] | --build [--source build-dir])\`
Use this command to deploy an image or a complete multicontainer project to an
application, optionally building it first. The source images are searched for
Use this command to deploy an image or a complete multicontainer project to a
fleet, optionally building it first. The source images are searched for
(and optionally built) using the docker daemon in your development machine or
balena device. (See also the \`balena push\` command for the option of building
the image in the balenaCloud build servers.)
@ -88,8 +89,8 @@ If a compose file isn't found, the command will look for a Dockerfile[.template]
file (or alternative Dockerfile specified with the \`-f\` option), and if yet
that isn't found, it will try to generate one.
To deploy to an app on which you're a collaborator, use
\`balena deploy <appOwnerUsername>/<appName>\`.
To deploy to a fleet where you are a collaborator, use fleet slug including the
organization: \`balena deploy <organization>/<fleet>\`.
${registrySecretsHelp}
@ -97,25 +98,21 @@ ${dockerignoreHelp}
`;
public static examples = [
'$ balena deploy myApp',
'$ balena deploy myApp --build --source myBuildDir/',
'$ balena deploy myApp myApp/myImage',
'$ balena deploy myApp myApp/myImage --release-tag key1 "" key2 "value2 with spaces"',
'$ balena deploy myFleet',
'$ balena deploy myorg/myfleet --build --source myBuildDir/',
'$ balena deploy myorg/myfleet myRepo/myImage',
'$ balena deploy myFleet myRepo/myImage --release-tag key1 "" key2 "value2 with spaces"',
];
public static args = [
{
name: 'appName',
description: 'the name of the application to deploy to',
required: true,
},
ca.fleetRequired,
{
name: 'image',
description: 'the image to deploy',
},
];
public static usage = 'deploy <appName> [image]';
public static usage = 'deploy <fleet> [image]';
public static flags: flags.Input<FlagsDef> = {
source: flags.string({
@ -160,7 +157,7 @@ ${dockerignoreHelp}
const logger = await Command.getLogger();
logger.logDebug('Parsing input...');
const { appName, image } = params;
const { fleet, image } = params;
// Build args are under consideration for removal - warn user
if (options.buildArg) {
@ -200,7 +197,7 @@ ${dockerignoreHelp}
}
const helpers = await import('../utils/helpers');
const app = await helpers.getAppWithArch(appName);
const app = await helpers.getAppWithArch(fleet);
const dockerUtils = await import('../utils/docker');
const [docker, buildOpts, composeOpts] = await Promise.all([
@ -211,7 +208,7 @@ ${dockerignoreHelp}
const release = await this.deployProject(docker, logger, composeOpts, {
app,
appName, // may be prefixed by 'owner/', unlike app.app_name
appName: fleet, // may be prefixed by 'owner/', unlike app.app_name
image,
shouldPerformBuild: !!options.build,
shouldUploadLogs: !options.nologupload,
@ -254,7 +251,7 @@ ${dockerignoreHelp}
const project = await loadProject(logger, composeOpts, opts.image);
if (project.descriptors.length > 1 && !appType?.supports_multicontainer) {
throw new ExpectedError(
'Target application does not support multiple containers. Aborting!',
'Target fleet does not support multiple containers. Aborting!',
);
}
@ -326,7 +323,7 @@ ${dockerignoreHelp}
const { deployLegacy } = require('../utils/deploy-legacy');
const msg = getChalk().yellow(
'Target application requires legacy deploy method.',
'Target fleet requires legacy deploy method.',
);
logger.logWarn(msg);

View File

@ -21,7 +21,10 @@ import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { appToFleetOutputMsg, warnify } from '../../utils/messages';
import { tryAsInteger } from '../../utils/validation';
import { isV13 } from '../../utils/version';
import type { Application, Release } from 'balena-sdk';
interface ExtendedDevice extends DeviceWithDeviceType {
@ -43,6 +46,7 @@ interface ExtendedDevice extends DeviceWithDeviceType {
interface FlagsDef {
help: void;
v13: boolean;
}
interface ArgsDef {
@ -70,13 +74,17 @@ export default class DeviceCmd extends Command {
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
v13: cf.v13,
};
public static authenticated = true;
public static primary = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceCmd);
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceCmd,
);
const useAppWord = !options.v13 && !isV13();
const balena = getBalenaSdk();
@ -162,6 +170,10 @@ export default class DeviceCmd extends Command {
);
}
if (useAppWord && process.stderr.isTTY) {
console.error(warnify(appToFleetOutputMsg));
}
console.log(
getVisuals().table.vertical(device, [
`$${device.device_name}$`,
@ -172,7 +184,7 @@ export default class DeviceCmd extends Command {
'ip_address',
'public_address',
'mac_address',
'application_name',
useAppWord ? 'application_name' : 'application_name => FLEET',
'last_seen',
'uuid',
'commit',

View File

@ -19,12 +19,18 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { runCommand } from '../../utils/helpers';
import { isV13 } from '../../utils/version';
interface FlagsDef {
application?: string;
app?: string;
fleet?: string;
yes: boolean;
advanced: boolean;
'os-version'?: string;
@ -37,26 +43,30 @@ export default class DeviceInitCmd extends Command {
public static description = stripIndent`
Initialize a device with balenaOS.
Initialize a device by downloading the OS image of a certain application
Initialize a device by downloading the OS image of the specified fleet
and writing it to an SD Card.
Note, if the application option is omitted it will be prompted
for interactively.
If the --fleet option is omitted, it will be prompted for interactively.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena device init',
'$ balena device init --application MyApp',
'$ balena device init -a myorg/myapp',
'$ balena device init --fleet MyFleet',
'$ balena device init -f myorg/myfleet',
];
public static usage = 'device init';
public static flags: flags.Input<FlagsDef> = {
application: cf.application,
app: cf.app,
...(isV13()
? {}
: {
application: cf.application,
app: cf.app,
}),
fleet: cf.fleet,
yes: cf.yes,
advanced: flags.boolean({
char: 'v',
@ -95,8 +105,11 @@ export default class DeviceInitCmd extends Command {
const logger = await Command.getLogger();
const balena = getBalenaSdk();
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
// Consolidate application options
options.application = options.application || options.app;
options.application ||= options.app || options.fleet;
delete options.app;
// Get application and

View File

@ -15,14 +15,19 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import type { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import type { Application, BalenaSDK } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import { ExpectedError } from '../../errors';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
interface ExtendedDevice extends DeviceWithDeviceType {
application_name?: string;
@ -31,6 +36,7 @@ interface ExtendedDevice extends DeviceWithDeviceType {
interface FlagsDef {
application?: string;
app?: string;
fleet?: string;
help: void;
}
@ -40,12 +46,11 @@ interface ArgsDef {
export default class DeviceMoveCmd extends Command {
public static description = stripIndent`
Move one or more devices to another application.
Move one or more devices to another fleet.
Move one or more devices to another application.
Move one or more devices to another fleet.
Note, if the application option is omitted it will be prompted
for interactively.
If --fleet is omitted, the fleet will be prompted for interactively.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
@ -53,8 +58,8 @@ export default class DeviceMoveCmd extends Command {
public static examples = [
'$ balena device move 7cf02a6',
'$ balena device move 7cf02a6,dc39e52',
'$ balena device move 7cf02a6 --application MyNewApp',
'$ balena device move 7cf02a6 -a myorg/mynewapp',
'$ balena device move 7cf02a6 --fleet MyNewFleet',
'$ balena device move 7cf02a6 -f myorg/mynewfleet',
];
public static args: Array<IArg<any>> = [
@ -69,8 +74,8 @@ export default class DeviceMoveCmd extends Command {
public static usage = 'device move <uuid(s)>';
public static flags: flags.Input<FlagsDef> = {
application: cf.application,
app: cf.app,
...(isV13() ? {} : { app: cf.app, application: cf.application }),
fleet: cf.fleet,
help: cf.help,
};
@ -81,14 +86,16 @@ export default class DeviceMoveCmd extends Command {
DeviceMoveCmd,
);
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
const balena = getBalenaSdk();
const { tryAsInteger } = await import('../../utils/validation');
const { expandForAppName } = await import('../../utils/helpers');
options.application = options.application || options.app;
delete options.app;
// Parse ids string into array of correct types
const deviceIds: Array<string | number> = params.uuid
.split(',')
@ -126,9 +133,7 @@ export default class DeviceMoveCmd extends Command {
for (const uuid of deviceIds) {
try {
await balena.models.device.move(uuid, application.id);
console.info(
`Device ${uuid} was moved to application ${application.slug}`,
);
console.info(`Device ${uuid} was moved to fleet ${application.slug}`);
} catch (err) {
console.info(`${err.message}, uuid: ${uuid}`);
process.exitCode = 1;

View File

@ -31,10 +31,10 @@ interface ArgsDef {
export default class DevicePurgeCmd extends Command {
public static description = stripIndent`
Purge application data from a device.
Purge data from a device.
Purge application data from a device.
This will clear the application's /data directory.
Purge data from a device.
This will clear the device's "/data" directory.
Multiple devices may be specified with a comma-separated list
of values (no spaces).

View File

@ -29,27 +29,29 @@ interface FlagsDef {
}
interface ArgsDef {
application: string;
fleet: string;
}
export default class DeviceRegisterCmd extends Command {
public static description = stripIndent`
Register a device.
Register a new device.
Register a device to an application.
Register a new device with a balena fleet.
If --uuid is not provided, a new UUID will be automatically assigned.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena device register MyApp',
'$ balena device register MyApp --uuid <uuid>',
'$ balena device register myorg/myapp --uuid <uuid>',
'$ balena device register MyFleet',
'$ balena device register MyFleet --uuid <uuid>',
'$ balena device register myorg/myfleet --uuid <uuid>',
];
public static args: Array<IArg<any>> = [ca.applicationRequired];
public static args: Array<IArg<any>> = [ca.fleetRequired];
public static usage = 'device register <application>';
public static usage = 'device register <fleet>';
public static flags: flags.Input<FlagsDef> = {
uuid: flags.string({
@ -70,7 +72,7 @@ export default class DeviceRegisterCmd extends Command {
const balena = getBalenaSdk();
const application = await getApplication(balena, params.application);
const application = await getApplication(balena, params.fleet);
const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
console.info(`Registering to ${application.app_name}: ${uuid}`);

View File

@ -20,7 +20,15 @@ import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
appToFleetOutputMsg,
jsonInfo,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
import type { Application } from 'balena-sdk';
interface ExtendedDevice extends DeviceWithDeviceType {
@ -32,17 +40,19 @@ interface ExtendedDevice extends DeviceWithDeviceType {
interface FlagsDef {
application?: string;
app?: string;
fleet?: string;
help: void;
json: boolean;
v13: boolean;
}
export default class DevicesCmd extends Command {
public static description = stripIndent`
List all devices.
list all devices that belong to you.
List all of your devices.
You can filter the devices by application by using the \`--application\` option.
Devices can be filtered by fleet with the \`--fleet\` option.
${applicationIdInfo.split('\n').join('\n\t\t')}
@ -50,33 +60,51 @@ export default class DevicesCmd extends Command {
`;
public static examples = [
'$ balena devices',
'$ balena devices --application MyApp',
'$ balena devices --app MyApp',
'$ balena devices -a MyApp',
'$ balena devices -a myorg/myapp',
'$ balena devices --fleet MyFleet',
'$ balena devices -f myorg/myfleet',
];
public static usage = 'devices';
public static flags: flags.Input<FlagsDef> = {
application: cf.application,
app: cf.app,
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'v13'],
},
app: { ...cf.app, exclusive: ['application', 'fleet', 'v13'] },
}),
fleet: { ...cf.fleet, exclusive: ['app', 'application'] },
json: cf.json,
help: cf.help,
v13: cf.v13,
};
public static primary = true;
public static authenticated = true;
protected useAppWord = false;
protected hasWarned = false;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
this.useAppWord = !options.fleet && !options.v13 && !isV13();
const balena = getBalenaSdk();
if (
(options.application || options.app) &&
!options.json &&
process.stderr.isTTY
) {
this.hasWarned = true;
console.error(warnify(appToFleetFlagMsg));
}
// Consolidate application options
options.application = options.application || options.app;
delete options.app;
options.application ||= options.app || options.fleet;
let devices;
@ -106,28 +134,32 @@ export default class DevicesCmd extends Command {
return device;
});
const jName = this.useAppWord ? 'application_name' : 'fleet_name';
const tName = this.useAppWord ? 'APPLICATION NAME' : 'FLEET';
const fields = [
'id',
'uuid',
'device_name',
'device_type',
'application_name',
options.json
? `application_name => ${jName}`
: `application_name => ${tName}`,
'status',
'is_online',
'supervisor_version',
'os_version',
'dashboard_url',
];
const _ = await import('lodash');
if (options.json) {
console.log(
JSON.stringify(
devices.map((device) => _.pick(device, fields)),
null,
4,
),
);
const { pickAndRename } = await import('../../utils/helpers');
const mapped = devices.map((device) => pickAndRename(device, fields));
console.log(JSON.stringify(mapped, null, 4));
} else {
if (!this.hasWarned && this.useAppWord && process.stderr.isTTY) {
console.error(warnify(appToFleetOutputMsg));
}
const _ = await import('lodash');
console.log(
getVisuals().table.horizontal(
devices.map((dev) => _.mapValues(dev, (val) => val ?? 'N/a')),

View File

@ -21,10 +21,16 @@ import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
interface FlagsDef {
application?: string;
fleet?: string;
device?: string; // device UUID
help: void;
quiet: boolean;
@ -38,18 +44,17 @@ interface ArgsDef {
export default class EnvAddCmd extends Command {
public static description = stripIndent`
Add env or config variable to application(s), device(s) or service(s).
Add env or config variable to fleets, devices or services.
Add an environment or config variable to one or more applications, devices
or services, as selected by the respective command-line options. Either the
--application or the --device option must be provided, and either may be be
Add an environment or config variable to one or more fleets, devices or
services, as selected by the respective command-line options. Either the
--fleet or the --device option must be provided, and either may be be
used alongside the --service option to define a service-specific variable.
(A service is an application container in a "microservices" application.)
(A service corresponds to a Docker image/container in a microservices fleet.)
When the --service option is used in conjunction with the --device option,
the service variable applies to the selected device only. Otherwise, it
applies to all devices of the selected application (i.e., the application's
fleet). If the --service option is omitted, the variable applies to all
services.
the service variable applies to the selected device only. Otherwise, it
applies to all devices of the selected fleet. If the --service option is
omitted, the variable applies to all services.
If VALUE is omitted, the CLI will attempt to use the value of the environment
variable of same name in the CLI process' environment. In this case, a warning
@ -61,19 +66,18 @@ export default class EnvAddCmd extends Command {
running on devices. They are also stored differently in the balenaCloud API
database. Configuration variables cannot be set for specific services,
therefore the --service option cannot be used when the variable name starts
with a reserved prefix. When defining custom application variables, please
avoid the reserved prefixes.
with a reserved prefix. When defining custom fleet variables, please avoid
these reserved prefixes.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena env add TERM --application MyApp',
'$ balena env add EDITOR vim --application MyApp',
'$ balena env add EDITOR vim -a myorg/myapp',
'$ balena env add EDITOR vim --application MyApp,MyApp2',
'$ balena env add EDITOR vim --application MyApp --service MyService',
'$ balena env add EDITOR vim --application MyApp,MyApp2 --service MyService,MyService2',
'$ balena env add TERM --fleet MyFleet',
'$ balena env add EDITOR vim -f myorg/myfleet',
'$ balena env add EDITOR vim --fleet MyFleet,MyFleet2',
'$ balena env add EDITOR vim --fleet MyFleet --service MyService',
'$ balena env add EDITOR vim --fleet MyFleet,MyFleet2 --service MyService,MyService2',
'$ balena env add EDITOR vim --device 7cf02a6',
'$ balena env add EDITOR vim --device 7cf02a6,d6f1433',
'$ balena env add EDITOR vim --device 7cf02a6 --service MyService',
@ -97,8 +101,11 @@ export default class EnvAddCmd extends Command {
public static usage = 'env add <name> [value]';
public static flags: flags.Input<FlagsDef> = {
application: { ...cf.application, exclusive: ['device'] },
device: { ...cf.device, exclusive: ['application'] },
...(isV13()
? {}
: { application: { ...cf.application, exclusive: ['fleet', 'device'] } }),
fleet: { ...cf.fleet, exclusive: ['application', 'device'] },
device: { ...cf.device, exclusive: ['application', 'fleet'] },
help: cf.help,
quiet: cf.quiet,
service: cf.service,
@ -110,9 +117,13 @@ export default class EnvAddCmd extends Command {
);
const cmd = this;
if (options.application && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
if (!options.application && !options.device) {
throw new ExpectedError(
'Either the --application or the --device option must be specified',
'Either the --fleet or the --device option must be specified',
);
}
@ -161,7 +172,7 @@ export default class EnvAddCmd extends Command {
params.value,
);
} catch (err) {
console.error(`${err.message}, app: ${app}`);
console.error(`${err.message}, fleet: ${app}`);
process.exitCode = 1;
}
}
@ -183,7 +194,7 @@ export default class EnvAddCmd extends Command {
}
/**
* Add service variables for a device or application.
* Add service variables for a device or fleet.
*/
async function setServiceVars(
sdk: BalenaSdk.BalenaSDK,
@ -201,7 +212,7 @@ async function setServiceVars(
params.value!,
);
} catch (err) {
console.error(`${err.message}, application: ${app}`);
console.error(`${err.message}, fleet: ${app}`);
process.exitCode = 1;
}
}
@ -262,7 +273,7 @@ async function getServiceIdForApp(
}
if (serviceId === undefined) {
throw new ExpectedError(
`Cannot find service ${serviceName} for application ${appName}`,
`Cannot find service ${serviceName} for fleet ${appName}`,
);
}
return serviceId;

View File

@ -38,9 +38,9 @@ interface ArgsDef {
export default class EnvRenameCmd extends Command {
public static description = stripIndent`
Change the value of a config or env var for an app, device or service.
Change the value of a config or env var for a fleet, device or service.
Change the value of a configuration or environment variable for an application,
Change the value of a configuration or environment variable for a fleet,
device or service, as selected by command-line options.
${ec.rmRenameHelp.split('\n').join('\n\t\t')}

View File

@ -37,9 +37,9 @@ interface ArgsDef {
export default class EnvRmCmd extends Command {
public static description = stripIndent`
Remove a config or env var from an application, device or service.
Remove a config or env var from a fleet, device or service.
Remove a configuration or environment variable from an application, device
Remove a configuration or environment variable from a fleet, device
or service, as selected by command-line options.
${ec.rmRenameHelp.split('\n').join('\n\t\t')}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2016-2019 Balena Ltd.
* Copyright 2016-2021 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,17 +21,24 @@ import Command from '../command';
import { ExpectedError } from '../errors';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import { applicationIdInfo } from '../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
appToFleetOutputMsg,
warnify,
} from '../utils/messages';
import { isV13 } from '../utils/version';
interface FlagsDef {
application?: string;
fleet?: string;
config: boolean;
device?: string; // device UUID
json: boolean;
help: void;
service?: string; // service name
verbose: boolean;
v13: boolean;
}
interface EnvironmentVariableInfo extends SDK.EnvironmentVariableBase {
@ -56,20 +63,20 @@ interface ServiceEnvironmentVariableInfo
export default class EnvsCmd extends Command {
public static description = stripIndent`
List the environment or config variables of an application, device or service.
List the environment or config variables of a fleet, device or service.
List the environment or configuration variables of an application, device or
service, as selected by the respective command-line options. (A service is
an application container in a "microservices" application.)
List the environment or configuration variables of a fleet, device or
service, as selected by the respective command-line options. (A service
corresponds to a Docker image/container in a microservices fleet.)
The results include application-wide (fleet), device-wide (multiple services on
a device) and service-specific variables that apply to the selected application,
device or service. It can be thought of as including "inherited" variables;
for example, a service inherits device-wide variables, and a device inherits
application-wide variables.
The results include fleet-wide (multiple devices), device-specific (multiple
services on a specific device) and service-specific variables that apply to the
selected fleet, device or service. It can be thought of as including inherited
variables; for example, a service inherits device-wide variables, and a device
inherits fleet-wide variables.
The printed output may include DEVICE and/or SERVICE columns to distinguish
between application-wide, device-specific and service-specific variables.
between fleet-wide, device-specific and service-specific variables.
An asterisk in these columns indicates that the variable applies to
"all devices" or "all services".
@ -83,22 +90,22 @@ export default class EnvsCmd extends Command {
types like lists and empty strings. The 'jq' utility may be helpful in shell
scripts (https://stedolan.github.io/jq/manual/). When --json is used, an empty
JSON array ([]) is printed instead of an error message when no variables exist
for the given query. When querying variables for a device, note that the
application name may be null in JSON output (or 'N/A' in tabular output) if the
application linked to the device is no longer accessible by the current user
(for example, in case the current user has been removed from the application
by its owner).
for the given query. When querying variables for a device, note that the fleet
name may be null in JSON output (or 'N/A' in tabular output) if the fleet that
the device belonged to is no longer accessible by the current user (for example,
in case the current user was removed from the fleet by the fleet's owner).
${applicationIdInfo.split('\n').join('\n\t\t')}
${appToFleetOutputMsg.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena envs --application MyApp',
'$ balena envs --application myorg/myapp',
'$ balena envs --application MyApp --json',
'$ balena envs --application MyApp --service MyService',
'$ balena envs --application MyApp --service MyService',
'$ balena envs --application MyApp --config',
'$ balena envs --fleet myorg/myfleet',
'$ balena envs --fleet MyFleet --json',
'$ balena envs --fleet MyFleet --service MyService',
'$ balena envs --fleet MyFleet --service MyService',
'$ balena envs --fleet MyFleet --config',
'$ balena envs --device 7cf02a6',
'$ balena envs --device 7cf02a6 --json',
'$ balena envs --device 7cf02a6 --config --json',
@ -113,34 +120,47 @@ export default class EnvsCmd extends Command {
: {
all: flags.boolean({
default: false,
description: stripIndent`
No-op since balena CLI v12.0.0.`,
description: 'No-op since balena CLI v12.0.0.',
hidden: true,
}),
application: {
exclusive: ['device', 'fleet', 'v13'],
...cf.application,
},
}),
application: { exclusive: ['device'], ...cf.application },
fleet: { exclusive: ['device', 'application'], ...cf.fleet },
config: flags.boolean({
default: false,
char: 'c',
description: 'show configuration variables only',
exclusive: ['service'],
}),
device: { exclusive: ['application'], ...cf.device },
device: { exclusive: ['fleet', 'application'], ...cf.device },
help: cf.help,
json: cf.json,
verbose: cf.verbose,
service: { exclusive: ['config'], ...cf.service },
v13: cf.v13,
};
protected useAppWord = false;
protected hasWarned = false;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
this.useAppWord = !options.fleet && !options.v13 && !isV13();
const variables: EnvironmentVariableInfo[] = [];
await Command.checkLoggedIn();
if (options.application && !options.json && process.stderr.isTTY) {
this.hasWarned = true;
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
if (!options.application && !options.device) {
throw new ExpectedError('You must specify an application or device');
throw new ExpectedError('Missing --fleet or --device option');
}
const balena = getBalenaSdk();
@ -174,7 +194,7 @@ export default class EnvsCmd extends Command {
const target =
(options.service ? `service "${options.service}" of ` : '') +
(options.application
? `application "${options.application}"`
? `fleet "${options.application}"`
: `device "${options.device}"`);
throw new ExpectedError(`No environment variables found for ${target}`);
}
@ -194,7 +214,9 @@ export default class EnvsCmd extends Command {
return i;
});
fields.push(options.json ? 'appName' : 'appName => APPLICATION');
const jName = this.useAppWord ? 'appName' : 'fleetName';
const tName = this.useAppWord ? 'APPLICATION' : 'FLEET';
fields.push(options.json ? `appName => ${jName}` : `appName => ${tName}`);
if (options.device) {
fields.push(options.json ? 'deviceUUID' : 'deviceUUID => DEVICE');
}
@ -203,10 +225,13 @@ export default class EnvsCmd extends Command {
}
if (options.json) {
this.log(
stringifyVarArray<SDK.EnvironmentVariableBase>(varArray, fields),
);
const { pickAndRename } = await import('../utils/helpers');
const mapped = varArray.map((o) => pickAndRename(o, fields));
this.log(JSON.stringify(mapped, null, 4));
} else {
if (!this.hasWarned && this.useAppWord && process.stderr.isTTY) {
console.error(warnify(appToFleetOutputMsg));
}
this.log(
getVisuals().table.horizontal(
_.sortBy(varArray, (v: SDK.EnvironmentVariableBase) => v.name),
@ -227,7 +252,7 @@ async function validateServiceName(
});
if (services.length === 0) {
throw new ExpectedError(
`Service "${serviceName}" not found for application "${appName}"`,
`Service "${serviceName}" not found for fleet "${appName}"`,
);
}
}
@ -354,25 +379,3 @@ function fillInInfoFields(
envVar.deviceUUID = deviceUUID || '*';
}
}
/**
* Transform each object (item) of varArray to preserve only the
* fields (keys) listed in the fields argument.
*/
function stringifyVarArray<T = Dictionary<any>>(
varArray: T[],
fields: string[],
): string {
const transformed = varArray.map((o: Dictionary<any>) =>
_.transform(
o,
(result, value, key) => {
if (fields.includes(key)) {
result[key] = value;
}
},
{} as Dictionary<any>,
),
);
return JSON.stringify(transformed, null, 4);
}

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 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 { FleetCreateCmd } from '../app/create';
export default FleetCreateCmd;

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 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 { FleetCmd } from '../app';
export default FleetCmd;

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 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 { FleetPurgeCmd } from '../app/purge';
export default FleetPurgeCmd;

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 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 { FleetRenameCmd } from '../app/rename';
export default FleetRenameCmd;

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 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 { FleetRestartCmd } from '../app/restart';
export default FleetRestartCmd;

20
lib/commands/fleet/rm.ts Normal file
View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 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 { FleetRmCmd } from '../app/rm';
export default FleetRmCmd;

20
lib/commands/fleets.ts Normal file
View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2016-2021 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 { FleetsCmd } from './apps';
export default FleetsCmd;

View File

@ -21,9 +21,11 @@ import * as cf from '../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { applicationIdInfo } from '../utils/messages';
import { parseAsLocalHostnameOrIp } from '../utils/validation';
import { isV13 } from '../utils/version';
interface FlagsDef {
application?: string;
fleet?: string;
pollInterval?: number;
help?: void;
}
@ -34,22 +36,22 @@ interface ArgsDef {
export default class JoinCmd extends Command {
public static description = stripIndent`
Move a local device to an application on another balena server.
Move a local device to a fleet on another balena server.
Move a local device to an application on another balena server, causing
Move a local device to a fleet on another balena server, causing
the device to "join" the new server. The device must be running balenaOS.
For example, you could provision a device against an openBalena installation
where you perform end-to-end tests and then move it to balenaCloud when it's
ready for production.
To move a device between applications on the same server, use the
To move a device between fleets on the same server, use the
\`balena device move\` command instead of \`balena join\`.
If you don't specify a device hostname or IP, this command will automatically
scan the local network for balenaOS devices and prompt you to select one
from an interactive picker. This may require administrator/root privileges.
Likewise, if the application flag is not provided then a picker will be shown.
Likewise, if the fleet option is not provided then a picker will be shown.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
@ -57,10 +59,10 @@ export default class JoinCmd extends Command {
public static examples = [
'$ balena join',
'$ balena join balena.local',
'$ balena join balena.local --application MyApp',
'$ balena join balena.local -a myorg/myapp',
'$ balena join balena.local --fleet MyFleet',
'$ balena join balena.local -f myorg/myfleet',
'$ balena join 192.168.1.25',
'$ balena join 192.168.1.25 --application MyApp',
'$ balena join 192.168.1.25 --fleet MyFleet',
];
public static args = [
@ -75,7 +77,8 @@ export default class JoinCmd extends Command {
public static usage = 'join [deviceIpOrHostname]';
public static flags: flags.Input<FlagsDef> = {
application: cf.application,
...(isV13() ? {} : { application: cf.application }),
fleet: cf.fleet,
pollInterval: flags.integer({
description: 'the interval in minutes to check for updates',
char: 'i',
@ -98,7 +101,7 @@ export default class JoinCmd extends Command {
logger,
sdk,
params.deviceIpOrHostname,
options.application,
options.application || options.fleet,
options.pollInterval,
);
}

View File

@ -31,9 +31,9 @@ interface ArgsDef {
export default class LeaveCmd extends Command {
public static description = stripIndent`
Remove a local device from its balena application.
Remove a local device from its balena fleet.
Remove a local device from its balena application, causing the device to
Remove a local device from its balena fleet, causing the device to
"leave" the server it is provisioned on. This effectively makes the device
"unmanaged". The device must be running balenaOS.

View File

@ -23,7 +23,12 @@ import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
const CONNECTIONS_FOLDER = '/system-connections';
@ -31,6 +36,7 @@ interface FlagsDef {
advanced?: boolean;
application?: string;
app?: string;
fleet?: string;
config?: string;
'config-app-update-poll-interval'?: number;
'config-network'?: string;
@ -66,8 +72,8 @@ export default class OsConfigureCmd extends Command {
public static description = stripIndent`
Configure a previously downloaded balenaOS image.
Configure a previously downloaded balenaOS image for a specific device type or
balena application.
Configure a previously downloaded balenaOS image for a specific device type
or fleet.
Configuration settings such as WiFi authentication will be taken from the
following sources, in precedence order:
@ -75,8 +81,8 @@ export default class OsConfigureCmd extends Command {
2. A given \`config.json\` file specified with the \`--config\` option.
3. User input through interactive prompts (text menus).
The --device-type option may be used to override the application's default
device type, in case of an application with mixed device types.
The --device-type option may be used to override the fleet's default device
type, in case of a fleet with mixed device types.
The --system-connection (-c) option can be used to inject NetworkManager connection
profiles for additional network interfaces, such as cellular/GSM or additional
@ -98,11 +104,10 @@ export default class OsConfigureCmd extends Command {
public static examples = [
'$ balena os configure ../path/rpi3.img --device 7cf02a6',
'$ balena os configure ../path/rpi3.img --device 7cf02a6 --device-api-key <existingDeviceKey>',
'$ balena os configure ../path/rpi3.img --app MyApp',
'$ balena os configure ../path/rpi3.img -a myorg/myapp',
'$ balena os configure ../path/rpi3.img --app MyApp --version 2.12.7',
'$ balena os configure ../path/rpi3.img --app MyFinApp --device-type raspberrypi3',
'$ balena os configure ../path/rpi3.img --app MyFinApp --device-type raspberrypi3 --config myWifiConfig.json',
'$ balena os configure ../path/rpi3.img --fleet myorg/myfleet',
'$ balena os configure ../path/rpi3.img --fleet MyFleet --version 2.12.7',
'$ balena os configure ../path/rpi3.img -f MyFinFleet --device-type raspberrypi3',
'$ balena os configure ../path/rpi3.img -f MyFinFleet --device-type raspberrypi3 --config myWifiConfig.json',
];
public static args = [
@ -121,15 +126,29 @@ export default class OsConfigureCmd extends Command {
description:
'ask advanced configuration questions (when in interactive mode)',
}),
application: { ...cf.application, exclusive: ['app', 'device'] },
app: { ...cf.app, exclusive: ['application', 'device'] },
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'device'],
},
app: {
...cf.app,
exclusive: ['application', 'fleet', 'device'],
},
}),
fleet: {
...cf.fleet,
exclusive: ['app', 'application', 'device'],
},
config: flags.string({
description:
'path to a pre-generated config.json file to be injected in the OS image',
}),
'config-app-update-poll-interval': flags.integer({
description:
'interval (in minutes) for the on-device balena supervisor periodic app update check',
'supervisor cloud polling interval in minutes (e.g. for variable updates)',
}),
'config-network': flags.string({
description: 'device network type (non-interactive configuration)',
@ -141,7 +160,7 @@ export default class OsConfigureCmd extends Command {
'config-wifi-ssid': flags.string({
description: 'WiFi SSID (network name) (non-interactive configuration)',
}),
device: { exclusive: ['app', 'application'], ...cf.device },
device: { exclusive: ['app', 'application', 'fleet'], ...cf.device },
'device-api-key': flags.string({
char: 'k',
description:
@ -149,7 +168,7 @@ export default class OsConfigureCmd extends Command {
}),
'device-type': flags.string({
description:
'device type slug (e.g. "raspberrypi3") to override the application device type',
'device type slug (e.g. "raspberrypi3") to override the fleet device type',
}),
'initial-device-name': flags.string({
description:
@ -172,9 +191,10 @@ export default class OsConfigureCmd extends Command {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
OsConfigureCmd,
);
// Prefer options.application over options.app
options.application = options.application || options.app;
delete options.app;
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
await validateOptions(options);
@ -302,12 +322,12 @@ async function validateOptions(options: FlagsDef) {
// flag definitions above, so oclif will enforce that they are not both used together.
if (!options.device && !options.application) {
throw new ExpectedError(
"Either the '--device' or the '--application' option must be provided",
"Either the '--device' or the '--fleet' option must be provided",
);
}
if (!options.application && options['device-type']) {
throw new ExpectedError(
"The '--device-type' option can only be used in conjunction with the '--application' option",
"The '--device-type' option can only be used in conjunction with the '--fleet' option",
);
}
if (options['device-api-key']) {
@ -366,7 +386,7 @@ async function checkDeviceTypeCompatibility(
const helpers = await import('../../utils/helpers');
if (!helpers.areDeviceTypesCompatible(appDeviceType, optionDeviceType)) {
throw new ExpectedError(
`Device type ${options['device-type']} is incompatible with application ${options.application}`,
`Device type ${options['device-type']} is incompatible with fleet ${options.application}`,
);
}
}

View File

@ -15,8 +15,8 @@
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../command';
import { ExpectedError } from '../errors';
import * as cf from '../utils/common-flags';
import {
getBalenaSdk,
@ -24,9 +24,17 @@ import {
getVisuals,
stripIndent,
} from '../utils/lazy';
import { applicationIdInfo } from '../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../utils/messages';
import type { DockerConnectionCliFlags } from '../utils/docker';
import { dockerConnectionCliFlags } from '../utils/docker';
import { parseAsInteger } from '../utils/validation';
import { isV13 } from '../utils/version';
import { flags } from '@oclif/command';
import * as _ from 'lodash';
import type {
Application,
@ -36,11 +44,10 @@ import type {
Release,
} from 'balena-sdk';
import type { Preloader } from 'balena-preload';
import { parseAsInteger } from '../utils/validation';
import { ExpectedError } from '../errors';
interface FlagsDef extends DockerConnectionCliFlags {
app?: string;
fleet?: string;
commit?: string;
'splash-image'?: string;
'dont-check-arch': boolean;
@ -56,16 +63,16 @@ interface ArgsDef {
export default class PreloadCmd extends Command {
public static description = stripIndent`
Preload an app on a disk image (or Edison zip archive).
Preload a release on a disk image (or Edison zip archive).
Preload a balena application release (app images/containers), and optionally
Preload a release (service images/containers) from a balena fleet, and optionally
a balenaOS splash screen, in a previously downloaded '.img' balenaOS image file
in the local disk (a zip file is only accepted for the Intel Edison device type).
After preloading, the balenaOS image file can be flashed to a device's SD card.
When the device boots, it will not need to download the application, as it was
When the device boots, it will not need to download the release, as it was
preloaded. This is usually combined with release pinning
(https://www.balena.io/docs/learn/deploy/release-strategy/release-policy/)
to avoid the device dowloading a newer release straight away, if one is available.
to avoid the device downloading a newer release straight away, if available.
Check also the Preloading and Preregistering section of the balena CLI's advanced
masterclass document:
https://www.balena.io/docs/learn/more/masterclasses/advanced-cli/#5-preloading-and-preregistering
@ -82,8 +89,8 @@ export default class PreloadCmd extends Command {
`;
public static examples = [
'$ balena preload balena.img --app MyApp --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0',
'$ balena preload balena.img --app myorg/myapp --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0 --splash-image image.png',
'$ balena preload balena.img --fleet MyFleet --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0',
'$ balena preload balena.img --fleet myorg/myfleet --splash-image image.png',
'$ balena preload balena.img',
];
@ -98,13 +105,16 @@ export default class PreloadCmd extends Command {
public static usage = 'preload <image>';
public static flags: flags.Input<FlagsDef> = {
// TODO: Replace with application/a in #v13?
app: cf.application,
...(isV13() ? {} : { app: cf.application }),
fleet: cf.fleet,
commit: flags.string({
description: `\
The commit hash for a specific application release to preload, use "current" to specify the current
release (ignored if no appId is given). The current release is usually also the latest, but can be
manually pinned using https://github.com/balena-io-projects/staged-releases .\
The commit hash of the release to preload. Use "current" to specify the current
release (ignored if no appId is given). The current release is usually also the
latest, but can be pinned to a specific release. See:
https://www.balena.io/docs/learn/deploy/release-strategy/release-policy/
https://www.balena.io/docs/learn/more/masterclasses/fleet-management/#63-pin-using-the-api
https://github.com/balena-io-examples/staged-releases\
`,
char: 'c',
}),
@ -115,7 +125,7 @@ manually pinned using https://github.com/balena-io-projects/staged-releases .\
'dont-check-arch': flags.boolean({
default: false,
description:
'disables check for matching architecture in image and application',
'disable architecture compatibility check between image and fleet',
}),
'pin-device-to-release': flags.boolean({
default: false,
@ -159,6 +169,11 @@ Can be repeated to add multiple certificates.\
PreloadCmd,
);
if (options.app && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.app ||= options.fleet;
const balena = getBalenaSdk();
const balenaPreload = await import('balena-preload');
const visuals = getVisuals();
@ -190,7 +205,7 @@ Can be repeated to add multiple certificates.\
const { getApplication } = await import('../utils/sdk');
const application = await getApplication(balena, options.app);
if (!application) {
throw new ExpectedError(`Application not found: ${options.app}`);
throw new ExpectedError(`Fleet not found: ${options.app}`);
}
options.app = application.slug;
}
@ -239,7 +254,7 @@ Can be repeated to add multiple certificates.\
if (dontCheckArch && !appId) {
throw new ExpectedError(
'You need to specify an application if you disable the architecture check.',
'You need to specify a fleet if you disable the architecture check.',
);
}
@ -417,11 +432,11 @@ Can be repeated to add multiple certificates.\
applicationInfoSpinner.stop();
if (applications.length === 0) {
throw new ExpectedError(
`You have no apps with successful releases for a '${deviceTypeSlug}' device type.`,
`No fleets found with successful releases for device type '${deviceTypeSlug}'`,
);
}
return getCliForm().ask({
message: 'Select an application',
message: 'Select a fleet',
type: 'list',
choices: applications.map((app) => ({
name: app.app_name,
@ -432,7 +447,7 @@ Can be repeated to add multiple certificates.\
selectApplicationCommit(releases: Release[]) {
if (releases.length === 0) {
throw new ExpectedError('This application has no successful releases.');
throw new ExpectedError('This fleet has no successful releases.');
}
const DEFAULT_CHOICE = { name: 'current', value: 'current' };
const choices = [DEFAULT_CHOICE].concat(
@ -465,23 +480,23 @@ Can be repeated to add multiple certificates.\
}
const message = `\
This application is set to track the latest release, and non-pinned devices
This fleet is set to track the latest release, and non-pinned devices
are automatically updated when a new release is available. This may lead to
unexpected behavior: The preloaded device will download and install the latest
release once it is online.
This prompt gives you the opportunity to disable automatic updates for this
application now. Note that this would result in the application being pinned
to the current latest release, rather than some other release that may have
This prompt gives you the opportunity to disable automatic updates for
this fleet now. Note that this would result in the fleet being pinned to
the current latest release, rather than some other release that may have
been selected for preloading. The pinned released may be further managed
through the web dashboard or programatically through the balena API / SDK.
Documentation about release policies and app/device pinning can be found at:
Documentation about release policies and pinning can be found at:
https://www.balena.io/docs/learn/deploy/release-strategy/release-policy/
Alternatively, the --pin-device-to-release flag may be used to pin only the
preloaded device to the selected release.
Would you like to disable automatic updates for this application now?\
Would you like to disable automatic updates for this fleet now?\
`;
const update = await getCliForm().ask({
message,
@ -531,7 +546,7 @@ Would you like to disable automatic updates for this application now?\
if (this.isCurrentCommit(options.commit)) {
if (!appCommit) {
throw new Error(
`Unexpected empty commit hash for app ID "${application.id}"`,
`Unexpected empty commit hash for fleet ID "${application.id}"`,
);
}
// handle `--commit current` (and its `--commit latest` synonym)

View File

@ -36,7 +36,7 @@ enum BuildTarget {
}
interface ArgsDef {
applicationOrDevice: string;
fleetOrDevice: string;
}
interface FlagsDef {
@ -63,9 +63,9 @@ interface FlagsDef {
export default class PushCmd extends Command {
public static description = stripIndent`
Start a build on the remote balenaCloud build servers, or a local mode device.
Build release images on balenaCloud servers or on a local mode device.
Start a build on the remote balenaCloud build servers, or a local mode device.
Build release images on balenaCloud servers or on a local mode device.
When building on the balenaCloud servers, the given source directory will be
sent to the remote server. This can be used as a drop-in replacement for the
@ -92,16 +92,16 @@ export default class PushCmd extends Command {
${dockerignoreHelp.split('\n').join('\n\t\t')}
Note: the --service and --env flags must come after the applicationOrDevice
Note: the --service and --env flags must come after the fleetOrDevice
parameter, as per examples.
`;
public static examples = [
'$ balena push myApp',
'$ balena push myApp --source <source directory>',
'$ balena push myApp -s <source directory>',
'$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"',
'$ balena push myorg/myapp',
'$ balena push myFleet',
'$ balena push myFleet --source <source directory>',
'$ balena push myFleet -s <source directory>',
'$ balena push myFleet --release-tag key1 "" key2 "value2 with spaces"',
'$ balena push myorg/myfleet',
'',
'$ balena push 10.0.0.1',
'$ balena push 10.0.0.1 --source <source directory>',
@ -115,15 +115,15 @@ export default class PushCmd extends Command {
public static args = [
{
name: 'applicationOrDevice',
name: 'fleetOrDevice',
description:
'application name or slug, or local device IP address or hostname',
'fleet name or slug, or local device IP address or ".local" hostname',
required: true,
parse: lowercaseIfSlug,
},
];
public static usage = 'push <applicationOrDevice>';
public static usage = 'push <fleetOrDevice>';
public static flags: flags.Input<FlagsDef> = {
source: flags.string({
@ -188,7 +188,7 @@ export default class PushCmd extends Command {
When pushing to the cloud, this option will cause the build to start, then
return execution back to the shell, with the status and release ID (if
applicable). When pushing to a local mode device, this option will cause
the command to not tail application logs when the build has completed.`,
the command to not tail logs when the build has completed.`,
char: 'd',
default: false,
}),
@ -260,7 +260,7 @@ export default class PushCmd extends Command {
}),
'release-tag': flags.string({
description: stripIndent`
Set release tags if the push to a cloud application is successful. Multiple
Set release tags if the image build is successful (balenaCloud only). Multiple
arguments may be provided, alternating tag keys and values (see examples).
Hint: Empty values may be specified with "" (bash, cmd.exe) or '""' (PowerShell).
`,
@ -292,14 +292,12 @@ export default class PushCmd extends Command {
},
);
switch (await this.getBuildTarget(params.applicationOrDevice)) {
switch (await this.getBuildTarget(params.fleetOrDevice)) {
case BuildTarget.Cloud:
logger.logDebug(
`Pushing to cloud for application: ${params.applicationOrDevice}`,
);
logger.logDebug(`Pushing to cloud for fleet: ${params.fleetOrDevice}`);
await this.pushToCloud(
params.applicationOrDevice,
params.fleetOrDevice,
options,
sdk,
dockerfilePath,
@ -308,11 +306,9 @@ export default class PushCmd extends Command {
break;
case BuildTarget.Device:
logger.logDebug(
`Pushing to local device: ${params.applicationOrDevice}`,
);
logger.logDebug(`Pushing to local device: ${params.fleetOrDevice}`);
await this.pushToDevice(
params.applicationOrDevice,
params.fleetOrDevice,
options,
dockerfilePath,
registrySecrets,
@ -402,7 +398,7 @@ export default class PushCmd extends Command {
this.checkInvalidOptions(
remoteOnlyOptions,
options,
'is only valid when pushing to an application',
'is only valid when pushing to a fleet',
);
const deviceDeploy = await import('../utils/device/deploy');

View File

@ -31,20 +31,20 @@ interface FlagsDef {
}
interface ArgsDef {
applicationOrDevice: string;
fleetOrDevice: string;
service?: string;
}
export default class SshCmd extends Command {
public static description = stripIndent`
SSH into the host or application container of a device.
Open a SSH prompt on a device's host OS or service container.
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 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.
If a fleet 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.
For local devices, the IP address and .local domain name are supported.
If the device is referenced by IP or \`.local\` address, the connection
@ -64,7 +64,7 @@ export default class SshCmd extends Command {
`;
public static examples = [
'$ balena ssh MyApp',
'$ balena ssh MyFleet',
'$ balena ssh f49cefd',
'$ balena ssh f49cefd my-service',
'$ balena ssh f49cefd --port <port>',
@ -76,9 +76,9 @@ export default class SshCmd extends Command {
public static args = [
{
name: 'applicationOrDevice',
name: 'fleetOrDevice',
description:
'application name/slug/id, device uuid, or address of local device',
'fleet name/slug/id, device uuid, or address of local device',
required: true,
},
{
@ -88,7 +88,7 @@ export default class SshCmd extends Command {
},
];
public static usage = 'ssh <applicationOrDevice> [service]';
public static usage = 'ssh <fleetOrDevice> [service]';
public static flags: flags.Input<FlagsDef> = {
port: flags.integer({
@ -124,10 +124,10 @@ export default class SshCmd extends Command {
);
// Local connection
if (validateLocalHostnameOrIp(params.applicationOrDevice)) {
if (validateLocalHostnameOrIp(params.fleetOrDevice)) {
const { performLocalDeviceSSH } = await import('../utils/device/ssh');
return await performLocalDeviceSSH({
address: params.applicationOrDevice,
address: params.fleetOrDevice,
port: options.port,
forceTTY: options.tty,
verbose: options.verbose,
@ -147,7 +147,7 @@ export default class SshCmd extends Command {
await Command.checkLoggedIn();
const deviceUuid = await getOnlineTargetDeviceUuid(
sdk,
params.applicationOrDevice,
params.fleetOrDevice,
);
const device = await sdk.models.device.get(deviceUuid, {

View File

@ -20,10 +20,16 @@ import Command from '../command';
import { ExpectedError } from '../errors';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../utils/lazy';
import { applicationIdInfo } from '../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../utils/messages';
import { isV13 } from '../utils/version';
interface FlagsDef {
application?: string;
fleet?: string;
device?: string;
duration?: string;
help: void;
@ -35,16 +41,16 @@ interface ArgsDef {
export default class SupportCmd extends Command {
public static description = stripIndent`
Grant or revoke support access for devices and applications.
Grant or revoke support access for devices or fleets.
Grant or revoke balena support agent access to devices and applications
Grant or revoke balena support agent access to devices or fleets
on balenaCloud. (This command does not apply to openBalena.)
Access will be automatically revoked once the specified duration has elapsed.
Duration defaults to 24h, but can be specified using --duration flag in days
or hours, e.g. '12h', '2d'.
Both --device and --application flags accept multiple values, specified as
Both --device and --fleet flags accept multiple values, specified as
a comma-separated list (with no spaces).
${applicationIdInfo.split('\n').join('\n\t\t')}
@ -52,8 +58,8 @@ export default class SupportCmd extends Command {
public static examples = [
'balena support enable --device ab346f,cd457a --duration 3d',
'balena support enable --application app3 --duration 12h',
'balena support disable -a myorg/myapp',
'balena support enable --fleet myFleet --duration 12h',
'balena support disable -f myorg/myfleet',
];
public static args = [
@ -71,10 +77,11 @@ export default class SupportCmd extends Command {
description: 'comma-separated list (no spaces) of device UUIDs',
char: 'd',
}),
application: {
...cf.application,
...(isV13() ? {} : { application: cf.application }),
fleet: {
...cf.fleet,
description:
'comma-separated list (no spaces) of application names or org/name slugs',
'comma-separated list (no spaces) of fleet names or org/name slugs',
},
duration: flags.string({
description:
@ -91,6 +98,11 @@ export default class SupportCmd extends Command {
SupportCmd,
);
if (options.application && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.fleet;
const balena = getBalenaSdk();
const ux = getCliUx();
@ -98,9 +110,7 @@ export default class SupportCmd extends Command {
// Validation
if (!options.device && !options.application) {
throw new ExpectedError(
'At least one device or application must be specified',
);
throw new ExpectedError('At least one device or fleet must be specified');
}
if (options.duration != null && !enabling) {
@ -135,10 +145,10 @@ export default class SupportCmd extends Command {
// Process applications
for (const appName of appNames) {
if (enabling) {
ux.action.start(`${enablingMessage} application ${appName}`);
ux.action.start(`${enablingMessage} fleet ${appName}`);
await balena.models.application.grantSupportAccess(appName, expiryTs);
} else if (params.action === 'disable') {
ux.action.start(`${disablingMessage} application ${appName}`);
ux.action.start(`${disablingMessage} fleet ${appName}`);
await balena.models.application.revokeSupportAccess(appName);
}
ux.action.stop();

View File

@ -19,14 +19,20 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
interface FlagsDef {
app?: string;
application?: string;
fleet?: string;
device?: string;
release?: string;
help: void;
app?: string;
}
interface ArgsDef {
@ -35,16 +41,16 @@ interface ArgsDef {
export default class TagRmCmd extends Command {
public static description = stripIndent`
Remove a tag from an application, device or release.
Remove a tag from a fleet, device or release.
Remove a tag from an application, device or release.
Remove a tag from a fleet, device or release.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena tag rm myTagKey --application MyApp',
'$ balena tag rm myTagKey -a myorg/myapp',
'$ balena tag rm myTagKey --fleet MyFleet',
'$ balena tag rm myTagKey -f myorg/myfleet',
'$ balena tag rm myTagKey --device 7cf02a6',
'$ balena tag rm myTagKey --release 1234',
'$ balena tag rm myTagKey --release b376b0e544e9429483b656490e5b9443b4349bd6',
@ -61,21 +67,29 @@ export default class TagRmCmd extends Command {
public static usage = 'tag rm <tagKey>';
public static flags: flags.Input<FlagsDef> = {
application: {
...cf.application,
exclusive: ['app', 'device', 'release'],
},
app: {
...cf.app,
exclusive: ['application', 'device', 'release'],
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'device', 'release'],
},
app: {
...cf.app,
exclusive: ['application', 'fleet', 'device', 'release'],
},
}),
fleet: {
...cf.fleet,
exclusive: ['app', 'application', 'device', 'release'],
},
device: {
...cf.device,
exclusive: ['app', 'application', 'release'],
exclusive: ['app', 'application', 'fleet', 'release'],
},
release: {
...cf.release,
exclusive: ['app', 'application', 'device'],
exclusive: ['app', 'application', 'fleet', 'device'],
},
help: cf.help,
};
@ -87,9 +101,10 @@ export default class TagRmCmd extends Command {
TagRmCmd,
);
// Prefer options.application over options.app
options.application = options.application || options.app;
delete options.app;
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
const balena = getBalenaSdk();
@ -130,9 +145,9 @@ export default class TagRmCmd extends Command {
protected static missingResourceMessage = stripIndent`
To remove a resource tag, you must provide exactly one of:
* An application, with --application <appNameOrSlug>
* A device, with --device <uuid>
* A release, with --release <id or commit>
* A fleet, with --fleet <fleetNameOrSlug>
* A device, with --device <UUID>
* A release, with --release <ID or commit>
See the help page for examples:

View File

@ -19,14 +19,20 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../../utils/messages';
import { isV13 } from '../../utils/version';
interface FlagsDef {
app?: string;
application?: string;
fleet?: string;
device?: string;
release?: string;
help: void;
app?: string;
}
interface ArgsDef {
@ -36,9 +42,9 @@ interface ArgsDef {
export default class TagSetCmd extends Command {
public static description = stripIndent`
Set a tag on an application, device or release.
Set a tag on a fleet, device or release.
Set a tag on an application, device or release.
Set a tag on a fleet, device or release.
You can optionally provide a value to be associated with the created
tag, as an extra argument after the tag key. If a value isn't
@ -48,9 +54,9 @@ export default class TagSetCmd extends Command {
`;
public static examples = [
'$ balena tag set mySimpleTag --application MyApp',
'$ balena tag set mySimpleTag -a myorg/myapp',
'$ balena tag set myCompositeTag myTagValue --application MyApp',
'$ balena tag set mySimpleTag --fleet MyFleet',
'$ balena tag set mySimpleTag -f myorg/myfleet',
'$ balena tag set myCompositeTag myTagValue --fleet MyFleet',
'$ balena tag set myCompositeTag myTagValue --device 7cf02a6',
'$ balena tag set myCompositeTag "my tag value with whitespaces" --device 7cf02a6',
'$ balena tag set myCompositeTag myTagValue --release 1234',
@ -74,21 +80,29 @@ export default class TagSetCmd extends Command {
public static usage = 'tag set <tagKey> [value]';
public static flags: flags.Input<FlagsDef> = {
application: {
...cf.application,
exclusive: ['app', 'device', 'release'],
},
app: {
...cf.app,
exclusive: ['application', 'device', 'release'],
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'device', 'release'],
},
app: {
...cf.app,
exclusive: ['application', 'fleet', 'device', 'release'],
},
}),
fleet: {
...cf.fleet,
exclusive: ['app', 'application', 'device', 'release'],
},
device: {
...cf.device,
exclusive: ['app', 'application', 'release'],
exclusive: ['app', 'application', 'fleet', 'release'],
},
release: {
...cf.release,
exclusive: ['app', 'application', 'device'],
exclusive: ['app', 'application', 'fleet', 'device'],
},
help: cf.help,
};
@ -100,9 +114,10 @@ export default class TagSetCmd extends Command {
TagSetCmd,
);
// Prefer options.application over options.app
options.application = options.application || options.app;
delete options.app;
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
const balena = getBalenaSdk();
@ -151,9 +166,9 @@ export default class TagSetCmd extends Command {
protected static missingResourceMessage = stripIndent`
To set a resource tag, you must provide exactly one of:
* An application, with --application <appNameOrSlug>
* A device, with --device <uuid>
* A release, with --release <id or commit>
* A fleet, with --fleet <fleetNameOrSlug>
* A device, with --device <UUID>
* A release, with --release <ID or commit>
See the help page for examples:

View File

@ -20,29 +20,34 @@ import Command from '../command';
import { ExpectedError } from '../errors';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import { applicationIdInfo } from '../utils/messages';
import {
applicationIdInfo,
appToFleetFlagMsg,
warnify,
} from '../utils/messages';
import { isV13 } from '../utils/version';
interface FlagsDef {
app?: string;
application?: string;
fleet?: string;
device?: string;
release?: string;
help: void;
app?: string;
}
export default class TagsCmd extends Command {
public static description = stripIndent`
List all tags for an application, device or release.
List all tags for a fleet, device or release.
List all tags and their values for a particular application,
device or release.
List all tags and their values for the specified fleet, device or release.
${applicationIdInfo.split('\n').join('\n\t\t')}
`;
public static examples = [
'$ balena tags --application MyApp',
'$ balena tags -a myorg/myapp',
'$ balena tags --fleet MyFleet',
'$ balena tags -f myorg/myfleet',
'$ balena tags --device 7cf02a6',
'$ balena tags --release 1234',
'$ balena tags --release b376b0e544e9429483b656490e5b9443b4349bd6',
@ -51,21 +56,29 @@ export default class TagsCmd extends Command {
public static usage = 'tags';
public static flags: flags.Input<FlagsDef> = {
application: {
...cf.application,
exclusive: ['app', 'device', 'release'],
},
app: {
...cf.app,
exclusive: ['application', 'device', 'release'],
...(isV13()
? {}
: {
application: {
...cf.application,
exclusive: ['app', 'fleet', 'device', 'release'],
},
app: {
...cf.app,
exclusive: ['application', 'fleet', 'device', 'release'],
},
}),
fleet: {
...cf.fleet,
exclusive: ['app', 'application', 'device', 'release'],
},
device: {
...cf.device,
exclusive: ['app', 'application', 'release'],
exclusive: ['app', 'application', 'fleet', 'release'],
},
release: {
...cf.release,
exclusive: ['app', 'application', 'device'],
exclusive: ['app', 'application', 'fleet', 'device'],
},
help: cf.help,
};
@ -75,9 +88,10 @@ export default class TagsCmd extends Command {
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(TagsCmd);
// Prefer options.application over options.app
options.application = options.application || options.app;
delete options.app;
if ((options.application || options.app) && process.stderr.isTTY) {
console.error(warnify(appToFleetFlagMsg));
}
options.application ||= options.app || options.fleet;
const balena = getBalenaSdk();
@ -123,7 +137,7 @@ export default class TagsCmd extends Command {
protected missingResourceMessage = stripIndent`
To list tags for a resource, you must provide exactly one of:
* An application, with --application <appNameOrSlug>
* A fleet, with --fleet <fleetNameOrSlug>
* A device, with --device <uuid>
* A release, with --release <id or commit>

View File

@ -24,6 +24,8 @@ import {
} from '../errors';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { lowercaseIfSlug } from '../utils/normalization';
import type { Server, Socket } from 'net';
interface FlagsDef {
@ -32,7 +34,7 @@ interface FlagsDef {
}
interface ArgsDef {
deviceOrApplication: string;
deviceOrFleet: string;
}
export default class TunnelCmd extends Command {
@ -62,7 +64,7 @@ export default class TunnelCmd extends Command {
public static examples = [
'# map remote port 22222 to localhost:22222',
'$ balena tunnel myApp -p 22222',
'$ balena tunnel myFleet -p 22222',
'',
'# map remote port 22222 to localhost:222',
'$ balena tunnel 2ead211 -p 22222:222',
@ -71,21 +73,22 @@ export default class TunnelCmd extends Command {
'$ balena tunnel 1546690 -p 22222:0.0.0.0',
'',
'# map remote port 22222 to any address on your host machine, port 222',
'$ balena tunnel myApp -p 22222:0.0.0.0:222',
'$ balena tunnel myFleet -p 22222:0.0.0.0:222',
'',
'# multiple port tunnels can be specified at any one time',
'$ balena tunnel myApp -p 8080:3000 -p 8081:9000',
'$ balena tunnel myFleet -p 8080:3000 -p 8081:9000',
];
public static args = [
{
name: 'deviceOrApplication',
description: 'device uuid or application name/slug/id',
name: 'deviceOrFleet',
description: 'device UUID or fleet name/slug/ID',
required: true,
parse: lowercaseIfSlug,
},
];
public static usage = 'tunnel <deviceOrApplication>';
public static usage = 'tunnel <deviceOrFleet>';
public static flags: flags.Input<FlagsDef> = {
port: flags.string({
@ -132,10 +135,7 @@ export default class TunnelCmd extends Command {
// Ascertain device uuid
const { getOnlineTargetDeviceUuid } = await import('../utils/patterns');
const uuid = await getOnlineTargetDeviceUuid(
sdk,
params.deviceOrApplication,
);
const uuid = await getOnlineTargetDeviceUuid(sdk, params.deviceOrFleet);
const device = await sdk.models.device.get(uuid);
logger.logInfo(`Opening a tunnel to ${device.uuid}...`);

View File

@ -37,9 +37,9 @@ interface CachedUsername {
* Mixpanel.com analytics tracking (information on balena CLI usage).
*
* @param commandSignature A string like, for example:
* "push <applicationOrDevice>"
* That's literally so: "applicationOrDevice" is NOT replaced with the actual
* application ID or device ID. The purpose is to find out the most / least
* "push <fleetOrDevice>"
* That's literally so: "fleetOrDevice" is NOT replaced with the actual
* fleet ID or device ID. The purpose is to find out the most / least
* used command verbs, so we can focus our development effort where it is most
* beneficial to end users.
*

View File

@ -182,8 +182,8 @@ export default class BalenaHelp extends Help {
'push',
'logs',
'ssh',
'apps',
'app',
'fleets',
'fleet',
'devices',
'device',
'tunnel',

View File

@ -64,8 +64,8 @@ export const getDeviceAndAppFromUUID = _.memoize(
);
if (app == null) {
throw new ExpectedError(stripIndent`
Unable to access the application that device ${deviceUUID} belongs to.
Hint: check whether the application owner might have withdrawn access to it.
Unable to access the fleet that device ${deviceUUID} belongs to.
Hint: check whether the fleet owner withdrew access to it.
`);
}
return [device, app];

View File

@ -16,9 +16,9 @@
*/
import { lowercaseIfSlug } from './normalization';
export const applicationRequired = {
name: 'application',
description: 'application name, slug (preferred), or numeric ID (deprecated)',
export const fleetRequired = {
name: 'fleet',
description: 'fleet name, slug (preferred), or numeric ID (deprecated)',
required: true,
parse: lowercaseIfSlug,
};

View File

@ -20,15 +20,33 @@ import { flags } from '@oclif/command';
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
import { stripIndent } from './lazy';
import { lowercaseIfSlug } from './normalization';
import { isV13 } from './version';
export const v13: IBooleanFlag<boolean> = flags.boolean({
description: stripIndent`\
enable selected balena CLI v13 pre-release features, like the renaming
from "application" to "fleet" in command output`,
default: false,
});
export const application = flags.string({
char: 'a',
description: 'application name, slug (preferred), or numeric ID (deprecated)',
description: 'DEPRECATED alias for -f, --fleet',
parse: lowercaseIfSlug,
});
// TODO: Consider remove second alias 'app' when we can, to simplify.
export const app = flags.string({
description: "same as '--application'",
description: 'DEPRECATED alias for -f, --fleet',
parse: lowercaseIfSlug,
});
export const fleet = flags.string({
char: 'f',
description: isV13()
? 'fleet name, slug (preferred), or numeric ID (deprecated)'
: // avoid the '(deprecated)' remark in v12 while cf.application and
// cf.app are also described as deprecated, to avoid the impression
// that cf.fleet is deprecated as well.
'fleet name, slug (preferred), or numeric ID',
parse: lowercaseIfSlug,
});

View File

@ -824,16 +824,16 @@ function printDockerignoreWarn(
);
if (multiDockerignore) {
msg.push(stripIndent`
When --multi-dockerignore (-m) is used, only .dockerignore files at the root of
each service's build context (in a microservices/multicontainer application),
plus a .dockerignore file at the overall project root, are used.
When --multi-dockerignore (-m) is used, only .dockerignore files at the
root of each service's build context (in a microservices/multicontainer
fleet), plus a .dockerignore file at the overall project root, are used.
See "balena help ${Logger.command}" for more details.`);
} else {
msg.push(stripIndent`
By default, only one .dockerignore file at the source folder (project root)
is used. Microservices (multicontainer) applications may use a separate
.dockerignore file for each service with the --multi-dockerignore (-m) option.
See "balena help ${Logger.command}" for more details.`);
By default, only one .dockerignore file at the source folder (project
root) is used. Microservices (multicontainer) fleets may use a separate
.dockerignore file for each service with the --multi-dockerignore (-m)
option. See "balena help ${Logger.command}" for more details.`);
}
}
// No unused .dockerignore files. Print info-level advice in some cases.
@ -853,7 +853,7 @@ function printDockerignoreWarn(
msg.push(
stripIndent`
The --multi-dockerignore (-m) option was specified, but it has no effect for
single-container (non-microservices) apps. Only one .dockerignore file at the
single-container (non-microservices) fleets. Only one .dockerignore file at the
project source (root) directory, if any, is used. See "balena help ${Logger.command}".`,
);
}

View File

@ -32,8 +32,7 @@ export const booleanConfig: IBooleanFlag<boolean> = flags.boolean({
export const booleanDevice: IBooleanFlag<boolean> = flags.boolean({
char: 'd',
description:
'select a device-specific variable instead of an application (fleet) variable',
description: 'select a device-specific variable instead of a fleet variable',
default: false,
});
@ -49,23 +48,23 @@ export const rmRenameHelp = stripIndent`
Variables are selected by their database ID (as reported by the 'balena envs'
command) and one of six database "resource types":
- application (fleet) environment variable
- application (fleet) configuration variable (--config)
- application (fleet) service variable (--service)
- fleet environment variable
- fleet configuration variable (--config)
- fleet service variable (--service)
- device environment variable (--device)
- device configuration variable (--device --config)
- device service variable (--device --service)
The --device option selects a device-specific variable instead of an application
(fleet) variable.
The --device option selects a device-specific variable instead of a fleet
variable.
The --config option selects a configuration variable. Configuration variable
names typically start with the 'BALENA_' or 'RESIN_' prefixes and are used to
configure balena platform features.
The --service option selects a service variable, which is an environment variable
that applies to a specifc service (application container) in a microservices
(multicontainer) application.
that applies to a specifc service (container) in a microservices (multicontainer)
fleet.
The --service and --config options cannot be used together, but they can be
used alongside the --device option to select a device-specific service or

View File

@ -160,7 +160,7 @@ export function getAppWithArch(
});
if (!config) {
throw new Error('Could not read application information!');
throw new Error(`balena API request failed for fleet ${applicationName}`);
}
return { ...app, arch: config.arch };
@ -530,3 +530,31 @@ export async function awaitInterruptibleTask<
process.removeListener('SIGINT', sigintHandler);
}
}
/**
* Pick object fields like lodash _.pick(), but also interpret "renaming
* specifications" for object keys such as "appName => fleetName" as used
* by the 'resin-cli-visuals' package.
*
* Sample input: ({ 'a': 1, 'b': 2, 'c': 3 }, ['b => x', 'c', 'd'])
* Sample output: { 'x': 2, 'c': 3 }
*/
export function pickAndRename<T extends Dictionary<any>>(
obj: T,
fields: string[],
): Dictionary<any> {
const rename: Dictionary<any> = {};
// map 'a => b' to 'a' and setup rename['a'] = 'b'
fields = fields.map((f) => {
let renameFrom = f;
let renameTo = f;
const match = f.match(/(?<from>\S+)\s+=>\s+(?<to>\S+)/);
if (match && match.groups) {
renameFrom = match.groups.from;
renameTo = match.groups.to;
}
rename[renameFrom] = renameTo;
return renameFrom;
});
return _.mapKeys(_.pick(obj, fields), (_val, key) => rename[key]);
}

View File

@ -30,6 +30,24 @@ export const help = reachingOut;
// is parsed, so its evaluation cannot happen at module loading time.
export const getHelp = () => (process.env.DEBUG ? '' : debugHint) + help;
/**
* Take a multiline string like:
* Line One
* Line Two
* and return a string like:
* ---------------
* [Warn] Line One
* [Warn] Line Two
* ---------------
* where the length of the dash rows matches the length of the longest line.
*/
export function warnify(msg: string) {
const lines = msg.split('\n').map((l) => `[Warn] ${l}`);
const maxLength = Math.max(...lines.map((l) => l.length));
const hr = '-'.repeat(maxLength);
return [hr, ...lines, hr].join('\n');
}
export const balenaAsciiArt = `\
_ _
| |__ __ _ | | ____ _ __ __ _
@ -66,12 +84,12 @@ export const dockerignoreHelp =
`By default, the balena CLI will use a single ".dockerignore" file (if any) at
the project root (--source directory) in order to decide which source files to
exclude from the "build context" (tar stream) sent to balenaCloud, Docker
daemon or balenaEngine. In a microservices (multicontainer) application, the
daemon or balenaEngine. In a microservices (multicontainer) fleet, the
source directory is the directory that contains the "docker-compose.yml" file.
The --multi-dockerignore (-m) option may be used with microservices
(multicontainer) applications that define a docker-compose.yml file. When
this option is used, each service subdirectory (defined by the \`build\` or
(multicontainer) fleets that define a docker-compose.yml file. When this
option is used, each service subdirectory (defined by the \`build\` or
\`build.context\` service properties in the docker-compose.yml file) is
filtered separately according to a .dockerignore file defined in the service
subdirectory. If no .dockerignore file exists in a service subdirectory, then
@ -115,18 +133,17 @@ adding counter patterns to the applicable .dockerignore file(s), for example
- https://www.npmjs.com/package/@balena/dockerignore`;
export const applicationIdInfo = `\
Applications may be specified by app name, slug, or numeric ID. App slugs
are the recommended option, as they are unique and unambiguous. Slugs
can be listed with the \`balena apps\` command. Note that slugs may change
if the application is renamed.
App names are not unique and may result in "Application is ambiguous" errors
at any time (even if it "used to work in the past"), for example if the name
clashes with a newly created public application, or with apps from other balena
accounts that you may have been invited to as a member. For this reason, app
names are especially discouraged in scripts (e.g. CI environments).
Numeric app IDs are deprecated because they consist of an implementation detail
of the balena backend. We intend to remove support for numeric IDs at some point
in the future.`;
Fleets may be specified by fleet name, slug, or numeric ID. Fleet slugs are
the recommended option, as they are unique and unambiguous. Slugs can be
listed with the \`balena fleets\` command. Note that slugs may change if the
fleet is renamed. Fleet names are not unique and may result in "Fleet is
ambiguous" errors at any time (even if it "used to work in the past"), for
example if the name clashes with a newly created public fleet, or with fleets
from other balena accounts that you may be invited to join under any role.
For this reason, fleet names are especially discouraged in scripts (e.g. CI
environments). Numeric fleet IDs are deprecated because they consist of an
implementation detail of the balena backend. We intend to remove support for
numeric IDs at some point in the future.`;
export const jsonInfo = `\
The --json option is recommended when scripting the output of this command,
@ -143,3 +160,27 @@ https://www.balena.io/docs/learn/deploy/deployment/#build-time-secrets-and-varia
If you have a particular use for buildArg, which is not satisfied by build-time
secrets, please contact us via support or the forums: https://forums.balena.io/
\n`;
// Note: if you edit this message, check that the regex replace
// logic in lib/commands/apps.ts still works.
export const appToFleetCmdMsg = `\
Renaming notice: The 'app' command was renamed to 'fleet', and 'app'
is now an alias. THE ALIAS WILL BE REMOVED in the next major version
of the balena CLI (so that a different 'app' command can be implemented
in the future). Use 'fleet' instead of 'app' to avoid this warning.
Find out more at: https://git.io/JRuZr`;
export const appToFleetFlagMsg = `\
Renaming notice: The '-a', '--app' or '--application' options are now
aliases for the '-f' or '--fleet' options. THE ALIASES WILL BE REMOVED
in the next major version of the balena CLI (so that a different '--app'
option can be implemented in the future). Use '-f' or '--fleet' instead.
Find out more at: https://git.io/JRuZr`;
export const appToFleetOutputMsg = `\
Renaming notice: The 'app' or 'application' words in table headers
or in JSON object keys/properties will be replaced with 'fleet' in
the next major version of the CLI (v13). The --v13 option may be used
to enable the new names already now, and suppress a warning message.
(The --v13 option will be silently ignored in CLI v13.)
Find out more at: https://git.io/JRuZr`;

View File

@ -184,7 +184,7 @@ export function selectApplication(
.hasAny()
.then(async (hasAnyApplications) => {
if (!hasAnyApplications) {
throw new ExpectedError("You don't have any applications");
throw new ExpectedError('No fleets found');
}
const apps = (await balena.models.application.getAll({
@ -198,7 +198,7 @@ export function selectApplication(
})
.then((applications) => {
if (errorOnEmptySelection && applications.length === 0) {
throw new ExpectedError('No suitable applications found for selection');
throw new ExpectedError('No suitable fleets found for selection');
}
return getCliForm().ask({
message: 'Select an application',
@ -378,15 +378,13 @@ export async function getOnlineTargetDeviceUuid(
// Not a device UUID, try app
let app: Application;
try {
logger.logDebug(
`Trying to fetch application by name/slug/ID: ${applicationOrDevice}`,
);
logger.logDebug(`Fetching fleet ${applicationOrDevice}`);
app = await getApplication(sdk, applicationOrDevice);
} catch (err) {
const { BalenaApplicationNotFound } = await import('balena-errors');
if (instanceOf(err, BalenaApplicationNotFound)) {
throw new ExpectedError(
`Application or Device not found: ${applicationOrDevice}`,
`Fleet or Device not found: ${applicationOrDevice}`,
);
} else {
throw err;
@ -402,13 +400,13 @@ export async function getOnlineTargetDeviceUuid(
// Throw if no devices online
if (_.isEmpty(devices)) {
throw new ExpectedError(
`Application ${app.slug} found, but has no devices online.`,
`Fleet ${app.slug} found, but has no devices online.`,
);
}
// Ask user to select from online devices for application
return getCliForm().ask({
message: `Select a device on application ${app.slug}`,
message: `Select a device on fleet ${app.slug}`,
type: 'list',
default: devices[0].uuid,
choices: _.map(devices, (device) => ({

View File

@ -39,10 +39,10 @@ export async function join(
const deviceType = await getDeviceType(deviceHostnameOrIp);
logger.logDebug(`Device type: ${deviceType}`);
logger.logDebug('Determining application...');
logger.logDebug('Determining fleet...');
const app = await getOrSelectApplication(sdk, deviceType, appName);
logger.logDebug(
`Using application: ${app.app_name} (${app.is_for__device_type[0].slug})`,
`Using fleet: ${app.app_name} (${app.is_for__device_type[0].slug})`,
);
if (app.is_for__device_type[0].slug !== deviceType) {
logger.logDebug(`Forcing device type to: ${deviceType}`);
@ -53,7 +53,7 @@ export async function join(
const deviceOsVersion = await getOsVersion(deviceHostnameOrIp);
logger.logDebug(`Device OS version: ${deviceOsVersion}`);
logger.logDebug('Generating application config...');
logger.logDebug('Generating fleet config...');
const config = await generateApplicationConfig(sdk, app, {
version: deviceOsVersion,
appUpdatePollInterval,
@ -175,10 +175,10 @@ async function selectAppFromList(
const _ = await import('lodash');
const { selectFromList } = await import('../utils/patterns');
// Present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select.
// Present a list to the user which shows the fully qualified fleet
// name (user/fleetname) and allows them to select.
return selectFromList(
'Select application',
'Select fleet',
_.map(applications, (app) => {
return { name: app.slug, ...app };
}),
@ -220,11 +220,11 @@ async function getOrSelectApplication(
},
};
// Check for an app of the form `user/application` and update the API query.
// Check for a fleet slug of the form `user/fleet` and update the API query.
let name: string;
const match = appName.split('/');
if (match.length > 1) {
// These will match at most one app
// These will match at most one fleet
options.$filter = { slug: appName.toLowerCase() };
name = match[1];
} else {
@ -241,7 +241,7 @@ async function getOrSelectApplication(
if (applications.length === 0) {
const shouldCreateApp = await getCliForm().ask({
message:
`No application found with name "${appName}".\n` +
`No fleet found with name "${appName}".\n` +
'Would you like to create it now?',
type: 'confirm',
default: true,
@ -252,14 +252,14 @@ async function getOrSelectApplication(
process.exit(1);
}
// We've found at least one app with the given name.
// Filter out apps for non-matching device types and see what we're left with.
// We've found at least one fleet with the given name.
// Filter out fleets for non-matching device types and see what we're left with.
const validApplications = applications.filter((app) =>
_.includes(compatibleDeviceTypes, app.is_for__device_type[0].slug),
);
if (validApplications.length === 0) {
throw new ExpectedError('No application found with a matching device type');
throw new ExpectedError('No fleet found with a matching device type');
}
if (validApplications.length === 1) {
@ -277,7 +277,7 @@ async function createOrSelectAppOrExit(
compatibleDeviceTypes: string[],
deviceType: string,
): Promise<ApplicationWithDeviceType> {
// No application specified, show a list to select one.
// No fleet specified, show a list to select one.
const applications = (await sdk.models.application.getAll({
$expand: { is_for__device_type: { $select: 'slug' } },
$filter: {
@ -293,7 +293,7 @@ async function createOrSelectAppOrExit(
if (applications.length === 0) {
const shouldCreateApp = await getCliForm().ask({
message:
'You have no applications this device can join.\n' +
'You have no fleets this device can join.\n' +
'Would you like to create one now?',
type: 'confirm',
default: true,
@ -323,7 +323,7 @@ async function createApplication(
while (true) {
try {
const appName = await getCliForm().ask({
message: 'Enter a name for your new application:',
message: 'Enter a name for your new fleet:',
type: 'input',
default: name,
validate: validation.validateApplicationName,
@ -341,7 +341,7 @@ async function createApplication(
// TODO: This is the only example in the codebase where `printErrorMessage()`
// is called directly. Consider refactoring.
printErrorMessage(
'You already have an application with that name; please choose another.',
'You already have a fleet with that name; please choose another.',
);
continue;
} catch (err) {

View File

@ -43,7 +43,7 @@ export function validatePassword(input: string) {
export function validateApplicationName(input: string) {
if (input.length < 4) {
return 'The application name should be at least 4 characters';
return 'The fleet name should be at least 4 characters long';
}
return APPNAME_REGEX.test(input);

View File

@ -19,27 +19,7 @@ import { expect } from 'chai';
import { BalenaAPIMock } from '../../balena-api-mock';
import { cleanOutput, runCommand } from '../../helpers';
const HELP_MESSAGE = `
Usage: app create <name>
Use this command to create a new balena application.
You can specify the application device type with the \`--type\` option.
Otherwise, an interactive dropdown will be shown for you to select from.
You can see a list of supported device types with
\t$ balena devices supported
Examples:
\t$ balena app create MyApp
\t$ balena app create MyApp --type raspberry-pi
Options:
--type, -t <type> application device type (Check available types with \`balena devices supported\`)
`;
const HELP_MESSAGE = '';
describe('balena app create', function () {
let api: BalenaAPIMock;

View File

@ -29,6 +29,8 @@ import { cleanOutput, runCommand } from '../helpers';
import {
ExpectedTarStreamFiles,
ExpectedTarStreamFilesByService,
getDockerignoreWarn1,
getDockerignoreWarn2,
} from '../projects';
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
@ -230,16 +232,10 @@ describe('balena build', function () {
`[Info] Creating default composition with source: "${projectPath}"`,
'[Info] Building for rpi/raspberry-pi',
'[Info] Emulation is enabled',
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help build" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'build',
),
'[Build] main Step 1/4 : FROM busybox',
'[Success] Build succeeded!',
];
@ -315,16 +311,10 @@ describe('balena build', function () {
...commonResponseLines[responseFilename],
`[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`,
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] When --multi-dockerignore (-m) is used, only .dockerignore files at the root of',
"[Warn] each service's build context (in a microservices/multicontainer application),",
'[Warn] plus a .dockerignore file at the overall project root, are used.',
'[Warn] See "balena help build" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn2(
[path.join(projectPath, 'src', '.dockerignore')],
'build',
),
'[Build] main Step 1/4 : FROM busybox',
];
if (isWindows) {
@ -410,16 +400,10 @@ describe('balena build', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'service2', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help build" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'service2', '.dockerignore')],
'build',
),
];
if (isWindows) {
expectedResponseLines.push(

View File

@ -28,6 +28,7 @@ import { cleanOutput, runCommand, switchSentry } from '../helpers';
import {
ExpectedTarStreamFiles,
ExpectedTarStreamFilesByService,
getDockerignoreWarn1,
} from '../projects';
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
@ -124,16 +125,10 @@ describe('balena deploy', function () {
...commonResponseLines[responseFilename],
`[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`,
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help deploy" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'deploy',
),
];
if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');

View File

@ -21,24 +21,8 @@ import * as path from 'path';
import { apiResponsePath, BalenaAPIMock } from '../../balena-api-mock';
import { cleanOutput, runCommand } from '../../helpers';
const HELP_RESPONSE = `
Show info about a single device.
USAGE
$ balena device <uuid>
ARGUMENTS
<uuid> the device uuid
OPTIONS
-h, --help show CLI help
DESCRIPTION
Show information about a single device.
EXAMPLE
$ balena device 7cf02a6
`;
import { appToFleetOutputMsg, warnify } from '../../../build/utils/messages';
import { isV13 } from '../../../build/utils/version';
describe('balena device', function () {
let api: BalenaAPIMock;
@ -54,13 +38,12 @@ describe('balena device', function () {
api.done();
});
it('should print help text with the -h flag', async () => {
const { out, err } = await runCommand('device -h');
expect(cleanOutput(out)).to.deep.equal(cleanOutput([HELP_RESPONSE]));
expect(err).to.eql([]);
});
const expectedWarn =
!isV13() &&
process.stderr.isTTY &&
process.env.BALENA_CLI_TEST_TYPE !== 'standalone'
? warnify(appToFleetOutputMsg) + '\n'
: '';
it('should error if uuid not provided', async () => {
const { out, err } = await runCommand('device');
@ -88,7 +71,7 @@ describe('balena device', function () {
expect(lines[0]).to.equal('== SPARKLING WOOD');
expect(lines[6].split(':')[1].trim()).to.equal('test app');
expect(err).to.eql([]);
expect(err.join('')).to.eql(expectedWarn);
});
it.skip('correctly handles devices with missing fields', async () => {
@ -112,7 +95,7 @@ describe('balena device', function () {
expect(lines[0]).to.equal('== SPARKLING WOOD');
expect(lines[6].split(':')[1].trim()).to.equal('test app');
expect(err).to.eql([]);
expect(err.join('')).to.eql(expectedWarn);
});
it('correctly handles devices with missing application', async () => {
@ -138,6 +121,6 @@ describe('balena device', function () {
expect(lines[0]).to.equal('== SPARKLING WOOD');
expect(lines[6].split(':')[1].trim()).to.equal('N/a');
expect(err).to.eql([]);
expect(err.join('')).to.eql(expectedWarn);
});
});

View File

@ -21,6 +21,9 @@ import * as path from 'path';
import { apiResponsePath, BalenaAPIMock } from '../../balena-api-mock';
import { cleanOutput, runCommand } from '../../helpers';
import { appToFleetOutputMsg, warnify } from '../../../build/utils/messages';
import { isV13 } from '../../../build/utils/version';
describe('balena devices', function () {
let api: BalenaAPIMock;
@ -35,6 +38,13 @@ describe('balena devices', function () {
api.done();
});
const expectedWarn =
!isV13() &&
process.stderr.isTTY &&
process.env.BALENA_CLI_TEST_TYPE !== 'standalone'
? warnify(appToFleetOutputMsg) + '\n'
: '';
it('should list devices from own and collaborator apps', async () => {
api.scope
.get(
@ -60,6 +70,6 @@ describe('balena devices', function () {
// e.g. When user has a device associated with app that user is no longer a collaborator of.
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
expect(err).to.eql([]);
expect(err.join('')).to.eql(expectedWarn);
});
});

View File

@ -21,6 +21,13 @@ import { stripIndent } from '../../../lib/utils/lazy';
import { BalenaAPIMock } from '../../balena-api-mock';
import { runCommand } from '../../helpers';
import {
appToFleetFlagMsg,
appToFleetOutputMsg,
warnify,
} from '../../../build/utils/messages';
import { isV13 } from '../../../build/utils/version';
describe('balena envs', function () {
const appName = 'test';
let fullUUID: string;
@ -41,7 +48,21 @@ describe('balena envs', function () {
api.done();
});
it('should successfully list env vars for a test app', async () => {
const appToFleetFlagWarn =
!isV13() &&
process.stderr.isTTY &&
process.env.BALENA_CLI_TEST_TYPE !== 'standalone'
? warnify(appToFleetFlagMsg) + '\n'
: '';
const appToFleetOutputWarn =
!isV13() &&
process.stderr.isTTY &&
process.env.BALENA_CLI_TEST_TYPE !== 'standalone'
? warnify(appToFleetOutputMsg) + '\n'
: '';
it('should successfully list env vars for a test fleet', async () => {
api.expectGetApplication();
api.expectGetAppEnvVars();
api.expectGetAppServiceVars();
@ -57,18 +78,18 @@ describe('balena envs', function () {
120102 var2 22 test *
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetFlagWarn);
});
it('should successfully list config vars for a test app', async () => {
it('should successfully list config vars for a test fleet', async () => {
api.expectGetApplication();
api.expectGetAppConfigVars();
const { out, err } = await runCommand(`envs -a ${appName} --config`);
const { out, err } = await runCommand(`envs -f ${appName} --config`);
expect(out.join('')).to.equal(
stripIndent`
ID NAME VALUE APPLICATION
ID NAME VALUE FLEET
120300 RESIN_SUPERVISOR_NATIVE_LOGGER false test
` + '\n',
);
@ -76,7 +97,7 @@ describe('balena envs', function () {
expect(err.join('')).to.equal('');
});
it('should successfully list config vars for a test app (JSON output)', async () => {
it('should successfully list config vars for a test fleet (JSON output)', async () => {
api.expectGetApplication();
api.expectGetAppConfigVars();
@ -93,7 +114,7 @@ describe('balena envs', function () {
expect(err.join('')).to.equal('');
});
it('should successfully list service variables for a test app (-s flag)', async () => {
it('should successfully list service variables for a test fleet (-s flag)', async () => {
const serviceName = 'service2';
api.expectGetService({ serviceName });
api.expectGetApplication();
@ -112,10 +133,10 @@ describe('balena envs', function () {
120102 var2 22 test *
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetFlagWarn);
});
it('should successfully list env and service vars for a test app (-s flags)', async () => {
it('should successfully list env and service vars for a test fleet (-s flags)', async () => {
const serviceName = 'service1';
api.expectGetService({ serviceName });
api.expectGetApplication();
@ -134,7 +155,7 @@ describe('balena envs', function () {
120102 var2 22 test *
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetFlagWarn);
});
it('should successfully list env variables for a test device', async () => {
@ -162,7 +183,7 @@ describe('balena envs', function () {
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetOutputWarn);
});
it('should successfully list env variables for a test device (JSON output)', async () => {
@ -207,7 +228,7 @@ describe('balena envs', function () {
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetOutputWarn);
});
it('should successfully list service variables for a test device (-s flag)', async () => {
@ -235,10 +256,10 @@ describe('balena envs', function () {
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetOutputWarn);
});
it('should successfully list env and service variables for a test device (unknown app)', async () => {
it('should successfully list env and service variables for a test device (unknown fleet)', async () => {
api.expectGetDevice({ fullUUID, inaccessibleApp: true });
api.expectGetDeviceEnvVars();
api.expectGetDeviceServiceVars();
@ -256,7 +277,7 @@ describe('balena envs', function () {
120204 var4 44 N/A ${uuid} *
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetOutputWarn);
});
it('should successfully list env and service vars for a test device (-s flags)', async () => {
@ -283,7 +304,7 @@ describe('balena envs', function () {
120204 var4 44 test ${uuid} *
` + '\n',
);
expect(err.join('')).to.equal('');
expect(err.join('')).to.equal(appToFleetOutputWarn);
});
it('should successfully list env and service vars for a test device (-js flags)', async () => {

View File

@ -55,7 +55,7 @@ if (process.platform !== 'win32') {
`os configure ${tmpPath}`,
'--device-type raspberrypi3',
'--version 2.47.0+rev1',
'--application testApp',
'--fleet testApp',
'--config-app-update-poll-interval 10',
'--config-network ethernet',
'--initial-device-name testDeviceName',

View File

@ -26,6 +26,8 @@ import { cleanOutput, runCommand } from '../helpers';
import {
addRegSecretsEntries,
ExpectedTarStreamFiles,
getDockerignoreWarn1,
getDockerignoreWarn2,
setupDockerignoreTestData,
} from '../projects';
@ -124,16 +126,10 @@ describe('balena push', function () {
);
const expectedResponseLines = [
...commonResponseLines[responseFilename],
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help push" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'push',
),
];
if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
@ -171,16 +167,10 @@ describe('balena push', function () {
);
const expectedResponseLines = [
...commonResponseLines[responseFilename],
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help push" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'push',
),
];
const expectedQueryParams = commonQueryParams.map((i) =>
i[0] === 'dockerfilePath' ? ['dockerfilePath', 'Dockerfile-alt'] : i,
@ -218,16 +208,10 @@ describe('balena push', function () {
);
const expectedResponseLines = [
...commonResponseLines[responseFilename],
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help push" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'push',
),
];
if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
@ -434,16 +418,10 @@ describe('balena push', function () {
'utf8',
);
const expectedResponseLines: string[] = [
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'lib', '.dockerignore')}`,
'[Warn] When --multi-dockerignore (-m) is used, only .dockerignore files at the root of',
"[Warn] each service's build context (in a microservices/multicontainer application),",
'[Warn] plus a .dockerignore file at the overall project root, are used.',
'[Warn] See "balena help push" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn2(
[path.join(projectPath, 'lib', '.dockerignore')],
'push',
),
...commonResponseLines[responseFilename],
];
@ -484,16 +462,10 @@ describe('balena push', function () {
);
const expectedResponseLines: string[] = [
...commonResponseLines[responseFilename],
...[
`[Warn] ${hr}`,
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'service2', '.dockerignore')}`,
'[Warn] By default, only one .dockerignore file at the source folder (project root)',
'[Warn] is used. Microservices (multicontainer) applications may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m) option.',
'[Warn] See "balena help push" for more details.',
`[Warn] ${hr}`,
],
...getDockerignoreWarn1(
[path.join(projectPath, 'service2', '.dockerignore')],
'push',
),
];
if (isWindows) {
expectedResponseLines.push(

View File

@ -83,3 +83,39 @@ export async function addRegSecretsEntries(
};
return regSecretsPath;
}
export function getDockerignoreWarn1(paths: string[], cmd: string) {
const lines = [
'[Warn] ----------------------------------------------------------------------',
'[Warn] The following .dockerignore file(s) will not be used:',
];
lines.push(...paths.map((p) => `[Warn] * ${p}`));
lines.push(
...[
'[Warn] By default, only one .dockerignore file at the source folder (project',
'[Warn] root) is used. Microservices (multicontainer) fleets may use a separate',
'[Warn] .dockerignore file for each service with the --multi-dockerignore (-m)',
`[Warn] option. See "balena help ${cmd}" for more details.`,
'[Warn] ----------------------------------------------------------------------',
],
);
return lines;
}
export function getDockerignoreWarn2(paths: string[], cmd: string) {
const lines = [
'[Warn] ----------------------------------------------------------------------',
'[Warn] The following .dockerignore file(s) will not be used:',
];
lines.push(...paths.map((p) => `[Warn] * ${p}`));
lines.push(
...[
'[Warn] When --multi-dockerignore (-m) is used, only .dockerignore files at the',
"[Warn] root of each service's build context (in a microservices/multicontainer",
'[Warn] fleet), plus a .dockerignore file at the overall project root, are used.',
`[Warn] See "balena help ${cmd}" for more details.`,
'[Warn] ----------------------------------------------------------------------',
],
);
return lines;
}

View File

@ -56,20 +56,20 @@ describe('validatePassword() function', () => {
});
describe('validateApplicationName() function', () => {
it('should reject applications names shorter than 4 characters, with a message', () => {
const errorMessage = 'The application name should be at least 4 characters';
it('should reject fleet names shorter than 4 characters, with a message', () => {
const errorMessage = 'The fleet name should be at least 4 characters long';
expect(v.validateApplicationName('abc')).to.equal(errorMessage);
expect(v.validateApplicationName('')).to.equal(errorMessage);
});
it('should return false for application names with characters other than `a-z,A-Z,0-9,_-`', () => {
it('should return false for fleet names with characters other than `a-z,A-Z,0-9,_-`', () => {
expect(v.validateApplicationName('abcd.')).to.equal(false);
expect(v.validateApplicationName('abcd$')).to.equal(false);
expect(v.validateApplicationName('ab cd')).to.equal(false);
expect(v.validateApplicationName('(abcd)')).to.equal(false);
});
it('should return true for valid application names', () => {
it('should return true for valid fleet names', () => {
expect(v.validateApplicationName('Test-Application1')).to.equal(true);
expect(v.validateApplicationName('test_application2')).to.equal(true);
});