From 0d30d743f271c758c5e40554f8bebc5107d74fbe Mon Sep 17 00:00:00 2001 From: Radek Pazdera Date: Fri, 8 Sep 2017 18:05:42 +0100 Subject: [PATCH] Finishing basic functionality --- lib/index.js | 230 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 4 +- test/playground.js | 8 +- 3 files changed, 229 insertions(+), 13 deletions(-) diff --git a/lib/index.js b/lib/index.js index e0eb7f5..41297fd 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,7 @@ -const aws = require('aws-sdk'); +const aws = require('aws-sdk'), + TarGz = require('tar.gz'), + semver = require('semver'), + stream = require('stream'); let s3 = null, config = { @@ -93,8 +96,62 @@ function archiveBuild(buildDir, projectName, channel, version, number, arch) { function _doArchiveBuild(buildDir, projectName, channel, version, number, arch) { return new Promise((resolve, reject) => { - // tar gz directory - // upload to s3 + let tgz = new TarGz({}, {fromBase: true}), + stream; + + stream = tgz.createReadStream(buildDir); + + s3.upload( + { + Bucket: config.rootBucket.name, + Key: `${projectName}/${channel}/${projectName}_${version}-${number}_${arch}.tar.gz`, + Body: stream + }, + (err, data) => { + if (err) { + return reject('Failed to upload archive' + err); + } + + resolve({ + project: projectName, + channel: channel, + path: data.Key, + version: version, + build: number, + arch: arch, + date: Date.new(), + url: data.Location + }); + } + ); + }); +} + +/* + * Remove a build from the archive by path or build object. + * + * @param {String, Object} build Either a direct path to build or a build object. + * + */ +function removeBuild(build) { + if (typeof build === 'string') { + build = {path: build}; + } + + return new Promise((resolve, reject) => { + s3.deleteObject( + { + Bucket: config.rootBucket.name, + Key: build.path + }, + (err, data) => { + if (err) { + return reject('Failed to remove ' + path + ':' + err); + } + + resolve(); + } + ); }); } @@ -130,11 +187,23 @@ function _getComparator(opts) { while (res === 0 && keys.length) { let key = keys.shift(); - - if (a[key] > b[key]) { - res = order > 0 ? 1 : -1; - } else if (a[key] < b[key]) { - res = order > 0 ? -1 : 1; + + /* Exception: use semver to compare versions */ + if (key === 'version') { + let ca = semver.clean(a[key]), + cb = semver.clean(b[key]); + + if (semver.gt(ca, cb)) { + res = order > 0 ? 1 : -1; + } else if (semver.lt(ca, cb)) { + res = order > 0 ? -1 : 1; + } + } else { + if (a[key] > b[key]) { + res = order > 0 ? 1 : -1; + } else if (a[key] < b[key]) { + res = order > 0 ? -1 : 1; + } } } } @@ -214,8 +283,151 @@ function list(project, channel, opts) { }); } +function _deleteEntries(bucketName, entries) { + let p = entries.map((entry) => { + return new Promise((resolve, reject) => { + if (!entry.Key.match(/\/$/)) { + s3.deleteObject( + { + Bucket: bucketName, + Key: entry.Key + }, + (err, data) => { + if (err) { + return reject('Failed to remove ' + entry.Key + ': ' + err); + } + + resolve(entry.Key); + } + ); + } + }); + }); + + return Promise.all(p); +} + +function _clearBucket(bucketName) { + return new Promise((resolve, reject) => { + s3.listObjects( + { + Bucket: bucketName + }, + (err, data) => { + if (err) { + return reject('Failed to list files in ' + bucketName + ': ' + err); + } + + _deleteEntries(bucketName, data.Contents) + .then(resolve); + } + ); + }); +} + +function _uploadKartFile(build, target) { + let info = { + project: build.project, + channel: build.channel, + version: build.version, + build: build.build, + releaseDate: new Date(), + buildDate: build.date + }; + + return new Promise((resolve, reject) => { + s3.upload( + { + Bucket: target, + Key: 'kart.json', + Body: JSON.stringify(info) + }, + (err, data) => { + if (err) { + return reject('Failed uploading the kart file:' + err); + } + + resolve(info); + } + ); + }); +} + +/* + * Download, unzip and release a build to a target bucket. + */ +function release(build) { + let bucket = config.projects[build.project].channels[build.channel].bucket; + + return _clearBucket(bucket) + .then(() => { + return _doRelease(build, bucket); + }).then(() => { + return _uploadKartFile(build, bucket) + }); +} + +function _doRelease(build, target) { + return new Promise((resolve, reject) => { + s3.getObject( + { + Bucket: config.rootBucket.name, + Key: build.path + }, + (err, data) => { + if (err) { + return reject('Failed to download archive config: ' + error); + } + + let rs = new stream.Readable(), + targz = new TarGz(), + parse = targz.createParseStream(); + + rs._read = function () {} + rs.push(data.Body); + rs.push(null); + + parse.on('entry', (entry) => { + if (entry.type === 'File') { + let p = new stream.PassThrough(); + entry.pipe(p); + + s3.upload( + { + Bucket: target, + Key: entry.path, + Body: p, + ACL: 'public-read' + }, + (err, data) => { + if (err) { + return reject('Failed deploying ' + entry.path + ':' + err); + } + } + ); + } + }); + + rs.pipe(parse); + + parse.on('finish', () => { + resolve(); + }); + } + ); + // Download build + // unzip into a temporary directory + // wipe the target bucket + // upload to s3 + // Write build information + // Make files public + }); +} + module.exports = { configure, archiveBuild, - list + removeBuild, + list, + release }; \ No newline at end of file diff --git a/package.json b/package.json index 12bb437..008517e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ }, "homepage": "https://github.com/KanoComputing/kart#readme", "dependencies": { - "aws-sdk": "^2.111.0" + "aws-sdk": "^2.111.0", + "semver": "^5.4.1", + "tar.gz": "^1.0.5" } } diff --git a/test/playground.js b/test/playground.js index 9f6f5fb..eaf5841 100644 --- a/test/playground.js +++ b/test/playground.js @@ -1,6 +1,6 @@ -const rlib = require('../lib'); +const kart = require('../lib'); -rlib.configure({ +kart.configure({ awsKey: process.env.AWS_KEY, awsSecret: process.env.AWS_SECRET, rootBucket: { @@ -9,9 +9,11 @@ rlib.configure({ } }).then(() => { - rlib.list('kano-code', 'staging', {filter: {version: '1.0.7'},sort: {key: ['version', 'build'], order: 1}, limit: 1}) + kart.list('kano-code', 'staging', {sort: {key: ['version', 'build'], order: -1}}) .then((data) => { console.log(data); + + kart.release(data[0]); }).catch((err) => { console.log(err); });