mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-21 01:42:26 +00:00
Merge pull request #1771 from balena-io/js-lib-actions-deploy
Convert lib/utils/deploy.coffee to javascript
This commit is contained in:
commit
eb5cfecfaf
@ -1,270 +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.
|
||||
###
|
||||
|
||||
# Imported here because it's needed for the setup
|
||||
# of this action
|
||||
Promise = require('bluebird')
|
||||
dockerUtils = require('../utils/docker')
|
||||
compose = require('../utils/compose')
|
||||
{ registrySecretsHelp } = require('../utils/messages')
|
||||
{ ExpectedError } = require('../errors')
|
||||
{ getBalenaSdk, getChalk } = require('../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
|
||||
###
|
||||
deployProject = (docker, logger, composeOpts, opts) ->
|
||||
_ = require('lodash')
|
||||
doodles = require('resin-doodles')
|
||||
sdk = getBalenaSdk()
|
||||
{ loadProject } = require('../utils/compose_ts')
|
||||
|
||||
Promise.resolve(loadProject(logger, composeOpts, opts.image))
|
||||
.then (project) ->
|
||||
if project.descriptors.length > 1 and !opts.app.application_type?[0]?.supports_multicontainer
|
||||
throw new Error('Target application does not support multiple containers. Aborting!')
|
||||
|
||||
# find which services use images that already exist locally
|
||||
Promise.map project.descriptors, (d) ->
|
||||
# unconditionally build (or pull) if explicitly requested
|
||||
return d if opts.shouldPerformBuild
|
||||
docker.getImage(d.image.tag ? d.image).inspect()
|
||||
.return(d.serviceName)
|
||||
.catchReturn()
|
||||
.filter (d) -> !!d
|
||||
.then (servicesToSkip) ->
|
||||
# multibuild takes in a composition and always attempts to
|
||||
# build or pull all services. we workaround that here by
|
||||
# passing a modified composition.
|
||||
compositionToBuild = _.cloneDeep(project.composition)
|
||||
compositionToBuild.services = _.omit(compositionToBuild.services, servicesToSkip)
|
||||
if _.size(compositionToBuild.services) is 0
|
||||
logger.logInfo('Everything is up to date (use --build to force a rebuild)')
|
||||
return {}
|
||||
compose.buildProject(
|
||||
docker
|
||||
logger
|
||||
project.path
|
||||
project.name
|
||||
compositionToBuild
|
||||
opts.app.arch
|
||||
opts.app.device_type
|
||||
opts.buildEmulated
|
||||
opts.buildOpts
|
||||
composeOpts.inlineLogs
|
||||
opts.convertEol
|
||||
composeOpts.dockerfilePath
|
||||
)
|
||||
.then (builtImages) ->
|
||||
_.keyBy(builtImages, 'serviceName')
|
||||
.then (builtImages) ->
|
||||
project.descriptors.map (d) ->
|
||||
builtImages[d.serviceName] ? {
|
||||
serviceName: d.serviceName,
|
||||
name: d.image.tag ? d.image
|
||||
logs: 'Build skipped; image for service already exists.'
|
||||
props: {}
|
||||
}
|
||||
.then (images) ->
|
||||
if opts.app.application_type?[0]?.is_legacy
|
||||
{ deployLegacy } = require('../utils/deploy-legacy')
|
||||
|
||||
msg = getChalk().yellow('Target application requires legacy deploy method.')
|
||||
logger.logWarn(msg)
|
||||
|
||||
return Promise.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) ->
|
||||
sdk.models.release.get(releaseId, $select: [ 'commit' ])
|
||||
Promise.join(
|
||||
sdk.auth.getUserId()
|
||||
sdk.auth.getToken()
|
||||
sdk.settings.get('apiUrl')
|
||||
(userId, auth, apiEndpoint) ->
|
||||
compose.deployProject(
|
||||
docker
|
||||
logger
|
||||
project.composition
|
||||
images
|
||||
opts.app.id
|
||||
userId
|
||||
"Bearer #{auth}"
|
||||
apiEndpoint
|
||||
!opts.shouldUploadLogs
|
||||
)
|
||||
)
|
||||
.then (release) ->
|
||||
logger.outputDeferredMessages()
|
||||
logger.logSuccess('Deploy succeeded!')
|
||||
logger.logSuccess("Release: #{release.commit}")
|
||||
console.log()
|
||||
console.log(doodles.getDoodle()) # Show charlie
|
||||
console.log()
|
||||
.tapCatch (e) ->
|
||||
logger.logError('Deploy failed')
|
||||
|
||||
module.exports =
|
||||
signature: 'deploy <appName> [image]'
|
||||
description: 'Deploy a single image or a multicontainer project to a balena application'
|
||||
help: """
|
||||
Usage: `deploy <appName> ([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 <appOwnerUsername>/<appName>`.
|
||||
|
||||
When --build is used, all options supported by `balena build` are also supported
|
||||
by this command.
|
||||
|
||||
#{registrySecretsHelp}
|
||||
|
||||
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
|
||||
},
|
||||
]
|
||||
action: (params, options) ->
|
||||
# compositions with many services trigger misleading warnings
|
||||
require('events').defaultMaxListeners = 1000
|
||||
sdk = getBalenaSdk()
|
||||
{ ExpectedError } = require('../errors')
|
||||
{ getRegistrySecrets, validateProjectDirectory } = require('../utils/compose_ts')
|
||||
helpers = require('../utils/helpers')
|
||||
Logger = require('../utils/logger')
|
||||
|
||||
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
|
||||
{ appName, appName_raw, image } = params
|
||||
|
||||
# look into "balena build" options if appName isn't given
|
||||
appName = appName_raw || appName || options.application
|
||||
delete options.application
|
||||
|
||||
options.convertEol = options['convert-eol'] || false
|
||||
delete options['convert-eol']
|
||||
if options.convertEol and not options.build
|
||||
return Promise.reject(new ExpectedError('The --eol-conversion flag is only valid with --build.'))
|
||||
|
||||
Promise.try ->
|
||||
if not appName?
|
||||
throw new ExpectedError('Please specify the name of the application to deploy')
|
||||
|
||||
if image? and options.build
|
||||
throw new ExpectedError('Build option is not applicable when specifying an image')
|
||||
.then ->
|
||||
if image
|
||||
getRegistrySecrets(sdk, options['registry-secrets'])
|
||||
.then (registrySecrets) ->
|
||||
options['registry-secrets'] = registrySecrets
|
||||
else
|
||||
validateProjectDirectory(sdk, {
|
||||
dockerfilePath: options.dockerfile,
|
||||
noParentCheck: options['noparent-check'] || false,
|
||||
projectPath: options.source || '.',
|
||||
registrySecretsPath: options['registry-secrets'],
|
||||
})
|
||||
.then ({ dockerfilePath, registrySecrets }) ->
|
||||
options.dockerfile = dockerfilePath
|
||||
options['registry-secrets'] = registrySecrets
|
||||
.then ->
|
||||
Promise.join(
|
||||
helpers.getApplication(appName)
|
||||
helpers.getArchAndDeviceType(appName)
|
||||
(app, { arch, device_type }) ->
|
||||
app.arch = arch
|
||||
app.device_type = device_type
|
||||
return app
|
||||
)
|
||||
.then (app) ->
|
||||
[ app, image, !!options.build, !options.nologupload]
|
||||
|
||||
.then ([ app, image, shouldPerformBuild, shouldUploadLogs, convertEol ]) ->
|
||||
Promise.join(
|
||||
dockerUtils.getDocker(options)
|
||||
dockerUtils.generateBuildOpts(options)
|
||||
compose.generateOpts(options)
|
||||
(docker, buildOpts, composeOpts) ->
|
||||
deployProject(docker, logger, composeOpts, {
|
||||
app
|
||||
appName # may be prefixed by 'owner/', unlike app.app_name
|
||||
image
|
||||
shouldPerformBuild
|
||||
shouldUploadLogs
|
||||
buildEmulated: !!options.emulated
|
||||
buildOpts
|
||||
convertEol: options.convertEol
|
||||
})
|
||||
)
|
331
lib/actions/deploy.js
Normal file
331
lib/actions/deploy.js
Normal file
@ -0,0 +1,331 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
// Imported here because it's needed for the setup
|
||||
// of this action
|
||||
import * as Promise from 'bluebird';
|
||||
|
||||
import * as dockerUtils from '../utils/docker';
|
||||
import * as compose from '../utils/compose';
|
||||
import { 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
|
||||
*/
|
||||
const deployProject = function(docker, logger, composeOpts, opts) {
|
||||
const _ = require('lodash');
|
||||
const doodles = require('resin-doodles');
|
||||
const sdk = getBalenaSdk();
|
||||
const { loadProject } = require('../utils/compose_ts');
|
||||
|
||||
return Promise.resolve(loadProject(logger, composeOpts, opts.image))
|
||||
.then(function(project) {
|
||||
if (
|
||||
project.descriptors.length > 1 &&
|
||||
!opts.app.application_type?.[0]?.supports_multicontainer
|
||||
) {
|
||||
throw new Error(
|
||||
'Target application does not support multiple containers. Aborting!',
|
||||
);
|
||||
}
|
||||
|
||||
// find which services use images that already exist locally
|
||||
return Promise.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.device_type,
|
||||
opts.buildEmulated,
|
||||
opts.buildOpts,
|
||||
composeOpts.inlineLogs,
|
||||
opts.convertEol,
|
||||
composeOpts.dockerfilePath,
|
||||
)
|
||||
.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: {},
|
||||
},
|
||||
),
|
||||
)
|
||||
.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 Promise.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.join(
|
||||
sdk.auth.getUserId(),
|
||||
sdk.auth.getToken(),
|
||||
sdk.settings.get('apiUrl'),
|
||||
(userId, auth, apiEndpoint) =>
|
||||
compose.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();
|
||||
})
|
||||
.tapCatch(() => {
|
||||
logger.logError('Deploy failed');
|
||||
});
|
||||
};
|
||||
|
||||
export const deploy = {
|
||||
signature: 'deploy <appName> [image]',
|
||||
description:
|
||||
'Deploy a single image or a multicontainer project to a balena application',
|
||||
help: `\
|
||||
Usage: \`deploy <appName> ([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 <appOwnerUsername>/<appName>\`.
|
||||
|
||||
When --build is used, all options supported by \`balena build\` are also supported
|
||||
by this command.
|
||||
|
||||
${registrySecretsHelp}
|
||||
|
||||
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,
|
||||
},
|
||||
]),
|
||||
),
|
||||
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;
|
||||
|
||||
options.convertEol = options['convert-eol'] || false;
|
||||
delete options['convert-eol'];
|
||||
if (options.convertEol && !options.build) {
|
||||
return Promise.reject(
|
||||
new ExpectedError(
|
||||
'The --eol-conversion flag is only valid with --build.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.try(function() {
|
||||
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',
|
||||
);
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
if (image) {
|
||||
return getRegistrySecrets(sdk, options['registry-secrets']).then(
|
||||
registrySecrets => {
|
||||
options['registry-secrets'] = registrySecrets;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return validateProjectDirectory(sdk, {
|
||||
dockerfilePath: options.dockerfile,
|
||||
noParentCheck: options['noparent-check'] || false,
|
||||
projectPath: options.source || '.',
|
||||
registrySecretsPath: options['registry-secrets'],
|
||||
}).then(function({ dockerfilePath, registrySecrets }) {
|
||||
options.dockerfile = dockerfilePath;
|
||||
options['registry-secrets'] = registrySecrets;
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() =>
|
||||
Promise.join(
|
||||
helpers.getApplication(appName),
|
||||
helpers.getArchAndDeviceType(appName),
|
||||
function(app, { arch, device_type }) {
|
||||
// @ts-ignore extending the app object
|
||||
app.arch = arch;
|
||||
app.device_type = device_type;
|
||||
return app;
|
||||
},
|
||||
).then(app => [app, !!options.build, !options.nologupload]),
|
||||
)
|
||||
|
||||
.then(function([app, shouldPerformBuild, shouldUploadLogs]) {
|
||||
return Promise.join(
|
||||
dockerUtils.getDocker(options),
|
||||
dockerUtils.generateBuildOpts(options),
|
||||
compose.generateOpts(options),
|
||||
(docker, buildOpts, composeOpts) =>
|
||||
deployProject(docker, logger, composeOpts, {
|
||||
app,
|
||||
appName, // may be prefixed by 'owner/', unlike app.app_name
|
||||
image,
|
||||
shouldPerformBuild,
|
||||
shouldUploadLogs,
|
||||
buildEmulated: !!options.emulated,
|
||||
buildOpts,
|
||||
convertEol: options.convertEol,
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
@ -27,7 +27,7 @@ module.exports =
|
||||
config: require('./config')
|
||||
ssh: require('./ssh')
|
||||
build: require('./build').build
|
||||
deploy: require('./deploy')
|
||||
deploy: require('./deploy').deploy
|
||||
util: require('./util')
|
||||
preload: require('./preload').preload
|
||||
push: require('./push')
|
||||
|
@ -87,6 +87,9 @@ const uploadToPromise = (uploadRequest, logger) =>
|
||||
uploadRequest.on('error', reject).on('data', handleMessage);
|
||||
});
|
||||
|
||||
/**
|
||||
* @returns {Promise<{ buildId: number }>}
|
||||
*/
|
||||
const uploadImage = function(
|
||||
imageStream,
|
||||
token,
|
||||
|
Loading…
x
Reference in New Issue
Block a user