mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-29 15:44:26 +00:00
Add legacy deploy method back
This mostly reverts the removal of the legacy deploy code that pushed image tars via the builder. It’s needed for users to avoid having to switch between CLI versions in order to push to legacy apps as well. Note: this pins resin-sdk to 9.0.0-beta14 as I couldn’t get it to install otherwise — npm would always install 9.0.0-beta9 instead. Change-Type: minor
This commit is contained in:
parent
e4c9defb70
commit
62f006b89a
@ -7,10 +7,9 @@ compose = require('../utils/compose')
|
||||
###
|
||||
Opts must be an object with the following keys:
|
||||
|
||||
appName: the name of the app this build is for; optional
|
||||
app: the app this build is for
|
||||
arch: the architecture to build for
|
||||
deviceType: the device type to build for
|
||||
projectPath: the project root directory; must be absolute
|
||||
buildEmulated
|
||||
buildOpts: arguments to forward to docker build command
|
||||
###
|
||||
@ -111,18 +110,25 @@ module.exports =
|
||||
if arch? and deviceType?
|
||||
[ undefined, arch, deviceType ]
|
||||
else
|
||||
helpers.getArchAndDeviceType(application)
|
||||
Promise.join(
|
||||
helpers.getApplication(application)
|
||||
helpers.getArchAndDeviceType(application)
|
||||
(app, { arch, device_type }) ->
|
||||
app.arch = arch
|
||||
app.device_type = device_type
|
||||
return app
|
||||
)
|
||||
.then (app) ->
|
||||
[ application, app.arch, app.device_type ]
|
||||
[ app, app.arch, app.device_type ]
|
||||
|
||||
.then ([ appName, arch, deviceType ]) ->
|
||||
.then ([ app, arch, deviceType ]) ->
|
||||
Promise.join(
|
||||
dockerUtils.getDocker(options)
|
||||
dockerUtils.generateBuildOpts(options)
|
||||
compose.generateOpts(options)
|
||||
(docker, buildOpts, composeOpts) ->
|
||||
buildProject(docker, logger, composeOpts, {
|
||||
appName
|
||||
app
|
||||
arch
|
||||
deviceType
|
||||
buildEmulated: !!options.emulated
|
||||
|
@ -66,6 +66,33 @@ deployProject = (docker, logger, composeOpts, opts) ->
|
||||
props: {}
|
||||
}
|
||||
.then (images) ->
|
||||
if opts.app.application_type?[0]?.is_legacy
|
||||
chalk = require('chalk')
|
||||
legacyDeploy = require('../utils/deploy-legacy')
|
||||
|
||||
msg = chalk.yellow('Target application requires legacy deploy method.')
|
||||
logger.logWarn(msg)
|
||||
|
||||
return Promise.join(
|
||||
docker
|
||||
logger
|
||||
sdk.auth.getToken()
|
||||
sdk.auth.whoami()
|
||||
sdk.settings.get('resinUrl')
|
||||
{
|
||||
appName: opts.app.app_name
|
||||
imageName: images[0].name
|
||||
buildLogs: images[0].logs
|
||||
shouldUploadLogs: opts.shouldUploadLogs
|
||||
}
|
||||
legacyDeploy
|
||||
)
|
||||
.then (releaseId) ->
|
||||
sdk.pine.get
|
||||
resource: 'release'
|
||||
id: releaseId
|
||||
options:
|
||||
$select: [ 'commit' ]
|
||||
Promise.join(
|
||||
sdk.auth.getUserId()
|
||||
sdk.auth.getToken()
|
||||
|
153
lib/utils/deploy-legacy.coffee
Normal file
153
lib/utils/deploy-legacy.coffee
Normal file
@ -0,0 +1,153 @@
|
||||
Promise = require('bluebird')
|
||||
|
||||
getBuilderPushEndpoint = (baseUrl, owner, app) ->
|
||||
querystring = require('querystring')
|
||||
args = querystring.stringify({ owner, app })
|
||||
"https://builder.#{baseUrl}/v1/push?#{args}"
|
||||
|
||||
getBuilderLogPushEndpoint = (baseUrl, buildId, owner, app) ->
|
||||
querystring = require('querystring')
|
||||
args = querystring.stringify({ owner, app, buildId })
|
||||
"https://builder.#{baseUrl}/v1/pushLogs?#{args}"
|
||||
|
||||
formatImageName = (image) ->
|
||||
image.split('/').pop()
|
||||
|
||||
bufferImage = (docker, imageId, bufferFile) ->
|
||||
Promise = require('bluebird')
|
||||
streamUtils = require('./streams')
|
||||
|
||||
image = docker.getImage(imageId)
|
||||
imageMetadata = image.inspect()
|
||||
|
||||
Promise.join image.get(), imageMetadata.get('Size'), (imageStream, imageSize) ->
|
||||
streamUtils.buffer(imageStream, bufferFile)
|
||||
.tap (bufferedStream) ->
|
||||
bufferedStream.length = imageSize
|
||||
|
||||
getSpinner = (message) ->
|
||||
visuals = require('resin-cli-visuals')
|
||||
return new visuals.Spinner(message)
|
||||
|
||||
showPushProgress = (message) ->
|
||||
visuals = require('resin-cli-visuals')
|
||||
progressBar = new visuals.Progress(message)
|
||||
progressBar.update({ percentage: 0 })
|
||||
return progressBar
|
||||
|
||||
forwardStatusMessageFromRemote = (logger, msg) ->
|
||||
msg.split(/\r\n|\n/).forEach (line) ->
|
||||
if /^Warning: This endpoint is deprecated/.test(line)
|
||||
return
|
||||
logger.logInfo(line)
|
||||
|
||||
uploadToPromise = (uploadRequest, logger) ->
|
||||
new Promise (resolve, reject) ->
|
||||
|
||||
handleMessage = (data) ->
|
||||
data = data.toString()
|
||||
logger.logDebug("Received data: #{data}")
|
||||
|
||||
try
|
||||
obj = JSON.parse(data)
|
||||
catch e
|
||||
logger.logError('Error parsing reply from remote side')
|
||||
reject(e)
|
||||
return
|
||||
|
||||
if obj.type?
|
||||
switch obj.type
|
||||
when 'error' then reject(new Error("Remote error: #{obj.error}"))
|
||||
when 'success' then resolve(obj)
|
||||
when 'status' then forwardStatusMessageFromRemote(logger, obj.message)
|
||||
else reject(new Error("Received unexpected reply from remote: #{data}"))
|
||||
else
|
||||
reject(new Error("Received unexpected reply from remote: #{data}"))
|
||||
|
||||
uploadRequest
|
||||
.on('error', reject)
|
||||
.on('data', handleMessage)
|
||||
|
||||
uploadImage = (imageStream, token, username, url, appName, logger) ->
|
||||
request = require('request')
|
||||
progressStream = require('progress-stream')
|
||||
zlib = require('zlib')
|
||||
|
||||
# Need to strip off the newline
|
||||
progressMessage = logger.formatMessage('info', 'Uploading').slice(0, -1)
|
||||
progressBar = showPushProgress(progressMessage)
|
||||
streamWithProgress = imageStream.pipe progressStream
|
||||
time: 500,
|
||||
length: imageStream.length
|
||||
, ({ percentage, eta }) ->
|
||||
progressBar.update
|
||||
percentage: Math.min(percentage, 100)
|
||||
eta: eta
|
||||
|
||||
uploadRequest = request.post
|
||||
url: getBuilderPushEndpoint(url, username, appName)
|
||||
headers:
|
||||
'Content-Encoding': 'gzip'
|
||||
auth:
|
||||
bearer: token
|
||||
body: streamWithProgress.pipe(zlib.createGzip({
|
||||
level: 6
|
||||
}))
|
||||
|
||||
uploadToPromise(uploadRequest, logger)
|
||||
|
||||
uploadLogs = (logs, token, url, buildId, username, appName) ->
|
||||
request = require('request')
|
||||
request.post
|
||||
json: true
|
||||
url: getBuilderLogPushEndpoint(url, buildId, username, appName)
|
||||
auth:
|
||||
bearer: token
|
||||
body: Buffer.from(logs)
|
||||
|
||||
###
|
||||
opts must be a hash with the following keys:
|
||||
|
||||
- appName: the name of the app to deploy to
|
||||
- imageName: the name of the image to deploy
|
||||
- buildLogs: a string with build output
|
||||
- shouldUploadLogs
|
||||
###
|
||||
module.exports = (docker, logger, token, username, url, opts) ->
|
||||
_ = require('lodash')
|
||||
tmp = require('tmp')
|
||||
tmpNameAsync = Promise.promisify(tmp.tmpName)
|
||||
|
||||
# Ensure the tmp files gets deleted
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
{ appName, imageName, buildLogs, shouldUploadLogs } = opts
|
||||
logs = buildLogs
|
||||
|
||||
tmpNameAsync()
|
||||
.then (bufferFile) ->
|
||||
logger.logInfo('Initializing deploy...')
|
||||
bufferImage(docker, imageName, bufferFile)
|
||||
.then (stream) ->
|
||||
uploadImage(stream, token, username, url, appName, logger)
|
||||
.finally ->
|
||||
# If the file was never written to (for instance because an error
|
||||
# has occured before any data was written) this call will throw an
|
||||
# ugly error, just suppress it
|
||||
Promise.try ->
|
||||
require('mz/fs').unlink(bufferFile)
|
||||
.catchReturn()
|
||||
.tap ({ buildId }) ->
|
||||
return if not shouldUploadLogs
|
||||
|
||||
logger.logInfo('Uploading logs...')
|
||||
Promise.join(
|
||||
logs
|
||||
token
|
||||
url
|
||||
buildId
|
||||
username
|
||||
appName
|
||||
uploadLogs
|
||||
)
|
||||
.get('buildId')
|
@ -138,11 +138,19 @@ export function getApplication(applicationName: string) {
|
||||
// that off to a special handler (before importing any modules)
|
||||
const match = /(\w+)\/(\w+)/.exec(applicationName);
|
||||
|
||||
const extraOptions = {
|
||||
$expand: {
|
||||
application_type: {
|
||||
$select: [ 'name', 'slug', 'supports_multicontainer', 'is_legacy' ],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (match) {
|
||||
return resin.models.application.getAppByOwner(match[2], match[1]);
|
||||
return resin.models.application.getAppByOwner(match[2], match[1], extraOptions);
|
||||
}
|
||||
|
||||
return resin.models.application.get(applicationName);
|
||||
return resin.models.application.get(applicationName, extraOptions);
|
||||
}
|
||||
|
||||
// A function to reliably execute a command
|
||||
|
@ -83,6 +83,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@resin.io/valid-email": "^0.1.0",
|
||||
"@types/stream-to-promise": "^2.2.0",
|
||||
"ansi-escapes": "^2.0.0",
|
||||
"any-promise": "^1.3.0",
|
||||
"archiver": "^2.1.0",
|
||||
@ -129,7 +130,7 @@
|
||||
"resin-cli-errors": "^1.2.0",
|
||||
"resin-cli-form": "^1.4.1",
|
||||
"resin-cli-visuals": "^1.4.0",
|
||||
"resin-compose-parse": "^1.5.2",
|
||||
"resin-compose-parse": "^1.8.0",
|
||||
"resin-config-json": "^1.0.0",
|
||||
"resin-device-config": "^4.0.0",
|
||||
"resin-device-init": "^4.0.0",
|
||||
@ -139,8 +140,8 @@
|
||||
"resin-image-manager": "^5.0.0",
|
||||
"resin-multibuild": "^0.5.1",
|
||||
"resin-preload": "^6.1.2",
|
||||
"resin-release": "^1.1.1",
|
||||
"resin-sdk": "^9.0.0-beta7",
|
||||
"resin-release": "^1.2.0",
|
||||
"resin-sdk": "9.0.0-beta14",
|
||||
"resin-sdk-preconfigured": "^6.9.0",
|
||||
"resin-settings-client": "^3.6.1",
|
||||
"resin-stream-logger": "^0.1.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user