Convert command push to oclif

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-07-24 14:25:46 +02:00 committed by Balena CI
parent 66f793e327
commit 0f058a4441
10 changed files with 447 additions and 477 deletions

View File

@ -141,7 +141,7 @@ const capitanoDoc = {
},
{
title: 'Push',
files: ['build/actions/push.js'],
files: ['build/actions-oclif/push.js'],
},
{
title: 'Settings',

View File

@ -242,7 +242,7 @@ Users are encouraged to regularly update the balena CLI to the latest version.
- Push
- [push &#60;applicationOrDevice&#62;](#push-applicationordevice)
- [push &#60;applicationordevice&#62;](#push-applicationordevice)
- Settings
@ -2006,7 +2006,7 @@ Docker host TLS key file
## push &#60;applicationOrDevice&#62;
This command can be used to start a build on the remote balena cloud builders,
start a build on the remote balena cloud builders,
or a local mode balena device.
When building on the balenaCloud servers, the given source directory will be
@ -2103,36 +2103,45 @@ adding counter patterns to the applicable .dockerignore file(s), for example
- https://docs.docker.com/engine/reference/builder/#dockerignore-file
- https://www.npmjs.com/package/@balena/dockerignore
Note: --service and --env flags must come after the applicationOrDevice parameter,
as per examples.
Examples:
$ balena push myApp
$ balena push myApp --source <source directory>
$ balena push myApp -s <source directory>
$ balena push 10.0.0.1
$ balena push 10.0.0.1 --source <source directory>
$ balena push 10.0.0.1 --service my-service
$ balena push 10.0.0.1 --env MY_ENV_VAR=value --env my-service:SERVICE_VAR=value
$ balena push 10.0.0.1 --nolive
$ balena push 23c73a1.local --system
$ balena push 23c73a1.local --system --service my-service
### Arguments
#### APPLICATIONORDEVICE
application name, or device address (for local pushes)
### Options
#### --source, -s &#60;source&#62;
#### -s, --source SOURCE
Source directory to be sent to balenaCloud or balenaOS device (default: current working dir)
#### --emulated, -e
#### -f, --emulated
Force an emulated build to occur on the remote builder
#### --dockerfile &#60;Dockerfile&#62;
#### --dockerfile DOCKERFILE
Alternative Dockerfile name/path, relative to the source folder
#### --nocache, -c
#### -c, --nocache
Don't use cached layers of previously built images for this project. This ensures
that the latest base image and packages are pulled. Note that build logs may still
@ -2144,7 +2153,7 @@ display the "Using cache" lines for each build step of a Dockerfile.
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 local YAML or JSON file containing Docker registry passwords used to pull base images.
Note that if registry-secrets are not provided on the command line, a secrets configuration
@ -2156,7 +2165,7 @@ Don't run a live session on this push. The filesystem will not be monitored, and
will not be synchronized to any running containers. Note that both this flag and --detached
and required to cause the process to end once the initial build has completed.
#### --detached, -d
#### -d, --detached
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).
@ -2164,7 +2173,7 @@ 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.
#### --service &#60;service&#62;
#### --service SERVICE
Reject logs not originating from this service.
This can be used in combination with --system and other --service flags.
@ -2175,7 +2184,7 @@ Only valid when pushing to a local mode device.
Only show system logs. This can be used in combination with --service.
Only valid when pushing to a local mode device.
#### --env &#60;env&#62;
#### --env ENV
When performing a push to device, run the built containers with environment
variables provided with this argument. Environment variables can be applied
@ -2185,7 +2194,7 @@ separated by a colon, e.g:
Note that if the service name cannot be found in the composition, the entire
left hand side of the = character will be treated as the variable name.
#### --convert-eol, -l
#### -l, --convert-eol
No-op and deprecated since balena CLI v12.0.0
@ -2193,15 +2202,15 @@ No-op and deprecated since balena CLI v12.0.0
Don't convert line endings from CRLF (Windows format) to LF (Unix format).
#### --multi-dockerignore, -m
#### -m, --multi-dockerignore
Have each service use its own .dockerignore file. See "balena help push".
#### --nogitignore, -G
#### -G, --nogitignore
No-op (default behavior) since balena CLI v12.0.0. See "balena help push".
#### --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

View File

@ -39,7 +39,8 @@ export default class NoteCmd extends Command {
it will be read from stdin.
To view device notes, use the \`balena device <uuid>\` command.
`;
`;
public static examples = [
'$ balena note "My useful note" --device 7cf02a6',
'$ cat note.txt | balena note --device 7cf02a6',

411
lib/actions-oclif/push.ts Normal file
View File

@ -0,0 +1,411 @@
/**
* @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, stripIndent } from '../utils/lazy';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
import type { BalenaSDK } from 'balena-sdk';
import { ExpectedError, instanceOf } from '../errors';
enum BuildTarget {
Cloud,
Device,
}
interface FlagsDef {
source?: string;
emulated: boolean;
dockerfile?: string; // DeviceDeployOptions.dockerfilePath (alternative Dockerfile)
nocache?: boolean;
'noparent-check'?: boolean;
'registry-secrets'?: string;
gitignore?: boolean;
nogitignore?: boolean;
nolive?: boolean;
detached?: boolean;
service?: string[];
system?: boolean;
env?: string[];
'convert-eol'?: boolean;
'noconvert-eol'?: boolean;
'multi-dockerignore'?: boolean;
help: void;
}
interface ArgsDef {
applicationOrDevice: string;
}
export default class PushCmd extends Command {
public static description = stripIndent`
Start a remote build on the balena cloud build servers or a local mode device.
start a build on the remote balena cloud builders,
or a local mode balena 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
"git push" deployment method.
When building on a local mode device, the given source directory will be
built on the device, and the resulting containers will be run on the device.
Logs will be streamed back from the device as part of the same invocation.
The web dashboard can be used to switch a device to local mode:
https://www.balena.io/docs/learn/develop/local-mode/
Note that local mode requires a supervisor version of at least v7.21.0.
The logs from only a single service can be shown with the --service flag, and
showing only the system logs can be achieved with --system. Note that these
flags can be used together.
When pushing to a local device a live session will be started.
The project source folder is watched for filesystem events, and changes
to files and folders are automatically synchronized to the running
containers. The synchronization is only in one direction, from this machine to
the device, and changes made on the device itself may be overwritten.
This feature requires a device running supervisor version v9.7.0 or greater.
${registrySecretsHelp.split('\n').join('\n\t\t')}
${dockerignoreHelp.split('\n').join('\n\t\t')}
Note: --service and --env flags must come after the applicationOrDevice parameter,
as per examples.
`;
public static examples = [
'$ balena push myApp',
'$ balena push myApp --source <source directory>',
'$ balena push myApp -s <source directory>',
'',
'$ balena push 10.0.0.1',
'$ balena push 10.0.0.1 --source <source directory>',
'$ balena push 10.0.0.1 --service my-service',
'$ balena push 10.0.0.1 --env MY_ENV_VAR=value --env my-service:SERVICE_VAR=value',
'$ balena push 10.0.0.1 --nolive',
'',
'$ balena push 23c73a1.local --system',
'$ balena push 23c73a1.local --system --service my-service',
];
public static args = [
{
name: 'applicationOrDevice',
description: 'application name, or device address (for local pushes)',
required: true,
},
];
public static usage = 'push <applicationOrDevice>';
public static flags: flags.Input<FlagsDef> = {
source: flags.string({
description:
'Source directory to be sent to balenaCloud or balenaOS device (default: current working dir)',
char: 's',
}),
emulated: flags.boolean({
description: 'Force an emulated build to occur on the remote builder',
char: 'f',
}),
dockerfile: flags.string({
description:
'Alternative Dockerfile name/path, relative to the source folder',
}),
nocache: flags.boolean({
description: stripIndent`
Don't use cached layers of previously built images for this project. This ensures
that the latest base image and packages are pulled. Note that build logs may still
display the message _"Pulling previous images for caching purposes" (as the cloud
builder needs previous images to compute delta updates), but the logs will not
display the "Using cache" lines for each build step of a Dockerfile.`,
char: 'c',
}),
'noparent-check': flags.boolean({
description: `Disable project validation check of 'docker-compose.yml' file in parent folder`,
}),
'registry-secrets': flags.string({
description: stripIndent`
Path to a local YAML or JSON file containing Docker registry passwords used to pull base images.
Note that if registry-secrets are not provided on the command line, a secrets configuration
file from the balena directory will be used (usually $HOME/.balena/secrets.yml|.json)`,
char: 'R',
}),
nolive: flags.boolean({
description: stripIndent`
Don't run a live session on this push. The filesystem will not be monitored, and changes
will not be synchronized to any running containers. Note that both this flag and --detached
and required to cause the process to end once the initial build has completed.`,
}),
detached: flags.boolean({
description: stripIndent`
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.`,
char: 'd',
}),
service: flags.string({
description: stripIndent`
Reject logs not originating from this service.
This can be used in combination with --system and other --service flags.
Only valid when pushing to a local mode device.`,
multiple: true,
}),
system: flags.boolean({
description: stripIndent`
Only show system logs. This can be used in combination with --service.
Only valid when pushing to a local mode device.`,
}),
env: flags.string({
description: stripIndent`
When performing a push to device, run the built containers with environment
variables provided with this argument. Environment variables can be applied
to individual services by adding their service name before the argument,
separated by a colon, e.g:
--env main:MY_ENV=value
Note that if the service name cannot be found in the composition, the entire
left hand side of the = character will be treated as the variable name.
`,
multiple: true,
}),
'convert-eol': flags.boolean({
description: 'No-op and deprecated since balena CLI v12.0.0',
char: 'l',
hidden: true,
}),
'noconvert-eol': flags.boolean({
description: `Don't convert line endings from CRLF (Windows format) to LF (Unix format).`,
}),
'multi-dockerignore': flags.boolean({
description:
'Have each service use its own .dockerignore file. See "balena help push".',
char: 'm',
exclusive: ['gitignore'],
}),
nogitignore: flags.boolean({
description:
'No-op (default behavior) since balena CLI v12.0.0. See "balena help push".',
char: 'G',
hidden: true,
}),
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',
exclusive: ['multi-dockerignore'],
}),
help: cf.help,
};
public static primary = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
PushCmd,
);
const sdk = getBalenaSdk();
const { validateProjectDirectory } = await import('../utils/compose_ts');
const source = options.source || '.';
if (process.env.DEBUG) {
console.error(`[debug] Using ${source} as build source`);
}
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
sdk,
{
dockerfilePath: options.dockerfile,
noParentCheck: options['noparent-check'] || false,
projectPath: source,
registrySecretsPath: options['registry-secrets'],
},
);
const nogitignore = !options.gitignore;
const convertEol = !options['noconvert-eol'];
const appOrDevice = params.applicationOrDevice;
const buildTarget = await this.getBuildTarget(appOrDevice);
switch (buildTarget) {
case BuildTarget.Cloud:
const remote = await import('../utils/remote-build');
// Check for invalid options
const localOnlyOptions = ['nolive', 'service', 'system', 'env'];
localOnlyOptions.forEach((opt) => {
// @ts-ignore : Not sure why typescript wont let me do this?
if (options[opt]) {
throw new ExpectedError(
`The --${opt} flag is only valid when pushing to a local mode device`,
);
}
});
const app = appOrDevice;
await Command.checkLoggedIn();
const [token, baseUrl, owner] = await Promise.all([
sdk.auth.getToken(),
sdk.settings.get('balenaUrl'),
this.getAppOwner(sdk, app),
]);
const opts = {
dockerfilePath,
emulated: options.emulated || false,
multiDockerignore: options['multi-dockerignore'] || false,
nocache: options.nocache || false,
registrySecrets,
headless: options.detached || false,
convertEol,
};
const args = {
app,
owner,
source,
auth: token,
baseUrl,
nogitignore,
sdk,
opts,
};
await remote.startRemoteBuild(args);
break;
case BuildTarget.Device:
const deviceDeploy = await import('../utils/device/deploy');
const device = appOrDevice;
const servicesToDisplay = options.service;
// TODO: Support passing a different port
try {
await deviceDeploy.deployToDevice({
source,
deviceHost: device,
dockerfilePath,
registrySecrets,
multiDockerignore: options['multi-dockerignore'] || false,
nocache: options.nocache || false,
nogitignore,
noParentCheck: options['noparent-check'] || false,
nolive: options.nolive || false,
detached: options.detached || false,
services: servicesToDisplay,
system: options.system || false,
env: options.env || [],
convertEol,
});
} catch (e) {
const { BuildError } = await import('../utils/device/errors');
if (instanceOf(e, BuildError)) {
throw new ExpectedError(e.toString());
} else {
throw e;
}
}
break;
default:
throw new ExpectedError(
stripIndent`
Build target not recognized. Please provide either an application name or device address.
The only supported device addresses currently are IP addresses.
If you believe your build target should have been detected, and this is an error, please
create an issue.`,
);
}
}
async getBuildTarget(appOrDevice: string): Promise<BuildTarget | null> {
const {
validateApplicationName,
validateDotLocalUrl,
validateIPAddress,
} = await import('../utils/validation');
// First try the application regex from the api
if (validateApplicationName(appOrDevice)) {
return BuildTarget.Cloud;
}
if (validateIPAddress(appOrDevice) || validateDotLocalUrl(appOrDevice)) {
return BuildTarget.Device;
}
return null;
}
async getAppOwner(sdk: BalenaSDK, appName: string) {
const _ = await import('lodash');
const applications = await sdk.models.application.getAll({
$expand: {
user: {
$select: ['username'],
},
},
$filter: {
$eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()],
},
$select: ['id'],
});
if (applications == null || applications.length === 0) {
throw new ExpectedError(
stripIndent`
No applications found with name: ${appName}.
This could mean that the application does not exist, or you do
not have the permissions required to access it.`,
);
}
if (applications.length === 1) {
return _.get(applications, '[0].user[0].username');
}
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app. We
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select
const entries = _.map(applications, (app) => {
const username = _.get(app, 'user[0].username');
return {
name: `${username}/${appName}`,
extra: username,
};
});
const { selectFromList } = await import('../utils/patterns');
const selected = await selectFromList(
`${entries.length} applications found with that name, please select the application you would like to push to`,
entries,
);
return selected.extra;
}
}

View File

@ -15,7 +15,6 @@ limitations under the License.
*/
export * as help from './help';
export * as push from './push';
export { build } from './build';
export { deploy } from './deploy';

View File

@ -1,448 +0,0 @@
/*
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 type { BalenaSDK } from 'balena-sdk';
import type { CommandDefinition } from 'capitano';
import { ExpectedError } from '../errors';
import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
import {
validateApplicationName,
validateDotLocalUrl,
validateIPAddress,
} from '../utils/validation';
import { isV12 } from '../utils/version';
enum BuildTarget {
Cloud,
Device,
}
function getBuildTarget(appOrDevice: string): BuildTarget | null {
// First try the application regex from the api
if (validateApplicationName(appOrDevice)) {
return BuildTarget.Cloud;
}
if (validateIPAddress(appOrDevice) || validateDotLocalUrl(appOrDevice)) {
return BuildTarget.Device;
}
return null;
}
async function getAppOwner(sdk: BalenaSDK, appName: string) {
const { selectFromList } = await import('../utils/patterns');
const _ = await import('lodash');
const applications = await sdk.models.application.getAll({
$expand: {
user: {
$select: ['username'],
},
},
$filter: {
$eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()],
},
$select: ['id'],
});
if (applications == null || applications.length === 0) {
throw new ExpectedError(
stripIndent`
No applications found with name: ${appName}.
This could mean that the application does not exist, or you do
not have the permissions required to access it.`,
);
}
if (applications.length === 1) {
return _.get(applications, '[0].user[0].username');
}
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app. We
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select
const entries = _.map(applications, (app) => {
const username = _.get(app, 'user[0].username');
return {
name: `${username}/${appName}`,
extra: username,
};
});
const selected = await selectFromList(
`${entries.length} applications found with that name, please select the application you would like to push to`,
entries,
);
return selected.extra;
}
export const push: CommandDefinition<
{
// when Capitano converts a positional parameter (but not an option)
// to a number, the original value is preserved with the _raw suffix
applicationOrDevice: string;
applicationOrDevice_raw: string;
},
{
source?: string;
emulated?: boolean;
dockerfile?: string; // DeviceDeployOptions.dockerfilePath (alternative Dockerfile)
nocache?: boolean;
'noparent-check'?: boolean;
'registry-secrets'?: string;
gitignore?: boolean;
nogitignore?: boolean;
nolive?: boolean;
detached?: boolean;
service?: string | string[];
system?: boolean;
env?: string | string[];
'convert-eol'?: boolean;
'noconvert-eol'?: boolean;
'multi-dockerignore'?: boolean;
}
> = {
signature: 'push <applicationOrDevice>',
primary: true,
description:
'Start a remote build on the balena cloud build servers or a local mode device',
help: stripIndent`
This command can be used to start a build on the remote balena cloud builders,
or a local mode balena 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
"git push" deployment method.
When building on a local mode device, the given source directory will be
built on the device, and the resulting containers will be run on the device.
Logs will be streamed back from the device as part of the same invocation.
The web dashboard can be used to switch a device to local mode:
https://www.balena.io/docs/learn/develop/local-mode/
Note that local mode requires a supervisor version of at least v7.21.0.
The logs from only a single service can be shown with the --service flag, and
showing only the system logs can be achieved with --system. Note that these
flags can be used together.
When pushing to a local device a live session will be started.
The project source folder is watched for filesystem events, and changes
to files and folders are automatically synchronized to the running
containers. The synchronization is only in one direction, from this machine to
the device, and changes made on the device itself may be overwritten.
This feature requires a device running supervisor version v9.7.0 or greater.
${registrySecretsHelp.split('\n').join('\n\t\t')}
${dockerignoreHelp.split('\n').join('\n\t\t')}
Examples:
$ balena push myApp
$ balena push myApp --source <source directory>
$ balena push myApp -s <source directory>
$ balena push 10.0.0.1
$ balena push 10.0.0.1 --source <source directory>
$ balena push 10.0.0.1 --service my-service
$ balena push 10.0.0.1 --env MY_ENV_VAR=value --env my-service:SERVICE_VAR=value
$ balena push 10.0.0.1 --nolive
$ balena push 23c73a1.local --system
$ balena push 23c73a1.local --system --service my-service
`,
options: [
{
signature: 'source',
alias: 's',
description:
'Source directory to be sent to balenaCloud or balenaOS device (default: current working dir)',
parameter: 'source',
},
{
signature: 'emulated',
alias: 'e',
description: 'Force an emulated build to occur on the remote builder',
boolean: true,
},
{
signature: 'dockerfile',
parameter: 'Dockerfile',
description:
'Alternative Dockerfile name/path, relative to the source folder',
},
{
signature: 'nocache',
alias: 'c',
description: stripIndent`
Don't use cached layers of previously built images for this project. This ensures
that the latest base image and packages are pulled. Note that build logs may still
display the message _"Pulling previous images for caching purposes" (as the cloud
builder needs previous images to compute delta updates), but the logs will not
display the "Using cache" lines for each build step of a Dockerfile.`,
boolean: true,
},
{
signature: 'noparent-check',
description:
"Disable project validation check of 'docker-compose.yml' file in parent folder",
boolean: true,
},
{
signature: 'registry-secrets',
alias: 'R',
parameter: 'secrets.yml|.json',
description: stripIndent`
Path to a local YAML or JSON file containing Docker registry passwords used to pull base images.
Note that if registry-secrets are not provided on the command line, a secrets configuration
file from the balena directory will be used (usually $HOME/.balena/secrets.yml|.json)`,
},
{
signature: 'nolive',
boolean: true,
description: stripIndent`
Don't run a live session on this push. The filesystem will not be monitored, and changes
will not be synchronized to any running containers. Note that both this flag and --detached
and required to cause the process to end once the initial build has completed.`,
},
{
signature: 'detached',
alias: 'd',
description: stripIndent`
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.`,
boolean: true,
},
{
signature: 'service',
description: stripIndent`
Reject logs not originating from this service.
This can be used in combination with --system and other --service flags.
Only valid when pushing to a local mode device.`,
parameter: 'service',
},
{
signature: 'system',
description: stripIndent`
Only show system logs. This can be used in combination with --service.
Only valid when pushing to a local mode device.`,
boolean: true,
},
{
signature: 'env',
parameter: 'env',
description: stripIndent`
When performing a push to device, run the built containers with environment
variables provided with this argument. Environment variables can be applied
to individual services by adding their service name before the argument,
separated by a colon, e.g:
--env main:MY_ENV=value
Note that if the service name cannot be found in the composition, the entire
left hand side of the = character will be treated as the variable name.
`,
},
{
signature: 'convert-eol',
alias: 'l',
description: isV12()
? 'No-op and deprecated since balena CLI v12.0.0'
: stripIndent`
On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format).
Source files are not modified.`,
boolean: true,
},
...(isV12()
? [
{
signature: 'noconvert-eol',
description:
"Don't convert line endings from CRLF (Windows format) to LF (Unix format).",
boolean: true,
},
]
: []),
{
signature: 'multi-dockerignore',
alias: 'm',
description:
'Have each service use its own .dockerignore file. See "balena help push".',
boolean: true,
},
{
signature: 'nogitignore',
alias: 'G',
description:
'No-op (default behavior) since balena CLI v12.0.0. See "balena help push".',
boolean: true,
},
{
signature: 'gitignore',
alias: 'g',
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.`,
boolean: true,
},
],
async action(params, options) {
const sdk = getBalenaSdk();
const Bluebird = await import('bluebird');
const remote = await import('../utils/remote-build');
const deviceDeploy = await import('../utils/device/deploy');
const { checkLoggedIn } = await import('../utils/patterns');
const { validateProjectDirectory } = await import('../utils/compose_ts');
const { BuildError } = await import('../utils/device/errors');
const appOrDevice: string | null =
params.applicationOrDevice_raw || params.applicationOrDevice;
if (appOrDevice == null) {
throw new ExpectedError('You must specify an application or a device');
}
if (options.gitignore && options['multi-dockerignore']) {
throw new ExpectedError(
'The --gitignore and --multi-dockerignore options cannot be used together',
);
}
const source = options.source || '.';
if (process.env.DEBUG) {
console.error(`[debug] Using ${source} as build source`);
}
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
sdk,
{
dockerfilePath: options.dockerfile,
noParentCheck: options['noparent-check'] || false,
projectPath: source,
registrySecretsPath: options['registry-secrets'],
},
);
const nogitignore = !options.gitignore;
const convertEol = isV12()
? !options['noconvert-eol']
: !!options['convert-eol'];
const buildTarget = getBuildTarget(appOrDevice);
switch (buildTarget) {
case BuildTarget.Cloud:
// Ensure that the live argument has not been passed to a cloud build
if (options.nolive != null) {
throw new ExpectedError(
'The --nolive flag is only valid when pushing to a local mode device',
);
}
if (options.service) {
throw new ExpectedError(
'The --service flag is only valid when pushing to a local mode device.',
);
}
if (options.system) {
throw new ExpectedError(
'The --system flag is only valid when pushing to a local mode device.',
);
}
if (options.env) {
throw new ExpectedError(
'The --env flag is only valid when pushing to a local mode device.',
);
}
const app = appOrDevice;
await checkLoggedIn();
const [token, baseUrl, owner] = await Promise.all([
sdk.auth.getToken(),
sdk.settings.get('balenaUrl'),
getAppOwner(sdk, app),
]);
const opts = {
dockerfilePath,
emulated: options.emulated || false,
multiDockerignore: options['multi-dockerignore'] || false,
nocache: options.nocache || false,
registrySecrets,
headless: options.detached || false,
convertEol,
};
const args = {
app,
owner,
source,
auth: token,
baseUrl,
nogitignore,
sdk,
opts,
};
await remote.startRemoteBuild(args);
break;
case BuildTarget.Device:
const device = appOrDevice;
const servicesToDisplay =
options.service != null
? Array.isArray(options.service)
? options.service
: [options.service]
: undefined;
// TODO: Support passing a different port
await Bluebird.resolve(
deviceDeploy.deployToDevice({
source,
deviceHost: device,
dockerfilePath,
registrySecrets,
multiDockerignore: options['multi-dockerignore'] || false,
nocache: options.nocache || false,
nogitignore,
noParentCheck: options['noparent-check'] || false,
nolive: options.nolive || false,
detached: options.detached || false,
services: servicesToDisplay,
system: options.system || false,
env:
typeof options.env === 'string'
? [options.env]
: options.env || [],
convertEol,
}),
).catch(BuildError, (e) => {
throw new ExpectedError(e.toString());
});
break;
default:
throw new ExpectedError(
stripIndent`
Build target not recognized. Please provide either an application name or device address.
The only supported device addresses currently are IP addresses.
If you believe your build target should have been detected, and this is an error, please
create an issue.`,
);
}
},
};

View File

@ -53,9 +53,6 @@ capitano.command(actions.preload);
capitano.command(actions.build);
capitano.command(actions.deploy);
// ------------ Push/remote builds -------
capitano.command(actions.push.push);
export function run(argv: string[]) {
const cli = capitano.parse(argv.slice(2));
const runCommand = function () {

View File

@ -189,6 +189,7 @@ export const convertedCommands = [
'os:versions',
'os:download',
'os:initialize',
'push',
'scan',
'settings',
'ssh',

View File

@ -27,7 +27,7 @@ Primary commands:
help [command...] show help
login login to balena
push <applicationOrDevice> Start a remote build on the balena cloud build servers or a local mode device
push <applicationordevice> start a remote build on the balena cloud build servers or a local mode device
logs <device> show device logs
ssh <applicationordevice> [servicename] SSH into the host or application container of a device
apps list all applications

View File

@ -309,7 +309,7 @@ describe('balena push', function () {
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l -g`,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -g`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines,
@ -348,7 +348,7 @@ describe('balena push', function () {
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l -G`,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -G`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines: commonResponseLines[responseFilename],
@ -406,7 +406,7 @@ describe('balena push', function () {
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l --gitignore`,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --gitignore`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines,
@ -453,7 +453,7 @@ describe('balena push', function () {
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l -m`,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -m`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines,
@ -511,7 +511,7 @@ describe('balena push', function () {
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l`,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath}`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines,
@ -566,7 +566,7 @@ describe('balena push', function () {
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -l -m`,
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -m`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines,