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