Skip to content

Commit

Permalink
Merge pull request #1 from KanoComputing/kart-test-suite
Browse files Browse the repository at this point in the history
Adding test suite
  • Loading branch information
pazdera authored Jan 11, 2018
2 parents 8aaabe4 + cffe44b commit 3a63c64
Show file tree
Hide file tree
Showing 15 changed files with 1,391 additions and 231 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: node_js
node_js:
- "node"
- "6"
91 changes: 68 additions & 23 deletions bin/kart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ const inquirer = require('inquirer'),
colors = require('colors'),
gitRev = require('git-rev-sync'),
fs = require('fs');
os = require('os'),
path = require('path'),
updateNotifier = require('update-notifier');
EventEmitter = require('events'),
updateNotifier = require('update-notifier'),

KART_RC_PATH = os.homedir() + '/.kartrc';

updateNotifier({pkg: package}).notify();

Expand Down Expand Up @@ -203,8 +207,13 @@ function release(argv) {
}
}).then(answers => {
if (answers.proceed) {
let reporter = new EventEmitter();
reporter.on('update', (detail) => {
console.log(' ', detail.message);
});

console.log(`Deploying ${build.project.cyan} ${build.buildVersion.yellow} to ${build.channel.cyan}`);
return kart.release(build)
return kart.release(build, reporter)
.then(() => {
let url = project.config['channels'][project.channel].url;
if (url) {
Expand Down Expand Up @@ -247,7 +256,7 @@ function archive(argv) {
}

name = argv.name ? argv.name : name;
version = argv.version ? argv.version : version;
version = argv.buildVersion ? argv.buildVersion : version;

return kart.archive.store(argv.buildDir, name, argv.channel, version, null, argv.arch, metadata)
.then((b) => {
Expand All @@ -261,9 +270,53 @@ function archive(argv) {
});
}

/* Used for testing the UI only */
function setupMockS3(root) {
try {
let AWSMock = require('mock-aws-s3'),
s3mock;

AWSMock.config.basePath = root;
s3mock = AWSMock.S3();

kart.__mockS3API(s3mock);
console.log('!!! RUNNING IN TESTING MODE !!!'.red);
} catch (err) {
throw new Error('Failed to configure kart for testing.');
}
}

function handleCommonOptions(argv) {
let rcPath = argv.mockS3Root ? null : KART_RC_PATH,
configOverrides = {};

if (argv.mockS3Root) {
setupMockS3(argv.mockS3Root);
}

if (argv.archiveRoot) {
configOverrides.rootBucket = {
name: argv.archiveRoot
};
}

return kart.configure(configOverrides, rcPath);
}

// ----

let argv = yargs
.options({
'archive-root': {
describe: 'override archive root bucket',
type: 'string',
alias: 'a'
},
'mock-s3-root': {
type: 'string',
hidden: true
}
})
.command('release', 'deploy a new release of a project', {
'name': {
alias: 'n',
Expand All @@ -281,8 +334,10 @@ let argv = yargs
type: 'string'
},
}, (argv) => {
console.log('kart: Making a release');
kart.getMOTD().then((motd) => {
handleCommonOptions(argv).then(() => {
console.log('kart: Making a release');
return kart.getMOTD();
}).then((motd) => {
if (motd) {
console.log("\n" + motd + "\n");
}
Expand All @@ -304,8 +359,10 @@ let argv = yargs
type: 'string'
}
}, (argv) => {
console.log('kart: Showing status for channel');
kart.getMOTD().then((motd) => {
handleCommonOptions(argv).then(() => {
console.log('kart: Showing status for channel');
return kart.getMOTD();
}).then((motd) => {
if (motd) {
console.log("\n" + motd + "\n");
}
Expand All @@ -325,7 +382,8 @@ let argv = yargs
describe: 'set project name (or override autodetected one)',
type: 'string'
},
'version': {
'build-version': {
alias: 'V',
describe: 'set project version (or override autodetected one)',
type: 'string'
},
Expand All @@ -344,27 +402,14 @@ let argv = yargs
type: 'string',
demandOption: true
},
'archive-root': {
describe: 'override archive root bucket',
type: 'string',
alias: 'a'
},
'release': {
describe: 'release after archiving',
type: 'boolean'
}
}, (argv) => {
if (argv.archiveRoot) {
return kart.configure({
rootBucket: {
name: argv.archiveRoot
}
}).then(() => {
return archive(argv);
});
} else {
handleCommonOptions(argv).then(() => {
return archive(argv);
}
});
})
.alias('h', 'help')
.help('help')
Expand Down
125 changes: 72 additions & 53 deletions lib/archive.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const aws = require('aws-sdk'),
TarGz = require('tar.gz'),
const tarFs = require('tar-fs'),
fs = require('fs'),
zlib = require('zlib'),
semver = require('semver'),
config = require('./config'),
Build = require('./data').Build;

Build = require('./data').Build,
s3 = require('./s3-helpers');

function _getNextBuildNumber(projectName, channel, version) {
let opts = {
Expand All @@ -16,7 +17,7 @@ function _getNextBuildNumber(projectName, channel, version) {
},
limit: 1
};

return list(projectName, channel, opts).then((res) => {
if (res.length > 0) {
return res[0].number + 1;
Expand All @@ -28,7 +29,7 @@ function _getNextBuildNumber(projectName, channel, version) {

/*
* Compress and upload a build to the apropriate place in the archive.
*
*
* @param {String} buildDir Path to a directory to be archived (top level dir won't be included).
* @param {String} projectName Name of the project being archived (e.g., kano-code).
* @param {String} channel Target channel (e.g., staging).
Expand All @@ -42,6 +43,14 @@ function _getNextBuildNumber(projectName, channel, version) {
function storeBuild(buildDir, projectName, channel, version, number, arch, metadata) {
arch = arch || 'all';

if (Object.keys(config.remote.projects).indexOf(projectName) < 0) {
return Promise.reject(`Project '${projectName}' not found`);
}

if (Object.keys(config.remote.projects[projectName].channels).indexOf(channel) < 0) {
return Promise.reject(`Channel '${channel}' not found in project '${projectName}'`);
}

if (number) {
return _doStoreBuild(buildDir, projectName, channel, version, number, arch, metadata);
} else {
Expand All @@ -54,56 +63,60 @@ function storeBuild(buildDir, projectName, channel, version, number, arch, metad

function _doStoreBuild(buildDir, projectName, channel, version, number, arch, metadata) {
return new Promise((resolve, reject) => {
let tgz = new TarGz({}, {fromBase: true}),
stream,
build;

stream = tgz.createReadStream(buildDir);

build = new Build({
project: projectName,
channel: channel,
version: version,
metadata: metadata,
number: number,
arch: arch,
buildDate: new Date()
});

let s3 = new aws.S3();
s3.upload(
{
Bucket: config.local.rootBucket.name,
Key: build.path,
Body: stream,
ACL: 'public-read',
Metadata: metadata
},
(err, data) => {
if (err) {
return reject('Failed to upload archive' + err);
}
fs.lstat(buildDir, (err, stat) => {
if (err) {
return reject(new Error(`Could not find or access build directory: ${buildDir}`));
}

resolve(build);
if (!stat.isDirectory()) {
return reject(new Error(`Path provided is not a directory: ${buildDir}`));
}
);

let gzip = zlib.createGzip(),
stream = tarFs.pack(buildDir),
build = new Build({
project: projectName,
channel: channel,
version: version,
metadata: metadata,
number: number,
arch: arch,
buildDate: new Date()
});

s3.getInstance().upload(
{
Bucket: config.local.rootBucket.name,
Key: build.path,
Body: stream.pipe(gzip),
ACL: 'public-read',
Metadata: metadata
},
(err, data) => {
if (err) {
return reject(new Error('Failed to upload archive: ' + err));
}

resolve(build);
}
);
});
});
}

/*
* 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) => {
let s3 = new aws.S3();
s3.deleteObject(
s3.getInstance().deleteObject(
{
Bucket: config.local.rootBucket.name,
Key: build.path
Expand All @@ -112,7 +125,6 @@ function removeBuild(build) {
if (err) {
return reject('Failed to remove ' + path + ':' + err);
}

resolve();
}
);
Expand Down Expand Up @@ -146,13 +158,13 @@ function _getComparator(opts) {
if (opts.sort) {
let keys,
order = opts.sort.order || 1;

if (!Array.isArray(opts.sort.key)) {
keys = [opts.sort.key];
} else {
keys = opts.sort.key.slice(0);
}

while (res === 0 && keys.length) {
let key = keys.shift();

Expand All @@ -175,37 +187,44 @@ function _getComparator(opts) {
}
}
}

return res;
};
}

/*
* List builds of a project in a channel.
*
*
* @param {String} project The name of the project (e.g., kano-code).
* @param {String} channel The name of the channel (e.g., staging).
*
*
* @param {Object} opts (optional) Modify the listing results.
* @param {Object} opts.filter Filtering options (key value pairs).
* Filtering based on metadata not supported at the moment.
*
*
* @param {Object} opts.sort Sorting options.
* @param {Sring, Array} opts.sort.key One or more keys to sort by in order of priority
* @param {Number} opts.sort.order 1 for ascending, -1 for descending.
*
*
* @param {Number} opts.limit Limit the list to N entries.
*
*/
function list(project, channel, opts) {
opts = Object.assign({}, opts);


if (!config.remote.projects[project]) {
return Promise.reject(`Project '${project}' not found`);
}

if (!config.remote.projects[project].channels[channel]) {
return Promise.reject(`Channel '${channel}' not found in project '${project}'`);
}

return new Promise((resolve, reject) => {
let folder = `${project}/${channel}/`,
results;

let s3 = new aws.S3();
s3.listObjects(

s3.getInstance().listObjects(
{
Bucket: config.local.rootBucket.name,
Delimiter: '/',
Expand Down
Loading

0 comments on commit 3a63c64

Please sign in to comment.