Skip to content

Commit

Permalink
fix #7136 - check SHA of downloaded node binaries
Browse files Browse the repository at this point in the history
  • Loading branch information
ppisljar committed Oct 29, 2016
1 parent adc95d0 commit 955972b
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 69 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"boom": "2.8.0",
"brace": "0.5.1",
"bunyan": "1.7.1",
"check-hash": "1.0.1",
"commander": "2.8.1",
"css-loader": "0.17.0",
"d3": "3.5.6",
Expand Down
193 changes: 127 additions & 66 deletions tasks/build/download_node_builds.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,145 @@
module.exports = function (grunt) {
let { map, fromNode } = require('bluebird');
let { resolve } = require('path');
let { pluck } = require('lodash');
let { createWriteStream } = require('fs');
let { createGunzip } = require('zlib');
let { Extract } = require('tar');
let { rename } = require('fs');
let wreck = require('wreck');

let platforms = grunt.config.get('platforms');
let activeDownloads = [];

let start = async (platform) => {
let finalDir = platform.nodeDir;
let downloadDir = `${finalDir}.temp`;
import { Promise, map, fromNode, promisify } from 'bluebird';
import { resolve, basename, dirname, join } from 'path';
import { createReadStream, createWriteStream, writeFile } from 'fs';
import { createGunzip } from 'zlib';
import { Extract } from 'tar';
import { fromFile } from 'check-hash';
import wreck from 'wreck';

if (grunt.file.isDir(platform.nodeDir)) {
grunt.log.ok(`${platform.name} exists`);
return;
}
const wreckGetAsync = promisify(wreck.get, wreck);
const checkHashFromFileAsync = promisify(fromFile);
const writeFileAsync = promisify(writeFile);

let resp = await fromNode(cb => {
let req = wreck.request('GET', platform.nodeUrl, null, function (err, resp) {
if (err) {
return cb(err);
}
export default function downloadNodeBuilds(grunt) {
const platforms = grunt.config.get('platforms');
const downloadLimit = 3;

if (resp.statusCode !== 200) {
return cb(new Error(`${platform.nodeUrl} failed with a ${resp.statusCode} response`));
}
let shaSums = {};
const getShaSums = () => {
const nodeVersion = grunt.config.get('nodeVersion');
const shaSumsUri = `https://nodejs.org/dist/v${nodeVersion}/SHASUMS256.txt`;

return cb(null, resp);
return wreckGetAsync(shaSumsUri).then(([resp, payload]) => {
if (resp.statusCode !== 200) {
throw new Error(`${shaSumsUri} failed with a ${resp.statusCode} response`);
}
payload
.toString('utf8')
.split('\n')
.forEach(line => {
const [sha, platform] = line.split(' ');
shaSums[platform] = sha;
});
grunt.log.ok(`sha sums download complete`);
});
};

const checkShaSum = (platform) => {
const file = basename(platform.nodeUrl);
const downloadDir = join(platform.nodeDir, '..');
const filePath = resolve(downloadDir, file);
const expected = {
hash: 'sha256',
expected: platform.win ? shaSums[basename(dirname(platform.nodeUrl)) + '/' + file] : shaSums[file]
};

// use an async iife to store promise for download
// then store platform in active downloads list
// which we will read from in the finish task
platform.downloadPromise = (async function () {
grunt.file.mkdir(downloadDir);

if (platform.win) {
await fromNode(cb => {
resp
.pipe(createWriteStream(resolve(downloadDir, 'node.exe')))
.on('error', cb)
.on('finish', cb);
});
if (!grunt.file.isFile(filePath)) {
return false;
}

return checkHashFromFileAsync(filePath, expected).then(([passed, actual]) => {
if (passed) {
grunt.log.ok(`${platform.name} shasum verified`);
} else {
await fromNode(cb => {
resp
.pipe(createGunzip())
.on('error', cb)
.pipe(new Extract({ path: downloadDir, strip: 1 }))
.on('error', cb)
.on('end', cb);
});
grunt.log.error(`${platform.name} shasum check failed`);
}
return passed;
});
};

const getNodeBuild = (platform) => {
const downloadDir = join(platform.nodeDir, '..');
const file = basename(platform.nodeUrl);
const filePath = resolve(downloadDir, file);

if (grunt.file.isFile(filePath)) {
grunt.file.delete(filePath);
}

grunt.log.ok(`downloading ${platform.name}`);

return wreckGetAsync(platform.nodeUrl)
.then(([resp, payload]) => {
if (resp.statusCode !== 200) {
throw new Error(`${platform.nodeUrl} failed with a ${resp.statusCode} response`);
}
return payload;
})
.then(payload => writeFileAsync(filePath, payload));

await fromNode(cb => {
rename(downloadDir, finalDir, cb);
});
}());
};

activeDownloads.push(platform);
const start = async (platform) => {
let downloadCounter = 0;
let isDownloadValid = await checkShaSum(platform);

var bytes = parseInt(resp.headers['content-length'], 10) || 'unknown number of';
var mb = ((bytes / 1024) / 1024).toFixed(2);
grunt.log.ok(`downloading ${platform.name} - ${mb} mb`);
if (isDownloadValid) return;

grunt.log.ok('starting download ...');
while (!isDownloadValid && (downloadCounter < downloadLimit)) {
await getNodeBuild(platform);
isDownloadValid = await checkShaSum(platform);
++downloadCounter;
}

if (!isDownloadValid) {
throw new Error(`${platform.name} download failed`);
}

grunt.log.ok(`download of ${platform.name} completed successfully`);
};

grunt.registerTask('_build:downloadNodeBuilds:start', function () {
map(platforms, start).nodeify(this.async());
grunt.registerTask('_build:downloadNodeBuilds', function () {
const done = this.async();
getShaSums()
.then(() => map(platforms, start))
.nodeify(done);
});

grunt.registerTask('_build:downloadNodeBuilds:finish', function () {
map(activeDownloads, async (platform) => {
await platform.downloadPromise;
grunt.log.ok(`${platform.name} download complete`);
})
.nodeify(this.async());
const extractNodeBuild = async (platform) => {
const file = basename(platform.nodeUrl);
const downloadDir = join(platform.nodeDir, '..');
const filePath = resolve(downloadDir, file);

return new Promise((resolve, reject) => {
createReadStream(filePath)
.pipe(createGunzip())
.on('error', reject)
.pipe(new Extract({path: platform.nodeDir, strip: 1}))
.on('error', reject)
.on('end', resolve);
});
};

const extract = async(platform) => {
const file = basename(platform.nodeUrl);
const downloadDir = join(platform.nodeDir, '..');
const filePath = resolve(downloadDir, file);

if (grunt.file.isDir(platform.nodeDir)) {
grunt.file.delete(platform.nodeDir);
}

if (platform.win) {
grunt.file.mkdir(platform.nodeDir);
grunt.file.copy(filePath, resolve(platform.nodeDir, file));
} else {
await extractNodeBuild(platform);
}
};

grunt.registerTask('_build:extractNodeBuilds', function () {
map(platforms, extract).nodeify(this.async());
});

};
4 changes: 2 additions & 2 deletions tasks/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module.exports = function (grunt) {
grunt.task.run(flatten([
'clean:build',
'clean:target',
'_build:downloadNodeBuilds:start',
'_build:downloadNodeBuilds',
'_build:extractNodeBuilds',
'copy:devSource',
'babel:build',
'_build:babelOptions',
Expand All @@ -21,7 +22,6 @@ module.exports = function (grunt) {
'clean:deepModules',
'run:optimizeBuild',
'stop:optimizeBuild',
'_build:downloadNodeBuilds:finish',
'_build:versionedLinks',
'_build:osShellScripts',
grunt.option('skip-archives') ? [] : ['_build:archives'],
Expand Down
4 changes: 3 additions & 1 deletion tasks/config/platforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ module.exports = function (grunt) {
? baseName.replace('-x64', '-x86_64')
: baseName;

let nodeShaSums = `${baseUri}/SHASUMS256.txt`;

let buildName = `kibana-${version}-${name}`;
let buildDir = resolve(rootPath, `build/${buildName}`);

Expand All @@ -49,7 +51,7 @@ module.exports = function (grunt) {
}
return {
name, win,
nodeUrl, nodeDir,
nodeUrl, nodeDir, nodeShaSums,
buildName, buildDir,
tarName, tarPath,
zipName, zipPath,
Expand Down

0 comments on commit 955972b

Please sign in to comment.