UNPKG

balena-cli

Version:

The official balena Command Line Interface

236 lines (229 loc) • 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@oclif/core"); const errors_1 = require("../../errors"); const lazy_1 = require("../../utils/lazy"); const messages_1 = require("../../utils/messages"); const ca = require("../../utils/common-args"); const compose = require("../../utils/compose"); const compose_ts_1 = require("../../utils/compose_ts"); const docker_1 = require("../../utils/docker"); class DeployCmd extends core_1.Command { async run() { var _a; const { args: params, flags: options } = await this.parse(DeployCmd); (await Promise.resolve().then(() => require('events'))).defaultMaxListeners = 1000; const Logger = await Promise.resolve().then(() => require('../../utils/logger')); const logger = Logger.getLogger(); logger.logDebug('Parsing input...'); const { fleet, image } = params; if (options.buildArg) { console.log(messages_1.buildArgDeprecation); } if (image != null && options.build) { throw new errors_1.ExpectedError('Build option is not applicable when specifying an image'); } const sdk = (0, lazy_1.getBalenaSdk)(); const { getRegistrySecrets, validateProjectDirectory } = await Promise.resolve().then(() => require('../../utils/compose_ts')); const { releaseTagKeys, releaseTagValues } = (0, compose_ts_1.parseReleaseTagKeysAndValues)((_a = options['release-tag']) !== null && _a !== void 0 ? _a : []); 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 Promise.resolve().then(() => require('../../utils/helpers')); const app = await helpers.getAppWithArch(fleet); const dockerUtils = await Promise.resolve().then(() => require('../../utils/docker')); const [docker, buildOpts, composeOpts] = await Promise.all([ dockerUtils.getDocker(options), dockerUtils.generateBuildOpts(options), compose.generateOpts(options), ]); const release = await this.deployProject(docker, logger, composeOpts, { app, appName: fleet, image, shouldPerformBuild: !!options.build, shouldUploadLogs: !options.nologupload, buildEmulated: !!options.emulated, createAsDraft: options.draft, buildOpts, }); await (0, compose_ts_1.applyReleaseTagKeysAndValues)(sdk, release.id, releaseTagKeys, releaseTagValues); if (options.note) { await sdk.models.release.setNote(release.id, options.note); } } async deployProject(docker, logger, composeOpts, opts) { const _ = await Promise.resolve().then(() => require('lodash')); const doodles = await Promise.resolve().then(() => require('resin-doodles')); const sdk = (0, lazy_1.getBalenaSdk)(); const { deployProject: $deployProject, loadProject } = await Promise.resolve().then(() => require('../../utils/compose_ts')); const appType = opts.app.application_type[0]; try { const project = await loadProject(logger, composeOpts, opts.image, opts.buildOpts.t); if (project.descriptors.length > 1 && !(appType === null || appType === void 0 ? void 0 : appType.supports_multicontainer)) { throw new errors_1.ExpectedError('Target fleet does not support multiple containers. Aborting!'); } let servicesToSkip = await Promise.all(project.descriptors.map(async function (d) { if (opts.shouldPerformBuild) { return ''; } try { await docker .getImage(((0, compose_ts_1.isBuildConfig)(d.image) ? d.image.tag : d.image) || '') .inspect(); return d.serviceName; } catch (_a) { return ''; } })); servicesToSkip = servicesToSkip.filter((d) => !!d); const compositionToBuild = _.cloneDeep(project.composition); compositionToBuild.services = _.omit(compositionToBuild.services, servicesToSkip); let builtImagesByService = {}; if (_.size(compositionToBuild.services) === 0) { logger.logInfo('Everything is up to date (use --build to force a rebuild)'); } else { const builtImages = await (0, compose_ts_1.buildProject)({ docker, logger, projectPath: project.path, projectName: project.name, composition: compositionToBuild, arch: opts.app.arch, deviceType: opts.app.is_for__device_type[0].slug, emulated: opts.buildEmulated, buildOpts: opts.buildOpts, inlineLogs: composeOpts.inlineLogs, convertEol: composeOpts.convertEol, dockerfilePath: composeOpts.dockerfilePath, multiDockerignore: composeOpts.multiDockerignore, }); builtImagesByService = _.keyBy(builtImages, 'serviceName'); } const images = project.descriptors.map((d) => { var _a; return (_a = builtImagesByService[d.serviceName]) !== null && _a !== void 0 ? _a : { serviceName: d.serviceName, name: ((0, compose_ts_1.isBuildConfig)(d.image) ? d.image.tag : d.image) || '', logs: 'Build skipped; image for service already exists.', props: {}, }; }); let release; if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') { const { deployLegacy } = require('../../utils/deploy-legacy'); const msg = (0, lazy_1.getChalk)().yellow('Target fleet requires legacy deploy method.'); logger.logWarn(msg); const [token, { username }, url, options] = await Promise.all([ sdk.auth.getToken(), sdk.auth.getUserInfo(), sdk.settings.get('balenaUrl'), { appName: opts.appName, imageName: images[0].name, buildLogs: images[0].logs, shouldUploadLogs: opts.shouldUploadLogs, }, ]); const releaseId = await deployLegacy(docker, logger, token, username, url, options); release = await sdk.models.release.get(releaseId, { $select: ['commit'], }); } else { release = await $deployProject(docker, sdk, logger, project.composition, images, opts.app.id, !opts.shouldUploadLogs, composeOpts.projectPath, opts.createAsDraft); } logger.outputDeferredMessages(); logger.logSuccess('Deploy succeeded!'); logger.logSuccess(`Release: ${release.commit}`); console.log(); console.log(doodles.getDoodle()); console.log(); return release; } catch (err) { logger.logError('Deploy failed'); throw err; } } } DeployCmd.description = `\ Deploy a single image or a multicontainer project to a balena fleet. Usage: \`deploy <fleet> ([image] | --build [--source build-dir])\` Use this command to deploy an image or a complete multicontainer project to a fleet, 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. Image names will be looked up according to the scheme: \`<projectName>_<serviceName>\`. 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 a fleet where you are a collaborator, use fleet slug including the organization: \`balena deploy <organization>/<fleet>\`. ${messages_1.registrySecretsHelp} ${messages_1.dockerignoreHelp} `; DeployCmd.examples = [ '$ balena deploy myFleet', '$ balena deploy myorg/myfleet --build --source myBuildDir/', '$ balena deploy myorg/myfleet --build --source myBuildDir/ --note "this is the note for this release"', '$ balena deploy myorg/myfleet myRepo/myImage', '$ balena deploy myFleet myRepo/myImage --release-tag key1 "" key2 "value2 with spaces"', ]; DeployCmd.args = { fleet: ca.fleetRequired, image: core_1.Args.string({ description: 'the image to deploy' }), }; DeployCmd.flags = { source: core_1.Flags.string({ description: 'specify an alternate source directory; default is the working directory', char: 's', }), build: core_1.Flags.boolean({ description: 'force a rebuild before deploy', char: 'b', }), nologupload: core_1.Flags.boolean({ description: "don't upload build logs to the dashboard with image (if building)", }), 'release-tag': core_1.Flags.string({ description: (0, lazy_1.stripIndent) ` Set release tags if the image deployment is successful. Multiple arguments may be provided, alternating tag keys and values (see examples). Hint: Empty values may be specified with "" (bash, cmd.exe) or '""' (PowerShell). `, multiple: true, }), draft: core_1.Flags.boolean({ description: (0, lazy_1.stripIndent) ` Deploy the release as a draft. Draft releases are ignored by the 'track latest' release policy but can be used through release pinning. Draft releases can be marked as final through the API. Releases are created as final by default unless this option is given.`, default: false, }), note: core_1.Flags.string({ description: 'The notes for this release' }), ...compose_ts_1.composeCliFlags, ...docker_1.dockerCliFlags, }; DeployCmd.authenticated = true; DeployCmd.primary = true; exports.default = DeployCmd; //# sourceMappingURL=index.js.map