diff --git a/meteor/.jshintrc b/meteor/.jshintrc
index ab5b799ac1..dca33cf8e9 100755
--- a/meteor/.jshintrc
+++ b/meteor/.jshintrc
@@ -169,7 +169,11 @@
"Boot2Docker": true,
"Installer": true,
"VirtualBox": true,
-
+ "ImageUtil": true,
+ "AppUtil": true,
+
+ "getBinDir": true,
+ "getHomePath": true,
"boot2dockerexec": true,
"getBoot2DockerIp": true,
"getBoot2DockerState": true,
@@ -228,7 +232,8 @@
"KITE_PATH": true,
"KITE_TAR_PATH": true,
"KITE_IMAGES_PATH": true,
- "COMMON_WEB_PORTS": true
+ "COMMON_WEB_PORTS": true,
+ "DOCKER_HOST": true
}
}
diff --git a/meteor/.meteor/packages b/meteor/.meteor/packages
index 0739b78bee..45a51bb89e 100755
--- a/meteor/.meteor/packages
+++ b/meteor/.meteor/packages
@@ -10,7 +10,6 @@ npm
iron-router
headers
handlebar-helpers
-collection2
collection-hooks
moment
underscore-string-latest
diff --git a/meteor/client/lib/apps.js b/meteor/client/lib/apps.js
new file mode 100644
index 0000000000..f8745c4306
--- /dev/null
+++ b/meteor/client/lib/apps.js
@@ -0,0 +1,119 @@
+AppUtil = {};
+
+AppUtil.run = function (app) {
+ var image = Images.findOne({_id: app.imageId});
+ // Delete old container if one already exists
+ Docker.removeContainer(app.name, function (err) {
+ if (err) { console.error(err); }
+ Docker.runContainer(app, image, function (err, container) {
+ if (err) { throw err; }
+ Docker.getContainerData(container.id, function (err, data) {
+ if (err) { console.error(err); }
+ // Set a delay for app to spin up
+ Meteor.setTimeout(function () {
+ Apps.update(app._id, {$set: {
+ docker: data,
+ status: 'READY'
+ }});
+ }, 2500);
+ });
+ });
+ });
+};
+
+AppUtil.restartHelper = function (app) {
+ if (app.docker && app.docker.Id) {
+ Docker.restartContainer(app.docker.Id, function (err) {
+ if (err) { console.error(err); }
+ Docker.getContainerData(app.docker.Id, function (err, data) {
+ if (err) { console.error(err); }
+ Apps.update(app._id, {$set: {
+ status: 'READY',
+ docker: data
+ }});
+ // Use dig to refresh the DNS
+ // exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {});
+ });
+ });
+ }
+};
+
+AppUtil.restart = function (appId) {
+ var app = Apps.findOne(appId);
+ if (app && app.docker) {
+ Apps.update(app._id, {$set: {
+ status: 'STARTING'
+ }});
+ AppUtil.restartHelper(app);
+ }
+};
+
+AppUtil.remove = function (appId) {
+ var app = Apps.findOne(appId);
+ if (app.docker) {
+ Apps.remove({_id: appId});
+ Docker.removeContainer(app.docker.Id, function (err) {
+ if (err) { console.error(err); }
+ var appPath = path.join(KITE_PATH, app.name);
+ Util.deleteFolder(appPath);
+ Docker.removeBindFolder(app.name, function () {
+ console.log('Deleted Kite ' + app.name + ' directory.');
+ });
+ });
+ }
+};
+
+AppUtil.configVar = function (appId, configVars) {
+ Apps.update(appId, {$set: {
+ config: configVars,
+ status: 'STARTING'
+ }});
+ var app = Apps.findOne({_id: appId});
+ AppUtil.run(app);
+};
+
+AppUtil.logs = function (appId) {
+ var app = Apps.findOne(appId);
+ if (app.docker && app.docker.Id) {
+ var container = docker.getContainer(app.docker.Id);
+ container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) {
+ if (err) { throw err; }
+ Apps.update(app._id, {
+ $set: {
+ logs: []
+ }
+ });
+ var logs = [];
+ response.setEncoding('utf8');
+ response.on('data', function (line) {
+ logs.push(convert.toHtml(line.slice(8)));
+ Apps.update(app._id, {
+ $set: {
+ logs: logs
+ }
+ });
+ });
+ response.on('end', function () {});
+ });
+ }
+};
+
+AppUtil.recover = function () {
+ var apps = Apps.find({}).fetch();
+ _.each(apps, function (app) {
+ // Update the app with the latest container info
+ if (!app.docker) {
+ return;
+ }
+ var container = docker.getContainer(app.docker.Id);
+ container.inspect(function (err, data) {
+ if (app.status !== 'STARTING' && data && data.State && !data.State.Running) {
+ console.log('Restarting: ' + app.name);
+ console.log(app.docker.Id);
+ AppUtil.restartHelper(app, function (err) {
+ if (err) { console.error(err); }
+ });
+ }
+ });
+ });
+};
diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js
index 589b85b67d..7dfac20eb0 100644
--- a/meteor/client/lib/boot2docker.js
+++ b/meteor/client/lib/boot2docker.js
@@ -6,7 +6,7 @@ Boot2Docker = {};
Boot2Docker.REQUIRED_IP = '192.168.60.103';
Boot2Docker.exec = function (command, callback) {
- exec(path.join(Util.getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout, stderr) {
+ exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout, stderr) {
callback(err, stdout, stderr);
});
};
@@ -32,7 +32,7 @@ Boot2Docker.stop = function (callback) {
};
Boot2Docker.erase = function (callback) {
- var VMFileLocation = path.join(Util.getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm');
+ var VMFileLocation = path.join(getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm');
exec('rm -rf ' + VMFileLocation, function (err) {
callback(err);
});
@@ -233,7 +233,7 @@ Boot2Docker.version = function (callback) {
};
Boot2Docker.injectUtilities = function (callback) {
- exec('/bin/cat ' + path.join(Util.getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(Util.getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) {
+ exec('/bin/cat ' + path.join(getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) {
callback(err);
});
};
diff --git a/meteor/server/docker.js b/meteor/client/lib/docker.js
old mode 100755
new mode 100644
similarity index 79%
rename from meteor/server/docker.js
rename to meteor/client/lib/docker.js
index 6978024245..02d55c6954
--- a/meteor/server/docker.js
+++ b/meteor/client/lib/docker.js
@@ -1,6 +1,5 @@
-Dockerode = Meteor.require('dockerode');
+Dockerode = require('dockerode');
-var DOCKER_HOST='192.168.60.103';
docker = new Dockerode({host: DOCKER_HOST, port: '2375'});
Docker = {};
@@ -17,10 +16,6 @@ Docker.removeContainer = function (containerId, callback) {
});
};
-Docker.removeContainerSync = function (containerId) {
- return Meteor._wrapAsync(Docker.removeContainer)(containerId);
-};
-
Docker.getContainerData = function (containerId, callback) {
var container = docker.getContainer(containerId);
container.inspect(function (err, data) {
@@ -37,10 +32,6 @@ Docker.getContainerData = function (containerId, callback) {
});
};
-Docker.getContainerDataSync = function (containerId) {
- return Meteor._wrapAsync(Docker.getContainerData)(containerId);
-};
-
Docker.runContainer = function (app, image, callback) {
var envParam = [];
_.each(_.keys(app.config), function (key) {
@@ -78,10 +69,6 @@ Docker.runContainer = function (app, image, callback) {
});
};
-Docker.runContainerSync = function (app, image) {
- return Meteor._wrapAsync(Docker.runContainer)(app, image);
-};
-
Docker.restartContainer = function (containerId, callback) {
var container = docker.getContainer(containerId);
container.restart(function (err) {
@@ -95,10 +82,6 @@ Docker.restartContainer = function (containerId, callback) {
});
};
-Docker.restartContainerSync = function (containerId) {
- return Meteor._wrapAsync(Docker.restartContainer)(containerId);
-};
-
var convertVolumeObjToArray = function (obj) {
var result = [];
if (obj !== null && typeof obj === 'object') {
@@ -127,10 +110,6 @@ Docker.getImageData = function (imageId, callback) {
});
};
-Docker.getImageDataSync = function (imageId) {
- return Meteor._wrapAsync(Docker.getImageData)(imageId);
-};
-
Docker.removeImage = function (imageId, callback) {
var image = docker.getImage(imageId.toLowerCase());
image.remove({force: true}, function (err) {
@@ -140,12 +119,8 @@ Docker.removeImage = function (imageId, callback) {
});
};
-Docker.removeImageSync = function (imageId) {
- return Meteor._wrapAsync(Docker.removeImage)(imageId);
-};
-
Docker.removeBindFolder = function (name, callback) {
- exec(path.join(Util.getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function (err, stdout) {
+ exec(path.join(getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function (err, stdout) {
callback(err, stdout);
});
};
@@ -196,7 +171,7 @@ resolveDefaultImages = function () {
image.inspect(function (err) {
if (err) {
if (err.reason === 'no such image') {
- docker.loadImage(path.join(Util.getBinDir(), 'base-images.tar.gz'), {}, function (err) {
+ docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) {
if (err) {
innerCallback(err);
return;
@@ -283,7 +258,7 @@ reloadDefaultContainers = function (callback) {
return;
}
console.log('Loading new Kitematic default images.');
- docker.loadImage(path.join(Util.getBinDir(), 'base-images.tar.gz'), {}, function (err) {
+ docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) {
if (err) {
callback(err);
return;
@@ -391,50 +366,3 @@ killAndRemoveContainers = function (names, callback) {
callback(err);
});
};
-
-Meteor.methods({
- runApp: function (app) {
- this.unblock();
- var image = Images.findOne({_id: app.imageId});
- // Delete old container if one already exists
- try {
- Docker.removeContainerSync(app.name);
- } catch (e) {}
- try {
- var container = Docker.runContainerSync(app, image);
- var containerData = Docker.getContainerDataSync(container.id);
- // Set a delay for app to spin up
- Meteor.setTimeout(function () {
- Apps.update(app._id, {$set: {
- docker: containerData,
- status: 'READY'
- }});
- }, 2500);
- } catch (e) {
- console.error(e);
- }
- },
- getDockerHost: function () {
- return DOCKER_HOST;
- },
- reloadDefaultContainers: function () {
- this.unblock();
- return Meteor._wrapAsync(reloadDefaultContainers)();
- },
- checkDefaultImages: function () {
- this.unblock();
- return Meteor._wrapAsync(checkDefaultImages)();
- },
- resolveDefaultImages: function () {
- this.unblock();
- return Meteor._wrapAsync(resolveDefaultImages)();
- },
- checkDefaultContainers: function () {
- this.unblock();
- return Meteor._wrapAsync(checkDefaultContainers)();
- },
- resolveDefaultContainers: function () {
- this.unblock();
- return Meteor._wrapAsync(resolveDefaultContainers)();
- }
-});
diff --git a/meteor/client/lib/images.js b/meteor/client/lib/images.js
new file mode 100644
index 0000000000..0c121d86ec
--- /dev/null
+++ b/meteor/client/lib/images.js
@@ -0,0 +1,246 @@
+ImageUtil = {};
+
+var createTarFile = function (image, callback) {
+ var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar');
+ exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) {
+ if (err) { callback(err, null); return; }
+ console.log('Created tar file: ' + TAR_PATH);
+ callback(null, TAR_PATH);
+ });
+};
+
+var getFromImage = function (dockerfile) {
+ var patternString = "(FROM)(.*)";
+ var regex = new RegExp(patternString, "g");
+ var fromInstruction = dockerfile.match(regex);
+ if (fromInstruction && fromInstruction.length > 0) {
+ return fromInstruction[0].replace('FROM', '').trim();
+ } else {
+ return null;
+ }
+};
+
+var getImageJSON = function (directory) {
+ var KITE_JSON_PATH = path.join(directory, 'image.json');
+ if (fs.existsSync(KITE_JSON_PATH)) {
+ var data = fs.readFileSync(KITE_JSON_PATH, 'utf8');
+ return JSON.parse(data);
+ } else {
+ return null;
+ }
+};
+
+ImageUtil.getMetaData = function (directory) {
+ var kiteJSON = getImageJSON(directory);
+ if (kiteJSON) {
+ if (!kiteJSON.name) {
+ kiteJSON.name = _.last(directory.split(path.sep));
+ }
+ } else {
+ kiteJSON = {
+ name: _.last(directory.split(path.sep))
+ };
+ }
+ return kiteJSON;
+};
+
+ImageUtil.saveFolder = function (directory, imageId, callback) {
+ var destinationPath = path.join(KITE_IMAGES_PATH, imageId);
+ if (!fs.existsSync(destinationPath)) {
+ fs.mkdirSync(destinationPath, function (err) {
+ if (err) { callback(err); return; }
+ });
+ Util.copyFolder(directory, destinationPath);
+ console.log('Copied image folder for: ' + imageId);
+ callback(null);
+ }
+};
+
+ImageUtil.rebuild = function (image, callback) {
+ Util.deleteFolder(image.path);
+ var imageMetaData = ImageUtil.getMetaData(image.originPath);
+ if (imageMetaData.logo) {
+ Images.update(image._id, {
+ $set: {
+ logoPath: path.join(image.path, imageMetaData.logo)
+ }
+ });
+ } else {
+ Images.update(image._id, {
+ $set: {
+ logoPath: null
+ }
+ });
+ }
+ Images.update(image._id, {
+ $set: {
+ status: 'BUILDING',
+ meta: imageMetaData
+ }
+ });
+ image = Images.findOne(image._id);
+ ImageUtil.saveFolder(image.originPath, image._id, function (err) {
+ if (err) { console.error(err); }
+ ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) {
+ if (err) { callback(err, null); return; }
+ ImageUtil.build(image, function (err) {
+ if (err) { console.error(err); }
+ callback(null, null);
+ });
+ });
+ });
+};
+
+ImageUtil.rebuildImage = function (imageId) {
+ var image = Images.findOne(imageId);
+ if (!image) {
+ throw new Meteor.Error(403, "No image found with this ID.");
+ }
+ var apps = Apps.find({imageId: imageId}).fetch();
+ if (apps.length > 0) {
+ _.each(apps, function (app) {
+ console.log('Updating app: ' + app.name);
+ if (app.docker) {
+ Docker.removeContainer(app.docker.Id, function (err) {
+ if (err) { console.error(err); }
+ });
+ }
+ Apps.update(app._id, {
+ $set: {
+ 'docker.Id': null,
+ status: 'STARTING',
+ logs: []
+ }
+ });
+ });
+ ImageUtil.rebuild(image, function (err) {
+ if (err) { console.error(err); }
+ });
+ _.each(apps, function (app) {
+ app = Apps.findOne(app._id);
+ /*Meteor.call('runApp', app, function (err) {
+ if (err) { console.error(err); }
+ });*/
+ });
+ } else {
+ ImageUtil.rebuild(image, function (err) {
+ if (err) { console.error(err); }
+ });
+ }
+},
+
+ImageUtil.pull = function (dockerfile, imageId, callback) {
+ var fromImage = getFromImage(dockerfile);
+ console.log('From image: ' + fromImage);
+ var installedImage = null;
+ Docker.getImageData(imageId, function (err, data) {
+ if (err) { console.error(err); }
+ installedImage = data;
+ if (fromImage && !installedImage) {
+ Images.update(imageId, {
+ $set: {
+ buildLogs: []
+ }
+ });
+ var logs = [];
+ docker.pull(fromImage, function (err, response) {
+ if (err) { callback(err); return; }
+ response.setEncoding('utf8');
+ response.on('data', function (data) {
+ try {
+ var logData = JSON.parse(data);
+ var logDisplay = '';
+ if (logData.id) {
+ logDisplay += logData.id + ' | ';
+ }
+ logDisplay += logData.status;
+ if (logData.progressDetail && logData.progressDetail.current && logData.progressDetail.total) {
+ logDisplay += ' - ' + Math.round(logData.progressDetail.current / logData.progressDetail.total * 100) + '%';
+ }
+ logs.push(logDisplay);
+ Images.update(imageId, {
+ $set: {
+ buildLogs: logs
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ response.on('end', function () {
+ console.log('Finished pulling image: ' + fromImage);
+ callback(null);
+ });
+ });
+ } else {
+ callback(null);
+ }
+ });
+};
+
+ImageUtil.build = function (image, callback) {
+ createTarFile(image, function (err, tarFilePath) {
+ if (err) { console.error(err); }
+ Images.update(image._id, {
+ $set: {
+ buildLogs: []
+ }
+ });
+ docker.buildImage(tarFilePath, {t: image._id.toLowerCase()}, function (err, response) {
+ if (err) { callback(err); }
+ console.log('Building Docker image...');
+ var logs = [];
+ response.setEncoding('utf8');
+ response.on('data', function (data) {
+ try {
+ var line = JSON.parse(data).stream;
+ logs.push(convert.toHtml(line));
+ Images.update(image._id, {
+ $set: {
+ buildLogs: logs
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ response.on('end', function () {
+ console.log('Finished building Docker image.');
+ try {
+ fs.unlinkSync(tarFilePath);
+ console.log('Cleaned up tar file.');
+ } catch (e) {
+ console.error(e);
+ }
+ var imageData = null;
+ Docker.getImageData(image._id, function (err, data) {
+ if (err) {
+ Images.update(image._id, {
+ $set: {
+ status: 'ERROR'
+ }
+ });
+ } else {
+ imageData = data;
+ Images.update(image._id, {
+ $set: {
+ docker: imageData,
+ status: 'READY'
+ }
+ });
+ var oldImageId = null;
+ if (image.docker && image.docker.Id) {
+ oldImageId = image.docker.Id;
+ }
+ if (oldImageId && imageData && oldImageId !== imageData.Id) {
+ Docker.removeImage(oldImageId, function (err) {
+ if (err) { console.error(err); }
+ });
+ }
+ }
+ callback(null);
+ });
+ });
+ });
+ });
+};
diff --git a/meteor/client/lib/init/constants.js b/meteor/client/lib/init/constants.js
new file mode 100644
index 0000000000..0aafe5ef0f
--- /dev/null
+++ b/meteor/client/lib/init/constants.js
@@ -0,0 +1,33 @@
+var path = require('path');
+
+getHomePath = function () {
+ return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
+};
+
+getBinDir = function () {
+ if (process.env.NODE_ENV === 'development') {
+ return path.join(path.join(process.env.PWD, '..'), 'resources');
+ } else {
+ if (Meteor.isClient) {
+ return path.join(process.cwd(), 'resources');
+ } else {
+ return path.join(process.cwd(), '../../../resources');
+ }
+ }
+};
+
+KITE_PATH = path.join(getHomePath(), 'Kitematic');
+KITE_TAR_PATH = path.join(KITE_PATH, '.tar');
+KITE_IMAGES_PATH = path.join(KITE_PATH, '.images');
+
+DOCKER_HOST = '192.168.60.103';
+
+COMMON_WEB_PORTS = [
+ 80,
+ 8000,
+ 8080,
+ 3000,
+ 5000,
+ 2368,
+ 1337
+];
diff --git a/meteor/server/form-schemas.js b/meteor/client/lib/init/form-schemas.js
old mode 100755
new mode 100644
similarity index 55%
rename from meteor/server/form-schemas.js
rename to meteor/client/lib/init/form-schemas.js
index d3092abe04..ea165b9b54
--- a/meteor/server/form-schemas.js
+++ b/meteor/client/lib/init/form-schemas.js
@@ -27,18 +27,3 @@ FormSchema = {
}
};
-
-// Auto-subscribe forms
-_.each(_.keys(FormSchema), function (schemaName) {
- console.log('Subscribed form schema: ' + schemaName);
- var method = {};
- method[schemaName] = function (formInput) {
- var result = formValidate(formInput, FormSchema[schemaName]);
- if (result.errors) {
- throw new Meteor.Error(400, 'Validation Failed.', result.errors);
- } else {
- return result.cleaned;
- }
- };
- Meteor.methods(method);
-});
diff --git a/meteor/client/lib/init/requires.js b/meteor/client/lib/init/requires.js
new file mode 100644
index 0000000000..78e07108dc
--- /dev/null
+++ b/meteor/client/lib/init/requires.js
@@ -0,0 +1,10 @@
+https = require('https');
+tar = require('tar');
+zlib = require('zlib');
+fs = require('fs');
+path = require('path');
+exec = require('child_process').exec;
+async = require('async');
+child_process = require('child_process');
+Convert = require('ansi-to-html');
+convert = new Convert();
diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js
index 459ed06f7b..a5bc41ec81 100644
--- a/meteor/client/lib/installer.js
+++ b/meteor/client/lib/installer.js
@@ -100,7 +100,7 @@ Installer.steps = [
},
pastMessage: 'Started the Boot2Docker VM',
message: 'Starting the Boot2Docker VM',
- futureMessage: 'Start the Kitematic VM',
+ futureMessage: 'Start the Kitematic VM'
},
{
@@ -117,14 +117,14 @@ Installer.steps = [
// Set up the default Kitematic images
{
run: function (callback) {
- Meteor.call('reloadDefaultContainers', function (err) {
- callback(err);
+ reloadDefaultContainers(function (err) {
+ if (err) { console.error(err); }
});
},
pastMessage: 'Started the Boot2Docker VM',
message: 'Setting up the default Kitematic images...',
subMessage: '(This may take a few minutes)',
- futureMessage: 'Set up the default Kitematic images',
+ futureMessage: 'Set up the default Kitematic images'
}
];
diff --git a/meteor/client/lib/requires.js b/meteor/client/lib/requires.js
deleted file mode 100644
index 2b261be406..0000000000
--- a/meteor/client/lib/requires.js
+++ /dev/null
@@ -1,2 +0,0 @@
-path = require('path');
-fs = require('fs');
diff --git a/meteor/client/lib/startup.js b/meteor/client/lib/startup.js
index 512c2ffc32..51407e4460 100644
--- a/meteor/client/lib/startup.js
+++ b/meteor/client/lib/startup.js
@@ -1,3 +1,21 @@
Meteor.startup(function () {
console.log('Kitematic started.');
+ if (!fs.existsSync(KITE_PATH)) {
+ console.log('Created Kitematic directory.');
+ fs.mkdirSync(KITE_PATH, function (err) {
+ if (err) { throw err; }
+ });
+ }
+ if (!fs.existsSync(KITE_TAR_PATH)) {
+ console.log('Created Kitematic .tar directory.');
+ fs.mkdirSync(KITE_TAR_PATH, function (err) {
+ if (err) { throw err; }
+ });
+ }
+ if (!fs.existsSync(KITE_IMAGES_PATH)) {
+ console.log('Created Kitematic .images directory.');
+ fs.mkdirSync(KITE_IMAGES_PATH, function (err) {
+ if (err) { throw err; }
+ });
+ }
});
diff --git a/meteor/client/lib/sync.js b/meteor/client/lib/sync.js
index cd2468ef2d..242cabf89e 100644
--- a/meteor/client/lib/sync.js
+++ b/meteor/client/lib/sync.js
@@ -15,7 +15,7 @@ removeAppWatcher = function (id) {
addAppWatcher = function (app) {
removeAppWatcher(app._id);
- var appPath = path.join(path.join(Util.getHomePath(), 'Kitematic'), app.name);
+ var appPath = path.join(path.join(getHomePath(), 'Kitematic'), app.name);
var vmDir = path.join('/var/lib/docker/binds', app.name);
var vmPath = 'ssh://docker@localhost:2022/' + vmDir;
var watcher = chokidar.watch(appPath, {ignored: /.*\.DS_Store/});
@@ -30,7 +30,7 @@ addAppWatcher = function (app) {
syncing = true;
var errorPattern = /The\sfile\s(.*)\son\shost/g;
var archiveErrorPattern = /Archive\s(.*)\son\shost\s.*\sshould\sbe\sDELETED/g;
- var cmd = path.join(Util.getBinDir(), 'unison');
+ var cmd = path.join(getBinDir(), 'unison');
var args = [
cmd,
vmPath,
@@ -46,7 +46,7 @@ addAppWatcher = function (app) {
'Name\ {*.tmp,*.unison,*.swp,*.pyc,.DS_Store}',
'-auto',
'-sshargs',
- '-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(Util.getHomePath(), '.ssh/id_boot2docker')
+ '-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(getHomePath(), '.ssh/id_boot2docker')
];
if (!fs.existsSync(appPath)) {
@@ -70,7 +70,7 @@ addAppWatcher = function (app) {
if (err.indexOf('The archive file is missing on some hosts') !== -1) {
var results = archiveErrorPattern.exec(err);
var location = results[1].replace(' ', '\\ ');
- var fullLocation = path.join(Util.getHomePath(), 'Library/Application\\ Support/Unison', location);
+ var fullLocation = path.join(getHomePath(), 'Library/Application\\ Support/Unison', location);
var cmd = '/bin/rm -rf ' + fullLocation;
exec(cmd, function () {});
}
diff --git a/meteor/client/lib/utilities.js b/meteor/client/lib/utilities.js
index 3315ad4040..5bc891436b 100755
--- a/meteor/client/lib/utilities.js
+++ b/meteor/client/lib/utilities.js
@@ -28,3 +28,137 @@ trackLink = function (trackLabel) {
ga('send', 'event', 'link', 'click', trackLabel);
}
};
+
+Util = {};
+
+Util.deleteFolder = function (directory) {
+ if (fs.existsSync(directory)) {
+ fs.readdirSync(directory).forEach(function (file) {
+ var curDirectory = directory + '/' + file;
+ if (fs.lstatSync(curDirectory).isDirectory()) {
+ // Recurse
+ Util.deleteFolder(curDirectory);
+ } else {
+ // Delete File
+ try {
+ fs.unlinkSync(curDirectory);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ });
+ fs.rmdirSync(directory);
+ }
+};
+
+Util.copyFolder = function (src, dest) {
+ var exists = fs.existsSync(src);
+ var stats = exists && fs.statSync(src);
+ var isDirectory = exists && stats.isDirectory();
+ if (exists && isDirectory) {
+ try {
+ fs.mkdirSync(dest);
+ } catch (e) {
+ console.error(e);
+ }
+ fs.readdirSync(src).forEach(function (childItemName) {
+ Util.copyFolder(path.join(src, childItemName), path.join(dest, childItemName));
+ });
+ } else {
+ try {
+ fs.linkSync(src, dest);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+};
+
+Util.copyVolumes = function (directory, appName) {
+ var KITE_VOLUMES_PATH = path.join(directory, 'volumes');
+ if (fs.existsSync(KITE_VOLUMES_PATH)) {
+ var destinationPath = path.join(KITE_PATH, appName);
+ Util.copyFolder(KITE_VOLUMES_PATH, destinationPath);
+ console.log('Copied volumes for: ' + appName);
+ }
+};
+
+Util.hasDockerfile = function (directory) {
+ return fs.existsSync(path.join(directory, 'Dockerfile'));
+};
+
+/**
+ * Compares two software version numbers (e.g. "1.7.1" or "1.2b").
+ *
+ * @param {string} v1 The first version to be compared.
+ * @param {string} v2 The second version to be compared.
+ * @param {object} [options] Optional flags that affect comparison behavior:
+ *
+ * -
+ * lexicographical: true compares each part of the version strings lexicographically instead of
+ * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
+ * "1.2".
+ *
+ * -
+ * zeroExtend: true changes the result if one version string has less parts than the other. In
+ * this case the shorter string will be padded with "zero" parts instead of being considered smaller.
+ *
+ *
+ * @returns {number|NaN}
+ *
+ * - 0 if the versions are equal
+ * - a negative integer iff v1 < v2
+ * - a positive integer iff v1 > v2
+ * - NaN if either version string is in the wrong format
+ *
+ *
+ */
+Util.compareVersions = function (v1, v2, options) {
+ var lexicographical = options && options.lexicographical,
+ zeroExtend = options && options.zeroExtend,
+ v1parts = v1.split('.'),
+ v2parts = v2.split('.');
+
+ function isValidPart(x) {
+ return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
+ }
+
+ if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
+ return NaN;
+ }
+
+ if (zeroExtend) {
+ while (v1parts.length < v2parts.length) {
+ v1parts.push('0');
+ }
+ while (v2parts.length < v1parts.length) {
+ v2parts.push('0');
+ }
+ }
+
+ if (!lexicographical) {
+ v1parts = v1parts.map(Number);
+ v2parts = v2parts.map(Number);
+ }
+
+ for (var i = 0; i < v1parts.length; ++i) {
+ if (v2parts.length === i) {
+ return 1;
+ }
+
+ if (v1parts[i] === v2parts[i]) {
+ continue;
+ }
+ else if (v1parts[i] > v2parts[i]) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+ }
+
+ if (v1parts.length !== v2parts.length) {
+ return -1;
+ }
+
+ return 0;
+};
diff --git a/meteor/server/lib/validations.js b/meteor/client/lib/validations.js
old mode 100755
new mode 100644
similarity index 100%
rename from meteor/server/lib/validations.js
rename to meteor/client/lib/validations.js
diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js
index 4368f5c72f..577c610797 100644
--- a/meteor/client/lib/virtualbox.js
+++ b/meteor/client/lib/virtualbox.js
@@ -24,7 +24,7 @@ VirtualBox.exec = function (command, callback) {
VirtualBox.install = function (callback) {
// -W waits for the process to close before finishing.
- exec('open -W ' + path.join(Util.getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) {
+ exec('open -W ' + path.join(getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) {
if (error) {
callback(error);
return;
@@ -73,7 +73,7 @@ VirtualBox.hostOnlyIfs = function (callback) {
currentIf = value;
hostOnlyIfs[value] = {};
}
- hostOnlyIfs[currentIf][key] = value;
+ hostOnlyIfs[currentIf][key] = value;
});
callback(null, hostOnlyIfs);
});
@@ -134,8 +134,8 @@ VirtualBox.addCustomHostAdapter = function (vm, callback) {
VirtualBox.setupRouting = function (vm, callback) {
// Get the host only adapter or create it if it doesn't exist
this.addCustomHostAdapter(vm, function (err, ifname) {
- var installFile = path.join(Util.getBinDir(), 'install');
- var cocoaSudo = path.join(Util.getBinDir(), 'cocoasudo');
+ var installFile = path.join(getBinDir(), 'install');
+ var cocoaSudo = path.join(getBinDir(), 'cocoasudo');
var execCommand = cocoaSudo + ' --prompt="Kitematic needs your password to allow routing *.kite requests to containers." ' + installFile;
exec(execCommand, {env: {IFNAME: ifname, GATEWAY: Boot2Docker.REQUIRED_IP}}, function (error, stdout, stderr) {
if (error) {
diff --git a/meteor/client/main.js b/meteor/client/main.js
index 429201d295..8b28f32921 100755
--- a/meteor/client/main.js
+++ b/meteor/client/main.js
@@ -63,11 +63,6 @@ Handlebars.registerHelper('timeSince', function (date) {
return moment(date).fromNow();
});
-Meteor.call('getDockerHost', function (err, host) {
- if (err) { throw err; }
- Session.set('dockerHost', host);
-});
-
fixBoot2DockerVM = function (callback) {
Boot2Docker.check(function (err) {
if (err) {
@@ -87,10 +82,10 @@ fixBoot2DockerVM = function (callback) {
};
fixDefaultImages = function (callback) {
- Meteor.call('checkDefaultImages', function (err) {
+ checkDefaultImages(function (err) {
if (err) {
Session.set('available', false);
- Meteor.call('resolveDefaultImages', function (err) {
+ resolveDefaultImages(function (err) {
if (err) {
callback();
} else {
@@ -106,10 +101,10 @@ fixDefaultImages = function (callback) {
};
fixDefaultContainers = function (callback) {
- Meteor.call('checkDefaultContainers', function (err) {
+ checkDefaultContainers(function (err) {
if (err) {
Session.set('available', false);
- Meteor.call('resolveDefaultContainers', function (err) {
+ resolveDefaultContainers(function (err) {
if (err) {
callback(err);
} else {
@@ -151,7 +146,7 @@ Meteor.setInterval(function () {
if (!Session.get('boot2dockerOff')) {
fixBoot2DockerVM(function (err) {
if (err) { console.log(err); return; }
- Meteor.call('recoverApps');
+ AppUtil.recover();
fixDefaultImages(function (err) {
if (err) { console.log(err); return; }
fixDefaultContainers(function (err) {
@@ -162,4 +157,3 @@ Meteor.setInterval(function () {
}
}
}, 5000);
-
diff --git a/meteor/client/views/dashboard/apps/dashboard-apps-settings.js b/meteor/client/views/dashboard/apps/dashboard-apps-settings.js
index 4b65f986d0..ca119c5d9a 100755
--- a/meteor/client/views/dashboard/apps/dashboard-apps-settings.js
+++ b/meteor/client/views/dashboard/apps/dashboard-apps-settings.js
@@ -19,9 +19,8 @@ Template.dashboard_apps_settings.events({
var envKey = $button.data('key');
var configVars = getConfigVars($form);
delete configVars[envKey];
- Meteor.call('configVar', appId, configVars, function () {
- $button.removeAttr('disabled');
- });
+ AppUtil.configVar(appId, configVars);
+ $button.removeAttr('disabled');
},
'submit .form-env-vars': function (e) {
var $form = $(e.currentTarget);
@@ -31,10 +30,9 @@ Template.dashboard_apps_settings.events({
var newVal = $form.find('input[name="env-var-value"]').val().trim();
if (newKey && newVal) {
configVars[newKey] = newVal;
- Meteor.call('configVar', appId, configVars, function () {
- $form.find('input[name="env-var-key"]').val('');
- $form.find('input[name="env-var-value"]').val('');
- });
+ AppUtil.configVar(appId, configVars);
+ $form.find('input[name="env-var-key"]').val('');
+ $form.find('input[name="env-var-value"]').val('');
}
e.preventDefault();
e.stopPropagation();
@@ -43,9 +41,7 @@ Template.dashboard_apps_settings.events({
'click .btn-delete-app': function () {
var result = confirm("Are you sure you want to delete this app?");
if (result === true) {
- Meteor.call('deleteApp', this._id, function (err) {
- if (err) { throw err; }
- });
+ AppUtil.remove(this._id);
Router.go('dashboard_apps');
}
}
diff --git a/meteor/client/views/dashboard/apps/dashboard-single-app.js b/meteor/client/views/dashboard/apps/dashboard-single-app.js
index 5d32c4818f..964c082d3c 100755
--- a/meteor/client/views/dashboard/apps/dashboard-single-app.js
+++ b/meteor/client/views/dashboard/apps/dashboard-single-app.js
@@ -21,8 +21,8 @@ Template.dashboard_single_app.events({
},
'click .btn-terminal': function () {
var app = this;
- var cmd = path.join(Util.getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"';
- var terminalCmd = path.join(Util.getBinDir(), 'terminal') + ' ' + cmd;
+ var cmd = path.join(getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"';
+ var terminalCmd = path.join(getBinDir(), 'terminal') + ' ' + cmd;
var exec = require('child_process').exec;
console.log(terminalCmd);
exec(terminalCmd, function (err, stdout) {
@@ -33,9 +33,7 @@ Template.dashboard_single_app.events({
});
},
'click .btn-restart': function () {
- Meteor.call('restartApp', this._id, function (err) {
- if (err) { throw err; }
- });
+ AppUtil.restart(this._id);
},
'click .btn-folder': function () {
var exec = require('child_process').exec;
@@ -44,8 +42,6 @@ Template.dashboard_single_app.events({
});
},
'click .btn-logs': function () {
- Meteor.call('getAppLogs', this._id, function (err) {
- if (err) { throw err; }
- });
+ AppUtil.logs(this._id);
}
});
diff --git a/meteor/client/views/dashboard/components/modal-create-app.js b/meteor/client/views/dashboard/components/modal-create-app.js
index 1458d0566d..19f5d75f69 100755
--- a/meteor/client/views/dashboard/components/modal-create-app.js
+++ b/meteor/client/views/dashboard/components/modal-create-app.js
@@ -8,24 +8,53 @@ Template.modal_create_app.events({
'submit #form-create-app': function (e) {
var $form = $(e.currentTarget);
var formData = $form.serializeObject();
- Meteor.call('formCreateApp', formData, function (errors, cleaned) {
- if (errors) {
- clearFormErrors($form);
- showFormErrors($form, errors.details);
- } else {
- clearFormErrors($form);
- Meteor.call('createApp', cleaned, function (err) {
+ var validationResult = formValidate(formData, FormSchema.formCreateApp);
+ if (validationResult.errors) {
+ clearFormErrors($form);
+ showFormErrors($form, validationResult.errors.details);
+ } else {
+ clearFormErrors($form);
+ var cleaned = validationResult.cleaned;
+ var appName = cleaned.name;
+ var appPath = path.join(KITE_PATH, appName);
+ if (!fs.existsSync(appPath)) {
+ console.log('Created Kite ' + appName + ' directory.');
+ fs.mkdirSync(appPath, function (err) {
if (err) { throw err; }
});
- $('#modal-create-app').bind('hidden.bs.modal', function () {
- $('#slug-create-app-name').html('');
- resetForm($form);
- $('#image-picker').find('.fa-check-square-o').hide();
- $('#image-picker').find('.fa-square-o').show();
- Router.go('dashboard_apps');
- }).modal('hide');
}
- });
+ var appObj = {
+ name: appName,
+ imageId: cleaned.imageId,
+ status: 'STARTING',
+ config: {},
+ path: appPath,
+ logs: [],
+ createdAt: new Date()
+ };
+ var appId = Apps.insert(appObj);
+ Apps.update(appId, {
+ $set: {
+ 'config.APP_ID': appId
+ }
+ });
+ var app = Apps.findOne(appId);
+ var image = Images.findOne(app.imageId);
+ Util.copyVolumes(image.path, app.name);
+ Docker.removeBindFolder(app.name, function (err) {
+ if (err) { console.error(err); }
+ AppUtil.run(app, function (err) {
+ if (err) { console.error(err); }
+ });
+ });
+ $('#modal-create-app').bind('hidden.bs.modal', function () {
+ $('#slug-create-app-name').html('');
+ resetForm($form);
+ $('#image-picker').find('.fa-check-square-o').hide();
+ $('#image-picker').find('.fa-square-o').show();
+ Router.go('dashboard_apps');
+ }).modal('hide');
+ }
e.preventDefault();
e.stopPropagation();
return false;
diff --git a/meteor/client/views/dashboard/components/modal-create-image.js b/meteor/client/views/dashboard/components/modal-create-image.js
index 25e16aac51..8b212222a1 100755
--- a/meteor/client/views/dashboard/components/modal-create-image.js
+++ b/meteor/client/views/dashboard/components/modal-create-image.js
@@ -20,26 +20,55 @@ Template.modal_create_image.events({
$('#picked-directory-error').html('');
if (pickedDirectory) {
$('#picked-directory').html('' + pickedDirectory + '');
- Meteor.call('validateDirectory', pickedDirectory, function (err) {
- if (err) {
- $('#picked-directory-error').html(err.reason);
- $('#btn-create-image').attr('disabled', 'disabled');
- } else {
- $('#btn-create-image').removeAttr('disabled');
- }
- });
+ if (!Util.hasDockerfile(pickedDirectory)) {
+ $('#picked-directory-error').html('Only directories with Dockerfiles are supported now.');
+ $('#btn-create-image').attr('disabled', 'disabled');
+ } else {
+ $('#btn-create-image').removeAttr('disabled');
+ }
} else {
$('#picked-directory').html('');
$('#btn-create-image').attr('disabled', 'disabled');
}
},
'click #btn-create-image': function () {
- var pickedDirectory = $('#directory-picker').val();
+ var directory = $('#directory-picker').val();
$('#directory-picker').val('');
$('#picked-directory-error').html('');
$('#picked-directory').html('');
$('#btn-create-image').attr('disabled', 'disabled');
$('#modal-create-image').modal('hide');
- Meteor.call('createImage', pickedDirectory);
+ var imageObj = {
+ status: 'BUILDING',
+ originPath: directory,
+ buildLogs: [],
+ createdAt: new Date()
+ };
+ var imageMetaData = ImageUtil.getMetaData(directory);
+ imageObj.meta = imageMetaData;
+ var imageId = Images.insert(imageObj);
+ var imagePath = path.join(KITE_IMAGES_PATH, imageId);
+ Images.update(imageId, {
+ $set: {
+ path: imagePath
+ }
+ });
+ if (imageObj.meta.logo) {
+ Images.update(imageId, {
+ $set: {
+ logoPath: path.join(imagePath, imageObj.meta.logo)
+ }
+ });
+ }
+ var image = Images.findOne(imageId);
+ ImageUtil.saveFolder(image.originPath, imageId, function (err) {
+ if (err) { console.error(err); }
+ ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) {
+ if (err) { throw err; }
+ ImageUtil.build(image, function (err) {
+ if (err) { console.error(err); }
+ });
+ });
+ });
}
});
diff --git a/meteor/client/views/dashboard/images/dashboard-images-settings.js b/meteor/client/views/dashboard/images/dashboard-images-settings.js
index cfb1f8aa2d..764be3cf3e 100755
--- a/meteor/client/views/dashboard/images/dashboard-images-settings.js
+++ b/meteor/client/views/dashboard/images/dashboard-images-settings.js
@@ -2,15 +2,27 @@ Template.dashboard_images_settings.events({
'click .btn-delete-image': function () {
var result = confirm("Are you sure you want to delete this image?");
if (result === true) {
- Meteor.call('deleteImage', this._id, function (err) {
- if (err) {
- $('#error-delete-image').html('' + err.reason + '');
- $('#error-delete-image').fadeIn();
- } else {
- removeAppWatcher(this._id);
- Router.go('dashboard_images');
+ var imageId = this._id;
+ var image = Images.findOne(imageId);
+ var app = Apps.findOne({imageId: imageId});
+ if (!app) {
+ Images.remove({_id: image._id});
+ if (image.docker) {
+ Docker.removeImage(image.docker.Id, function (err) {
+ if (err) { console.error(err); }
+ });
}
- });
+ try {
+ Util.deleteFolder(image.path);
+ } catch (e) {
+ console.error(e);
+ }
+ removeAppWatcher(imageId);
+ Router.go('dashboard_images');
+ } else {
+ $('#error-delete-image').html('This image is currently being used by ' + app.name + '.');
+ $('#error-delete-image').fadeIn();
+ }
}
},
'click #btn-pick-directory': function () {
@@ -22,15 +34,15 @@ Template.dashboard_images_settings.events({
var pickedDirectory = $picker.val();
$('#picked-directory-error').html('');
if (pickedDirectory) {
- Meteor.call('validateDirectory', pickedDirectory, function (err) {
- if (err) {
- $('#picked-directory-error').html(err.reason);
- } else {
- Meteor.call('changeDirectory', imageId, pickedDirectory, function (err) {
- if (err) { throw err; }
- });
- }
- });
+ if (!Util.hasDockerfile(pickedDirectory)) {
+ $('#picked-directory-error').html('Only directories with Dockerfiles are supported now.');
+ } else {
+ Images.update(imageId, {
+ $set: {
+ originPath: pickedDirectory
+ }
+ });
+ }
}
}
});
diff --git a/meteor/client/views/dashboard/images/dashboard-single-image.js b/meteor/client/views/dashboard/images/dashboard-single-image.js
index b6c44f1b27..aa0723790e 100755
--- a/meteor/client/views/dashboard/images/dashboard-single-image.js
+++ b/meteor/client/views/dashboard/images/dashboard-single-image.js
@@ -18,8 +18,8 @@ Template.dashboard_single_image.events({
},
'click .btn-rebuild': function () {
$('.btn-icon').tooltip('hide');
- Meteor.call('rebuildImage', this._id, function (err) {
- if (err) { throw err; }
+ ImageUtil.rebuildImage(this._id, function (err) {
+ if (err) { console.error(err); }
});
}
});
diff --git a/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js b/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js
index ae4b40de04..9a32743715 100644
--- a/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js
+++ b/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js
@@ -21,9 +21,7 @@ Template.dashboard_apps_layout.events({
$('.header .icons a').tooltip('hide');
},
'click .btn-logs': function () {
- Meteor.call('getAppLogs', this._id, function (err) {
- if (err) { throw err; }
- });
+ AppUtil.logs(this._id);
},
'click .btn-terminal': function () {
var buildCmd = function (dockerId, termApp) {
@@ -42,9 +40,7 @@ Template.dashboard_apps_layout.events({
});
},
'click .btn-restart': function () {
- Meteor.call('restartApp', this._id, function (err) {
- if (err) { throw err; }
- });
+ AppUtil.restart(this._id);
},
'click .btn-folder': function () {
var exec = require('child_process').exec;
diff --git a/meteor/client/views/dashboard/layouts/dashboard-images-layout.js b/meteor/client/views/dashboard/layouts/dashboard-images-layout.js
index 78e84305c3..f4051a5332 100755
--- a/meteor/client/views/dashboard/layouts/dashboard-images-layout.js
+++ b/meteor/client/views/dashboard/layouts/dashboard-images-layout.js
@@ -18,8 +18,8 @@ Template.dashboard_images_layout.events({
},
'click .btn-rebuild': function () {
$('.header .icons a').tooltip('hide');
- Meteor.call('rebuildImage', this._id, function (err) {
- if (err) { throw err; }
+ ImageUtil.rebuildImage(this._id, function (err) {
+ if (err) { console.error(err); }
});
}
});
diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js
index 85cf5c5266..df5836415e 100755
--- a/meteor/collections/apps.js
+++ b/meteor/collections/apps.js
@@ -1,57 +1,14 @@
Apps = new Meteor.Collection('apps');
-schemaApps = new SimpleSchema({
- imageId: {
- type: Meteor.ObjectID,
- label: "ID of the image used by the app",
- max: 200
+Apps.allow({
+ 'update': function () {
+ return true;
},
- docker: {
- type: Object,
- label: "Docker container data",
- blackbox: true,
- optional: true
+ 'insert': function () {
+ return true;
},
- status: {
- type: String,
- allowedValues: ['STARTING', 'READY', 'ERROR'],
- label: "App current status",
- max: 200
- },
- config: {
- type: Object,
- label: "App environment variables",
- blackbox: true
- },
- name: {
- type: String,
- label: "App name",
- max: 200
- },
- logs: {
- type: [String],
- label: "Logs",
- defaultValue: []
- },
- path: {
- type: String,
- label: "Path to the app directory",
- optional: true
- },
- createdAt: {
- type: Date,
- autoValue: function() {
- var now = new Date();
- if (this.isInsert) {
- return now;
- } else if (this.isUpsert) {
- return {$setOnInsert: now};
- } else {
- this.unset();
- }
- },
- denyUpdate: true,
- label: "Time of app created"
+ 'remove': function () {
+ return true;
}
});
@@ -114,43 +71,3 @@ Apps.helpers({
}
}
});
-
-Apps.attachSchema(schemaApps);
-
-Apps.after.insert(function (userId, app) {
- // Give app an unique environment variable
- var appId = this._id;
- Apps.update(appId, {
- $set: {
- 'config.APP_ID': appId
- }
- });
- var image = Images.findOne(app.imageId);
- Util.copyVolumes(image.path, app.name);
- app = Apps.findOne(appId);
- Docker.removeBindFolder(app.name, function (err) {
- if (err) {
- console.error(err);
- }
- Fiber(function () {
- Meteor.call('runApp', app, function (err) {
- if (err) { throw err; }
- });
- }).run();
- });
-});
-
-Apps.after.remove(function (userId, app) {
- if (app.docker) {
- try {
- Docker.removeContainerSync(app.docker.Id);
- } catch (e) {
- console.error(e);
- }
- }
- var appPath = path.join(KITE_PATH, app.name);
- Util.deleteFolder(appPath);
- Docker.removeBindFolder(app.name, function () {
- console.log('Deleted Kite ' + app.name + ' directory.');
- });
-});
diff --git a/meteor/collections/images.js b/meteor/collections/images.js
index 5a4de5b4f8..1c94dfe578 100755
--- a/meteor/collections/images.js
+++ b/meteor/collections/images.js
@@ -1,61 +1,5 @@
Images = new Meteor.Collection('images');
-schemaImages = new SimpleSchema({
- path: {
- type: String,
- label: "Path to the image directory",
- optional: true
- },
- originPath: {
- type: String,
- label: "Path to the folder where image is built from",
- optional: true
- },
- logoPath: {
- type: String,
- label: "Path to the image logo",
- optional: true
- },
- meta: {
- type: Object,
- label: "Meta data for the image",
- blackbox: true,
- optional: true
- },
- docker: {
- type: Object,
- label: "Docker image data",
- blackbox: true,
- optional: true
- },
- status: {
- type: String,
- allowedValues: ['BUILDING', 'READY', 'ERROR'],
- label: "Image build current status",
- max: 200
- },
- buildLogs: {
- type: [String],
- label: "Build logs",
- defaultValue: []
- },
- createdAt: {
- type: Date,
- autoValue: function() {
- var now = new Date();
- if (this.isInsert) {
- return now;
- } else if (this.isUpsert) {
- return {$setOnInsert: now};
- } else {
- this.unset();
- }
- },
- denyUpdate: true,
- label: "Time of image created"
- }
-});
-
Images.helpers({
downloadStatus: function () {
if (this.buildLogs.length > 0) {
@@ -89,45 +33,3 @@ Images.allow({
return true;
}
});
-
-Images.attachSchema(schemaImages);
-
-Images.after.insert(function (userId, image) {
- var imageId = this._id;
- var imagePath = path.join(KITE_IMAGES_PATH, imageId);
- Images.update(imageId, {
- $set: {
- path: imagePath
- }
- });
- if (image.meta.logo) {
- Images.update(imageId, {
- $set: {
- logoPath: path.join(imagePath, image.meta.logo)
- }
- });
- }
- image = Images.findOne(imageId);
- Images.saveFolderSync(image.originPath, imageId);
- Images.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) {
- if (err) { throw err; }
- Images.build(image, function (err) {
- if (err) { console.error(err); }
- });
- });
-});
-
-Images.after.remove(function (userId, image) {
- if (image.docker) {
- try {
- Docker.removeImageSync(image.docker.Id);
- } catch (e) {
- console.error(e);
- }
- }
- try {
- Util.deleteFolder(image.path);
- } catch (e) {
- console.error(e);
- }
-});
diff --git a/meteor/collections/installs.js b/meteor/collections/installs.js
index 314b538050..7b1311731e 100644
--- a/meteor/collections/installs.js
+++ b/meteor/collections/installs.js
@@ -1,28 +1,5 @@
Installs = new Meteor.Collection('installs');
-schemaInstalls = new SimpleSchema({
- createdAt: {
- type: Date,
- autoValue: function() {
- var now = new Date();
- if (this.isInsert) {
- return now;
- } else if (this.isUpsert) {
- return {$setOnInsert: now};
- } else {
- this.unset();
- }
- },
- denyUpdate: true,
- label: 'Time of install created'
- },
- version: {
- type: String,
- label: 'Installed version',
- optional: true
- }
-});
-
Installs.allow({
'update': function () {
return true;
@@ -34,5 +11,3 @@ Installs.allow({
return true;
}
});
-
-Installs.attachSchema(schemaInstalls);
\ No newline at end of file
diff --git a/meteor/lib/constants.js b/meteor/lib/constants.js
deleted file mode 100644
index b53ada417d..0000000000
--- a/meteor/lib/constants.js
+++ /dev/null
@@ -1,9 +0,0 @@
-COMMON_WEB_PORTS = [
- 80,
- 8000,
- 8080,
- 3000,
- 5000,
- 2368,
- 1337
-]
diff --git a/meteor/lib/utilities.js b/meteor/lib/utilities.js
deleted file mode 100644
index 363ab245e7..0000000000
--- a/meteor/lib/utilities.js
+++ /dev/null
@@ -1,146 +0,0 @@
-Util = {};
-
-Util.getHomePath = function () {
- return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
-};
-
-Util.getBinDir = function () {
- if (process.env.NODE_ENV === 'development') {
- return path.join(path.join(process.env.PWD, '..'), 'resources');
- } else {
- if (Meteor.isClient) {
- return path.join(process.cwd(), 'resources');
- } else {
- return path.join(process.cwd(), '../../../resources');
- }
- }
-};
-
-Util.deleteFolder = function (directory) {
- if (fs.existsSync(directory)) {
- fs.readdirSync(directory).forEach(function (file) {
- var curDirectory = directory + '/' + file;
- if (fs.lstatSync(curDirectory).isDirectory()) {
- // Recurse
- Util.deleteFolder(curDirectory);
- } else {
- // Delete File
- try {
- fs.unlinkSync(curDirectory);
- } catch (e) {
- console.error(e);
- }
- }
- });
- fs.rmdirSync(directory);
- }
-};
-
-Util.copyFolder = function (src, dest) {
- var exists = fs.existsSync(src);
- var stats = exists && fs.statSync(src);
- var isDirectory = exists && stats.isDirectory();
- if (exists && isDirectory) {
- try {
- fs.mkdirSync(dest);
- } catch (e) {
- console.error(e);
- }
- fs.readdirSync(src).forEach(function (childItemName) {
- Util.copyFolder(path.join(src, childItemName), path.join(dest, childItemName));
- });
- } else {
- try {
- fs.linkSync(src, dest);
- } catch (e) {
- console.error(e);
- }
- }
-};
-
-Util.copyVolumes = function (directory, appName) {
- var KITE_VOLUMES_PATH = path.join(directory, 'volumes');
- if (fs.existsSync(KITE_VOLUMES_PATH)) {
- var destinationPath = path.join(KITE_PATH, appName);
- Util.copyFolder(KITE_VOLUMES_PATH, destinationPath);
- console.log('Copied volumes for: ' + appName);
- }
-};
-
-Util.hasDockerfile = function (directory) {
- return fs.existsSync(path.join(directory, 'Dockerfile'));
-};
-
-/**
- * Compares two software version numbers (e.g. "1.7.1" or "1.2b").
- *
- * @param {string} v1 The first version to be compared.
- * @param {string} v2 The second version to be compared.
- * @param {object} [options] Optional flags that affect comparison behavior:
- *
- * -
- * lexicographical: true compares each part of the version strings lexicographically instead of
- * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
- * "1.2".
- *
- * -
- * zeroExtend: true changes the result if one version string has less parts than the other. In
- * this case the shorter string will be padded with "zero" parts instead of being considered smaller.
- *
- *
- * @returns {number|NaN}
- *
- * - 0 if the versions are equal
- * - a negative integer iff v1 < v2
- * - a positive integer iff v1 > v2
- * - NaN if either version string is in the wrong format
- *
- *
- */
-Util.compareVersions = function (v1, v2, options) {
- var lexicographical = options && options.lexicographical,
- zeroExtend = options && options.zeroExtend,
- v1parts = v1.split('.'),
- v2parts = v2.split('.');
-
- function isValidPart(x) {
- return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
- }
-
- if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
- return NaN;
- }
-
- if (zeroExtend) {
- while (v1parts.length < v2parts.length) v1parts.push('0');
- while (v2parts.length < v1parts.length) v2parts.push('0');
- }
-
- if (!lexicographical) {
- v1parts = v1parts.map(Number);
- v2parts = v2parts.map(Number);
- }
-
- for (var i = 0; i < v1parts.length; ++i) {
- if (v2parts.length == i) {
- return 1;
- }
-
- if (v1parts[i] == v2parts[i]) {
- continue;
- }
- else if (v1parts[i] > v2parts[i]) {
- return 1;
- }
- else {
- return -1;
- }
- }
-
- if (v1parts.length != v2parts.length) {
- return -1;
- }
-
- return 0;
-};
-
diff --git a/meteor/server/apps.js b/meteor/server/apps.js
deleted file mode 100755
index d12a1e20a6..0000000000
--- a/meteor/server/apps.js
+++ /dev/null
@@ -1,149 +0,0 @@
-Apps.restart = function (app, callback) {
- if (app.docker && app.docker.Id) {
- try {
- Docker.restartContainerSync(app.docker.Id);
- } catch (e) {
- console.error(e);
- }
- var containerData = Docker.getContainerDataSync(app.docker.Id);
- Fiber(function () {
- Apps.update(app._id, {$set: {
- status: 'READY',
- docker: containerData
- }});
- }).run();
- callback(null);
- // Use dig to refresh the DNS
- exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {});
- } else {
- callback(null);
- }
-};
-
-Apps.logs = function (app) {
- if (app.docker && app.docker.Id) {
- var container = docker.getContainer(app.docker.Id);
- container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) {
- if (err) { throw err; }
- Fiber(function () {
- Apps.update(app._id, {
- $set: {
- logs: []
- }
- });
- }).run();
- var logs = [];
- response.setEncoding('utf8');
- response.on('data', function (line) {
- logs.push(convert.toHtml(line.slice(8)));
- Fiber(function () {
- Apps.update(app._id, {
- $set: {
- logs: logs
- }
- });
- }).run();
- });
- response.on('end', function () {});
- });
- }
-};
-
-Apps.recover = function (callback) {
- var apps = Apps.find({}).fetch();
- _.each(apps, function (app) {
- // Update the app with the latest container info
- if (!app.docker) {
- return;
- }
- var container = docker.getContainer(app.docker.Id);
- container.inspect(function (err, data) {
- if (app.status !== 'STARTING' && data && data.State && !data.State.Running) {
- console.log('restarting: ' + app.name);
- console.log(app.docker.Id);
- Fiber(function () {
- Apps.restart(app, function (err) {
- if (err) { console.error(err); }
- });
- }).run();
- }
- });
- });
- callback();
-};
-
-Meteor.methods({
- recoverApps: function () {
- this.unblock();
- return Meteor._wrapAsync(Apps.recover)();
- },
- configVar: function (appId, configVars) {
- this.unblock();
- Apps.update(appId, {$set: {
- config: configVars,
- status: 'STARTING'
- }});
- var app = Apps.findOne({_id: appId});
- Meteor.call('runApp', app, function (err) {
- if (err) { console.error(err); }
- });
- },
- deleteApp: function (appId) {
- this.unblock();
- var app = Apps.findOne(appId);
- if (!app) {
- throw new Meteor.Error(403, 'No app found with this ID');
- }
- Apps.remove({_id: app._id});
- },
- createApp: function (formData) {
- this.unblock();
- var validationResult = formValidate(formData, FormSchema.formCreateApp);
- if (validationResult.errors) {
- throw new Meteor.Error(400, 'Validation Failed.', validationResult.errors);
- } else {
- var cleaned = validationResult.cleaned;
- var appName = cleaned.name;
- var appPath = path.join(KITE_PATH, appName);
- if (!fs.existsSync(appPath)) {
- console.log('Created Kite ' + appName + ' directory.');
- fs.mkdirSync(appPath, function (err) {
- if (err) { throw err; }
- });
- }
- var appObj = {
- name: appName,
- imageId: cleaned.imageId,
- status: 'STARTING',
- config: {},
- path: appPath
- };
- Apps.insert(appObj);
- }
- },
- getAppLogs: function (appId) {
- this.unblock();
- var app = Apps.findOne(appId);
- if (app) {
- Apps.logs(app, function (err) {
- if (err) { throw err; }
- });
- }
- },
- restartApp: function (appId) {
- this.unblock();
- var app = Apps.findOne(appId);
- if (app && app.docker) {
- Apps.update(app._id, {$set: {
- status: 'STARTING'
- }});
- Apps.restart(app, function (err) {
- if (err) { console.error(err); }
- });
- }
- },
- resolveWatchers: function () {
- this.unblock();
- return Meteor._wrapAsync(resolveWatchers)();
- }
-});
diff --git a/meteor/server/constants.js b/meteor/server/constants.js
deleted file mode 100644
index cb35d90451..0000000000
--- a/meteor/server/constants.js
+++ /dev/null
@@ -1,24 +0,0 @@
-KITE_PATH = path.join(Util.getHomePath(), 'Kitematic');
-KITE_TAR_PATH = path.join(KITE_PATH, '.tar');
-KITE_IMAGES_PATH = path.join(KITE_PATH, '.images');
-
-if (!fs.existsSync(KITE_PATH)) {
- console.log('Created Kitematic directory.');
- fs.mkdirSync(KITE_PATH, function (err) {
- if (err) { throw err; }
- });
-}
-
-if (!fs.existsSync(KITE_TAR_PATH)) {
- console.log('Created Kitematic .tar directory.');
- fs.mkdirSync(KITE_TAR_PATH, function (err) {
- if (err) { throw err; }
- });
-}
-
-if (!fs.existsSync(KITE_IMAGES_PATH)) {
- console.log('Created Kitematic .images directory.');
- fs.mkdirSync(KITE_IMAGES_PATH, function (err) {
- if (err) { throw err; }
- });
-}
diff --git a/meteor/server/images.js b/meteor/server/images.js
deleted file mode 100755
index 6ad43fe7d0..0000000000
--- a/meteor/server/images.js
+++ /dev/null
@@ -1,306 +0,0 @@
-var createTarFile = function (image, callback) {
- var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar');
- exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) {
- if (err) { callback(err, null); return; }
- console.log('Created tar file: ' + TAR_PATH);
- callback(null, TAR_PATH);
- });
-};
-
-var createTarFileSync = function (image) {
- return Meteor._wrapAsync(createTarFile)(image);
-};
-
-var getFromImage = function (dockerfile) {
- var patternString = "(FROM)(.*)";
- var regex = new RegExp(patternString, "g");
- var fromInstruction = dockerfile.match(regex);
- if (fromInstruction && fromInstruction.length > 0) {
- return fromInstruction[0].replace('FROM', '').trim();
- } else {
- return null;
- }
-};
-
-var getImageJSON = function (directory) {
- var KITE_JSON_PATH = path.join(directory, 'image.json');
- if (fs.existsSync(KITE_JSON_PATH)) {
- var data = fs.readFileSync(KITE_JSON_PATH, 'utf8');
- return JSON.parse(data);
- } else {
- return null;
- }
-};
-
-var getImageMetaData = function (directory) {
- var kiteJSON = getImageJSON(directory);
- if (kiteJSON) {
- if (!kiteJSON.name) {
- kiteJSON.name = _.last(directory.split(path.sep));
- }
- } else {
- kiteJSON = {
- name: _.last(directory.split(path.sep))
- };
- }
- return kiteJSON;
-};
-
-Images.saveFolder = function (directory, imageId, callback) {
- var destinationPath = path.join(KITE_IMAGES_PATH, imageId);
- if (!fs.existsSync(destinationPath)) {
- fs.mkdirSync(destinationPath, function (err) {
- if (err) { callback(err); return; }
- });
- Util.copyFolder(directory, destinationPath);
- console.log('Copied image folder for: ' + imageId);
- callback(null);
- }
-};
-
-Images.saveFolderSync = function (directory, imageId) {
- return Meteor._wrapAsync(Images.saveFolder)(directory, imageId);
-};
-
-Images.rebuild = function (image, callback) {
- Util.deleteFolder(image.path);
- var imageMetaData = getImageMetaData(image.originPath);
- if (imageMetaData.logo) {
- Images.update(image._id, {
- $set: {
- logoPath: path.join(image.path, imageMetaData.logo)
- }
- });
- } else {
- Images.update(image._id, {
- $set: {
- logoPath: null
- }
- });
- }
- Images.update(image._id, {
- $set: {
- status: 'BUILDING',
- meta: imageMetaData
- }
- });
- image = Images.findOne(image._id);
- Images.saveFolderSync(image.originPath, image._id);
- Images.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) {
- if (err) { callback(err, null); return; }
- Images.build(image, function (err) {
- if (err) { console.error(err); }
- callback(null, null);
- });
- });
-};
-
-Images.rebuildSync = function (image) {
- return Meteor._wrapAsync(Images.rebuild)(image);
-};
-
-Images.pull = function (dockerfile, imageId, callback) {
- var fromImage = getFromImage(dockerfile);
- console.log('From image: ' + fromImage);
- var installedImage = null;
- try {
- installedImage = Docker.getImageDataSync(fromImage);
- } catch (e) {
- console.error(e);
- }
- if (fromImage && !installedImage) {
- Fiber(function () {
- Images.update(imageId, {
- $set: {
- buildLogs: []
- }
- });
- }).run();
- var logs = [];
- docker.pull(fromImage, function (err, response) {
- if (err) { callback(err); return; }
- response.setEncoding('utf8');
- response.on('data', function (data) {
- try {
- var logData = JSON.parse(data);
- var logDisplay = '';
- if (logData.id) {
- logDisplay += logData.id + ' | ';
- }
- logDisplay += logData.status;
- if (logData.progressDetail && logData.progressDetail.current && logData.progressDetail.total) {
- logDisplay += ' - ' + Math.round(logData.progressDetail.current / logData.progressDetail.total * 100) + '%';
- }
- logs.push(logDisplay);
- Fiber(function () {
- Images.update(imageId, {
- $set: {
- buildLogs: logs
- }
- });
- }).run();
- } catch (e) {
- console.error(e);
- }
- });
- response.on('end', function () {
- console.log('Finished pulling image: ' + fromImage);
- callback(null);
- });
- });
- } else {
- callback(null);
- }
-};
-
-Images.build = function (image, callback) {
- Fiber(function () {
- var tarFilePath = createTarFileSync(image);
- Images.update(image._id, {
- $set: {
- buildLogs: []
- }
- });
- docker.buildImage(tarFilePath, {t: image._id.toLowerCase()}, function (err, response) {
- if (err) { callback(err); }
- console.log('Building Docker image...');
- var logs = [];
- response.setEncoding('utf8');
- response.on('data', function (data) {
- try {
- var line = JSON.parse(data).stream;
- logs.push(convert.toHtml(line));
- Fiber(function () {
- Images.update(image._id, {
- $set: {
- buildLogs: logs
- }
- });
- }).run();
- } catch (e) {
- console.error(e);
- }
- });
- response.on('end', function () {
- console.log('Finished building Docker image.');
- try {
- fs.unlinkSync(tarFilePath);
- console.log('Cleaned up tar file.');
- } catch (e) {
- console.error(e);
- }
- Fiber(function () {
- var imageData = null;
- try {
- imageData = Docker.getImageDataSync(image._id);
- Images.update(image._id, {
- $set: {
- docker: imageData,
- status: 'READY'
- }
- });
- } catch (e) {
- console.log(e);
- Images.update(image._id, {
- $set: {
- status: 'ERROR'
- }
- });
- }
- var oldImageId = null;
- if (image.docker && image.docker.Id) {
- oldImageId = image.docker.Id;
- }
- if (oldImageId && imageData && oldImageId !== imageData.Id) {
- try {
- Docker.removeImageSync(oldImageId);
- } catch (e) {
- console.error(e);
- }
- }
- }).run();
- callback(null);
- });
- });
- }).run();
-};
-
-Meteor.methods({
- createImage: function (directory) {
- this.unblock();
- var imageObj = {
- status: 'BUILDING',
- originPath: directory
- };
- var imageMetaData = getImageMetaData(directory);
- imageObj.meta = imageMetaData;
- Images.insert(imageObj);
- },
- rebuildImage: function (imageId) {
- this.unblock();
- var image = Images.findOne(imageId);
- if (!image) {
- throw new Meteor.Error(403, "No image found with this ID.");
- }
- var apps = Apps.find({imageId: imageId}).fetch();
- if (apps.length > 0) {
- _.each(apps, function (app) {
- console.log('Updating app: ' + app.name);
- if (app.docker) {
- try {
- Docker.removeContainerSync(app.docker.Id);
- } catch (e) {
- console.error(e);
- }
- }
- Apps.update(app._id, {
- $set: {
- 'docker.Id': null,
- status: 'STARTING',
- logs: []
- }
- });
- });
- Images.rebuildSync(image);
- _.each(apps, function (app) {
- app = Apps.findOne(app._id);
- Meteor.call('runApp', app, function (err) {
- if (err) { console.error(err); }
- });
- });
- } else {
- Images.rebuildSync(image);
- }
- },
- changeDirectory: function (imageId, directory) {
- this.unblock();
- var image = Images.findOne(imageId);
- if (!image) {
- throw new Meteor.Error(403, "No image found with this ID.");
- }
- Images.update(imageId, {
- $set: {
- originPath: directory
- }
- });
- },
- validateDirectory: function (directory) {
- this.unblock();
- if (!Util.hasDockerfile(directory)) {
- throw new Meteor.Error(400, "Only directories with Dockerfiles are supported now.");
- }
- },
- deleteImage: function (imageId) {
- this.unblock();
- var image = Images.findOne(imageId);
- if (!image) {
- throw new Meteor.Error(403, "No image found with this ID.");
- }
- var app = Apps.findOne({imageId: imageId});
- if (!app) {
- Images.remove({_id: image._id});
- } else {
- throw new Meteor.Error(400, 'This image is currently being used by ' + app.name + ".");
- }
- }
-});
diff --git a/meteor/server/lib/requires.js b/meteor/server/lib/requires.js
deleted file mode 100755
index 6174cd0202..0000000000
--- a/meteor/server/lib/requires.js
+++ /dev/null
@@ -1,11 +0,0 @@
-https = Meteor.require('https');
-tar = Meteor.require('tar');
-zlib = Meteor.require('zlib');
-fs = Meteor.require('fs');
-path = Meteor.require('path');
-exec = Meteor.require('child_process').exec;
-async = Meteor.require('async');
-Fiber = Meteor.require('fibers');
-child_process = Meteor.require('child_process');
-Convert = Meteor.require('ansi-to-html');
-convert = new Convert();
diff --git a/meteor/smart.json b/meteor/smart.json
index 6959af2f2d..6680b5eb87 100755
--- a/meteor/smart.json
+++ b/meteor/smart.json
@@ -5,7 +5,6 @@
"npm": {},
"headers": {},
"handlebar-helpers": {},
- "collection2": {},
"collection-hooks": {},
"moment": {},
"underscore-string-latest": {},
diff --git a/meteor/smart.lock b/meteor/smart.lock
index 20d2835feb..bceed7a0ad 100755
--- a/meteor/smart.lock
+++ b/meteor/smart.lock
@@ -7,7 +7,6 @@
"npm": {},
"headers": {},
"handlebar-helpers": {},
- "collection2": {},
"collection-hooks": {},
"moment": {},
"underscore-string-latest": {},
@@ -42,11 +41,6 @@
"tag": "v0.1.1",
"commit": "0b407ab65e7c1ebd53d71aef0de2e2c1d21a597c"
},
- "collection2": {
- "git": "https://github.com/aldeed/meteor-collection2.git",
- "tag": "v0.4.6",
- "commit": "80554182486be0d8e74f7ed02194a5649d712e27"
- },
"collection-hooks": {
"git": "https://github.com/matb33/meteor-collection-hooks.git",
"tag": "v0.7.2",
@@ -92,11 +86,6 @@
"tag": "v0.0.8",
"commit": "90f2fbcc5b4bc17fa4d4535f47813e31d86033b4"
},
- "simple-schema": {
- "git": "https://github.com/aldeed/meteor-simple-schema.git",
- "tag": "v0.7.0",
- "commit": "77d267aec4ba8a70f677e5d9ef9fb91fb0e3f0f6"
- },
"blaze-layout": {
"git": "https://github.com/EventedMind/blaze-layout.git",
"tag": "v0.2.5",
diff --git a/package.json b/package.json
index 62c09055d8..cca8152c33 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
"version": "0.1.0",
"window": {
"show": false,
- "toolbar": false,
- "frame": false,
+ "toolbar": true,
+ "frame": true,
"width": 800,
"height": 600,
"resizable": false
@@ -19,6 +19,9 @@
"chokidar": "git+https://github.com/usekite/chokidar.git",
"exec": "^0.1.2",
"moment": "2.8.1",
- "open": "0.0.5"
+ "open": "0.0.5",
+ "dockerode": "2.0.3",
+ "tar": "0.1.20",
+ "ansi-to-html": "0.2.0"
}
}