mirror of https://github.com/docker/docs.git
Merge branch 'rc'
This commit is contained in:
commit
38181a9579
5
index.js
5
index.js
|
@ -51,10 +51,6 @@ var start = function (callback) {
|
||||||
});
|
});
|
||||||
var started = false;
|
var started = false;
|
||||||
mongoChild.stdout.setEncoding('utf8');
|
mongoChild.stdout.setEncoding('utf8');
|
||||||
mongoChild.stderr.setEncoding('utf8');
|
|
||||||
mongoChild.stderr.on('data', function (data) {
|
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
mongoChild.stdout.on('data', function (data) {
|
mongoChild.stdout.on('data', function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
if (data.indexOf('waiting for connections on port ' + mongoPort)) {
|
if (data.indexOf('waiting for connections on port ' + mongoPort)) {
|
||||||
|
@ -124,6 +120,7 @@ start(function (url, nodeChild, mongoChild) {
|
||||||
}, 400);
|
}, 400);
|
||||||
mainWindow.on('close', function (type) {
|
mainWindow.on('close', function (type) {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
console.log('closed');
|
||||||
if (type === 'quit') {
|
if (type === 'quit') {
|
||||||
console.log('here');
|
console.log('here');
|
||||||
if (nodeChild && mongoChild) {
|
if (nodeChild && mongoChild) {
|
||||||
|
|
|
@ -146,16 +146,6 @@
|
||||||
"convert": true,
|
"convert": true,
|
||||||
"Convert": true,
|
"Convert": true,
|
||||||
|
|
||||||
// Collections
|
|
||||||
"SimpleSchema": false,
|
|
||||||
"ServiceConfiguration": false,
|
|
||||||
"Apps": true,
|
|
||||||
"schemaApps": true,
|
|
||||||
"Images": true,
|
|
||||||
"schemaImages": true,
|
|
||||||
"Installs": true,
|
|
||||||
"schemaInstalls": true,
|
|
||||||
|
|
||||||
// Controllers
|
// Controllers
|
||||||
"RouteController": true,
|
"RouteController": true,
|
||||||
"DashboardController": true,
|
"DashboardController": true,
|
||||||
|
@ -164,51 +154,17 @@
|
||||||
"SetupController": true,
|
"SetupController": true,
|
||||||
|
|
||||||
// Server and Client
|
// Server and Client
|
||||||
|
"Images": true,
|
||||||
|
"Apps": true,
|
||||||
|
"Installs": true,
|
||||||
"Docker": true,
|
"Docker": true,
|
||||||
"Util": true,
|
"Util": true,
|
||||||
|
"Sync": true,
|
||||||
"Boot2Docker": true,
|
"Boot2Docker": true,
|
||||||
"Installer": true,
|
"Installer": true,
|
||||||
"VirtualBox": true,
|
"VirtualBox": true,
|
||||||
|
"ImageUtil": true,
|
||||||
"boot2dockerexec": true,
|
"AppUtil": true,
|
||||||
"getBoot2DockerIp": true,
|
|
||||||
"getBoot2DockerState": true,
|
|
||||||
"getBoot2DockerDiskUsage": true,
|
|
||||||
"getBoot2DockerMemoryUsage": true,
|
|
||||||
"getBoot2DockerInfo": true,
|
|
||||||
"boot2DockerVMExists": true,
|
|
||||||
"eraseBoot2DockerVMFiles": true,
|
|
||||||
"initBoot2Docker": true,
|
|
||||||
"isVirtualBoxInstalled": true,
|
|
||||||
"upgradeBoot2Docker": true,
|
|
||||||
"installBoot2DockerAddons": true,
|
|
||||||
"startBoot2Docker": true,
|
|
||||||
"stopBoot2Docker": true,
|
|
||||||
"checkBoot2DockerVM": true,
|
|
||||||
"resolveBoot2DockerVM": true,
|
|
||||||
"startFixInterval": true,
|
|
||||||
"trackLink": true,
|
|
||||||
"isResolverSetup": true,
|
|
||||||
"setupVirtualBoxAndResolver": true,
|
|
||||||
"setupVirtualBoxSharedFolder": true,
|
|
||||||
"updateBoot2DockerInfo": true,
|
|
||||||
"fixBoot2DockerVM": true,
|
|
||||||
"fixDefaultImages": true,
|
|
||||||
"fixDefaultContainers": true,
|
|
||||||
"fixInterval": true,
|
|
||||||
"stopFixInterval": true,
|
|
||||||
"runSetup": true,
|
|
||||||
"removeAppWatcher": true,
|
|
||||||
"addAppWatcher": true,
|
|
||||||
"resolveWatchers": true,
|
|
||||||
"checkDefaultImages": true,
|
|
||||||
"resolveDefaultImages": true,
|
|
||||||
"checkDefaultContainers": true,
|
|
||||||
"resolveDefaultContainers": true,
|
|
||||||
"killAndRemoveContainers": true,
|
|
||||||
"upContainers": true,
|
|
||||||
"reloadDefaultContainers": true,
|
|
||||||
"removeImages": true,
|
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
"showFormErrors": true,
|
"showFormErrors": true,
|
||||||
|
@ -217,18 +173,13 @@
|
||||||
"FormSchema": true,
|
"FormSchema": true,
|
||||||
"showFormSuccess": true,
|
"showFormSuccess": true,
|
||||||
"resetForm": true,
|
"resetForm": true,
|
||||||
|
"trackLink": true,
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
"require": false,
|
"require": false,
|
||||||
"suite": false,
|
"suite": false,
|
||||||
"test": false,
|
"test": false,
|
||||||
"emit": false,
|
"emit": false
|
||||||
|
|
||||||
// Constants
|
|
||||||
"KITE_PATH": true,
|
|
||||||
"KITE_TAR_PATH": true,
|
|
||||||
"KITE_IMAGES_PATH": true,
|
|
||||||
"COMMON_WEB_PORTS": true
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,8 @@
|
||||||
standard-app-packages
|
standard-app-packages
|
||||||
less
|
less
|
||||||
bootstrap3-less
|
bootstrap3-less
|
||||||
npm
|
|
||||||
iron-router
|
iron-router
|
||||||
headers
|
|
||||||
handlebar-helpers
|
handlebar-helpers
|
||||||
collection2
|
|
||||||
collection-hooks
|
|
||||||
moment
|
|
||||||
underscore-string-latest
|
underscore-string-latest
|
||||||
collection-helpers
|
collection-helpers
|
||||||
octicons
|
octicons
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
var exec = require('exec');
|
||||||
|
var path = require('path');
|
||||||
|
var Convert = require('ansi-to-html');
|
||||||
|
var convert = new Convert();
|
||||||
|
|
||||||
|
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); }
|
||||||
|
// Use dig to refresh the DNS
|
||||||
|
exec('/usr/bin/dig ' + app.name + '.kite @172.17.42.1', function(err, stdout, stderr) {
|
||||||
|
console.log(err);
|
||||||
|
console.log(stdout);
|
||||||
|
console.log(stderr);
|
||||||
|
Apps.update(app._id, {$set: {
|
||||||
|
status: 'READY',
|
||||||
|
docker: data
|
||||||
|
}});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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(Util.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.client().getContainer(app.docker.Id);
|
||||||
|
container.logs({follow: false, stdout: true, stderr: true, timestamps: false, 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) {
|
||||||
|
Apps.update(app._id, {
|
||||||
|
$push: {
|
||||||
|
logs: convert.toHtml(line.slice(8))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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.client().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); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
var exec = require('exec');
|
var exec = require('exec');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
Boot2Docker = {};
|
Boot2Docker = {};
|
||||||
|
|
||||||
|
@ -23,11 +24,8 @@ Boot2Docker.exists = function (callback) {
|
||||||
|
|
||||||
Boot2Docker.stop = function (callback) {
|
Boot2Docker.stop = function (callback) {
|
||||||
this.exec('stop', function (err, stdout) {
|
this.exec('stop', function (err, stdout) {
|
||||||
if (err) {
|
// Sometimes stop returns an error even though it worked
|
||||||
callback(err);
|
callback(null);
|
||||||
} else {
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,6 +200,10 @@ Boot2Docker.stats = function (callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Boot2Docker.sshKeyExists = function () {
|
||||||
|
return fs.existsSync(path.join(Util.getHomePath(), '.ssh', 'id_boot2docker'));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the VM's version.
|
* Get the VM's version.
|
||||||
* Node that this only works if the VM is up and running.
|
* Node that this only works if the VM is up and running.
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
Dockerode = Meteor.require('dockerode');
|
var Dockerode = require('dockerode');
|
||||||
|
var async = require('async');
|
||||||
var DOCKER_HOST='192.168.60.103';
|
var exec = require('exec');
|
||||||
docker = new Dockerode({host: DOCKER_HOST, port: '2375'});
|
var path = require('path');
|
||||||
|
|
||||||
Docker = {};
|
Docker = {};
|
||||||
|
Docker.DOCKER_HOST = '192.168.60.103';
|
||||||
|
|
||||||
|
Docker.client = function () {
|
||||||
|
return new Dockerode({host: Docker.DOCKER_HOST, port: '2375'});
|
||||||
|
};
|
||||||
|
|
||||||
|
var docker = Docker.client();
|
||||||
|
|
||||||
Docker.removeContainer = function (containerId, callback) {
|
Docker.removeContainer = function (containerId, callback) {
|
||||||
var container = docker.getContainer(containerId);
|
var container = docker.getContainer(containerId);
|
||||||
|
@ -17,10 +24,6 @@ Docker.removeContainer = function (containerId, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Docker.removeContainerSync = function (containerId) {
|
|
||||||
return Meteor._wrapAsync(Docker.removeContainer)(containerId);
|
|
||||||
};
|
|
||||||
|
|
||||||
Docker.getContainerData = function (containerId, callback) {
|
Docker.getContainerData = function (containerId, callback) {
|
||||||
var container = docker.getContainer(containerId);
|
var container = docker.getContainer(containerId);
|
||||||
container.inspect(function (err, data) {
|
container.inspect(function (err, data) {
|
||||||
|
@ -37,10 +40,6 @@ Docker.getContainerData = function (containerId, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Docker.getContainerDataSync = function (containerId) {
|
|
||||||
return Meteor._wrapAsync(Docker.getContainerData)(containerId);
|
|
||||||
};
|
|
||||||
|
|
||||||
Docker.runContainer = function (app, image, callback) {
|
Docker.runContainer = function (app, image, callback) {
|
||||||
var envParam = [];
|
var envParam = [];
|
||||||
_.each(_.keys(app.config), function (key) {
|
_.each(_.keys(app.config), function (key) {
|
||||||
|
@ -72,16 +71,16 @@ Docker.runContainer = function (app, image, callback) {
|
||||||
if (err) { callback(err, null); return; }
|
if (err) { callback(err, null); return; }
|
||||||
console.log('Started container: ' + container.id);
|
console.log('Started container: ' + container.id);
|
||||||
// Use dig to refresh the DNS
|
// Use dig to refresh the DNS
|
||||||
exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {});
|
exec('/usr/bin/dig ' + app.name + '.kite @172.17.42.1', function(err, stdout, stderr) {
|
||||||
callback(null, container);
|
console.log(err);
|
||||||
|
console.log(stdout);
|
||||||
|
console.log(stderr);
|
||||||
|
callback(null, container);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Docker.runContainerSync = function (app, image) {
|
|
||||||
return Meteor._wrapAsync(Docker.runContainer)(app, image);
|
|
||||||
};
|
|
||||||
|
|
||||||
Docker.restartContainer = function (containerId, callback) {
|
Docker.restartContainer = function (containerId, callback) {
|
||||||
var container = docker.getContainer(containerId);
|
var container = docker.getContainer(containerId);
|
||||||
container.restart(function (err) {
|
container.restart(function (err) {
|
||||||
|
@ -95,10 +94,6 @@ Docker.restartContainer = function (containerId, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Docker.restartContainerSync = function (containerId) {
|
|
||||||
return Meteor._wrapAsync(Docker.restartContainer)(containerId);
|
|
||||||
};
|
|
||||||
|
|
||||||
var convertVolumeObjToArray = function (obj) {
|
var convertVolumeObjToArray = function (obj) {
|
||||||
var result = [];
|
var result = [];
|
||||||
if (obj !== null && typeof obj === 'object') {
|
if (obj !== null && typeof obj === 'object') {
|
||||||
|
@ -127,10 +122,6 @@ Docker.getImageData = function (imageId, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Docker.getImageDataSync = function (imageId) {
|
|
||||||
return Meteor._wrapAsync(Docker.getImageData)(imageId);
|
|
||||||
};
|
|
||||||
|
|
||||||
Docker.removeImage = function (imageId, callback) {
|
Docker.removeImage = function (imageId, callback) {
|
||||||
var image = docker.getImage(imageId.toLowerCase());
|
var image = docker.getImage(imageId.toLowerCase());
|
||||||
image.remove({force: true}, function (err) {
|
image.remove({force: true}, function (err) {
|
||||||
|
@ -140,17 +131,13 @@ Docker.removeImage = function (imageId, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Docker.removeImageSync = function (imageId) {
|
|
||||||
return Meteor._wrapAsync(Docker.removeImage)(imageId);
|
|
||||||
};
|
|
||||||
|
|
||||||
Docker.removeBindFolder = function (name, callback) {
|
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(Util.getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function (err, stdout) {
|
||||||
callback(err, stdout);
|
callback(err, stdout);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var defaultContainerOptions = function () {
|
Docker.defaultContainerOptions = function () {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
Image: 'kite-dns',
|
Image: 'kite-dns',
|
||||||
|
@ -161,10 +148,12 @@ var defaultContainerOptions = function () {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
checkDefaultImages = function (callback) {
|
Docker.defaultContainerNames = Docker.defaultContainerOptions().map(function (container) {
|
||||||
var defaultNames = defaultContainerOptions().map(function (container) {
|
return container.name;
|
||||||
return container.name;
|
});
|
||||||
});
|
|
||||||
|
Docker.checkDefaultImages = function (callback) {
|
||||||
|
var defaultNames = Docker.defaultContainerNames;
|
||||||
async.each(defaultNames, function (name, innerCallback) {
|
async.each(defaultNames, function (name, innerCallback) {
|
||||||
var image = docker.getImage(name);
|
var image = docker.getImage(name);
|
||||||
image.inspect(function (err) {
|
image.inspect(function (err) {
|
||||||
|
@ -187,11 +176,8 @@ checkDefaultImages = function (callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
resolveDefaultImages = function () {
|
Docker.resolveDefaultImages = function () {
|
||||||
var defaultNames = defaultContainerOptions().map(function (container) {
|
async.each(Docker.defaultContainerNames, function (name, innerCallback) {
|
||||||
return container.name;
|
|
||||||
});
|
|
||||||
async.each(defaultNames, function (name, innerCallback) {
|
|
||||||
var image = docker.getImage(name);
|
var image = docker.getImage(name);
|
||||||
image.inspect(function (err) {
|
image.inspect(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -214,11 +200,8 @@ resolveDefaultImages = function () {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
checkDefaultContainers = function(callback) {
|
Docker.checkDefaultContainers = function(callback) {
|
||||||
var defaultNames = defaultContainerOptions().map(function (container) {
|
async.each(Docker.defaultContainerNames, function (name, innerCallback) {
|
||||||
return container.name;
|
|
||||||
});
|
|
||||||
async.each(defaultNames, function (name, innerCallback) {
|
|
||||||
var container = docker.getContainer(name);
|
var container = docker.getContainer(name);
|
||||||
container.inspect(function (err, data) {
|
container.inspect(function (err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -240,42 +223,33 @@ checkDefaultContainers = function(callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
resolveDefaultContainers = function (callback) {
|
Docker.resolveDefaultContainers = function (callback) {
|
||||||
var defaultNames = defaultContainerOptions().map(function (container) {
|
Docker.killAndRemoveContainers(Docker.defaultContainerNames, function (err) {
|
||||||
return container.name;
|
|
||||||
});
|
|
||||||
killAndRemoveContainers(defaultNames, function (err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
upContainers(defaultContainerOptions(), function (err) {
|
Docker.upContainers(Docker.defaultContainerOptions(), function (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
reloadDefaultContainers = function (callback) {
|
Docker.reloadDefaultContainers = function (callback) {
|
||||||
console.log('Reloading default containers.');
|
console.log('Reloading default containers.');
|
||||||
|
|
||||||
var defaultNames = defaultContainerOptions().map(function (container) {
|
|
||||||
return container.name;
|
|
||||||
});
|
|
||||||
|
|
||||||
var ready = false;
|
var ready = false;
|
||||||
async.until(function () {
|
async.until(function () {
|
||||||
return ready;
|
return ready;
|
||||||
}, function (callback) {
|
}, function (callback) {
|
||||||
docker.listContainers(function (err) {
|
docker.listContainers(function (err, containers) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
ready = true;
|
ready = true;
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
console.log(err);
|
|
||||||
console.log('Removing old Kitematic default containers.');
|
console.log('Removing old Kitematic default containers.');
|
||||||
killAndRemoveContainers(defaultNames, function (err) {
|
Docker.killAndRemoveContainers(Docker.defaultContainerNames, function (err) {
|
||||||
console.log('Removed old Kitematic default containers.');
|
console.log('Removed old Kitematic default containers.');
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Removing old Kitematic default containers ERROR.');
|
console.log('Removing old Kitematic default containers ERROR.');
|
||||||
|
@ -289,7 +263,7 @@ reloadDefaultContainers = function (callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Starting new Kitematic default containers.');
|
console.log('Starting new Kitematic default containers.');
|
||||||
upContainers(defaultContainerOptions(), function (err) {
|
Docker.upContainers(Docker.defaultContainerOptions(), function (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -297,7 +271,7 @@ reloadDefaultContainers = function (callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
upContainers = function (optionsList, callback) {
|
Docker.upContainers = function (optionsList, callback) {
|
||||||
var createDefaultContainer = function (options, innerCallback) {
|
var createDefaultContainer = function (options, innerCallback) {
|
||||||
docker.createContainer(options, function (err, container) {
|
docker.createContainer(options, function (err, container) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -340,7 +314,7 @@ upContainers = function (optionsList, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
removeImages = function (names, callback) {
|
Docker.removeImages = function (names, callback) {
|
||||||
async.each(names, function (name, innerCallback) {
|
async.each(names, function (name, innerCallback) {
|
||||||
var image = docker.getImage(name);
|
var image = docker.getImage(name);
|
||||||
image.remove(function (err) {
|
image.remove(function (err) {
|
||||||
|
@ -361,7 +335,7 @@ removeImages = function (names, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
killAndRemoveContainers = function (names, callback) {
|
Docker.killAndRemoveContainers = function (names, callback) {
|
||||||
async.each(names, function (name, innerCallback) {
|
async.each(names, function (name, innerCallback) {
|
||||||
var container = docker.getContainer(name);
|
var container = docker.getContainer(name);
|
||||||
container.inspect(function (err, data) {
|
container.inspect(function (err, data) {
|
||||||
|
@ -391,50 +365,3 @@ killAndRemoveContainers = function (names, callback) {
|
||||||
callback(err);
|
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)();
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
|
@ -21,10 +21,3 @@ clearFormErrors = function ($form) {
|
||||||
resetForm = function ($form) {
|
resetForm = function ($form) {
|
||||||
$form.find('input').val('');
|
$form.find('input').val('');
|
||||||
};
|
};
|
||||||
|
|
||||||
trackLink = function (trackLabel) {
|
|
||||||
if (trackLabel) {
|
|
||||||
console.log(trackLabel);
|
|
||||||
ga('send', 'event', 'link', 'click', trackLabel);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
var Convert = require('ansi-to-html');
|
||||||
|
var convert = new Convert();
|
||||||
|
var exec = require('exec');
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var docker = Docker.client();
|
||||||
|
|
||||||
|
ImageUtil = {};
|
||||||
|
|
||||||
|
var createTarFile = function (image, callback) {
|
||||||
|
var TAR_PATH = path.join(Util.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 jsonPath = path.join(directory, 'image.json');
|
||||||
|
if (fs.existsSync(jsonPath)) {
|
||||||
|
var data = fs.readFileSync(jsonPath, '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(Util.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.rebuildHelper = 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.rebuild = 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.rebuildHelper(image, function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
});
|
||||||
|
_.each(apps, function (app) {
|
||||||
|
app = Apps.findOne(app._id);
|
||||||
|
AppUtil.run(app, function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ImageUtil.rebuildHelper(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...');
|
||||||
|
response.setEncoding('utf8');
|
||||||
|
response.on('data', function (data) {
|
||||||
|
try {
|
||||||
|
var line = JSON.parse(data).stream;
|
||||||
|
Images.update(image._id, {
|
||||||
|
$push: {
|
||||||
|
buildLogs: convert.toHtml(line)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
Installer = {};
|
Installer = {};
|
||||||
|
|
||||||
|
@ -30,10 +32,13 @@ Installer.steps = [
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var needsUpdate = Util.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0;
|
if (Util.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0) {
|
||||||
if (needsUpdate) {
|
|
||||||
VirtualBox.install(function (err) {
|
VirtualBox.install(function (err) {
|
||||||
callback(err);
|
if (Util.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0) {
|
||||||
|
callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.');
|
||||||
|
} else {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
callback();
|
callback();
|
||||||
|
@ -52,10 +57,17 @@ Installer.steps = [
|
||||||
Boot2Docker.exists(function (err, exists) {
|
Boot2Docker.exists(function (err, exists) {
|
||||||
if (err) { callback(err); return; }
|
if (err) { callback(err); return; }
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
var vmFilesPath = path.join(Util.getHomePath(), 'VirtualBox\ VMs', 'boot2docker-vm');
|
||||||
|
if (fs.existsSync(vmFilesPath)) {
|
||||||
|
Util.deleteFolder(vmFilesPath);
|
||||||
|
}
|
||||||
Boot2Docker.init(function (err) {
|
Boot2Docker.init(function (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
if (!Boot2Docker.sshKeyExists()) {
|
||||||
|
callback('Boot2Docker SSH key doesn\'t exist. Fix by deleting the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.');
|
||||||
|
}
|
||||||
Boot2Docker.stop(function (err) {
|
Boot2Docker.stop(function (err) {
|
||||||
if (err) { callback(err); return; }
|
if (err) { callback(err); return; }
|
||||||
Boot2Docker.upgrade(function (err) {
|
Boot2Docker.upgrade(function (err) {
|
||||||
|
@ -100,7 +112,7 @@ Installer.steps = [
|
||||||
},
|
},
|
||||||
pastMessage: 'Started the Boot2Docker VM',
|
pastMessage: 'Started the Boot2Docker VM',
|
||||||
message: 'Starting the Boot2Docker VM',
|
message: 'Starting the Boot2Docker VM',
|
||||||
futureMessage: 'Start the Kitematic VM',
|
futureMessage: 'Start the Kitematic VM'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -117,14 +129,14 @@ Installer.steps = [
|
||||||
// Set up the default Kitematic images
|
// Set up the default Kitematic images
|
||||||
{
|
{
|
||||||
run: function (callback) {
|
run: function (callback) {
|
||||||
Meteor.call('reloadDefaultContainers', function (err) {
|
Docker.reloadDefaultContainers(function (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
pastMessage: 'Started the Boot2Docker VM',
|
pastMessage: 'Started the Boot2Docker VM',
|
||||||
message: 'Setting up the default Kitematic images...',
|
message: 'Setting up the default Kitematic images...',
|
||||||
subMessage: '(This may take a few minutes)',
|
subMessage: '(This may take a few minutes)',
|
||||||
futureMessage: 'Set up the default Kitematic images',
|
futureMessage: 'Set up the default Kitematic images'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
path = require('path');
|
|
||||||
fs = require('fs');
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
var fs = require('fs');
|
||||||
Meteor.startup(function () {
|
Meteor.startup(function () {
|
||||||
console.log('Kitematic started.');
|
console.log('Kitematic started.');
|
||||||
|
if (!fs.existsSync(Util.KITE_PATH)) {
|
||||||
|
console.log('Created Kitematic directory.');
|
||||||
|
fs.mkdirSync(Util.KITE_PATH);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(Util.KITE_TAR_PATH)) {
|
||||||
|
console.log('Created Kitematic .tar directory.');
|
||||||
|
fs.mkdirSync(Util.KITE_TAR_PATH);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(Util.KITE_IMAGES_PATH)) {
|
||||||
|
console.log('Created Kitematic .images directory.');
|
||||||
|
fs.mkdirSync(Util.KITE_IMAGES_PATH);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,18 +4,19 @@ var fs = require('fs');
|
||||||
var child_process = require('child_process');
|
var child_process = require('child_process');
|
||||||
var exec = require('exec');
|
var exec = require('exec');
|
||||||
|
|
||||||
var watchers = {};
|
Sync = {};
|
||||||
|
Sync.watchers = {};
|
||||||
|
|
||||||
removeAppWatcher = function (id) {
|
Sync.removeAppWatcher = function (id) {
|
||||||
if (watchers[id]) {
|
if (Sync.watchers[id]) {
|
||||||
watchers[id].watcher.close();
|
Sync.watchers[id].watcher.close();
|
||||||
delete watchers[id];
|
delete Sync.watchers[id];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addAppWatcher = function (app) {
|
Sync.addAppWatcher = function (app) {
|
||||||
removeAppWatcher(app._id);
|
Sync.removeAppWatcher(app._id);
|
||||||
var appPath = path.join(path.join(Util.getHomePath(), 'Kitematic'), app.name);
|
var appPath = path.join(Util.getHomePath(), 'Kitematic', app.name);
|
||||||
var vmDir = path.join('/var/lib/docker/binds', app.name);
|
var vmDir = path.join('/var/lib/docker/binds', app.name);
|
||||||
var vmPath = 'ssh://docker@localhost:2022/' + vmDir;
|
var vmPath = 'ssh://docker@localhost:2022/' + vmDir;
|
||||||
var watcher = chokidar.watch(appPath, {ignored: /.*\.DS_Store/});
|
var watcher = chokidar.watch(appPath, {ignored: /.*\.DS_Store/});
|
||||||
|
@ -85,7 +86,7 @@ addAppWatcher = function (app) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
watchers[app._id] = {
|
Sync.watchers[app._id] = {
|
||||||
watcher: watcher,
|
watcher: watcher,
|
||||||
sync: syncFunc
|
sync: syncFunc
|
||||||
};
|
};
|
||||||
|
@ -95,25 +96,25 @@ addAppWatcher = function (app) {
|
||||||
watcher.on('all', syncFunc);
|
watcher.on('all', syncFunc);
|
||||||
};
|
};
|
||||||
|
|
||||||
resolveWatchers = function (callback) {
|
Sync.resolveWatchers = function (callback) {
|
||||||
var apps = Apps.find({}).fetch();
|
var apps = Apps.find({}).fetch();
|
||||||
var ids = _.map(apps, function(app) {
|
var ids = _.map(apps, function(app) {
|
||||||
return app._id;
|
return app._id;
|
||||||
});
|
});
|
||||||
var watcherKeys = _.keys(watchers);
|
var watcherKeys = _.keys(Sync.watchers);
|
||||||
var toAdd = _.difference(ids, watcherKeys);
|
var toAdd = _.difference(ids, watcherKeys);
|
||||||
var toRemove = _.difference(watcherKeys, ids);
|
var toRemove = _.difference(watcherKeys, ids);
|
||||||
|
|
||||||
_.each(toAdd, function (id) {
|
_.each(toAdd, function (id) {
|
||||||
addAppWatcher(Apps.findOne(id), function () {});
|
Sync.addAppWatcher(Apps.findOne(id), function () {});
|
||||||
});
|
});
|
||||||
|
|
||||||
_.each(toRemove, function (id) {
|
_.each(toRemove, function (id) {
|
||||||
removeAppWatcher(id);
|
Sync.removeAppWatcher(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run a sync for 'pulling' changes in the volumes.
|
// Run a sync for 'pulling' changes in the volumes.
|
||||||
_.each(watchers, function (watcher) {
|
_.each(Sync.watchers, function (watcher) {
|
||||||
watcher.sync();
|
watcher.sync();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
Util = {};
|
Util = {};
|
||||||
|
|
||||||
Util.getHomePath = function () {
|
Util.getHomePath = function () {
|
||||||
|
@ -16,6 +19,10 @@ Util.getBinDir = function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Util.KITE_PATH = path.join(Util.getHomePath(), 'Kitematic');
|
||||||
|
Util.KITE_TAR_PATH = path.join(Util.KITE_PATH, '.tar');
|
||||||
|
Util.KITE_IMAGES_PATH = path.join(Util.KITE_PATH, '.images');
|
||||||
|
|
||||||
Util.deleteFolder = function (directory) {
|
Util.deleteFolder = function (directory) {
|
||||||
if (fs.existsSync(directory)) {
|
if (fs.existsSync(directory)) {
|
||||||
fs.readdirSync(directory).forEach(function (file) {
|
fs.readdirSync(directory).forEach(function (file) {
|
||||||
|
@ -59,10 +66,10 @@ Util.copyFolder = function (src, dest) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Util.copyVolumes = function (directory, appName) {
|
Util.copyVolumes = function (directory, appName) {
|
||||||
var KITE_VOLUMES_PATH = path.join(directory, 'volumes');
|
var volumesPath = path.join(directory, 'volumes');
|
||||||
if (fs.existsSync(KITE_VOLUMES_PATH)) {
|
if (fs.existsSync(volumesPath)) {
|
||||||
var destinationPath = path.join(KITE_PATH, appName);
|
var destinationPath = path.join(Util.KITE_PATH, appName);
|
||||||
Util.copyFolder(KITE_VOLUMES_PATH, destinationPath);
|
Util.copyFolder(volumesPath, destinationPath);
|
||||||
console.log('Copied volumes for: ' + appName);
|
console.log('Copied volumes for: ' + appName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -112,8 +119,12 @@ Util.compareVersions = function (v1, v2, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zeroExtend) {
|
if (zeroExtend) {
|
||||||
while (v1parts.length < v2parts.length) v1parts.push('0');
|
while (v1parts.length < v2parts.length) {
|
||||||
while (v2parts.length < v1parts.length) v2parts.push('0');
|
v1parts.push('0');
|
||||||
|
}
|
||||||
|
while (v2parts.length < v1parts.length) {
|
||||||
|
v2parts.push('0');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lexicographical) {
|
if (!lexicographical) {
|
||||||
|
@ -122,11 +133,11 @@ Util.compareVersions = function (v1, v2, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < v1parts.length; ++i) {
|
for (var i = 0; i < v1parts.length; ++i) {
|
||||||
if (v2parts.length == i) {
|
if (v2parts.length === i) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v1parts[i] == v2parts[i]) {
|
if (v1parts[i] === v2parts[i]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (v1parts[i] > v2parts[i]) {
|
else if (v1parts[i] > v2parts[i]) {
|
||||||
|
@ -137,10 +148,16 @@ Util.compareVersions = function (v1, v2, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v1parts.length != v2parts.length) {
|
if (v1parts.length !== v2parts.length) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
trackLink = function (trackLabel) {
|
||||||
|
if (trackLabel) {
|
||||||
|
console.log(trackLabel);
|
||||||
|
ga('send', 'event', 'link', 'click', trackLabel);
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,9 +4,9 @@ var path = require('path');
|
||||||
|
|
||||||
VirtualBox = {};
|
VirtualBox = {};
|
||||||
|
|
||||||
VirtualBox.REQUIRED_VERSION = '4.3.12';
|
VirtualBox.REQUIRED_VERSION = '4.3.14';
|
||||||
VirtualBox.INCLUDED_VERSION = '4.3.12';
|
VirtualBox.INCLUDED_VERSION = '4.3.14';
|
||||||
VirtualBox.INSTALLER_FILENAME = 'virtualbox-4.3.12.pkg';
|
VirtualBox.INSTALLER_FILENAME = 'virtualbox-4.3.14.pkg';
|
||||||
|
|
||||||
// Info for the hostonly interface we add to the VM.
|
// Info for the hostonly interface we add to the VM.
|
||||||
VirtualBox.HOSTONLY_HOSTIP = '192.168.60.3';
|
VirtualBox.HOSTONLY_HOSTIP = '192.168.60.3';
|
||||||
|
@ -25,6 +25,8 @@ VirtualBox.exec = function (command, callback) {
|
||||||
VirtualBox.install = function (callback) {
|
VirtualBox.install = function (callback) {
|
||||||
// -W waits for the process to close before finishing.
|
// -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(Util.getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) {
|
||||||
|
console.log(stdout);
|
||||||
|
console.log(stderr);
|
||||||
if (error) {
|
if (error) {
|
||||||
callback(error);
|
callback(error);
|
||||||
return;
|
return;
|
||||||
|
@ -73,7 +75,7 @@ VirtualBox.hostOnlyIfs = function (callback) {
|
||||||
currentIf = value;
|
currentIf = value;
|
||||||
hostOnlyIfs[value] = {};
|
hostOnlyIfs[value] = {};
|
||||||
}
|
}
|
||||||
hostOnlyIfs[currentIf][key] = value;
|
hostOnlyIfs[currentIf][key] = value;
|
||||||
});
|
});
|
||||||
callback(null, hostOnlyIfs);
|
callback(null, hostOnlyIfs);
|
||||||
});
|
});
|
||||||
|
|
|
@ -63,12 +63,7 @@ Handlebars.registerHelper('timeSince', function (date) {
|
||||||
return moment(date).fromNow();
|
return moment(date).fromNow();
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.call('getDockerHost', function (err, host) {
|
var fixBoot2DockerVM = function (callback) {
|
||||||
if (err) { throw err; }
|
|
||||||
Session.set('dockerHost', host);
|
|
||||||
});
|
|
||||||
|
|
||||||
fixBoot2DockerVM = function (callback) {
|
|
||||||
Boot2Docker.check(function (err) {
|
Boot2Docker.check(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Session.set('available', false);
|
Session.set('available', false);
|
||||||
|
@ -86,11 +81,11 @@ fixBoot2DockerVM = function (callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
fixDefaultImages = function (callback) {
|
var fixDefaultImages = function (callback) {
|
||||||
Meteor.call('checkDefaultImages', function (err) {
|
Docker.checkDefaultImages(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Session.set('available', false);
|
Session.set('available', false);
|
||||||
Meteor.call('resolveDefaultImages', function (err) {
|
Docker.resolveDefaultImages(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
|
@ -105,11 +100,11 @@ fixDefaultImages = function (callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
fixDefaultContainers = function (callback) {
|
var fixDefaultContainers = function (callback) {
|
||||||
Meteor.call('checkDefaultContainers', function (err) {
|
Docker.checkDefaultContainers(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Session.set('available', false);
|
Session.set('available', false);
|
||||||
Meteor.call('resolveDefaultContainers', function (err) {
|
Docker.resolveDefaultContainers(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,11 +142,11 @@ Meteor.setInterval(function () {
|
||||||
|
|
||||||
Meteor.setInterval(function () {
|
Meteor.setInterval(function () {
|
||||||
if (Installer.isUpToDate()) {
|
if (Installer.isUpToDate()) {
|
||||||
resolveWatchers(function () {});
|
Sync.resolveWatchers(function () {});
|
||||||
if (!Session.get('boot2dockerOff')) {
|
if (!Session.get('boot2dockerOff')) {
|
||||||
fixBoot2DockerVM(function (err) {
|
fixBoot2DockerVM(function (err) {
|
||||||
if (err) { console.log(err); return; }
|
if (err) { console.log(err); return; }
|
||||||
Meteor.call('recoverApps');
|
AppUtil.recover();
|
||||||
fixDefaultImages(function (err) {
|
fixDefaultImages(function (err) {
|
||||||
if (err) { console.log(err); return; }
|
if (err) { console.log(err); return; }
|
||||||
fixDefaultContainers(function (err) {
|
fixDefaultContainers(function (err) {
|
||||||
|
@ -162,4 +157,3 @@ Meteor.setInterval(function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,7 @@
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
min-height: @dashboard-content-height;
|
min-height: @dashboard-content-height;
|
||||||
|
.text-select();
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-badge {
|
.warning-badge {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
i {
|
i {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -12,3 +12,11 @@
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-select() {
|
||||||
|
-ms-user-select: text;
|
||||||
|
-moz-user-select: -moz-text;
|
||||||
|
-khtml-user-select: text;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
|
@ -19,9 +19,8 @@ Template.dashboard_apps_settings.events({
|
||||||
var envKey = $button.data('key');
|
var envKey = $button.data('key');
|
||||||
var configVars = getConfigVars($form);
|
var configVars = getConfigVars($form);
|
||||||
delete configVars[envKey];
|
delete configVars[envKey];
|
||||||
Meteor.call('configVar', appId, configVars, function () {
|
AppUtil.configVar(appId, configVars);
|
||||||
$button.removeAttr('disabled');
|
$button.removeAttr('disabled');
|
||||||
});
|
|
||||||
},
|
},
|
||||||
'submit .form-env-vars': function (e) {
|
'submit .form-env-vars': function (e) {
|
||||||
var $form = $(e.currentTarget);
|
var $form = $(e.currentTarget);
|
||||||
|
@ -31,10 +30,9 @@ Template.dashboard_apps_settings.events({
|
||||||
var newVal = $form.find('input[name="env-var-value"]').val().trim();
|
var newVal = $form.find('input[name="env-var-value"]').val().trim();
|
||||||
if (newKey && newVal) {
|
if (newKey && newVal) {
|
||||||
configVars[newKey] = newVal;
|
configVars[newKey] = newVal;
|
||||||
Meteor.call('configVar', appId, configVars, function () {
|
AppUtil.configVar(appId, configVars);
|
||||||
$form.find('input[name="env-var-key"]').val('');
|
$form.find('input[name="env-var-key"]').val('');
|
||||||
$form.find('input[name="env-var-value"]').val('');
|
$form.find('input[name="env-var-value"]').val('');
|
||||||
});
|
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -43,9 +41,7 @@ Template.dashboard_apps_settings.events({
|
||||||
'click .btn-delete-app': function () {
|
'click .btn-delete-app': function () {
|
||||||
var result = confirm("Are you sure you want to delete this app?");
|
var result = confirm("Are you sure you want to delete this app?");
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
Meteor.call('deleteApp', this._id, function (err) {
|
AppUtil.remove(this._id);
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
Router.go('dashboard_apps');
|
Router.go('dashboard_apps');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,7 @@ Template.dashboard_single_app.events({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'click .btn-restart': function () {
|
'click .btn-restart': function () {
|
||||||
Meteor.call('restartApp', this._id, function (err) {
|
AppUtil.restart(this._id);
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
'click .btn-folder': function () {
|
'click .btn-folder': function () {
|
||||||
var exec = require('child_process').exec;
|
var exec = require('child_process').exec;
|
||||||
|
@ -44,8 +42,6 @@ Template.dashboard_single_app.events({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'click .btn-logs': function () {
|
'click .btn-logs': function () {
|
||||||
Meteor.call('getAppLogs', this._id, function (err) {
|
AppUtil.logs(this._id);
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
Template.modal_create_app.helpers({
|
Template.modal_create_app.helpers({
|
||||||
images: function () {
|
images: function () {
|
||||||
return Images.find({status: 'READY'}, {sort: {createdAt: -1}});
|
return Images.find({status: 'READY'}, {sort: {createdAt: -1}});
|
||||||
|
@ -8,24 +11,53 @@ Template.modal_create_app.events({
|
||||||
'submit #form-create-app': function (e) {
|
'submit #form-create-app': function (e) {
|
||||||
var $form = $(e.currentTarget);
|
var $form = $(e.currentTarget);
|
||||||
var formData = $form.serializeObject();
|
var formData = $form.serializeObject();
|
||||||
Meteor.call('formCreateApp', formData, function (errors, cleaned) {
|
var validationResult = formValidate(formData, FormSchema.formCreateApp);
|
||||||
if (errors) {
|
if (validationResult.errors) {
|
||||||
clearFormErrors($form);
|
clearFormErrors($form);
|
||||||
showFormErrors($form, errors.details);
|
showFormErrors($form, validationResult.errors.details);
|
||||||
} else {
|
} else {
|
||||||
clearFormErrors($form);
|
clearFormErrors($form);
|
||||||
Meteor.call('createApp', cleaned, function (err) {
|
var cleaned = validationResult.cleaned;
|
||||||
|
var appName = cleaned.name;
|
||||||
|
var appPath = path.join(Util.KITE_PATH, appName);
|
||||||
|
if (!fs.existsSync(appPath)) {
|
||||||
|
console.log('Created Kite ' + appName + ' directory.');
|
||||||
|
fs.mkdirSync(appPath, function (err) {
|
||||||
if (err) { throw 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.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
Template.modal_create_image.rendered = function () {
|
Template.modal_create_image.rendered = function () {
|
||||||
$('#modal-create-image').bind('hidden.bs.modal', function () {
|
$('#modal-create-image').bind('hidden.bs.modal', function () {
|
||||||
Router.go('dashboard_images');
|
Router.go('dashboard_images');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Template.modal_create_image.helpers({
|
|
||||||
githubConfig: function () {
|
|
||||||
return ServiceConfiguration.configurations.findOne({service: 'github'});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.modal_create_image.events({
|
Template.modal_create_image.events({
|
||||||
'click #btn-pick-directory': function () {
|
'click #btn-pick-directory': function () {
|
||||||
$('#directory-picker').click();
|
$('#directory-picker').click();
|
||||||
|
@ -20,26 +17,55 @@ Template.modal_create_image.events({
|
||||||
$('#picked-directory-error').html('');
|
$('#picked-directory-error').html('');
|
||||||
if (pickedDirectory) {
|
if (pickedDirectory) {
|
||||||
$('#picked-directory').html('<strong>' + pickedDirectory + '<strong>');
|
$('#picked-directory').html('<strong>' + pickedDirectory + '<strong>');
|
||||||
Meteor.call('validateDirectory', pickedDirectory, function (err) {
|
if (!Util.hasDockerfile(pickedDirectory)) {
|
||||||
if (err) {
|
$('#picked-directory-error').html('Only directories with Dockerfiles are supported now.');
|
||||||
$('#picked-directory-error').html(err.reason);
|
$('#btn-create-image').attr('disabled', 'disabled');
|
||||||
$('#btn-create-image').attr('disabled', 'disabled');
|
} else {
|
||||||
} else {
|
$('#btn-create-image').removeAttr('disabled');
|
||||||
$('#btn-create-image').removeAttr('disabled');
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
$('#picked-directory').html('');
|
$('#picked-directory').html('');
|
||||||
$('#btn-create-image').attr('disabled', 'disabled');
|
$('#btn-create-image').attr('disabled', 'disabled');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'click #btn-create-image': function () {
|
'click #btn-create-image': function () {
|
||||||
var pickedDirectory = $('#directory-picker').val();
|
var directory = $('#directory-picker').val();
|
||||||
$('#directory-picker').val('');
|
$('#directory-picker').val('');
|
||||||
$('#picked-directory-error').html('');
|
$('#picked-directory-error').html('');
|
||||||
$('#picked-directory').html('');
|
$('#picked-directory').html('');
|
||||||
$('#btn-create-image').attr('disabled', 'disabled');
|
$('#btn-create-image').attr('disabled', 'disabled');
|
||||||
$('#modal-create-image').modal('hide');
|
$('#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(Util.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); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,15 +2,27 @@ Template.dashboard_images_settings.events({
|
||||||
'click .btn-delete-image': function () {
|
'click .btn-delete-image': function () {
|
||||||
var result = confirm("Are you sure you want to delete this image?");
|
var result = confirm("Are you sure you want to delete this image?");
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
Meteor.call('deleteImage', this._id, function (err) {
|
var imageId = this._id;
|
||||||
if (err) {
|
var image = Images.findOne(imageId);
|
||||||
$('#error-delete-image').html('<small class="error">' + err.reason + '</small>');
|
var app = Apps.findOne({imageId: imageId});
|
||||||
$('#error-delete-image').fadeIn();
|
if (!app) {
|
||||||
} else {
|
Images.remove({_id: image._id});
|
||||||
removeAppWatcher(this._id);
|
if (image.docker) {
|
||||||
Router.go('dashboard_images');
|
Docker.removeImage(image.docker.Id, function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
try {
|
||||||
|
Util.deleteFolder(image.path);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
Sync.removeAppWatcher(imageId);
|
||||||
|
Router.go('dashboard_images');
|
||||||
|
} else {
|
||||||
|
$('#error-delete-image').html('<small class="error">This image is currently being used by <a href="/apps/' + app.name + '">' + app.name + '</a>.</small>');
|
||||||
|
$('#error-delete-image').fadeIn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'click #btn-pick-directory': function () {
|
'click #btn-pick-directory': function () {
|
||||||
|
@ -22,15 +34,15 @@ Template.dashboard_images_settings.events({
|
||||||
var pickedDirectory = $picker.val();
|
var pickedDirectory = $picker.val();
|
||||||
$('#picked-directory-error').html('');
|
$('#picked-directory-error').html('');
|
||||||
if (pickedDirectory) {
|
if (pickedDirectory) {
|
||||||
Meteor.call('validateDirectory', pickedDirectory, function (err) {
|
if (!Util.hasDockerfile(pickedDirectory)) {
|
||||||
if (err) {
|
$('#picked-directory-error').html('Only directories with Dockerfiles are supported now.');
|
||||||
$('#picked-directory-error').html(err.reason);
|
} else {
|
||||||
} else {
|
Images.update(imageId, {
|
||||||
Meteor.call('changeDirectory', imageId, pickedDirectory, function (err) {
|
$set: {
|
||||||
if (err) { throw err; }
|
originPath: pickedDirectory
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,8 +18,8 @@ Template.dashboard_single_image.events({
|
||||||
},
|
},
|
||||||
'click .btn-rebuild': function () {
|
'click .btn-rebuild': function () {
|
||||||
$('.btn-icon').tooltip('hide');
|
$('.btn-icon').tooltip('hide');
|
||||||
Meteor.call('rebuildImage', this._id, function (err) {
|
ImageUtil.rebuild(this._id, function (err) {
|
||||||
if (err) { throw err; }
|
if (err) { console.error(err); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,9 +21,7 @@ Template.dashboard_apps_layout.events({
|
||||||
$('.header .icons a').tooltip('hide');
|
$('.header .icons a').tooltip('hide');
|
||||||
},
|
},
|
||||||
'click .btn-logs': function () {
|
'click .btn-logs': function () {
|
||||||
Meteor.call('getAppLogs', this._id, function (err) {
|
AppUtil.logs(this._id);
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
'click .btn-terminal': function () {
|
'click .btn-terminal': function () {
|
||||||
var buildCmd = function (dockerId, termApp) {
|
var buildCmd = function (dockerId, termApp) {
|
||||||
|
@ -42,9 +40,7 @@ Template.dashboard_apps_layout.events({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'click .btn-restart': function () {
|
'click .btn-restart': function () {
|
||||||
Meteor.call('restartApp', this._id, function (err) {
|
AppUtil.restart(this._id);
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
'click .btn-folder': function () {
|
'click .btn-folder': function () {
|
||||||
var exec = require('child_process').exec;
|
var exec = require('child_process').exec;
|
||||||
|
|
|
@ -18,8 +18,8 @@ Template.dashboard_images_layout.events({
|
||||||
},
|
},
|
||||||
'click .btn-rebuild': function () {
|
'click .btn-rebuild': function () {
|
||||||
$('.header .icons a').tooltip('hide');
|
$('.header .icons a').tooltip('hide');
|
||||||
Meteor.call('rebuildImage', this._id, function (err) {
|
ImageUtil.rebuild(this._id, function (err) {
|
||||||
if (err) { throw err; }
|
if (err) { console.error(err); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,57 +1,24 @@
|
||||||
Apps = new Meteor.Collection('apps');
|
Apps = new Meteor.Collection('apps');
|
||||||
|
|
||||||
schemaApps = new SimpleSchema({
|
Apps.COMMON_WEB_PORTS = [
|
||||||
imageId: {
|
80,
|
||||||
type: Meteor.ObjectID,
|
8000,
|
||||||
label: "ID of the image used by the app",
|
8080,
|
||||||
max: 200
|
3000,
|
||||||
|
5000,
|
||||||
|
2368,
|
||||||
|
1337
|
||||||
|
];
|
||||||
|
|
||||||
|
Apps.allow({
|
||||||
|
'update': function () {
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
docker: {
|
'insert': function () {
|
||||||
type: Object,
|
return true;
|
||||||
label: "Docker container data",
|
|
||||||
blackbox: true,
|
|
||||||
optional: true
|
|
||||||
},
|
},
|
||||||
status: {
|
'remove': function () {
|
||||||
type: String,
|
return true;
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,7 +55,7 @@ Apps.helpers({
|
||||||
var pickedPort = null;
|
var pickedPort = null;
|
||||||
_.each(keys, function (key) {
|
_.each(keys, function (key) {
|
||||||
var port = parseInt(key.split('/')[0], 10);
|
var port = parseInt(key.split('/')[0], 10);
|
||||||
if (_.contains(COMMON_WEB_PORTS, port) && port !== 22) {
|
if (_.contains(Apps.COMMON_WEB_PORTS, port) && port !== 22) {
|
||||||
pickedPort = port;
|
pickedPort = port;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -114,43 +81,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.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,61 +1,5 @@
|
||||||
Images = new Meteor.Collection('images');
|
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({
|
Images.helpers({
|
||||||
downloadStatus: function () {
|
downloadStatus: function () {
|
||||||
if (this.buildLogs.length > 0) {
|
if (this.buildLogs.length > 0) {
|
||||||
|
@ -89,45 +33,3 @@ Images.allow({
|
||||||
return true;
|
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,28 +1,5 @@
|
||||||
Installs = new Meteor.Collection('installs');
|
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({
|
Installs.allow({
|
||||||
'update': function () {
|
'update': function () {
|
||||||
return true;
|
return true;
|
||||||
|
@ -34,5 +11,3 @@ Installs.allow({
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Installs.attachSchema(schemaInstalls);
|
|
|
@ -1,9 +0,0 @@
|
||||||
COMMON_WEB_PORTS = [
|
|
||||||
80,
|
|
||||||
8000,
|
|
||||||
8080,
|
|
||||||
3000,
|
|
||||||
5000,
|
|
||||||
2368,
|
|
||||||
1337
|
|
||||||
]
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
var Rules = {
|
||||||
|
minLength: function (inputValue, ruleValue) {
|
||||||
|
return inputValue.length >= ruleValue;
|
||||||
|
},
|
||||||
|
uniqueAppName: function (inputValue, ruleValue) {
|
||||||
|
if (ruleValue) {
|
||||||
|
var existingApps = Apps.find({name: inputValue}).fetch();
|
||||||
|
return existingApps.length === 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validImageId: function (inputValue, ruleValue) {
|
||||||
|
if (ruleValue) {
|
||||||
|
var existingImage = Images.findOne(inputValue);
|
||||||
|
if (existingImage) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var Transforms = {
|
||||||
|
clean: function (string) {
|
||||||
|
return _(string).clean();
|
||||||
|
},
|
||||||
|
capitalize: function (string) {
|
||||||
|
return _(string).capitalize();
|
||||||
|
},
|
||||||
|
slugify: function (string) {
|
||||||
|
return _(string).slugify();
|
||||||
|
},
|
||||||
|
toLowerCase: function (string) {
|
||||||
|
return string.toLowerCase();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var Formats = {
|
||||||
|
letters: /^[a-zA-Z\ \']+$/,
|
||||||
|
alphanumeric: /^[a-zA-Z0-9\ \']+$/,
|
||||||
|
email: /^(([^<>()\[\]\\.,;:\s@\"]+(\.[^<>()\[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||||
|
phone: /^\b\d{3}[\-.]?\d{3}[\-.]?\d{4}\b$/
|
||||||
|
};
|
||||||
|
|
||||||
|
var runTransforms = function (input, transforms) {
|
||||||
|
var result = input;
|
||||||
|
_.each(transforms, function (transform) {
|
||||||
|
result = Transforms[transform](result);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
var cleanForm = function (formInput, formSchema) {
|
||||||
|
var cleanedForm = {};
|
||||||
|
for (var name in formInput) {
|
||||||
|
if (formSchema[name]) {
|
||||||
|
if (formSchema[name].transforms) {
|
||||||
|
cleanedForm[name] = runTransforms(formInput[name], formSchema[name].transforms);
|
||||||
|
} else {
|
||||||
|
cleanedForm[name] = formInput[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleanedForm;
|
||||||
|
};
|
||||||
|
|
||||||
|
var buildMessage = function (errorName, schema) {
|
||||||
|
if (errorName === 'required') {
|
||||||
|
if (!schema.messages || !schema.messages.required) {
|
||||||
|
var prefix = 'a';
|
||||||
|
if (_.contains(['a', 'e', 'i', 'o', 'u'], schema.label[0].toLowerCase())) {
|
||||||
|
prefix = 'an';
|
||||||
|
}
|
||||||
|
return 'Please provide ' + prefix + ' ' + schema.label + '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorName === 'format') {
|
||||||
|
if (!schema.messages || !schema.messages.format) {
|
||||||
|
return 'Please provide a valid ' + schema.label + '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schema.messages[errorName].replace('{{label}}', schema.label);
|
||||||
|
};
|
||||||
|
|
||||||
|
var validFormat = function (input, schema) {
|
||||||
|
return Formats[schema.format].test(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
var runRules = function (inputValue, schema, errorObj, cleanedFormInput) {
|
||||||
|
var rules = schema.rules;
|
||||||
|
if (rules) {
|
||||||
|
for (var name in rules) {
|
||||||
|
if (rules.hasOwnProperty(name)) {
|
||||||
|
var ruleValue = rules[name];
|
||||||
|
if (!Rules[name](inputValue, ruleValue, cleanedFormInput)) {
|
||||||
|
errorObj[name] = buildMessage(name, schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
formValidate = function (formInput, formSchema) {
|
||||||
|
var errorData = {};
|
||||||
|
var cleanedFormInput = cleanForm(formInput, formSchema);
|
||||||
|
for (var name in cleanedFormInput) {
|
||||||
|
if (cleanedFormInput.hasOwnProperty(name)) {
|
||||||
|
var errorObj = {};
|
||||||
|
var input = cleanedFormInput[name];
|
||||||
|
var schema = formSchema[name];
|
||||||
|
if (schema.required) {
|
||||||
|
if (!input) {
|
||||||
|
errorObj.required = buildMessage('required', schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input) {
|
||||||
|
if (schema.format) {
|
||||||
|
if (!validFormat(input, schema)) {
|
||||||
|
errorObj.format = buildMessage('format', schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schema.rules) {
|
||||||
|
runRules(input, schema, errorObj, cleanedFormInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(errorObj).length > 0) {
|
||||||
|
errorData[name] = errorObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(errorData).length > 0) {
|
||||||
|
return {
|
||||||
|
errors: errorData,
|
||||||
|
cleaned: cleanedFormInput
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
cleaned: cleanedFormInput
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"dockerode": "2.0.3",
|
|
||||||
"tar": "0.1.20",
|
|
||||||
"ansi-to-html": "0.2.0",
|
|
||||||
"async": "0.9.0"
|
|
||||||
}
|
|
|
@ -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)();
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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; }
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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 <a href="/apps/' + app.name + '">' + app.name + "</a>.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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();
|
|
|
@ -2,15 +2,9 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"bootstrap3-less": {},
|
"bootstrap3-less": {},
|
||||||
"iron-router": {},
|
"iron-router": {},
|
||||||
"npm": {},
|
|
||||||
"headers": {},
|
|
||||||
"handlebar-helpers": {},
|
"handlebar-helpers": {},
|
||||||
"collection2": {},
|
|
||||||
"collection-hooks": {},
|
|
||||||
"moment": {},
|
|
||||||
"underscore-string-latest": {},
|
"underscore-string-latest": {},
|
||||||
"collection-helpers": {},
|
"collection-helpers": {},
|
||||||
"octicons": {},
|
|
||||||
"fast-render": {},
|
"fast-render": {},
|
||||||
"iron-router-ga": {}
|
"iron-router-ga": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,9 @@
|
||||||
"basePackages": {
|
"basePackages": {
|
||||||
"bootstrap3-less": {},
|
"bootstrap3-less": {},
|
||||||
"iron-router": {},
|
"iron-router": {},
|
||||||
"npm": {},
|
|
||||||
"headers": {},
|
|
||||||
"handlebar-helpers": {},
|
"handlebar-helpers": {},
|
||||||
"collection2": {},
|
|
||||||
"collection-hooks": {},
|
|
||||||
"moment": {},
|
|
||||||
"underscore-string-latest": {},
|
"underscore-string-latest": {},
|
||||||
"collection-helpers": {},
|
"collection-helpers": {},
|
||||||
"octicons": {},
|
|
||||||
"fast-render": {},
|
"fast-render": {},
|
||||||
"iron-router-ga": {}
|
"iron-router-ga": {}
|
||||||
},
|
},
|
||||||
|
@ -27,36 +21,11 @@
|
||||||
"tag": "v0.8.2",
|
"tag": "v0.8.2",
|
||||||
"commit": "05415a8891ea87a00fb1e2388585f2ca5a38e0da"
|
"commit": "05415a8891ea87a00fb1e2388585f2ca5a38e0da"
|
||||||
},
|
},
|
||||||
"npm": {
|
|
||||||
"git": "https://github.com/arunoda/meteor-npm.git",
|
|
||||||
"tag": "v0.2.6",
|
|
||||||
"commit": "177ab6118de5bf8cffb19481343d5762ff7a2aaf"
|
|
||||||
},
|
|
||||||
"headers": {
|
|
||||||
"git": "https://github.com/gadicohen/meteor-headers.git",
|
|
||||||
"tag": "v0.0.24",
|
|
||||||
"commit": "3c09e682895e13c71ca0114baf3c09ee9c507709"
|
|
||||||
},
|
|
||||||
"handlebar-helpers": {
|
"handlebar-helpers": {
|
||||||
"git": "https://github.com/raix/Meteor-handlebar-helpers.git",
|
"git": "https://github.com/raix/Meteor-handlebar-helpers.git",
|
||||||
"tag": "v0.1.1",
|
"tag": "v0.1.1",
|
||||||
"commit": "0b407ab65e7c1ebd53d71aef0de2e2c1d21a597c"
|
"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",
|
|
||||||
"commit": "261f61f07b371ae913463fba8964a9b93cab531b"
|
|
||||||
},
|
|
||||||
"moment": {
|
|
||||||
"git": "https://github.com/acreeger/meteor-moment.git",
|
|
||||||
"tag": "v2.8.1",
|
|
||||||
"commit": "722ea63783d594341023836b7d418ab2567dab8c"
|
|
||||||
},
|
|
||||||
"underscore-string-latest": {
|
"underscore-string-latest": {
|
||||||
"git": "https://github.com/TimHeckel/meteor-underscore-string.git",
|
"git": "https://github.com/TimHeckel/meteor-underscore-string.git",
|
||||||
"tag": "v2.3.3",
|
"tag": "v2.3.3",
|
||||||
|
@ -67,11 +36,6 @@
|
||||||
"tag": "v0.3.1",
|
"tag": "v0.3.1",
|
||||||
"commit": "eff6c859cd91eae324f6c99ab755992d0f271d91"
|
"commit": "eff6c859cd91eae324f6c99ab755992d0f271d91"
|
||||||
},
|
},
|
||||||
"octicons": {
|
|
||||||
"git": "https://github.com/Keith-S/meteor-octicons.git",
|
|
||||||
"tag": "v0.1.0",
|
|
||||||
"commit": "6bb85a8b4e7d8a23ccbfb8d5c043431a135301b4"
|
|
||||||
},
|
|
||||||
"fast-render": {
|
"fast-render": {
|
||||||
"git": "https://github.com/arunoda/meteor-fast-render.git",
|
"git": "https://github.com/arunoda/meteor-fast-render.git",
|
||||||
"tag": "v1.0.0",
|
"tag": "v1.0.0",
|
||||||
|
@ -87,16 +51,6 @@
|
||||||
"tag": "v0.2.0",
|
"tag": "v0.2.0",
|
||||||
"commit": "4a2d53e35ba036b0c189c7ceca34be494d4c6c97"
|
"commit": "4a2d53e35ba036b0c189c7ceca34be494d4c6c97"
|
||||||
},
|
},
|
||||||
"inject-initial": {
|
|
||||||
"git": "https://github.com/gadicc/meteor-inject-initial.git",
|
|
||||||
"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": {
|
"blaze-layout": {
|
||||||
"git": "https://github.com/EventedMind/blaze-layout.git",
|
"git": "https://github.com/EventedMind/blaze-layout.git",
|
||||||
"tag": "v0.2.5",
|
"tag": "v0.2.5",
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
"chokidar": "git+https://github.com/usekite/chokidar.git",
|
"chokidar": "git+https://github.com/usekite/chokidar.git",
|
||||||
"exec": "^0.1.2",
|
"exec": "^0.1.2",
|
||||||
"moment": "2.8.1",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ BASE_IMAGE_VERSION=0.0.2
|
||||||
BASE_IMAGE_VERSION_FILE=base-images-$BASE_IMAGE_VERSION.tar.gz
|
BASE_IMAGE_VERSION_FILE=base-images-$BASE_IMAGE_VERSION.tar.gz
|
||||||
BASE_IMAGE_FILE=base-images.tar.gz
|
BASE_IMAGE_FILE=base-images.tar.gz
|
||||||
|
|
||||||
VIRTUALBOX_FILE=virtualbox-4.3.12.pkg
|
VIRTUALBOX_FILE=virtualbox-4.3.14.pkg
|
||||||
|
|
||||||
BOOT2DOCKER_CLI_VERSION=1.2.0
|
BOOT2DOCKER_CLI_VERSION=1.2.0
|
||||||
BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION
|
BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION
|
||||||
|
|
Loading…
Reference in New Issue