From 0f058a444139df541597dca741b0665ffb9bdbea Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Fri, 24 Jul 2020 14:25:46 +0200 Subject: [PATCH] Convert command `push` to oclif Change-type: patch Signed-off-by: Scott Lowe --- automation/capitanodoc/capitanodoc.ts | 2 +- doc/cli.markdown | 41 ++- lib/actions-oclif/note.ts | 3 +- lib/actions-oclif/push.ts | 411 +++++++++++++++++++++++ lib/actions/index.ts | 1 - lib/actions/push.ts | 448 -------------------------- lib/app-capitano.ts | 3 - lib/preparser.ts | 1 + tests/commands/help.spec.ts | 2 +- tests/commands/push.spec.ts | 12 +- 10 files changed, 447 insertions(+), 477 deletions(-) create mode 100644 lib/actions-oclif/push.ts delete mode 100644 lib/actions/push.ts diff --git a/automation/capitanodoc/capitanodoc.ts b/automation/capitanodoc/capitanodoc.ts index 4d53aa3e..2b2d1c45 100644 --- a/automation/capitanodoc/capitanodoc.ts +++ b/automation/capitanodoc/capitanodoc.ts @@ -141,7 +141,7 @@ const capitanoDoc = { }, { title: 'Push', - files: ['build/actions/push.js'], + files: ['build/actions-oclif/push.js'], }, { title: 'Settings', diff --git a/doc/cli.markdown b/doc/cli.markdown index eaa0cf24..32daaedf 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -242,7 +242,7 @@ Users are encouraged to regularly update the balena CLI to the latest version. - Push - - [push <applicationOrDevice>](#push-applicationordevice) + - [push <applicationordevice>](#push-applicationordevice) - Settings @@ -2006,7 +2006,7 @@ Docker host TLS key file ## push <applicationOrDevice> -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 $ balena push myApp -s - + $ balena push 10.0.0.1 $ balena push 10.0.0.1 --source $ 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 <source> +#### -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 <Dockerfile> +#### --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 <secrets.yml|.json> +#### -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 <service> +#### --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 <env> +#### --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 diff --git a/lib/actions-oclif/note.ts b/lib/actions-oclif/note.ts index f7d57081..fc396d24 100644 --- a/lib/actions-oclif/note.ts +++ b/lib/actions-oclif/note.ts @@ -39,7 +39,8 @@ export default class NoteCmd extends Command { it will be read from stdin. To view device notes, use the \`balena device \` command. -`; + `; + public static examples = [ '$ balena note "My useful note" --device 7cf02a6', '$ cat note.txt | balena note --device 7cf02a6', diff --git a/lib/actions-oclif/push.ts b/lib/actions-oclif/push.ts new file mode 100644 index 00000000..c150de09 --- /dev/null +++ b/lib/actions-oclif/push.ts @@ -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 ', + '$ balena push myApp -s ', + '', + '$ balena push 10.0.0.1', + '$ balena push 10.0.0.1 --source ', + '$ 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 '; + + public static flags: flags.Input = { + 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( + 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 { + 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; + } +} diff --git a/lib/actions/index.ts b/lib/actions/index.ts index 4d65b032..ab3774d2 100644 --- a/lib/actions/index.ts +++ b/lib/actions/index.ts @@ -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'; diff --git a/lib/actions/push.ts b/lib/actions/push.ts deleted file mode 100644 index ba3b5fcf..00000000 --- a/lib/actions/push.ts +++ /dev/null @@ -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 ', - 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 - $ balena push myApp -s - - $ balena push 10.0.0.1 - $ balena push 10.0.0.1 --source - $ 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.`, - ); - } - }, -}; diff --git a/lib/app-capitano.ts b/lib/app-capitano.ts index ecfc8021..d764a50c 100644 --- a/lib/app-capitano.ts +++ b/lib/app-capitano.ts @@ -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 () { diff --git a/lib/preparser.ts b/lib/preparser.ts index 6b557b4e..0a97fe6f 100644 --- a/lib/preparser.ts +++ b/lib/preparser.ts @@ -189,6 +189,7 @@ export const convertedCommands = [ 'os:versions', 'os:download', 'os:initialize', + 'push', 'scan', 'settings', 'ssh', diff --git a/tests/commands/help.spec.ts b/tests/commands/help.spec.ts index 6dfce591..aa8525ce 100644 --- a/tests/commands/help.spec.ts +++ b/tests/commands/help.spec.ts @@ -27,7 +27,7 @@ Primary commands: help [command...] show help login login to balena - push Start a remote build on the balena cloud build servers or a local mode device + push start a remote build on the balena cloud build servers or a local mode device logs show device logs ssh [servicename] SSH into the host or application container of a device apps list all applications diff --git a/tests/commands/push.spec.ts b/tests/commands/push.spec.ts index b7338b5c..1e294532 100644 --- a/tests/commands/push.spec.ts +++ b/tests/commands/push.spec.ts @@ -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,