Convert command build to typescript, oclif, and refactor

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-07-30 10:39:05 +02:00
parent 94cc84e5ce
commit 09b8cc495c
13 changed files with 500 additions and 312 deletions

View File

@ -156,7 +156,7 @@ const capitanoDoc = {
},
{
title: 'Deploy',
files: ['build/actions/build.js', 'build/actions/deploy.js'],
files: ['build/actions-oclif/build.js', 'build/actions/deploy.js'],
},
{
title: 'Platform',

View File

@ -2288,8 +2288,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 to use
the balena Dockerfile pre-processor (e.g. Dockerfile.template -> Dockerfile).
You must provide either an application or a device-type/architecture pair.
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,
@ -2373,37 +2372,38 @@ adding counter patterns to the applicable .dockerignore file(s), for example
Examples:
$ balena build
$ balena build ./source/
$ balena build --application myApp
$ balena build ./source/ --application myApp
$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated
$ balena build --application MyApp ./source/
$ balena build --docker /var/run/docker.sock # Linux, Mac
$ balena build --docker //./pipe/docker_engine # Windows
$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem
$ 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
### Arguments
#### SOURCE
path of project source directory
### Options
#### --arch, -A &#60;arch&#62;
#### -A, --arch ARCH
The architecture to build for
the architecture to build for
#### --deviceType, -d &#60;deviceType&#62;
#### -d, --deviceType DEVICETYPE
The type of device this build is for
the type of device this build is for
#### --application, -a &#60;application&#62;
#### -a, --application APPLICATION
The target balena application this build is for
name of the target balena application this build is for
#### --projectName, -n &#60;projectName&#62;
Specify an alternate project name; default is the directory name
#### --emulated, -e
#### -e, --emulated
Run an emulated build using Qemu
#### --dockerfile &#60;Dockerfile&#62;
#### --dockerfile DOCKERFILE
Alternative Dockerfile name/path, relative to the source folder
@ -2415,17 +2415,17 @@ No-op and deprecated since balena CLI v12.0.0. Build logs are now shown by defau
Hide the image build log output (produce less verbose output)
#### --gitignore, -g
#### -g, --gitignore
Consider .gitignore files in addition to the .dockerignore file. This reverts
to the CLI v11 behavior/implementation (deprecated) if compatibility is required
until your project can be adapted.
#### --multi-dockerignore, -m
#### -m, --multi-dockerignore
Have each service use its own .dockerignore file. See "balena help build".
#### --nogitignore, -G
#### -G, --nogitignore
No-op (default behavior) since balena CLI v12.0.0. See "balena help build".
@ -2433,11 +2433,11 @@ No-op (default behavior) since balena CLI v12.0.0. See "balena help build".
Disable project validation check of 'docker-compose.yml' file in parent folder
#### --registry-secrets, -R &#60;secrets.yml|.json&#62;
#### -R, --registry-secrets REGISTRY-SECRETS
Path to a YAML or JSON file with passwords for a private Docker registry
#### --convert-eol, -l
#### -l, --convert-eol
No-op and deprecated since balena CLI v12.0.0
@ -2445,39 +2445,19 @@ No-op and deprecated since balena CLI v12.0.0
Don't convert line endings from CRLF (Windows format) to LF (Unix format).
#### --docker, -P &#60;docker&#62;
#### -n, --projectName PROJECTNAME
Path to a local docker socket (e.g. /var/run/docker.sock)
Specify an alternate project name; default is the directory name
#### --dockerHost, -h &#60;dockerHost&#62;
Docker daemon hostname or IP address (dev machine or balena device)
#### --dockerPort, -p &#60;dockerPort&#62;
Docker daemon TCP port number (hint: 2375 for balena devices)
#### --ca &#60;ca&#62;
Docker host TLS certificate authority file
#### --cert &#60;cert&#62;
Docker host TLS certificate file
#### --key &#60;key&#62;
Docker host TLS key file
#### --tag, -t &#60;tag&#62;
#### -t, --tag TAG
The alias to the generated image
#### --buildArg, -B &#60;arg&#62;
#### -B, --buildArg BUILDARG
Set a build-time variable (eg. "-B 'ARG=value'"). Can be specified multiple times.
#### --cache-from &#60;image-list&#62;
#### --cache-from CACHE-FROM
Comma-separated list (no spaces) of image names for build cache resolution. Implements the same feature as the "docker build --cache-from" option.
@ -2606,10 +2586,6 @@ Force a rebuild before deploy
Don't upload build logs to the dashboard with image (if building)
#### --projectName, -n &#60;projectName&#62;
Specify an alternate project name; default is the directory name
#### --emulated, -e
Run an emulated build using Qemu
@ -2656,6 +2632,10 @@ No-op and deprecated since balena CLI v12.0.0
Don't convert line endings from CRLF (Windows format) to LF (Unix format).
#### --projectName, -n &#60;projectName&#62;
Specify an alternate project name; default is the directory name
#### --docker, -P &#60;docker&#62;
Path to a local docker socket (e.g. /var/run/docker.sock)

269
lib/actions-oclif/build.ts Normal file
View File

@ -0,0 +1,269 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk } from '../utils/lazy';
import * as compose from '../utils/compose';
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
import { composeCliFlags } from '../utils/compose_ts';
import type { DockerCliFlags } from '../utils/docker';
import { dockerCliFlags } from '../utils/docker';
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
arch?: string;
deviceType?: string;
application?: string;
source?: string; // Not part of command profile - source param copied here.
help: void;
}
interface ArgsDef {
source?: string;
}
export default class BuildCmd extends Command {
public static description = `\
Build a project locally.
Use this command to build an image or a complete multicontainer project with
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.
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,
each service defined in the compose file will be built. If a compose file isn't
found, it will look for a Dockerfile[.template] file (or alternative Dockerfile
specified with the \`--dockerfile\` option), and if no dockerfile is found, it
will try to generate one.
${registrySecretsHelp}
${dockerignoreHelp}
`;
public static examples = [
'$ balena build --application myApp',
'$ balena build ./source/ --application myApp',
'$ 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',
];
public static args = [
{
name: 'source',
description: 'path of project source directory',
},
];
public static usage = 'build [source]';
public static flags: flags.Input<FlagsDef> = {
arch: flags.string({
description: 'the architecture to build for',
char: 'A',
}),
deviceType: flags.string({
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',
}),
...composeCliFlags,
...dockerCliFlags,
help: cf.help,
};
public static primary = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
BuildCmd,
);
await Command.checkLoggedInIf(!!options.application);
// compositions with many services trigger misleading warnings
// @ts-ignore editing property that isn't typed but does exist
(await import('events')).defaultMaxListeners = 1000;
const sdk = getBalenaSdk();
const logger = await Command.getLogger();
logger.logDebug('Parsing input...');
this.translateParams(params, options);
await this.validateOptions(options, sdk);
const app = await this.getAppAndResolveArch(options);
const { docker, buildOpts, composeOpts } = await this.prepareBuild(options);
try {
await this.buildProject(docker, logger, composeOpts, {
app,
arch: options.arch!,
deviceType: options.deviceType!,
buildEmulated: options.emulated,
buildOpts,
});
} catch (err) {
logger.logError('Build failed.');
throw err;
}
logger.outputDeferredMessages();
logger.logSuccess('Build succeeded!');
}
protected translateParams(params: ArgsDef, options: FlagsDef) {
// Copy flags to those expected by other modules
options.arg = options.buildArg;
delete options.buildArg;
options['image-list'] = options['cache-from'];
delete options['cache-from'];
// `build` accepts `[source]` as a parameter, but compose expects it
// as an option. swap them here
if (options.source == null) {
options.source = params.source;
}
delete params.source;
}
protected async validateOptions(opts: FlagsDef, sdk: BalenaSDK) {
// Validate option combinations
if (
(opts.application == null &&
(opts.arch == null || opts.deviceType == null)) ||
(opts.application != null &&
(opts.arch != null || opts.deviceType != null))
) {
const { ExpectedError } = await import('../errors');
throw new ExpectedError(
'You must specify either an application or an arch/deviceType pair to build for',
);
}
// Validate project directory
const { validateProjectDirectory } = await import('../utils/compose_ts');
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
sdk,
{
dockerfilePath: opts.dockerfile,
noParentCheck: opts['noparent-check'] || false,
projectPath: opts.source || '.',
registrySecretsPath: opts['registry-secrets'],
},
);
opts.dockerfile = dockerfilePath;
opts['registry-secrets'] = registrySecrets;
}
protected async getAppAndResolveArch(opts: FlagsDef) {
if (opts.application) {
const { getAppWithArch } = await import('../utils/helpers');
const app = await getAppWithArch(opts.application);
opts.arch = app.arch;
opts.deviceType = app.device_type;
return app;
}
}
protected async prepareBuild(options: FlagsDef) {
const { getDocker, generateBuildOpts } = await import('../utils/docker');
const [docker, buildOpts, composeOpts] = await Promise.all([
getDocker(options),
generateBuildOpts(options),
compose.generateOpts(options),
]);
return {
docker,
buildOpts,
composeOpts,
};
}
/**
* Opts must be an object with the following keys:
* app: the app this build is for (optional)
* arch: the architecture to build for
* deviceType: the device type to build for
* buildEmulated
* buildOpts: arguments to forward to docker build command
*
* @param {DockerToolbelt} docker
* @param {Logger} logger
* @param {ComposeOpts} composeOpts
* @param opts
*/
protected async buildProject(
docker: import('docker-toolbelt'),
logger: import('../utils/logger'),
composeOpts: ComposeOpts,
opts: {
app?: Application;
arch: string;
deviceType: string;
buildEmulated: boolean;
buildOpts: any;
},
) {
const { loadProject } = await import('../utils/compose_ts');
const project = await loadProject(logger, composeOpts);
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
if (
appType != null &&
project.descriptors.length > 1 &&
!appType.supports_multicontainer
) {
logger.logWarn(
'Target application does not support multiple containers.\n' +
'Continuing with build, but you will not be able to deploy.',
);
}
await compose.buildProject(
docker,
logger,
project.path,
project.name,
project.composition,
opts.arch,
opts.deviceType,
opts.buildEmulated,
opts.buildOpts,
composeOpts.inlineLogs,
composeOpts.convertEol,
composeOpts.dockerfilePath,
composeOpts.nogitignore,
composeOpts.multiDockerignore,
);
}
}

View File

@ -1,206 +0,0 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as dockerUtils from '../utils/docker';
import * as compose from '../utils/compose';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
import { getBalenaSdk } from '../utils/lazy';
/**
* Opts must be an object with the following keys:
* app: the app this build is for (optional)
* arch: the architecture to build for
* deviceType: the device type to build for
* buildEmulated
* buildOpts: arguments to forward to docker build command
*
* @param {import('docker-toolbelt')} docker
* @param {import('../utils/logger')} logger
* @param {import('../utils/compose-types').ComposeOpts} composeOpts
* @param {any} opts
*/
const buildProject = function (docker, logger, composeOpts, opts) {
const { loadProject } = require('../utils/compose_ts');
return loadProject(logger, composeOpts)
.then(function (project) {
const appType = opts.app?.application_type?.[0];
if (
appType != null &&
project.descriptors.length > 1 &&
!appType.supports_multicontainer
) {
logger.logWarn(
'Target application does not support multiple containers.\n' +
'Continuing with build, but you will not be able to deploy.',
);
}
return compose.buildProject(
docker,
logger,
project.path,
project.name,
project.composition,
opts.arch,
opts.deviceType,
opts.buildEmulated,
opts.buildOpts,
composeOpts.inlineLogs,
composeOpts.convertEol,
composeOpts.dockerfilePath,
composeOpts.nogitignore,
composeOpts.multiDockerignore,
);
})
.then(function () {
logger.outputDeferredMessages();
logger.logSuccess('Build succeeded!');
})
.catch((err) => {
logger.logError('Build failed');
throw err;
});
};
export const build = {
signature: 'build [source]',
description: 'Build a single image or a multicontainer project locally',
primary: true,
help: `\
Use this command to build an image or a complete multicontainer project with
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 to use
the balena Dockerfile pre-processor (e.g. Dockerfile.template -> Dockerfile).
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,
each service defined in the compose file will be built. If a compose file isn't
found, it will look for a Dockerfile[.template] file (or alternative Dockerfile
specified with the \`--dockerfile\` option), and if no dockerfile is found, it
will try to generate one.
${registrySecretsHelp}
${dockerignoreHelp}
Examples:
$ balena build
$ balena build ./source/
$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated
$ balena build --application MyApp ./source/
$ balena build --docker /var/run/docker.sock # Linux, Mac
$ balena build --docker //./pipe/docker_engine # Windows
$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem\
`,
options: dockerUtils.appendOptions(
compose.appendOptions([
{
signature: 'arch',
parameter: 'arch',
description: 'The architecture to build for',
alias: 'A',
},
{
signature: 'deviceType',
parameter: 'deviceType',
description: 'The type of device this build is for',
alias: 'd',
},
{
signature: 'application',
parameter: 'application',
description: 'The target balena application this build is for',
alias: 'a',
},
]),
),
async action(params, options) {
// compositions with many services trigger misleading warnings
// @ts-ignore editing property that isn't typed but does exist
require('events').defaultMaxListeners = 1000;
const sdk = getBalenaSdk();
const { ExpectedError } = require('../errors');
const { checkLoggedIn } = require('../utils/patterns');
const { validateProjectDirectory } = require('../utils/compose_ts');
const helpers = require('../utils/helpers');
const Logger = require('../utils/logger');
const logger = Logger.getLogger();
logger.logDebug('Parsing input...');
// `build` accepts `[source]` as a parameter, but compose expects it
// as an option. swap them here
if (options.source == null) {
options.source = params.source;
}
delete params.source;
const { application, arch, deviceType } = options;
if (
(application == null && (arch == null || deviceType == null)) ||
(application != null && (arch != null || deviceType != null))
) {
throw new ExpectedError(
'You must specify either an application or an arch/deviceType pair to build for',
);
}
if (application) {
await checkLoggedIn();
}
return validateProjectDirectory(sdk, {
dockerfilePath: options.dockerfile,
noParentCheck: options['noparent-check'] || false,
projectPath: options.source || '.',
registrySecretsPath: options['registry-secrets'],
})
.then(function ({ dockerfilePath, registrySecrets }) {
options.dockerfile = dockerfilePath;
options['registry-secrets'] = registrySecrets;
if (arch != null && deviceType != null) {
return [undefined, arch, deviceType];
} else {
return helpers
.getAppWithArch(application)
.then((app) => [app, app.arch, app.device_type]);
}
})
.then(function ([app, resolvedArch, resolvedDeviceType]) {
return Promise.all([
dockerUtils.getDocker(options),
dockerUtils.generateBuildOpts(options),
compose.generateOpts(options),
]).then(([docker, buildOpts, composeOpts]) =>
buildProject(docker, logger, composeOpts, {
app,
arch: resolvedArch,
deviceType: resolvedDeviceType,
buildEmulated: !!options.emulated,
buildOpts,
}),
);
});
},
};

View File

@ -16,6 +16,5 @@ limitations under the License.
export * as help from './help';
export { build } from './build';
export { deploy } from './deploy';
export { preload } from './preload';

View File

@ -50,7 +50,6 @@ capitano.command(actions.help.help);
capitano.command(actions.preload);
// ------------ Local build and deploy -------
capitano.command(actions.build);
capitano.command(actions.deploy);
export function run(argv: string[]) {

View File

@ -78,11 +78,25 @@ export default abstract class BalenaCommand extends Command {
* Note, currently public to allow use outside of derived commands
* (as some command implementations require this. Can be made protected
* if this changes).
*
* @throws {NotLoggedInError}
*/
public static async checkLoggedIn() {
await (await import('./utils/patterns')).checkLoggedIn();
}
/**
* Throw NotLoggedInError if not logged in when condition true.
*
* @param {boolean} doCheck - will check if true.
* @throws {NotLoggedInError}
*/
public static async checkLoggedInIf(doCheck: boolean) {
if (doCheck) {
await this.checkLoggedIn();
}
}
/**
* Read stdin contents and make available to command.
*
@ -93,6 +107,13 @@ export default abstract class BalenaCommand extends Command {
this.stdin = await (await import('get-stdin'))();
}
/**
* Get a logger instance.
*/
protected static async getLogger() {
return (await import('./utils/logger')).getLogger();
}
protected async init() {
const ctr = this.constructor as typeof BalenaCommand;

View File

@ -153,6 +153,7 @@ export const convertedCommands = [
'config:read',
'config:reconfigure',
'config:write',
'build',
'device',
'device:identify',
'device:init',

View File

@ -55,6 +55,21 @@ export interface ComposeOpts {
projectPath: string;
}
export interface ComposeCliFlags {
emulated: boolean;
dockerfile?: string;
logs: boolean;
nologs: boolean;
gitignore: boolean;
'multi-dockerignore': boolean;
nogitignore: boolean;
'noparent-check': boolean;
'registry-secrets'?: string | RegistrySecrets;
'convert-eol': boolean;
'noconvert-eol': boolean;
projectName?: string;
}
export interface ComposeProject {
path: string;
name: string;

View File

@ -16,24 +16,11 @@
*/
import * as path from 'path';
import { ExpectedError } from '../errors';
import { getChalk, stripIndent } from './lazy';
export const appendProjectOptions = (opts) =>
opts.concat([
{
signature: 'projectName',
parameter: 'projectName',
description:
'Specify an alternate project name; default is the directory name',
alias: 'n',
},
]);
export function appendOptions(opts) {
const { isV12 } = require('./version');
return appendProjectOptions(opts).concat([
return opts.concat([
{
signature: 'emulated',
description: 'Run an emulated build using Qemu',
@ -48,21 +35,16 @@ export function appendOptions(opts) {
},
{
signature: 'logs',
description: isV12()
? 'No-op and deprecated since balena CLI v12.0.0. Build logs are now shown by default.'
: 'Display full log output',
description:
'No-op and deprecated since balena CLI v12.0.0. Build logs are now shown by default.',
boolean: true,
},
{
signature: 'nologs',
description:
'Hide the image build log output (produce less verbose output)',
boolean: true,
},
...(isV12()
? [
{
signature: 'nologs',
description:
'Hide the image build log output (produce less verbose output)',
boolean: true,
},
]
: []),
{
signature: 'gitignore',
alias: 'g',
@ -100,24 +82,23 @@ export function appendOptions(opts) {
},
{
signature: 'convert-eol',
description: isV12()
? 'No-op and deprecated since balena CLI v12.0.0'
: `\
On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). \
Source files are not modified.`,
description: 'No-op and deprecated since balena CLI v12.0.0',
boolean: true,
alias: 'l',
},
...(isV12()
? [
{
signature: 'noconvert-eol',
description:
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
boolean: true,
},
]
: []),
{
signature: 'noconvert-eol',
description:
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
boolean: true,
},
{
signature: 'projectName',
parameter: 'projectName',
description:
'Specify an alternate project name; default is the directory name',
alias: 'n',
},
]);
}
@ -126,7 +107,6 @@ Source files are not modified.`,
*/
export function generateOpts(options) {
const { promises: fs } = require('fs');
const { isV12 } = require('./version');
if (options.gitignore && options['multi-dockerignore']) {
throw new ExpectedError(
@ -136,8 +116,8 @@ export function generateOpts(options) {
return fs.realpath(options.source || '.').then((projectPath) => ({
projectName: options.projectName,
projectPath,
inlineLogs: !options.nologs && (!!options.logs || isV12()),
convertEol: isV12() ? !options['noconvert-eol'] : !!options['convert-eol'],
inlineLogs: !options.nologs,
convertEol: !options['noconvert-eol'],
dockerfilePath: options.dockerfile,
multiDockerignore: !!options['multi-dockerignore'],
nogitignore: !options.gitignore,

View File

@ -24,12 +24,11 @@ import { Composition } from 'resin-compose-parse';
import * as MultiBuild from 'resin-multibuild';
import { Readable } from 'stream';
import * as tar from 'tar-stream';
import { stripIndent } from './lazy';
import { ExpectedError } from '../errors';
import { getBalenaSdk, getChalk } from '../utils/lazy';
import { getBalenaSdk, getChalk, stripIndent } from './lazy';
import {
BuiltImage,
ComposeCliFlags,
ComposeOpts,
ComposeProject,
Release,
@ -38,6 +37,7 @@ import {
} from './compose-types';
import { DeviceInfo } from './device/api';
import Logger = require('./logger');
import { flags } from '@oclif/command';
export interface RegistrySecrets {
[registryAddress: string]: {
@ -897,3 +897,60 @@ export function createRunLoop(tick: (...args: any[]) => void) {
};
return runloop;
}
export const composeCliFlags: flags.Input<ComposeCliFlags> = {
emulated: flags.boolean({
description: 'Run an emulated build using Qemu',
char: 'e',
}),
dockerfile: flags.string({
description:
'Alternative Dockerfile name/path, relative to the source folder',
}),
logs: flags.boolean({
description:
'No-op and deprecated since balena CLI v12.0.0. Build logs are now shown by default.',
}),
nologs: flags.boolean({
description:
'Hide the image build log output (produce less verbose output)',
}),
gitignore: flags.boolean({
description: stripIndent`
Consider .gitignore files in addition to the .dockerignore file. This reverts
to the CLI v11 behavior/implementation (deprecated) if compatibility is required
until your project can be adapted.`,
char: 'g',
}),
'multi-dockerignore': flags.boolean({
description:
'Have each service use its own .dockerignore file. See "balena help build".',
char: 'm',
}),
nogitignore: flags.boolean({
description: `No-op (default behavior) since balena CLI v12.0.0. See "balena help build".`,
char: 'G',
}),
'noparent-check': flags.boolean({
description:
"Disable project validation check of 'docker-compose.yml' file in parent folder",
}),
'registry-secrets': flags.string({
description:
'Path to a YAML or JSON file with passwords for a private Docker registry',
char: 'R',
}),
'convert-eol': flags.boolean({
description: 'No-op and deprecated since balena CLI v12.0.0',
char: 'l',
}),
'noconvert-eol': flags.boolean({
description:
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
}),
projectName: flags.string({
description:
'Specify an alternate project name; default is the directory name',
char: 'n',
}),
};

View File

@ -16,6 +16,8 @@
*/
import type * as dockerode from 'dockerode';
import { flags } from '@oclif/command';
import { parseAsInteger } from './validation';
export * from './docker-js';
@ -23,6 +25,77 @@ interface BalenaEngineVersion extends dockerode.DockerVersion {
Engine?: string;
}
export interface DockerConnectionCliFlags {
docker?: string;
dockerHost?: string;
dockerPort?: number;
ca?: string;
cert?: string;
key?: string;
}
export interface DockerCliFlags extends DockerConnectionCliFlags {
tag?: string;
buildArg?: string; // maps to 'arg'
arg?: string; // Not part of command profile
'cache-from'?: string; // maps to 'image-list'
'image-list'?: string; // Not part of command profile
nocache: boolean;
squash: boolean;
}
export const dockerConnectionCliFlags: flags.Input<DockerConnectionCliFlags> = {
docker: flags.string({
description: 'Path to a local docker socket (e.g. /var/run/docker.sock)',
char: 'P',
}),
dockerHost: flags.string({
description:
'Docker daemon hostname or IP address (dev machine or balena device) ',
char: 'h',
}),
dockerPort: flags.integer({
description:
'Docker daemon TCP port number (hint: 2375 for balena devices)',
char: 'p',
parse: (p) => parseAsInteger(p, 'dockerPort'),
}),
ca: flags.string({
description: 'Docker host TLS certificate authority file',
}),
cert: flags.string({
description: 'Docker host TLS certificate file',
}),
key: flags.string({
description: 'Docker host TLS key file',
}),
};
export const dockerCliFlags: flags.Input<DockerCliFlags> = {
tag: flags.string({
description: 'The alias to the generated image',
char: 't',
}),
buildArg: flags.string({
description:
'Set a build-time variable (eg. "-B \'ARG=value\'"). Can be specified multiple times.',
char: 'B',
// Maps to flag `arg`
}),
'cache-from': flags.string({
description: `\
Comma-separated list (no spaces) of image names for build cache resolution. \
Implements the same feature as the "docker build --cache-from" option.`,
// Maps to flag `image-list`
}),
nocache: flags.boolean({
description: "Don't use docker layer caching when building",
}),
squash: flags.boolean({
description: 'Squash newly built layers into a single new layer',
}),
};
export async function isBalenaEngine(docker: dockerode): Promise<boolean> {
// dockerVersion.Engine should equal 'balena-engine' for the current/latest
// version of balenaEngine, but it was at one point (mis)spelt 'balaena':

View File

@ -36,7 +36,7 @@ Primary commands:
device <uuid> show info about a single device
tunnel <deviceorapplication> tunnel local ports to your balenaOS device
preload <image> preload an app on a disk image (or Edison zip archive)
build [source] Build a single image or a multicontainer project locally
build [source] build a project locally
deploy <appName> [image] Deploy a single image or a multicontainer project to a balena application
join [deviceiporhostname] move a local device to an application on another balena server
leave [deviceiporhostname] remove a local device from its balena application