From 8cb413c1c95263c5c0ffa9501460949ec00736c7 Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Fri, 21 Aug 2020 10:57:14 +0200 Subject: [PATCH] Convert deploy command to oclif Change-type: patch Signed-off-by: Scott Lowe --- automation/capitanodoc/capitanodoc.ts | 2 +- doc/cli.markdown | 119 +++++---- lib/actions-oclif/build.ts | 5 +- lib/actions-oclif/deploy.ts | 354 ++++++++++++++++++++++++++ lib/actions/deploy.js | 318 ----------------------- lib/actions/index.ts | 1 - lib/app-capitano.ts | 3 - lib/preparser.ts | 5 +- lib/utils/docker.ts | 1 + tests/commands/help.spec.ts | 2 +- 10 files changed, 438 insertions(+), 372 deletions(-) create mode 100644 lib/actions-oclif/deploy.ts delete mode 100644 lib/actions/deploy.js diff --git a/automation/capitanodoc/capitanodoc.ts b/automation/capitanodoc/capitanodoc.ts index 8199210a..d4650cf6 100644 --- a/automation/capitanodoc/capitanodoc.ts +++ b/automation/capitanodoc/capitanodoc.ts @@ -156,7 +156,7 @@ const capitanoDoc = { }, { title: 'Deploy', - files: ['build/actions-oclif/build.js', 'build/actions/deploy.js'], + files: ['build/actions-oclif/build.js', 'build/actions-oclif/deploy.js'], }, { title: 'Platform', diff --git a/doc/cli.markdown b/doc/cli.markdown index 201953e8..c2c64acc 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -256,7 +256,7 @@ Users are encouraged to regularly update the balena CLI to the latest version. - Deploy - [build [source]](#build-source) - - [deploy <appName> [image]](#deploy-appname-image) + - [deploy <appname> [image]](#deploy-appname-image) - Platform @@ -2488,6 +2488,30 @@ Don't use docker layer caching when building Squash newly built layers into a single new layer +#### -P, --docker DOCKER + +Path to a local docker socket (e.g. /var/run/docker.sock) + +#### -h, --dockerHost DOCKERHOST + +Docker daemon hostname or IP address (dev machine or balena device) + +#### -p, --dockerPort 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 + ## deploy <appName> [image] Usage: `deploy ([image] | --build [--source build-dir])` @@ -2509,9 +2533,6 @@ will try to generate one. To deploy to an app on which you're a collaborator, use `balena deploy /`. -When --build is used, all options supported by `balena build` are also supported -by this command. - REGISTRY SECRETS The --registry-secrets option specifies a JSON or YAML file containing private Docker registry usernames and passwords to be used when pulling base images. @@ -2591,25 +2612,35 @@ Examples: $ balena deploy myApp --build --source myBuildDir/ $ balena deploy myApp myApp/myImage +### Arguments + +#### APPNAME + +the name of the application to deploy to + +#### IMAGE + +the image to deploy + ### Options -#### --source, -s <source> +#### -s, --source SOURCE -Specify an alternate source directory; default is the working directory +specify an alternate source directory; default is the working directory -#### --build, -b +#### -b, --build -Force a rebuild before deploy +force a rebuild before deploy #### --nologupload -Don't upload build logs to the dashboard with image (if building) +don't upload build logs to the dashboard with image (if building) -#### --emulated, -e +#### -e, --emulated Run an emulated build using Qemu -#### --dockerfile <Dockerfile> +#### --dockerfile DOCKERFILE Alternative Dockerfile name/path, relative to the source folder @@ -2621,17 +2652,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". @@ -2639,11 +2670,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 @@ -2651,43 +2682,19 @@ 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> +#### -n, --projectName 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) - -#### --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. @@ -2699,6 +2706,30 @@ Don't use docker layer caching when building Squash newly built layers into a single new layer +#### -P, --docker DOCKER + +Path to a local docker socket (e.g. /var/run/docker.sock) + +#### -h, --dockerHost DOCKERHOST + +Docker daemon hostname or IP address (dev machine or balena device) + +#### -p, --dockerPort 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 + # Platform ## join [deviceIpOrHostname] diff --git a/lib/actions-oclif/build.ts b/lib/actions-oclif/build.ts index a893592a..5197c0cb 100644 --- a/lib/actions-oclif/build.ts +++ b/lib/actions-oclif/build.ts @@ -17,7 +17,6 @@ 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'; @@ -94,7 +93,9 @@ ${dockerignoreHelp} }), ...composeCliFlags, ...dockerCliFlags, - help: cf.help, + // NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags + // Revisit this in future release. + help: flags.help({}), }; public static primary = true; diff --git a/lib/actions-oclif/deploy.ts b/lib/actions-oclif/deploy.ts new file mode 100644 index 00000000..c017bb63 --- /dev/null +++ b/lib/actions-oclif/deploy.ts @@ -0,0 +1,354 @@ +/** + * @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 { ExpectedError } from '../errors'; +import { getBalenaSdk, getChalk } from '../utils/lazy'; +import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages'; +import * as compose from '../utils/compose'; +import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types'; +import type { DockerCliFlags } from '../utils/docker'; +import { composeCliFlags } from '../utils/compose_ts'; +import { dockerCliFlags } from '../utils/docker'; +import type { Application, ApplicationType, DeviceType } from 'balena-sdk'; + +interface ApplicationWithArch extends Application { + arch: string; +} + +interface FlagsDef extends ComposeCliFlags, DockerCliFlags { + source?: string; + build: boolean; + nologupload: boolean; + help: void; +} + +interface ArgsDef { + appName: string; + image?: string; +} + +export default class DeployCmd extends Command { + public static description = `\ +Deploy a single image or a multicontainer project to a balena application. + +Usage: \`deploy ([image] | --build [--source build-dir])\` + +Use this command to deploy an image or a complete multicontainer project to an +application, optionally building it first. The source images are searched for +(and optionally built) using the docker daemon in your development machine or +balena device. (See also the \`balena push\` command for the option of building +the image in the balenaCloud build servers.) + +Unless an image is specified, this command will look into the current directory +(or the one specified by --source) for a docker-compose.yml file. If one is +found, this command will deploy each service defined in the compose file, +building it first if an image for it doesn't exist. If a compose file isn't +found, the command will look for a Dockerfile[.template] file (or alternative +Dockerfile specified with the \`-f\` option), and if yet that isn't found, it +will try to generate one. + +To deploy to an app on which you're a collaborator, use +\`balena deploy /\`. + +${registrySecretsHelp} + +${dockerignoreHelp} +`; + + public static examples = [ + '$ balena deploy myApp', + '$ balena deploy myApp --build --source myBuildDir/', + '$ balena deploy myApp myApp/myImage', + ]; + + public static args = [ + { + name: 'appName', + description: 'the name of the application to deploy to', + required: true, + }, + { + name: 'image', + description: 'the image to deploy', + }, + ]; + + public static usage = 'deploy [image]'; + + public static flags: flags.Input = { + source: flags.string({ + description: + 'specify an alternate source directory; default is the working directory', + char: 's', + }), + build: flags.boolean({ + description: 'force a rebuild before deploy', + char: 'b', + }), + nologupload: flags.boolean({ + description: + "don't upload build logs to the dashboard with image (if building)", + }), + ...composeCliFlags, + ...dockerCliFlags, + // NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags + // Revisit this in future release. + help: flags.help({}), + }; + + public static authenticated = true; + + public static primary = true; + + public async run() { + const { args: params, flags: options } = this.parse( + DeployCmd, + ); + + // compositions with many services trigger misleading warnings + // @ts-ignore editing property that isn't typed but does exist + (await import('events')).defaultMaxListeners = 1000; + + const logger = await Command.getLogger(); + logger.logDebug('Parsing input...'); + + const { appName, image } = params; + + if (image != null && options.build) { + throw new ExpectedError( + 'Build option is not applicable when specifying an image', + ); + } + + const sdk = getBalenaSdk(); + const { getRegistrySecrets, validateProjectDirectory } = await import( + '../utils/compose_ts' + ); + + if (image) { + options['registry-secrets'] = await getRegistrySecrets( + sdk, + options['registry-secrets'], + ); + } else { + const { + dockerfilePath, + registrySecrets, + } = await validateProjectDirectory(sdk, { + dockerfilePath: options.dockerfile, + noParentCheck: options['noparent-check'] || false, + projectPath: options.source || '.', + registrySecretsPath: options['registry-secrets'], + }); + options.dockerfile = dockerfilePath; + options['registry-secrets'] = registrySecrets; + } + + const helpers = await import('../utils/helpers'); + const app = await helpers.getAppWithArch(appName); + + const dockerUtils = await import('../utils/docker'); + const [docker, buildOpts, composeOpts] = await Promise.all([ + dockerUtils.getDocker(options), + dockerUtils.generateBuildOpts(options), + compose.generateOpts(options), + ]); + + await this.deployProject(docker, logger, composeOpts, { + app, + appName, // may be prefixed by 'owner/', unlike app.app_name + image, + shouldPerformBuild: !!options.build, + shouldUploadLogs: !options.nologupload, + buildEmulated: !!options.emulated, + buildOpts, + }); + } + + async deployProject( + docker: import('docker-toolbelt'), + logger: import('../utils/logger'), + composeOpts: ComposeOpts, + opts: { + app: ApplicationWithArch; // the application instance to deploy to + appName: string; + image?: string; + dockerfilePath?: string; // alternative Dockerfile + shouldPerformBuild: boolean; + shouldUploadLogs: boolean; + buildEmulated: boolean; + buildOpts: any; // arguments to forward to docker build command + }, + ) { + const Bluebird = await import('bluebird'); + const _ = await import('lodash'); + const doodles = await import('resin-doodles'); + const sdk = getBalenaSdk(); + const { deployProject: $deployProject, loadProject } = await import( + '../utils/compose_ts' + ); + + const appType = (opts.app?.application_type as ApplicationType[])?.[0]; + + return loadProject(logger, composeOpts, opts.image) + .then(function (project) { + if ( + project.descriptors.length > 1 && + !appType?.supports_multicontainer + ) { + throw new ExpectedError( + 'Target application does not support multiple containers. Aborting!', + ); + } + + // find which services use images that already exist locally + return ( + Bluebird.map(project.descriptors, function (d: any) { + // unconditionally build (or pull) if explicitly requested + if (opts.shouldPerformBuild) { + return d; + } + return docker + .getImage( + (typeof d.image === 'string' ? d.image : d.image.tag) || '', + ) + .inspect() + .then(() => { + return d.serviceName; + }) + .catch(() => { + // Ignore + }); + }) + .filter((d) => !!d) + .then(function (servicesToSkip: any[]) { + // multibuild takes in a composition and always attempts to + // build or pull all services. we workaround that here by + // passing a modified composition. + const compositionToBuild = _.cloneDeep(project.composition); + compositionToBuild.services = _.omit( + compositionToBuild.services, + servicesToSkip, + ); + if (_.size(compositionToBuild.services) === 0) { + logger.logInfo( + 'Everything is up to date (use --build to force a rebuild)', + ); + return {}; + } + return compose + .buildProject( + docker, + logger, + project.path, + project.name, + compositionToBuild, + opts.app.arch, + (opts.app?.is_for__device_type as DeviceType[])?.[0].slug, + opts.buildEmulated, + opts.buildOpts, + composeOpts.inlineLogs, + composeOpts.convertEol, + composeOpts.dockerfilePath, + composeOpts.nogitignore, + composeOpts.multiDockerignore, + ) + .then((builtImages) => _.keyBy(builtImages, 'serviceName')); + }) + .then((builtImages: any) => + project.descriptors.map( + (d) => + builtImages[d.serviceName] ?? { + serviceName: d.serviceName, + name: typeof d.image === 'string' ? d.image : d.image.tag, + logs: 'Build skipped; image for service already exists.', + props: {}, + }, + ), + ) + // @ts-ignore slightly different return types of partial vs non-partial release + .then(function (images) { + if (appType?.is_legacy) { + const { deployLegacy } = require('../utils/deploy-legacy'); + + const msg = getChalk().yellow( + 'Target application requires legacy deploy method.', + ); + logger.logWarn(msg); + + return Promise.all([ + sdk.auth.getToken(), + sdk.auth.whoami(), + sdk.settings.get('balenaUrl'), + { + // opts.appName may be prefixed by 'owner/', unlike opts.app.app_name + appName: opts.appName, + imageName: images[0].name, + buildLogs: images[0].logs, + shouldUploadLogs: opts.shouldUploadLogs, + }, + ]) + .then(([token, username, url, options]) => { + return deployLegacy( + docker, + logger, + token, + username, + url, + options, + ); + }) + .then((releaseId) => + sdk.models.release.get(releaseId, { $select: ['commit'] }), + ); + } + return Promise.all([ + sdk.auth.getUserId(), + sdk.auth.getToken(), + sdk.settings.get('apiUrl'), + ]).then(([userId, auth, apiEndpoint]) => + $deployProject( + docker, + logger, + project.composition, + images, + opts.app.id, + userId, + `Bearer ${auth}`, + apiEndpoint, + !opts.shouldUploadLogs, + ), + ); + }) + ); + }) + .then(function (release: any) { + logger.outputDeferredMessages(); + logger.logSuccess('Deploy succeeded!'); + logger.logSuccess(`Release: ${release.commit}`); + console.log(); + console.log(doodles.getDoodle()); // Show charlie + console.log(); + }) + .catch((err) => { + logger.logError('Deploy failed'); + throw err; + }); + } +} diff --git a/lib/actions/deploy.js b/lib/actions/deploy.js deleted file mode 100644 index d25e551c..00000000 --- a/lib/actions/deploy.js +++ /dev/null @@ -1,318 +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 { ExpectedError } from '../errors'; -import { getBalenaSdk, getChalk } from '../utils/lazy'; - -/** - * Opts must be an object with the following keys: - * app: the application instance to deploy to - * image: the image to deploy; optional - * dockerfilePath: name of an alternative Dockerfile; optional - * shouldPerformBuild - * shouldUploadLogs - * buildEmulated - * buildOpts: arguments to forward to docker build command - * - * @param {any} docker - * @param {import('../utils/logger')} logger - * @param {import('../utils/compose-types').ComposeOpts} composeOpts - * @param {any} opts - */ -const deployProject = function (docker, logger, composeOpts, opts) { - const Bluebird = require('bluebird'); - const _ = require('lodash'); - const doodles = require('resin-doodles'); - const sdk = getBalenaSdk(); - const { - deployProject: $deployProject, - loadProject, - } = require('../utils/compose_ts'); - - return loadProject(logger, composeOpts, opts.image) - .then(function (project) { - if ( - project.descriptors.length > 1 && - !opts.app.application_type?.[0]?.supports_multicontainer - ) { - throw new ExpectedError( - 'Target application does not support multiple containers. Aborting!', - ); - } - - // find which services use images that already exist locally - return ( - Bluebird.map(project.descriptors, function (d) { - // unconditionally build (or pull) if explicitly requested - if (opts.shouldPerformBuild) { - return d; - } - return docker - .getImage(typeof d.image === 'string' ? d.image : d.image.tag) - .inspect() - .return(d.serviceName) - .catchReturn(); - }) - .filter((d) => !!d) - .then(function (servicesToSkip) { - // multibuild takes in a composition and always attempts to - // build or pull all services. we workaround that here by - // passing a modified composition. - const compositionToBuild = _.cloneDeep(project.composition); - compositionToBuild.services = _.omit( - compositionToBuild.services, - servicesToSkip, - ); - if (_.size(compositionToBuild.services) === 0) { - logger.logInfo( - 'Everything is up to date (use --build to force a rebuild)', - ); - return {}; - } - return compose - .buildProject( - docker, - logger, - project.path, - project.name, - compositionToBuild, - opts.app.arch, - opts.app.is_for__device_type[0].slug, - opts.buildEmulated, - opts.buildOpts, - composeOpts.inlineLogs, - composeOpts.convertEol, - composeOpts.dockerfilePath, - composeOpts.nogitignore, - composeOpts.multiDockerignore, - ) - .then((builtImages) => _.keyBy(builtImages, 'serviceName')); - }) - .then((builtImages) => - project.descriptors.map( - (d) => - builtImages[d.serviceName] ?? { - serviceName: d.serviceName, - name: typeof d.image === 'string' ? d.image : d.image.tag, - logs: 'Build skipped; image for service already exists.', - props: {}, - }, - ), - ) - // @ts-ignore slightly different return types of partial vs non-partial release - .then(function (images) { - if (opts.app.application_type?.[0]?.is_legacy) { - const { deployLegacy } = require('../utils/deploy-legacy'); - - const msg = getChalk().yellow( - 'Target application requires legacy deploy method.', - ); - logger.logWarn(msg); - - return Bluebird.join( - docker, - logger, - sdk.auth.getToken(), - sdk.auth.whoami(), - sdk.settings.get('balenaUrl'), - { - // opts.appName may be prefixed by 'owner/', unlike opts.app.app_name - appName: opts.appName, - imageName: images[0].name, - buildLogs: images[0].logs, - shouldUploadLogs: opts.shouldUploadLogs, - }, - deployLegacy, - ).then((releaseId) => - // @ts-ignore releaseId should be inferred as a number because that's what deployLegacy is - // typed as returning but the .js type-checking doesn't manage to infer it correctly due to - // Promise.join typings - sdk.models.release.get(releaseId, { $select: ['commit'] }), - ); - } - return Promise.all([ - sdk.auth.getUserId(), - sdk.auth.getToken(), - sdk.settings.get('apiUrl'), - ]).then(([userId, auth, apiEndpoint]) => - $deployProject( - docker, - logger, - project.composition, - images, - opts.app.id, - userId, - `Bearer ${auth}`, - apiEndpoint, - !opts.shouldUploadLogs, - ), - ); - }) - ); - }) - .then(function (release) { - logger.outputDeferredMessages(); - logger.logSuccess('Deploy succeeded!'); - logger.logSuccess(`Release: ${release.commit}`); - console.log(); - console.log(doodles.getDoodle()); // Show charlie - console.log(); - }) - .catch((err) => { - logger.logError('Deploy failed'); - throw err; - }); -}; - -export const deploy = { - signature: 'deploy [image]', - description: - 'Deploy a single image or a multicontainer project to a balena application', - help: `\ -Usage: \`deploy ([image] | --build [--source build-dir])\` - -Use this command to deploy an image or a complete multicontainer project to an -application, optionally building it first. The source images are searched for -(and optionally built) using the docker daemon in your development machine or -balena device. (See also the \`balena push\` command for the option of building -the image in the balenaCloud build servers.) - -Unless an image is specified, this command will look into the current directory -(or the one specified by --source) for a docker-compose.yml file. If one is -found, this command will deploy each service defined in the compose file, -building it first if an image for it doesn't exist. If a compose file isn't -found, the command will look for a Dockerfile[.template] file (or alternative -Dockerfile specified with the \`-f\` option), and if yet that isn't found, it -will try to generate one. - -To deploy to an app on which you're a collaborator, use -\`balena deploy /\`. - -When --build is used, all options supported by \`balena build\` are also supported -by this command. - -${registrySecretsHelp} - -${dockerignoreHelp} - -Examples: - - $ balena deploy myApp - $ balena deploy myApp --build --source myBuildDir/ - $ balena deploy myApp myApp/myImage\ -`, - permission: 'user', - primary: true, - options: dockerUtils.appendOptions( - compose.appendOptions([ - { - signature: 'source', - parameter: 'source', - description: - 'Specify an alternate source directory; default is the working directory', - alias: 's', - }, - { - signature: 'build', - boolean: true, - description: 'Force a rebuild before deploy', - alias: 'b', - }, - { - signature: 'nologupload', - description: - "Don't upload build logs to the dashboard with image (if building)", - boolean: true, - }, - ]), - ), - 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 { - getRegistrySecrets, - validateProjectDirectory, - } = require('../utils/compose_ts'); - const helpers = require('../utils/helpers'); - const Logger = require('../utils/logger'); - - const logger = Logger.getLogger(); - logger.logDebug('Parsing input...'); - - // when Capitano converts a positional parameter (but not an option) - // to a number, the original value is preserved with the _raw suffix - let { appName, appName_raw, image } = params; - - // look into "balena build" options if appName isn't given - appName = appName_raw || appName || options.application; - delete options.application; - - if (appName == null) { - throw new ExpectedError( - 'Please specify the name of the application to deploy', - ); - } - - if (image != null && options.build) { - throw new ExpectedError( - 'Build option is not applicable when specifying an image', - ); - } - - if (image) { - const registrySecrets = await getRegistrySecrets( - sdk, - options['registry-secrets'], - ); - options['registry-secrets'] = registrySecrets; - } else { - const { - dockerfilePath, - registrySecrets, - } = await validateProjectDirectory(sdk, { - dockerfilePath: options.dockerfile, - noParentCheck: options['noparent-check'] || false, - projectPath: options.source || '.', - registrySecretsPath: options['registry-secrets'], - }); - options.dockerfile = dockerfilePath; - options['registry-secrets'] = registrySecrets; - } - - const app = await helpers.getAppWithArch(appName); - - const [docker, buildOpts, composeOpts] = await Promise.all([ - dockerUtils.getDocker(options), - dockerUtils.generateBuildOpts(options), - compose.generateOpts(options), - ]); - await deployProject(docker, logger, composeOpts, { - app, - appName, // may be prefixed by 'owner/', unlike app.app_name - image, - shouldPerformBuild: !!options.build, - shouldUploadLogs: !options.nologupload, - buildEmulated: !!options.emulated, - buildOpts, - }); - }, -}; diff --git a/lib/actions/index.ts b/lib/actions/index.ts index 3d92e232..e0eb52aa 100644 --- a/lib/actions/index.ts +++ b/lib/actions/index.ts @@ -16,5 +16,4 @@ limitations under the License. export * as help from './help'; -export { deploy } from './deploy'; export { preload } from './preload'; diff --git a/lib/app-capitano.ts b/lib/app-capitano.ts index dab08095..28995e27 100644 --- a/lib/app-capitano.ts +++ b/lib/app-capitano.ts @@ -49,9 +49,6 @@ capitano.command(actions.help.help); // ---------- Preload Module ---------- capitano.command(actions.preload); -// ------------ Local build and deploy ------- -capitano.command(actions.deploy); - 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 b3a79d2f..9a70101c 100644 --- a/lib/preparser.ts +++ b/lib/preparser.ts @@ -142,18 +142,19 @@ function checkDeletedCommand(argvSlice: string[]): void { } export const convertedCommands = [ + 'api-key:generate', 'app', 'app:create', 'app:restart', 'app:rm', 'apps', - 'api-key:generate', + 'build', 'config:generate', 'config:inject', 'config:read', 'config:reconfigure', 'config:write', - 'build', + 'deploy', 'device', 'device:identify', 'device:init', diff --git a/lib/utils/docker.ts b/lib/utils/docker.ts index f15ff41e..6ab10dd1 100644 --- a/lib/utils/docker.ts +++ b/lib/utils/docker.ts @@ -91,6 +91,7 @@ Implements the same feature as the "docker build --cache-from" option.`, squash: flags.boolean({ description: 'Squash newly built layers into a single new layer', }), + ...dockerConnectionCliFlags, }; export async function isBalenaEngine(docker: dockerode): Promise { diff --git a/tests/commands/help.spec.ts b/tests/commands/help.spec.ts index 1c336b44..33f038e0 100644 --- a/tests/commands/help.spec.ts +++ b/tests/commands/help.spec.ts @@ -37,7 +37,7 @@ Primary commands: tunnel tunnel local ports to your balenaOS device preload preload an app on a disk image (or Edison zip archive) build [source] build a project locally - deploy [image] Deploy a single image or a multicontainer project to a balena application + deploy [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 scan scan for balenaOS devices on your local network