Merge branch 'backend-refactor' of github.com:kitematic/kitematic into installer_fix

Conflicts:
	meteor/client/lib/boot2docker.js
	meteor/client/lib/utilities.js
	meteor/client/lib/virtualbox.js
	meteor/client/main.js
	meteor/client/views/dashboard/setup/setup-install.js
	meteor/server/docker.js
This commit is contained in:
Jeff Morgan 2014-09-01 04:00:06 -07:00
commit b66bbc2c0d
24 changed files with 630 additions and 656 deletions

View File

@ -1,5 +1,10 @@
# [Kitematic](https://kitematic.com)
**Note:** If the installer gets stuck at any step for more than 1 minute, there is probably an error. Please help us troubleshoot by running it from the command line, and submit the logs to [contact@kitematic.com](mailto:contact@kitematic.com).
1. `cd <dir with Kitematic.app>`
2. Run `./Kitematic.app/Contents/MacOS/node-webkit`
Kitematic is still in Beta. Any effort in helping us find issues and improving the experience is greatly appreciated!
[Fixes for Known Issues](http://kitematic.com/docs/known-issue-fixes)
@ -73,20 +78,28 @@ For transparency into our release cycle and in striving to maintain backward com
## Creators
Team E-mail: [contact@kitematic.com](mailto:contact@kitematic.com)
**Sean Li**
- <https://twitter.com/lisean106>
- <https://github.com/Elesant>
- Email: [sean@kitematic.com](mailto:sean@kitematic.com)
- [LinkedIn](https://www.linkedin.com/in/lishang)
**Jeffrey Morgan**
- <https://twitter.com/jmorgan>
- <https://github.com/jeffdm>
- Email: [jeff@kitematic.com](mailto:jeff@kitematic.com)
- [LinkedIn](https://www.linkedin.com/in/jeffdmorgan)
**Michael Chiang**
- <https://twitter.com/mchiang0610>
- <https://github.com/mk101>
- Email: [mike@kitematic.com](mailto:mike@kitematic.com)
- [LinkedIn](https://www.linkedin.com/in/mchiang0610)
## Copyright and License

View File

@ -128,7 +128,7 @@
// Packages
"Fiber": true,
"moment": true,
"Docker": true,
"Dockerode": true,
"byline": true,
"fs": true,
"zlib": true,
@ -142,6 +142,9 @@
"chokidar": true,
"docker": true,
"async": true,
"child_process": true,
"convert": true,
"Convert": true,
// Collections
"SimpleSchema": false,
@ -161,8 +164,13 @@
"SetupController": true,
// Server and Client
"Docker": true,
"Util": true,
"Boot2Docker": true,
"Installer": true,
"VirtualBox": true,
"boot2dockerexec": true,
"getBinDir": true,
"getBoot2DockerIp": true,
"getBoot2DockerState": true,
"getBoot2DockerDiskUsage": true,
@ -170,7 +178,6 @@
"getBoot2DockerInfo": true,
"boot2DockerVMExists": true,
"eraseBoot2DockerVMFiles": true,
"getHomePath": true,
"initBoot2Docker": true,
"isVirtualBoxInstalled": true,
"upgradeBoot2Docker": true,
@ -191,46 +198,17 @@
"fixInterval": true,
"stopFixInterval": true,
"runSetup": true,
"removeBindFolder": true,
"removeAppWatcher": true,
"addAppWatcher": true,
"resolveWatchers": true,
"recoverApps": true,
"restartApp": true,
"deleteApp": true,
"deleteFolder": true,
"loadKiteVolumes": true,
"getAppLogs": true,
"hasDockerfile": true,
"runContainer": true,
"runContainerSync": true,
"restartContainer": true,
"restartContainerSync": true,
"createTarFile": true,
"createTarFileSync": true,
"getImageData": true,
"getImageDataSync": true,
"removeImage": true,
"removeImageSync": true,
"deleteImage": true,
"checkDefaultImages": true,
"resolveDefaultImages": true,
"checkDefaultContainers": true,
"resolveDefaultContainers": true,
"killAndRemoveContainers": true,
"deleteImageSync": true,
"upContainers": true,
"reloadDefaultContainers": true,
"removeImages": true,
"pullImageFromDockerfile": true,
"buildImage": true,
"getImageMetaData": true,
"getImageJSON": true,
"rebuildImage": true,
"saveImageFolderSync": true,
"rebuildImageSync": true,
"saveImageFolder": true,
"copyFolder": true,
// Forms
"showFormErrors": true,
@ -239,11 +217,6 @@
"FormSchema": true,
"showFormSuccess": true,
"resetForm": true,
"removeContainer": true,
"removeContainerSync": true,
"deleteAppSync": true,
"getContainerData": true,
"getContainerDataSync": true,
// Testing
"require": false,

View File

@ -6,7 +6,7 @@ Boot2Docker = {};
Boot2Docker.REQUIRED_IP = '192.168.60.103';
Boot2Docker.exec = function (command, callback) {
exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout, stderr) {
exec(path.join(Util.getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout, stderr) {
callback(err, stdout, stderr);
});
};
@ -77,22 +77,14 @@ Boot2Docker.start = function (callback) {
return;
}
self.exec('up -v', function (err, stdout) {
console.log('here0');
console.log('here1');
// Sometimes boot2docker returns an error code even though it's working / waiting, so treat that as
// Success as well
if (!err || (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1)) {
Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) {
console.log('here1');
if (err) { callback(err); return; }
VirtualBox.removeDHCP(function (err) {
console.log('here2');
self.correct(function (err) {
self.injectUtilities(function (err) {
console.log('here3');
callback(err);
});
});
});
})
} else {
callback(err);
}
@ -100,6 +92,15 @@ Boot2Docker.start = function (callback) {
});
};
Boot2Docker.correct = function (callback) {
Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) {
if (err) { callback(err); return; }
VirtualBox.removeDHCP(function (err) {
callback(err);
});
});
};
Boot2Docker.state = function (callback) {
this.exec('info', function (err, stdout) {
if (err) {
@ -228,7 +229,7 @@ Boot2Docker.version = function (callback) {
}
var match = stdout.match(/Client version: v(\d\.\d\.\d)/);
if (!match || match.length < 2) {
callback('Could not parse the boot2docker cli version.')
callback('Could not parse the boot2docker cli version.');
} else {
callback(null, match[1]);
}
@ -236,14 +237,14 @@ Boot2Docker.version = function (callback) {
};
Boot2Docker.injectUtilities = function (callback) {
exec('/bin/cat ' + path.join(getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) {
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) {
callback(err);
});
};
Boot2Docker.check = function (callback) {
var self = this;
self.exists(function (err) {
self.exists(function (err, exists) {
if (err) {
callback(err);
return;
@ -252,7 +253,9 @@ Boot2Docker.check = function (callback) {
if (state !== 'running') {
callback('boot2docker not running');
} else {
callback();
self.correct(function (err) {
callback(err);
});
}
});
}
@ -264,8 +267,8 @@ Boot2Docker.resolve = function (callback) {
self.exists(function (err, exists) {
// If somehow the boot2docker VM doesn't exist anymor then re-create it.
if (!exists) {
initBoot2Docker(function () {
startBoot2Docker(function (err) {
self.init(function () {
self.start(function (err) {
callback(err);
});
});
@ -273,7 +276,7 @@ Boot2Docker.resolve = function (callback) {
// If it exists but it's not running.. restart it.
self.state(function (err, state) {
if (state !== 'running') {
startBoot2Docker(function (err) {
self.start(function (err) {
callback(err);
});
} else {

View File

@ -0,0 +1,2 @@
path = require('path');
fs = require('fs');

View File

@ -53,7 +53,6 @@ Router.map(function () {
console.log('No installs detected, running installer again.');
this.redirect('/setup/intro');
} else {
startFixInterval();
this.redirect('/apps');
}
}

View File

@ -15,7 +15,7 @@ removeAppWatcher = function (id) {
addAppWatcher = function (app) {
removeAppWatcher(app._id);
var appPath = path.join(path.join(getHomePath(), 'Kitematic'), app.name);
var appPath = path.join(path.join(Util.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(getBinDir(), 'unison');
var cmd = path.join(Util.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(getHomePath(), '.ssh/id_boot2docker')
'-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(Util.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(getHomePath(), 'Library/Application\\ Support/Unison', location);
var fullLocation = path.join(Util.getHomePath(), 'Library/Application\\ Support/Unison', location);
var cmd = '/bin/rm -rf ' + fullLocation;
exec(cmd, function () {});
}

View File

@ -1,92 +1,3 @@
var path = require('path');
Utilities = {};
/**
* 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:
* <ul>
* <li>
* <tt>lexicographical: true</tt> 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".
* </li>
* <li>
* <tt>zeroExtend: true</tt> 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.
* </li>
* </ul>
* @returns {number|NaN}
* <ul>
* <li>0 if the versions are equal</li>
* <li>a negative integer iff v1 < v2</li>
* <li>a positive integer iff v1 > v2</li>
* <li>NaN if either version string is in the wrong format</li>
* </ul>
*
*/
Utilities.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;
};
getBinDir = function () {
if (process.env.NODE_ENV === 'development') {
return path.join(path.join(process.env.PWD, '..'), 'resources');
} else {
return path.join(process.cwd(), 'resources');
}
};
getHomePath = function () {
return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
};
showFormErrors = function ($form, errors) {
for (var name in errors) {
if (errors.hasOwnProperty(name)) {

View File

@ -24,14 +24,14 @@ VirtualBox.exec = function (command, callback) {
VirtualBox.install = function (callback) {
// -W waits for the process to close before finishing.
exec('open -W ' + path.join(getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) {
exec('open -W ' + path.join(Util.getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) {
if (error) {
callback(error);
return;
}
callback(null);
});
}
};
VirtualBox.version = function (callback) {
if (!this.installed()) {
@ -63,7 +63,9 @@ VirtualBox.hostOnlyIfs = function (callback) {
var hostOnlyIfs = {};
var currentIf = null;
_.each(lines, function (line) {
if (!line.length) return;
if (!line.length) {
return;
}
var pieces = line.split(':');
var key = pieces[0].trim();
var value = pieces[1] ? pieces[1].trim() : null;
@ -139,8 +141,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(getBinDir(), 'install');
var cocoaSudo = path.join(getBinDir(), 'cocoasudo');
var installFile = path.join(Util.getBinDir(), 'install');
var cocoaSudo = path.join(Util.getBinDir(), 'cocoasudo');
var execCommand = cocoaSudo + ' --prompt="Kitematic needs your password to allow routing *.dev requests to containers." ' + installFile;
exec(execCommand, {env: {IFNAME: ifname, GATEWAY: Boot2Docker.REQUIRED_IP}}, function (error, stdout, stderr) {
if (error) {
@ -152,17 +154,12 @@ VirtualBox.setupRouting = function (vm, callback) {
});
};
VirtualBox.removeDHCP = function (callback) {
var self = this;
self.hostOnlyAdapter(function (err, ifname) {
if (err) { callback(err); return; }
console.log(ifname);
self.exec('dhcpserver remove --ifname ' + ifname, function (err, stdout, stderr) {
callback(err);
});
});
};

View File

@ -69,10 +69,10 @@ Meteor.call('getDockerHost', function (err, host) {
});
fixBoot2DockerVM = function (callback) {
checkBoot2DockerVM(function (err) {
Boot2Docker.check(function (err) {
if (err) {
Session.set('available', false);
resolveBoot2DockerVM(function (err) {
Boot2Docker.resolve(function (err) {
if (err) {
callback(err);
} else {
@ -145,13 +145,12 @@ Meteor.setInterval(function () {
});
}, 5000);
fixInterval = null;
startFixInterval = function () {
stopFixInterval();
fixInterval = Meteor.setInterval(function () {
Meteor.setInterval(function () {
if (Installs.findOne()) {
resolveWatchers(function () {});
fixBoot2DockerVM(function (err) {
if (err) { console.log(err); return; }
Meteor.call('recoverApps');
fixDefaultImages(function (err) {
if (err) { console.log(err); return; }
fixDefaultContainers(function (err) {
@ -159,10 +158,6 @@ startFixInterval = function () {
});
});
});
}
}, 5000);
};
stopFixInterval = function () {
Meteor.clearInterval(fixInterval);
fixInterval = null;
};

View File

@ -21,8 +21,8 @@ Template.dashboard_single_app.events({
},
'click .btn-terminal': function () {
var app = this;
var cmd = path.join(getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"';
var terminalCmd = path.join(getBinDir(), 'terminal') + ' ' + cmd;
var cmd = path.join(Util.getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"';
var terminalCmd = path.join(Util.getBinDir(), 'terminal') + ' ' + cmd;
var exec = require('child_process').exec;
console.log(terminalCmd);
exec(terminalCmd, function (err, stdout) {

View File

@ -8,7 +8,7 @@
{{#if $.Session.equals 'boot2dockerState' 'poweroff'}}
<p class="help-block error">Please start Boot2Docker for Kitematic to work properly!</p>
{{else}}
<p class="help-block">All apps run in a Linux VM included with Kitematic. It needs to be turned to run apps.</p>
<p class="help-block">All apps run in a Linux VM included with Kitematic. It needs to be turned on to run apps.</p>
{{/if}}
</div>
<div class="right-section">

View File

@ -8,7 +8,6 @@ Template.setup_install.rendered = function() {
console.log(err);
} else {
Installs.insert({});
startFixInterval();
Router.go('dashboard_apps');
}
});

View File

@ -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.');
});
});

View File

@ -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);
}
});

146
meteor/lib/utilities.js Normal file
View File

@ -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:
* <ul>
* <li>
* <tt>lexicographical: true</tt> 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".
* </li>
* <li>
* <tt>zeroExtend: true</tt> 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.
* </li>
* </ul>
* @returns {number|NaN}
* <ul>
* <li>0 if the versions are equal</li>
* <li>a negative integer iff v1 < v2</li>
* <li>a positive integer iff v1 > v2</li>
* <li>NaN if either version string is in the wrong format</li>
* </ul>
*
*/
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;
};

View File

@ -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();
});
});
},
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,
var appObj = {
name: appName,
imageId: cleaned.imageId,
status: 'STARTING',
config: {},
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();
});
};
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)();
}
});

View File

@ -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; }
});
}

View File

@ -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)();
}
});

View File

@ -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});
}
} else {
throw new Meteor.Error(400, 'This image is currently being used by <a href="/apps/' + app.name + '">' + app.name + "</a>.");
}

View File

@ -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);
};

View File

@ -7,3 +7,5 @@ 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();

View File

@ -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);
}
}
};

View File

@ -7,5 +7,5 @@ Meteor.publish('images', function () {
});
Meteor.publish('installs', function () {
return Installs.find({}, {sort: {createdAt: -1}});
return Installs.find({});
});

View File

@ -28,9 +28,6 @@ echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<string>172.17.0.0</string>
<string>-netmask</string>
<string>255.255.0.0</string>
<string>-iface</string>
<string>$IFNAME</string>
<string>255.255.0.0</string>
<string>-gateway</string>
<string>$GATEWAY</string>
</array>
@ -45,4 +42,4 @@ echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
# 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
/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY