From 203ccaf97b79908e666a5dc7d878bdfdb8597ac2 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Mon, 27 Apr 2020 14:10:03 +0100 Subject: [PATCH] Convert lib/utils/deploy-legacy.coffee to javascript Change-type: patch --- lib/actions/deploy.coffee | 4 +- lib/utils/deploy-legacy.coffee | 137 --------------------- lib/utils/deploy-legacy.js | 209 +++++++++++++++++++++++++++++++++ npm-shrinkwrap.json | 9 ++ package.json | 1 + 5 files changed, 221 insertions(+), 139 deletions(-) delete mode 100644 lib/utils/deploy-legacy.coffee create mode 100644 lib/utils/deploy-legacy.js diff --git a/lib/actions/deploy.coffee b/lib/actions/deploy.coffee index d706163b..ce1c0ec7 100644 --- a/lib/actions/deploy.coffee +++ b/lib/actions/deploy.coffee @@ -89,7 +89,7 @@ deployProject = (docker, logger, composeOpts, opts) -> } .then (images) -> if opts.app.application_type?[0]?.is_legacy - legacyDeploy = require('../utils/deploy-legacy') + { deployLegacy } = require('../utils/deploy-legacy') msg = getChalk().yellow('Target application requires legacy deploy method.') logger.logWarn(msg) @@ -107,7 +107,7 @@ deployProject = (docker, logger, composeOpts, opts) -> buildLogs: images[0].logs shouldUploadLogs: opts.shouldUploadLogs } - legacyDeploy + deployLegacy ) .then (releaseId) -> sdk.models.release.get(releaseId, $select: [ 'commit' ]) diff --git a/lib/utils/deploy-legacy.coffee b/lib/utils/deploy-legacy.coffee deleted file mode 100644 index c7acad50..00000000 --- a/lib/utils/deploy-legacy.coffee +++ /dev/null @@ -1,137 +0,0 @@ -Promise = require('bluebird') -{ getVisuals } = require('./lazy') - -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}" - -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 - -showPushProgress = (message) -> - visuals = getVisuals() - progressBar = new visuals.Progress(message) - progressBar.update({ percentage: 0 }) - return progressBar - -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 - - switch obj.type - when 'error' then reject(new Error("Remote error: #{obj.error}")) - when 'success' then resolve(obj) - when 'status' then logger.logInfo(obj.message) - 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) -> - 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') diff --git a/lib/utils/deploy-legacy.js b/lib/utils/deploy-legacy.js new file mode 100644 index 00000000..7458e368 --- /dev/null +++ b/lib/utils/deploy-legacy.js @@ -0,0 +1,209 @@ +/** + * @license + * Copyright 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 Promise from 'bluebird'; +import { getVisuals } from './lazy'; + +const getBuilderPushEndpoint = function(baseUrl, owner, app) { + const querystring = require('querystring'); + const args = querystring.stringify({ owner, app }); + return `https://builder.${baseUrl}/v1/push?${args}`; +}; + +const getBuilderLogPushEndpoint = function(baseUrl, buildId, owner, app) { + const querystring = require('querystring'); + const args = querystring.stringify({ owner, app, buildId }); + return `https://builder.${baseUrl}/v1/pushLogs?${args}`; +}; + +const bufferImage = function(docker, imageId, bufferFile) { + const streamUtils = require('./streams'); + + const image = docker.getImage(imageId); + const imageMetadata = image.inspect(); + + return Promise.join( + image.get(), + imageMetadata.get('Size'), + (imageStream, imageSize) => + streamUtils.buffer(imageStream, bufferFile).tap(bufferedStream => { + // @ts-ignore adding an extra property + bufferedStream.length = imageSize; + }), + ); +}; + +const showPushProgress = function(message) { + const visuals = getVisuals(); + const progressBar = new visuals.Progress(message); + progressBar.update({ percentage: 0 }); + return progressBar; +}; + +const uploadToPromise = (uploadRequest, logger) => + new Promise(function(resolve, reject) { + const handleMessage = function(data) { + let obj; + 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; + } + + switch (obj.type) { + case 'error': + reject(new Error(`Remote error: ${obj.error}`)); + break; + case 'success': + resolve(obj); + break; + case 'status': + logger.logInfo(obj.message); + break; + default: + reject(new Error(`Received unexpected reply from remote: ${data}`)); + } + }; + + uploadRequest.on('error', reject).on('data', handleMessage); + }); + +const uploadImage = function( + imageStream, + token, + username, + url, + appName, + logger, +) { + const request = require('request'); + const progressStream = require('progress-stream'); + const zlib = require('zlib'); + + // Need to strip off the newline + const progressMessage = logger + .formatMessage('info', 'Uploading') + .slice(0, -1); + const progressBar = showPushProgress(progressMessage); + const streamWithProgress = imageStream.pipe( + progressStream( + { + time: 500, + length: imageStream.length, + }, + ({ percentage, eta }) => + progressBar.update({ + percentage: Math.min(percentage, 100), + eta, + }), + ), + ); + + const uploadRequest = request.post({ + url: getBuilderPushEndpoint(url, username, appName), + headers: { + 'Content-Encoding': 'gzip', + }, + auth: { + bearer: token, + }, + body: streamWithProgress.pipe( + zlib.createGzip({ + level: 6, + }), + ), + }); + + return uploadToPromise(uploadRequest, logger); +}; + +const uploadLogs = function(logs, token, url, buildId, username, appName) { + const request = require('request'); + return 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 +*/ +export const deployLegacy = function( + docker, + logger, + token, + username, + url, + opts, +) { + const tmp = require('tmp'); + const tmpNameAsync = Promise.promisify(tmp.tmpName); + + // Ensure the tmp files gets deleted + tmp.setGracefulCleanup(); + + const { appName, imageName, buildLogs, shouldUploadLogs } = opts; + const logs = buildLogs; + + return tmpNameAsync() + .then(function(bufferFile) { + logger.logInfo('Initializing deploy...'); + return 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( + undefined, + ), + ); + }) + .tap(function({ buildId }) { + if (!shouldUploadLogs) { + return; + } + + logger.logInfo('Uploading logs...'); + return Promise.join( + logs, + token, + url, + buildId, + username, + appName, + uploadLogs, + ); + }) + .get('buildId'); +}; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 132af14f..4e8f8330 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1217,6 +1217,15 @@ "integrity": "sha512-Zu4jAKE46yc6R8JrVkCBWbXhs18dUgI/JlbID4jziFgUBgEdAHxFekR5TlEnk9phHdGE80QlCznRBaxlk0rl7w==", "dev": true }, + "@types/progress-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/progress-stream/-/progress-stream-2.0.0.tgz", + "integrity": "sha512-KKboL4BvIezEvnd2bdbIfIIkr3Th1p0AJOBaPdKMWm69uMoisOGd4mynzfB/iia2z0k3nwAJCmixESHfVjsrgw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", diff --git a/package.json b/package.json index 10a80ede..173b6bf8 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "@types/node": "^10.17.20", "@types/node-cleanup": "^2.1.1", "@types/prettyjson": "0.0.29", + "@types/progress-stream": "^2.0.0", "@types/request": "^2.48.4", "@types/rewire": "^2.5.28", "@types/rimraf": "^2.0.4",