mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-19 21:57:51 +00:00
Convert command build
to typescript, oclif, and refactor
Change-type: patch Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
94cc84e5ce
commit
09b8cc495c
@ -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',
|
||||
|
@ -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 <arch>
|
||||
#### -A, --arch ARCH
|
||||
|
||||
The architecture to build for
|
||||
the architecture to build for
|
||||
|
||||
#### --deviceType, -d <deviceType>
|
||||
#### -d, --deviceType DEVICETYPE
|
||||
|
||||
The type of device this build is for
|
||||
the type of device this build is for
|
||||
|
||||
#### --application, -a <application>
|
||||
#### -a, --application APPLICATION
|
||||
|
||||
The target balena application this build is for
|
||||
name of the target balena application this build is for
|
||||
|
||||
#### --projectName, -n <projectName>
|
||||
|
||||
Specify an alternate project name; default is the directory name
|
||||
|
||||
#### --emulated, -e
|
||||
#### -e, --emulated
|
||||
|
||||
Run an emulated build using Qemu
|
||||
|
||||
#### --dockerfile <Dockerfile>
|
||||
#### --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 <secrets.yml|.json>
|
||||
#### -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 <docker>
|
||||
#### -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 <dockerHost>
|
||||
|
||||
Docker daemon hostname or IP address (dev machine or balena device)
|
||||
|
||||
#### --dockerPort, -p <dockerPort>
|
||||
|
||||
Docker daemon TCP port number (hint: 2375 for balena devices)
|
||||
|
||||
#### --ca <ca>
|
||||
|
||||
Docker host TLS certificate authority file
|
||||
|
||||
#### --cert <cert>
|
||||
|
||||
Docker host TLS certificate file
|
||||
|
||||
#### --key <key>
|
||||
|
||||
Docker host TLS key file
|
||||
|
||||
#### --tag, -t <tag>
|
||||
#### -t, --tag TAG
|
||||
|
||||
The alias to the generated image
|
||||
|
||||
#### --buildArg, -B <arg>
|
||||
#### -B, --buildArg BUILDARG
|
||||
|
||||
Set a build-time variable (eg. "-B 'ARG=value'"). Can be specified multiple times.
|
||||
|
||||
#### --cache-from <image-list>
|
||||
#### --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 <projectName>
|
||||
|
||||
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 <projectName>
|
||||
|
||||
Specify an alternate project name; default is the directory name
|
||||
|
||||
#### --docker, -P <docker>
|
||||
|
||||
Path to a local docker socket (e.g. /var/run/docker.sock)
|
||||
|
269
lib/actions-oclif/build.ts
Normal file
269
lib/actions-oclif/build.ts
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
@ -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';
|
||||
|
@ -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[]) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -153,6 +153,7 @@ export const convertedCommands = [
|
||||
'config:read',
|
||||
'config:reconfigure',
|
||||
'config:write',
|
||||
'build',
|
||||
'device',
|
||||
'device:identify',
|
||||
'device:init',
|
||||
|
15
lib/utils/compose-types.d.ts
vendored
15
lib/utils/compose-types.d.ts
vendored
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
}),
|
||||
};
|
||||
|
@ -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':
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user