2017-03-29 11:03:40 +00:00
|
|
|
Promise = require('bluebird')
|
2017-04-24 19:05:18 +00:00
|
|
|
dockerUtils = require('../utils/docker')
|
2017-03-29 11:03:40 +00:00
|
|
|
|
|
|
|
getBuilderPushEndpoint = (baseUrl, owner, app) ->
|
2017-04-24 10:06:53 +00:00
|
|
|
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}"
|
2017-03-29 11:03:40 +00:00
|
|
|
|
|
|
|
formatImageName = (image) ->
|
|
|
|
image.split('/').pop()
|
|
|
|
|
|
|
|
parseInput = Promise.method (params, options) ->
|
|
|
|
if not params.appName?
|
|
|
|
throw new Error('Need an application to deploy to!')
|
|
|
|
appName = params.appName
|
|
|
|
image = undefined
|
|
|
|
if params.image?
|
|
|
|
if options.build or options.source?
|
|
|
|
throw new Error('Build and source parameters are not applicable when specifying an image')
|
|
|
|
options.build = false
|
|
|
|
image = params.image
|
|
|
|
else if options.build
|
2017-04-24 14:55:54 +00:00
|
|
|
source = options.source || '.'
|
2017-03-29 11:03:40 +00:00
|
|
|
else
|
|
|
|
throw new Error('Need either an image or a build flag!')
|
|
|
|
|
2017-04-24 14:55:54 +00:00
|
|
|
return [appName, options.build, source, image]
|
2017-03-29 11:03:40 +00:00
|
|
|
|
2017-06-28 16:57:49 +00:00
|
|
|
showPushProgress = (message) ->
|
2017-06-15 13:40:57 +00:00
|
|
|
visuals = require('resin-cli-visuals')
|
2017-06-28 16:57:49 +00:00
|
|
|
progressBar = new visuals.Progress(message)
|
2017-06-15 13:40:57 +00:00
|
|
|
progressBar.update({ percentage: 0 })
|
|
|
|
return progressBar
|
2017-04-24 12:48:43 +00:00
|
|
|
|
2017-03-29 11:03:40 +00:00
|
|
|
getBundleInfo = (options) ->
|
|
|
|
helpers = require('../utils/helpers')
|
|
|
|
|
|
|
|
helpers.getAppInfo(options.appName)
|
|
|
|
.then (app) ->
|
|
|
|
[app.arch, app.device_type]
|
|
|
|
|
2017-06-28 16:30:37 +00:00
|
|
|
performUpload = (imageStream, token, username, url, appName, logger) ->
|
2017-03-29 11:03:40 +00:00
|
|
|
request = require('request')
|
2017-06-13 17:28:37 +00:00
|
|
|
progressStream = require('progress-stream')
|
|
|
|
zlib = require('zlib')
|
|
|
|
|
2017-06-28 16:57:49 +00:00
|
|
|
# Need to strip off the newline
|
|
|
|
progressMessage = logger.formatMessage('info', 'Deploying').slice(0, -1)
|
|
|
|
progressBar = showPushProgress(progressMessage)
|
2017-06-15 13:40:57 +00:00
|
|
|
streamWithProgress = imageStream.pipe progressStream
|
2017-06-13 17:28:37 +00:00
|
|
|
time: 500,
|
|
|
|
length: imageStream.length
|
2017-06-15 13:40:57 +00:00
|
|
|
, ({ percentage, eta }) ->
|
|
|
|
progressBar.update
|
|
|
|
percentage: Math.min(percentage, 100)
|
|
|
|
eta: eta
|
2017-06-13 17:28:37 +00:00
|
|
|
|
2017-06-13 17:28:37 +00:00
|
|
|
uploadRequest = request.post
|
2017-03-29 11:03:40 +00:00
|
|
|
url: getBuilderPushEndpoint(url, username, appName)
|
2017-06-13 17:28:37 +00:00
|
|
|
headers:
|
|
|
|
'Content-Encoding': 'gzip'
|
2017-03-29 11:03:40 +00:00
|
|
|
auth:
|
|
|
|
bearer: token
|
2017-06-14 15:42:33 +00:00
|
|
|
body: streamWithProgress.pipe(zlib.createGzip({
|
|
|
|
level: 6
|
|
|
|
}))
|
2017-03-29 11:03:40 +00:00
|
|
|
|
2017-06-28 16:30:37 +00:00
|
|
|
uploadToPromise(uploadRequest, logger)
|
2017-03-29 11:03:40 +00:00
|
|
|
|
2017-04-24 10:06:53 +00:00
|
|
|
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)
|
|
|
|
|
2017-06-28 16:30:37 +00:00
|
|
|
uploadToPromise = (uploadRequest, logger) ->
|
2017-03-29 11:03:40 +00:00
|
|
|
new Promise (resolve, reject) ->
|
|
|
|
|
|
|
|
handleMessage = (data) ->
|
|
|
|
data = data.toString()
|
2017-06-28 16:30:37 +00:00
|
|
|
logger.logDebug("Received data: #{data}")
|
2017-03-29 11:03:40 +00:00
|
|
|
|
2017-04-24 10:06:53 +00:00
|
|
|
try
|
|
|
|
obj = JSON.parse(data)
|
|
|
|
catch e
|
2017-06-28 16:30:37 +00:00
|
|
|
logger.logError('Error parsing reply from remote side')
|
2017-04-24 10:06:53 +00:00
|
|
|
reject(e)
|
|
|
|
return
|
|
|
|
|
2017-03-29 11:03:40 +00:00
|
|
|
if obj.type?
|
|
|
|
switch obj.type
|
|
|
|
when 'error' then reject(new Error("Remote error: #{obj.error}"))
|
2017-04-24 10:06:53 +00:00
|
|
|
when 'success' then resolve(obj)
|
2017-06-28 16:30:37 +00:00
|
|
|
when 'status' then logger.logInfo("Remote: #{obj.message}")
|
2017-03-29 11:03:40 +00:00
|
|
|
else reject(new Error("Received unexpected reply from remote: #{data}"))
|
|
|
|
else
|
|
|
|
reject(new Error("Received unexpected reply from remote: #{data}"))
|
|
|
|
|
2017-06-13 17:28:37 +00:00
|
|
|
uploadRequest
|
2017-03-29 11:03:40 +00:00
|
|
|
.on('error', reject)
|
|
|
|
.on('data', handleMessage)
|
|
|
|
|
|
|
|
module.exports =
|
|
|
|
signature: 'deploy <appName> [image]'
|
2017-06-13 17:45:48 +00:00
|
|
|
description: 'Deploy an image to a resin.io application'
|
2017-03-29 11:03:40 +00:00
|
|
|
help: '''
|
2017-06-13 17:45:48 +00:00
|
|
|
Use this command to deploy an image to an application, optionally building it first.
|
2017-03-29 11:03:40 +00:00
|
|
|
|
|
|
|
Usage: deploy <appName> ([image] | --build [--source build-dir])
|
|
|
|
|
|
|
|
Note: If building with this command, all options supported by `resin build`
|
2017-04-26 12:34:40 +00:00
|
|
|
are also supported with this command.
|
2017-03-29 11:03:40 +00:00
|
|
|
|
|
|
|
Examples:
|
2017-04-26 12:34:40 +00:00
|
|
|
$ resin deploy myApp --build --source myBuildDir/
|
|
|
|
$ resin deploy myApp myApp/myImage
|
2017-03-29 11:03:40 +00:00
|
|
|
'''
|
|
|
|
permission: 'user'
|
2017-04-24 19:05:18 +00:00
|
|
|
options: dockerUtils.appendOptions [
|
2017-03-29 11:03:40 +00:00
|
|
|
{
|
|
|
|
signature: 'build'
|
|
|
|
boolean: true
|
|
|
|
description: 'Build image then deploy'
|
|
|
|
alias: 'b'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
signature: 'source'
|
|
|
|
parameter: 'source'
|
|
|
|
description: 'The source directory to use when building the image'
|
|
|
|
alias: 's'
|
2017-04-24 10:06:53 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
signature: 'nologupload'
|
|
|
|
description: "Don't upload build logs to the dashboard with image (if building)"
|
|
|
|
boolean: true
|
2017-03-29 11:03:40 +00:00
|
|
|
}
|
|
|
|
]
|
|
|
|
action: (params, options, done) ->
|
|
|
|
_ = require('lodash')
|
|
|
|
tmp = require('tmp')
|
|
|
|
tmpNameAsync = Promise.promisify(tmp.tmpName)
|
|
|
|
resin = require('resin-sdk-preconfigured')
|
|
|
|
|
2017-06-28 16:30:37 +00:00
|
|
|
Logger = require('../utils/logger')
|
|
|
|
logger = new Logger()
|
2017-04-24 12:48:43 +00:00
|
|
|
|
2017-03-29 11:03:40 +00:00
|
|
|
# Ensure the tmp files gets deleted
|
|
|
|
tmp.setGracefulCleanup()
|
|
|
|
|
2017-04-24 10:06:53 +00:00
|
|
|
logs = ''
|
|
|
|
|
|
|
|
upload = (token, username, url) ->
|
2017-06-20 10:48:15 +00:00
|
|
|
dockerUtils.getDocker(options)
|
|
|
|
.then (docker) ->
|
|
|
|
# Check input parameters
|
|
|
|
parseInput(params, options)
|
|
|
|
.then ([appName, build, source, imageName]) ->
|
|
|
|
tmpNameAsync()
|
|
|
|
.then (bufferFile) ->
|
|
|
|
|
|
|
|
# Setup the build args for how the build routine expects them
|
|
|
|
options = _.assign({}, options, { appName })
|
|
|
|
params = _.assign({}, params, { source })
|
|
|
|
|
2017-06-14 10:26:16 +00:00
|
|
|
Promise.try ->
|
2017-06-20 10:48:15 +00:00
|
|
|
if build
|
2017-06-28 16:30:37 +00:00
|
|
|
dockerUtils.runBuild(params, options, getBundleInfo, logger)
|
2017-06-20 10:48:15 +00:00
|
|
|
else
|
|
|
|
{ image: imageName, log: '' }
|
|
|
|
.then ({ image: imageName, log: buildLogs }) ->
|
2017-06-28 16:30:37 +00:00
|
|
|
logger.logInfo('Initializing deploy...')
|
2017-06-20 10:48:15 +00:00
|
|
|
|
|
|
|
logs = buildLogs
|
|
|
|
Promise.all [
|
|
|
|
dockerUtils.bufferImage(docker, imageName, bufferFile)
|
|
|
|
token
|
|
|
|
username
|
|
|
|
url
|
|
|
|
params.appName
|
2017-06-28 16:30:37 +00:00
|
|
|
logger
|
2017-06-20 10:48:15 +00:00
|
|
|
]
|
|
|
|
.spread(performUpload)
|
|
|
|
.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)
|
|
|
|
.catch(_.noop)
|
|
|
|
.tap ({ image: imageName, buildId }) ->
|
2017-06-28 16:30:37 +00:00
|
|
|
logger.logSuccess("Successfully deployed image: #{formatImageName(imageName)}")
|
2017-06-20 10:48:15 +00:00
|
|
|
return buildId
|
|
|
|
.then ({ image: imageName, buildId }) ->
|
|
|
|
if logs is '' or options.nologupload?
|
|
|
|
return ''
|
|
|
|
|
2017-06-28 16:30:37 +00:00
|
|
|
logger.logInfo('Uploading logs to dashboard...')
|
2017-06-20 10:48:15 +00:00
|
|
|
|
|
|
|
Promise.join(
|
|
|
|
logs
|
|
|
|
token
|
|
|
|
url
|
|
|
|
buildId
|
|
|
|
username
|
|
|
|
params.appName
|
|
|
|
uploadLogs
|
|
|
|
)
|
|
|
|
.return('Successfully uploaded logs')
|
|
|
|
.then (msg) ->
|
2017-06-28 16:30:37 +00:00
|
|
|
logger.logSuccess(msg) if msg isnt ''
|
2017-06-20 10:48:15 +00:00
|
|
|
.asCallback(done)
|
2017-04-24 10:06:53 +00:00
|
|
|
|
|
|
|
Promise.join(
|
|
|
|
resin.auth.getToken()
|
|
|
|
resin.auth.whoami()
|
|
|
|
resin.settings.get('resinUrl')
|
|
|
|
upload
|
|
|
|
)
|