All apps run in a Linux VM included with Kitematic. It needs to be turned to run apps.
+ All apps run in a Linux VM included with Kitematic. It needs to be turned on to run apps.
{{/if}}
diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js
index 020e86413b..cc222f82e2 100644
--- a/meteor/client/views/dashboard/setup/setup-install.js
+++ b/meteor/client/views/dashboard/setup/setup-install.js
@@ -8,7 +8,6 @@ Template.setup_install.rendered = function() {
console.log(err);
} else {
Installs.insert({});
- startFixInterval();
Router.go('dashboard_apps');
}
});
diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js
index 3b0ff8b147..ba40c9b2e9 100755
--- a/meteor/collections/apps.js
+++ b/meteor/collections/apps.js
@@ -116,3 +116,41 @@ 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 15055ad021..5a4de5b4f8 100755
--- a/meteor/collections/images.js
+++ b/meteor/collections/images.js
@@ -91,3 +91,43 @@ Images.allow({
});
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/lib/utilities.js b/meteor/lib/utilities.js
new file mode 100644
index 0000000000..363ab245e7
--- /dev/null
+++ b/meteor/lib/utilities.js
@@ -0,0 +1,146 @@
+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
index af9383afa3..a8e7c8dce7 100755
--- a/meteor/server/apps.js
+++ b/meteor/server/apps.js
@@ -1,10 +1,55 @@
-removeBindFolder = function (name, callback) {
- exec(path.join(getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function(err, stdout) {
- callback(err, stdout);
- });
+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 + '.dev @172.17.42.1 ', function() {});
+ } else {
+ callback(null);
+ }
};
-recoverApps = function (callback) {
+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
@@ -17,7 +62,7 @@ recoverApps = function (callback) {
console.log('restarting: ' + app.name);
console.log(app.docker.Id);
Fiber(function () {
- restartApp(app, function (err) {
+ Apps.restart(app, function (err) {
if (err) { console.error(err); }
});
}).run();
@@ -29,7 +74,8 @@ recoverApps = function (callback) {
Meteor.methods({
recoverApps: function () {
- return Meteor._wrapAsync(recoverApps)();
+ this.unblock();
+ return Meteor._wrapAsync(Apps.recover)();
},
configVar: function (appId, configVars) {
this.unblock();
@@ -48,64 +94,38 @@ Meteor.methods({
if (!app) {
throw new Meteor.Error(403, 'No app found with this ID');
}
- deleteApp(app, function (err) {
- if (err) { console.error(err); }
- var appPath = path.join(KITE_PATH, app.name);
- deleteFolder(appPath);
- removeBindFolder(app.name, function () {
- console.log('Deleted Kite ' + app.name + ' directory.');
- Fiber(function () {
- Apps.remove({_id: app._id});
- }).run();
- });
- });
+ 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 appObj = {
- name: cleaned.name,
- imageId: cleaned.imageId,
- status: 'STARTING',
- config: {}
- };
- var appId = Apps.insert(appObj);
- var appPath = path.join(KITE_PATH, appObj.name);
+ var appName = cleaned.name;
+ var appPath = path.join(KITE_PATH, appName);
if (!fs.existsSync(appPath)) {
- console.log('Created Kite ' + appObj.name + ' directory.');
+ console.log('Created Kite ' + appName + ' directory.');
fs.mkdirSync(appPath, function (err) {
if (err) { throw err; }
});
}
- Apps.update(appId, {
- $set: {
- 'config.APP_ID': appId,
- path: appPath
- }
- });
- var image = Images.findOne(appObj.imageId);
- loadKiteVolumes(image.path, appObj.name);
- var app = Apps.findOne(appId);
- removeBindFolder(app.name, function (err) {
- if (err) {
- console.error(err);
- }
- Fiber(function () {
- Meteor.call('runApp', app, function (err) {
- if (err) { throw err; }
- });
- }).run();
- });
+ 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) {
- getAppLogs(app, function (err) {
+ Apps.logs(app, function (err) {
if (err) { throw err; }
});
}
@@ -117,12 +137,13 @@ Meteor.methods({
Apps.update(app._id, {$set: {
status: 'STARTING'
}});
- restartApp(app, function (err) {
+ 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
new file mode 100644
index 0000000000..cb35d90451
--- /dev/null
+++ b/meteor/server/constants.js
@@ -0,0 +1,24 @@
+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/docker.js b/meteor/server/docker.js
index 6aded68a47..37a27d6d88 100755
--- a/meteor/server/docker.js
+++ b/meteor/server/docker.js
@@ -1,16 +1,11 @@
-Docker = Meteor.require('dockerode');
-
-var Convert = Meteor.require('ansi-to-html');
-var convert = new Convert();
+Dockerode = Meteor.require('dockerode');
var DOCKER_HOST='192.168.60.103';
-docker = new Docker({host: DOCKER_HOST, port: '2375'});
+docker = new Dockerode({host: DOCKER_HOST, port: '2375'});
-hasDockerfile = function (directory) {
- return fs.existsSync(path.join(directory, 'Dockerfile'));
-};
+Docker = {};
-removeContainer = function (containerId, callback) {
+Docker.removeContainer = function (containerId, callback) {
var container = docker.getContainer(containerId);
container.kill(function (err) {
if (err) { callback(err); return; }
@@ -22,28 +17,11 @@ removeContainer = function (containerId, callback) {
});
};
-removeContainerSync = function (containerId) {
- return Meteor._wrapAsync(removeContainer)(containerId);
+Docker.removeContainerSync = function (containerId) {
+ return Meteor._wrapAsync(Docker.removeContainer)(containerId);
};
-deleteApp = function (app, callback) {
- if (!app.docker) {
- callback(null);
- return;
- }
- try {
- removeContainerSync(app.docker.Id);
- } catch (e) {
- console.error(e);
- }
- callback(null);
-};
-
-deleteAppSync = function (app) {
- return Meteor._wrapAsync(deleteApp)(app);
-};
-
-getContainerData = function (containerId, callback) {
+Docker.getContainerData = function (containerId, callback) {
var container = docker.getContainer(containerId);
container.inspect(function (err, data) {
if (err) {
@@ -59,11 +37,11 @@ getContainerData = function (containerId, callback) {
});
};
-getContainerDataSync = function (containerId) {
- return Meteor._wrapAsync(getContainerData)(containerId);
+Docker.getContainerDataSync = function (containerId) {
+ return Meteor._wrapAsync(Docker.getContainerData)(containerId);
};
-runContainer = function (app, image, callback) {
+Docker.runContainer = function (app, image, callback) {
var envParam = [];
_.each(_.keys(app.config), function (key) {
var builtStr = key + '=' + app.config[key];
@@ -100,11 +78,11 @@ runContainer = function (app, image, callback) {
});
};
-runContainerSync = function (app, image) {
- return Meteor._wrapAsync(runContainer)(app, image);
+Docker.runContainerSync = function (app, image) {
+ return Meteor._wrapAsync(Docker.runContainer)(app, image);
};
-restartContainer = function (containerId, callback) {
+Docker.restartContainer = function (containerId, callback) {
var container = docker.getContainer(containerId);
container.restart(function (err) {
if (err) {
@@ -117,83 +95,8 @@ restartContainer = function (containerId, callback) {
});
};
-restartContainerSync = function (containerId) {
- return Meteor._wrapAsync(restartContainer)(containerId);
-};
-
-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;
- }
-};
-
-restartApp = function (app, callback) {
- if (app.docker && app.docker.Id) {
- try {
- restartContainerSync(app.docker.Id);
- } catch (e) {
- console.error(e);
- }
- var containerData = 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 + '.dev @172.17.42.1 ', function() {});
- } else {
- callback(null);
- }
-};
-
-getAppLogs = 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 () {});
- });
- }
-};
-
-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);
- });
-};
-
-createTarFileSync = function (image) {
- return Meteor._wrapAsync(createTarFile)(image);
+Docker.restartContainerSync = function (containerId) {
+ return Meteor._wrapAsync(Docker.restartContainer)(containerId);
};
var convertVolumeObjToArray = function (obj) {
@@ -209,7 +112,7 @@ var convertVolumeObjToArray = function (obj) {
return result;
};
-getImageData = function (imageId, callback) {
+Docker.getImageData = function (imageId, callback) {
var image = docker.getImage(imageId.toLowerCase());
image.inspect(function (err, data) {
if (err) {
@@ -224,11 +127,11 @@ getImageData = function (imageId, callback) {
});
};
-getImageDataSync = function (imageId) {
- return Meteor._wrapAsync(getImageData)(imageId);
+Docker.getImageDataSync = function (imageId) {
+ return Meteor._wrapAsync(Docker.getImageData)(imageId);
};
-removeImage = function (imageId, callback) {
+Docker.removeImage = function (imageId, callback) {
var image = docker.getImage(imageId.toLowerCase());
image.remove({force: true}, function (err) {
if (err) { callback(err); return; }
@@ -237,25 +140,14 @@ removeImage = function (imageId, callback) {
});
};
-removeImageSync = function (imageId) {
- return Meteor._wrapAsync(removeImage)(imageId);
+Docker.removeImageSync = function (imageId) {
+ return Meteor._wrapAsync(Docker.removeImage)(imageId);
};
-deleteImage = function (image, callback) {
- if (!image.docker) {
- callback(null, {});
- return;
- }
- try {
- removeImageSync(image.docker.Id);
- } catch (e) {
- console.error(e);
- }
- callback(null);
-};
-
-deleteImageSync = function (image) {
- return Meteor._wrapAsync(deleteImage)(image);
+Docker.removeBindFolder = function (name, callback) {
+ exec(path.join(Util.getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function (err, stdout) {
+ callback(err, stdout);
+ });
};
var defaultContainerOptions = function () {
@@ -304,7 +196,7 @@ resolveDefaultImages = function () {
image.inspect(function (err) {
if (err) {
if (err.reason === 'no such image') {
- docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) {
+ docker.loadImage(path.join(Util.getBinDir(), 'base-images.tar.gz'), {}, function (err) {
if (err) {
innerCallback(err);
return;
@@ -374,7 +266,7 @@ reloadDefaultContainers = function (callback) {
async.until(function () {
return ready;
}, function (callback) {
- docker.listContainers(function (err, containers) {
+ docker.listContainers(function (err) {
if (!err) {
ready = true;
}
@@ -391,7 +283,7 @@ reloadDefaultContainers = function (callback) {
return;
}
console.log('Loading new Kitematic default images.');
- docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) {
+ docker.loadImage(path.join(Util.getBinDir(), 'base-images.tar.gz'), {}, function (err) {
if (err) {
callback(err);
return;
@@ -500,142 +392,18 @@ killAndRemoveContainers = function (names, callback) {
});
};
-pullImageFromDockerfile = function (dockerfile, imageId, callback) {
- var fromImage = getFromImage(dockerfile);
- console.log('From image: ' + fromImage);
- var installedImage = null;
- try {
- installedImage = 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);
- }
-};
-
-buildImage = 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 = 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 {
- removeImageSync(oldImageId);
- } catch (e) {
- console.error(e);
- }
- }
- }).run();
- callback(null);
- });
- });
- }).run();
-};
-
Meteor.methods({
runApp: function (app) {
this.unblock();
var image = Images.findOne({_id: app.imageId});
+ // Delete old container if one already exists
try {
- removeContainerSync(app.name);
+ Docker.removeContainerSync(app.name);
} catch (e) {}
try {
- var container = runContainerSync(app, image);
- var containerData = getContainerDataSync(container.id);
+ 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,
@@ -650,18 +418,23 @@ Meteor.methods({
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/server/images.js b/meteor/server/images.js
index 5429dd53b1..6ad43fe7d0 100755
--- a/meteor/server/images.js
+++ b/meteor/server/images.js
@@ -1,4 +1,38 @@
-getImageMetaData = function (directory) {
+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) {
@@ -12,8 +46,24 @@ getImageMetaData = function (directory) {
return kiteJSON;
};
-rebuildImage = function (image, callback) {
- deleteFolder(image.path);
+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, {
@@ -35,18 +85,144 @@ rebuildImage = function (image, callback) {
}
});
image = Images.findOne(image._id);
- saveImageFolderSync(image.originPath, image._id);
- pullImageFromDockerfile(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) {
+ 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; }
- buildImage(image, function (err) {
+ Images.build(image, function (err) {
if (err) { console.error(err); }
callback(null, null);
});
});
};
-rebuildImageSync = function (image) {
- return Meteor._wrapAsync(rebuildImage)(image);
+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({
@@ -58,29 +234,7 @@ Meteor.methods({
};
var imageMetaData = getImageMetaData(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);
- saveImageFolderSync(directory, imageId);
- console.log('Saved folder sync');
- pullImageFromDockerfile(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) {
- if (err) { throw err; }
- buildImage(image, function (err) {
- if (err) { console.error(err); }
- });
- });
+ Images.insert(imageObj);
},
rebuildImage: function (imageId) {
this.unblock();
@@ -92,7 +246,13 @@ Meteor.methods({
if (apps.length > 0) {
_.each(apps, function (app) {
console.log('Updating app: ' + app.name);
- deleteAppSync(app);
+ if (app.docker) {
+ try {
+ Docker.removeContainerSync(app.docker.Id);
+ } catch (e) {
+ console.error(e);
+ }
+ }
Apps.update(app._id, {
$set: {
'docker.Id': null,
@@ -101,7 +261,7 @@ Meteor.methods({
}
});
});
- rebuildImageSync(image);
+ Images.rebuildSync(image);
_.each(apps, function (app) {
app = Apps.findOne(app._id);
Meteor.call('runApp', app, function (err) {
@@ -109,7 +269,7 @@ Meteor.methods({
});
});
} else {
- rebuildImageSync(image);
+ Images.rebuildSync(image);
}
},
changeDirectory: function (imageId, directory) {
@@ -125,7 +285,8 @@ Meteor.methods({
});
},
validateDirectory: function (directory) {
- if (!hasDockerfile(directory)) {
+ this.unblock();
+ if (!Util.hasDockerfile(directory)) {
throw new Meteor.Error(400, "Only directories with Dockerfiles are supported now.");
}
},
@@ -137,15 +298,7 @@ Meteor.methods({
}
var app = Apps.findOne({imageId: imageId});
if (!app) {
- console.log('here');
- try {
- deleteImageSync(image);
- deleteFolder(image.path);
- } catch (e) {
- console.log(e);
- } finally {
- Images.remove({_id: image._id});
- }
+ 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/kite.js b/meteor/server/kite.js
deleted file mode 100755
index 712635616a..0000000000
--- a/meteor/server/kite.js
+++ /dev/null
@@ -1,59 +0,0 @@
-KITE_PATH = path.join(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; }
- });
-}
-
-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;
- }
-};
-
-loadKiteVolumes = function (directory, appName) {
- var KITE_VOLUMES_PATH = path.join(directory, 'volumes');
- if (fs.existsSync(KITE_VOLUMES_PATH)) {
- var destinationPath = path.join(KITE_PATH, appName);
- copyFolder(KITE_VOLUMES_PATH, destinationPath);
- console.log('Copied volumes for: ' + appName);
- }
-};
-
-saveImageFolder = 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; }
- });
- copyFolder(directory, destinationPath);
- console.log('Copied image folder for: ' + imageId);
- callback(null);
- }
-};
-
-saveImageFolderSync = function (directory, imageId) {
- return Meteor._wrapAsync(saveImageFolder)(directory, imageId);
-};
diff --git a/meteor/server/lib/requires.js b/meteor/server/lib/requires.js
index cac98d255a..6174cd0202 100755
--- a/meteor/server/lib/requires.js
+++ b/meteor/server/lib/requires.js
@@ -6,4 +6,6 @@ path = Meteor.require('path');
exec = Meteor.require('child_process').exec;
async = Meteor.require('async');
Fiber = Meteor.require('fibers');
-child_process = Meteor.require('child_process');
\ No newline at end of file
+child_process = Meteor.require('child_process');
+Convert = Meteor.require('ansi-to-html');
+convert = new Convert();
diff --git a/meteor/server/lib/utilities.js b/meteor/server/lib/utilities.js
deleted file mode 100755
index 6b4d68df1a..0000000000
--- a/meteor/server/lib/utilities.js
+++ /dev/null
@@ -1,53 +0,0 @@
-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 {
- return path.join(process.cwd(), '../../../resources');
- }
-};
-
-deleteFolder = function (directory) {
- if (fs.existsSync(directory)) {
- fs.readdirSync(directory).forEach(function (file) {
- var curDirectory = directory + '/' + file;
- if (fs.lstatSync(curDirectory).isDirectory()) {
- // Recurse
- deleteFolder(curDirectory);
- } else {
- // Delete File
- try {
- fs.unlinkSync(curDirectory);
- } catch (e) {
- console.error(e);
- }
- }
- });
- fs.rmdirSync(directory);
- }
-};
-
-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) {
- copyFolder(path.join(src, childItemName), path.join(dest, childItemName));
- });
- } else {
- try {
- fs.linkSync(src, dest);
- } catch (e) {
- console.error(e);
- }
- }
-};
diff --git a/meteor/server/publications.js b/meteor/server/publications.js
index fe75a8a9ba..6e3540fc61 100755
--- a/meteor/server/publications.js
+++ b/meteor/server/publications.js
@@ -7,5 +7,5 @@ Meteor.publish('images', function () {
});
Meteor.publish('installs', function () {
- return Installs.find({}, {sort: {createdAt: -1}});
+ return Installs.find({});
});
diff --git a/resources/install b/resources/install
index b0791ac3bc..afde39e80b 100755
--- a/resources/install
+++ b/resources/install
@@ -28,9 +28,6 @@ echo "
172.17.0.0
-netmask
255.255.0.0
-
-iface
-
$IFNAME
-
255.255.0.0
-gateway
$GATEWAY
@@ -45,4 +42,4 @@ echo "
# Add entries to routing table for Kitematic VM
/sbin/route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY
-/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -iface $IFNAME -gateway $GATEWAY
\ No newline at end of file
+/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY
\ No newline at end of file