Moving Sync to client side

This commit is contained in:
Jeff Morgan 2014-08-28 21:56:08 -07:00
parent 9a3ee1e363
commit 2cb401684d
18 changed files with 254 additions and 142 deletions

View File

@ -1,6 +1,6 @@
# [Kitematic](https://kitematic.com)
![Kitematic Screenshot](http://kitematic.com/img/screenshot.0c17.png)
![Kitematic Screenshot](https://kitematic.com/img/screenshot.5843.png)
## Table of Contents

View File

@ -2,7 +2,7 @@ var exec = require('exec');
var path = require('path');
boot2dockerexec = function (command, callback) {
exec(path.join(getBinDir(), 'boot2docker') + ' --lowerip=192.168.59.103 --upperip=192.168.59.103 --dhcp=false ' + command, function(err, stdout) {
exec(path.join(getBinDir(), 'boot2docker') + ' --lowerip=192.168.59.103 --upperip=192.168.59.103 ' + command, function(err, stdout) {
callback(err, stdout);
});
};
@ -93,26 +93,30 @@ getBoot2DockerMemoryUsage = function (callback) {
};
getBoot2DockerInfo = function (callback) {
getBoot2DockerState(function (err, state) {
if (err) {
callback(err, null);
return;
}
if (state === 'poweroff') {
callback(null, {state: state});
} else {
getBoot2DockerMemoryUsage(function (err, mem) {
if (err) { callback(null, {state: state}); }
getBoot2DockerDiskUsage(function (err, disk) {
boot2dockerexec('ssh "sudo ifconfig eth1 192.168.59.103 netmask 255.255.255.0"', function (err, stdout) {
exec('VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0', function (err, stdout) {
getBoot2DockerState(function (err, state) {
if (err) {
callback(err, null);
return;
}
if (state === 'poweroff') {
callback(null, {state: state});
} else {
getBoot2DockerMemoryUsage(function (err, mem) {
if (err) { callback(null, {state: state}); }
callback(null, {
state: state,
memory: mem,
disk: disk
getBoot2DockerDiskUsage(function (err, disk) {
if (err) { callback(null, {state: state}); }
callback(null, {
state: state,
memory: mem,
disk: disk
});
});
});
});
}
}
});
});
});
};
@ -174,6 +178,8 @@ installBoot2DockerAddons = function (callback) {
console.log(stdout);
callback(err);
});
boot2dockerexec('ssh "sudo ifconfig eth1 192.168.59.103 netmask 255.255.255.0"', function (err, stdout) {});
exec('VBoxManage dhcpserver remove --netname HostInterfaceNetworking-vboxnet0', function (err, stdout) {});
};
startBoot2Docker = function (callback) {

118
meteor/client/lib/sync.js Normal file
View File

@ -0,0 +1,118 @@
var chokidar = require('chokidar');
var path = require('path');
var fs = require('fs');
var child_process = require('child_process');
var exec = require('exec');
var watchers = {};
removeAppWatcher = function (id) {
if (watchers[id]) {
watchers[id].watcher.close();
delete watchers[id];
}
};
addAppWatcher = function (app) {
removeAppWatcher(app._id);
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/});
var syncing = false;
var syncFunc = function (event, changedPath) {
if (syncing) {
return;
}
syncing = true;
// Make sure that if they delete the app_name folder under ~/Kitematic, we don't delete all the volumes.
// Deleting files inside the app_name folder _will_ delete the volumes.
var errorPattern = /The\sfile\s(.*)\son\shost/g;
var archiveErrorPattern = /Archive\s(.*)\son\shost\s.*\sshould\sbe\sDELETED/g;
var cmd = path.join(getBinDir(), 'unison');
var args = [
cmd,
vmPath,
appPath,
'-prefer',
vmPath,
'-servercmd',
'sudo\ unison',
'-batch',
'-log=false',
'-confirmbigdel=false',
'-ignore',
'Name\ {*.tmp,*.unison,*.swp,*.pyc,.DS_Store}',
'-auto',
'-sshargs',
'-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(getHomePath(), '.ssh/id_boot2docker')
];
if (!fs.existsSync(appPath)) {
args.push('-ignorearchives');
console.log('Created Kite ' + app.name + ' directory.');
fs.mkdirSync(appPath, function (err) {
if (err) { throw err; }
});
}
exec(args, function (err, out, code) {
try {
if (err.indexOf('the archives are locked.') !== -1) {
var results = errorPattern.exec(err);
var location = results[1].replace(' ', '\\ ');
exec('/bin/rm -rf ' + location, function () {
console.log('Removed unison file.');
console.log(location);
});
}
if (err.indexOf('The archive file is missing on some hosts') !== -1) {
var results = archiveErrorPattern.exec(err);
var location = results[1].replace(' ', '\\ ');
var fullLocation = path.join(getHomePath(), 'Library/Application\\ Support/Unison', location);
var cmd = '/bin/rm -rf ' + fullLocation;
exec(cmd, function () {});
}
} catch (e) {
// console.error(e);
}
syncing = false;
});
};
watchers[app._id] = {
watcher: watcher,
sync: syncFunc
};
// do a sync
syncFunc();
watcher.on('all', syncFunc);
};
resolveWatchers = function (callback) {
var apps = Apps.find({}).fetch();
var ids = _.map(apps, function(app) {
return app._id;
});
var watcherKeys = _.keys(watchers);
var toAdd = _.difference(ids, watcherKeys);
var toRemove = _.difference(watcherKeys, ids);
_.each(toAdd, function (id) {
addAppWatcher(Apps.findOne(id), function () {});
});
_.each(toRemove, function (id) {
removeAppWatcher(id);
});
// Run a sync for 'pulling' changes in the volumes.
/*_.each(watchers, function (watcher) {
watcher.sync();
});*/
callback();
};

View File

@ -4,7 +4,7 @@ getBinDir = function () {
if (process.env.NODE_ENV === 'development') {
return path.join(path.join(process.env.PWD, '..'), 'resources');
} else {
return path.join(path.join(process.cwd(), '../../..'), 'resources');
return path.join(process.cwd(), 'resources');
}
};

View File

@ -105,7 +105,7 @@ fixDefaultImages = function (callback) {
Session.set('available', false);
Meteor.call('resolveDefaultImages', function (err) {
if (err) {
callback(err);
callback();
} else {
Session.set('available', true);
callback();
@ -145,10 +145,10 @@ fixInterval = null;
startFixInterval = function () {
stopFixInterval();
fixInterval = Meteor.setInterval(function () {
resolveWatchers(function () {});
fixBoot2DockerVM(function (err) {
if (err) { console.log(err); return; }
Meteor.call('resolveWatchers');
Meteor.call('recoverApps');
// Meteor.call('recoverApps');
fixDefaultImages(function (err) {
if (err) { console.log(err); return; }
fixDefaultContainers(function (err) {
@ -162,4 +162,4 @@ startFixInterval = function () {
stopFixInterval = function () {
Meteor.clearInterval(fixInterval);
fixInterval = null;
};
};

View File

@ -7,6 +7,7 @@ Template.dashboard_images_settings.events({
$('#error-delete-image').html('<small class="error">' + err.reason + '</small>');
$('#error-delete-image').fadeIn();
} else {
removeAppWatcher(this._id);
Router.go('dashboard_images');
}
});

View File

@ -30,7 +30,7 @@
<a onclick="trackLink('create app from image')" href="#" class="btn-icon btn-create-app" data-toggle="tooltip" data-placement="bottom" title="Create App"><span class="typcn typcn-plus-outline"></span></a>
{{/if}}
<a onclick="trackLink('open image folder')" href="#" class="btn-icon btn-folder" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Folder"><span class="typcn typcn-folder-open"></span></a>
{{#if $eq status 'READY'}}
{{#if $neq status 'BUILDING'}}
<a onclick="trackLink('rebuild image')" href="#" class="btn-icon btn-rebuild" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Rebuild"><span class="typcn typcn-refresh-outline"></span></a>
{{/if}}
<a onclick="trackLink('image logs')" href="/images/{{_id}}/logs" class="btn-icon" data-toggle="tooltip" data-placement="bottom" title="Logs"><span class="typcn typcn-document-text"></span></a>

View File

@ -15,7 +15,7 @@
<a onclick="trackLink('create app from image')" href="#" class="btn-create-app" data-toggle="tooltip" data-placement="bottom" title="Create App" data-container="body"><span class="typcn typcn-plus-outline"></span></a>
{{/if}}
<a onclick="trackLink('open image folder')" href="#" class="btn-folder" data-toggle="tooltip" data-placement="bottom" title="Folder" data-container="body"><span class="typcn typcn-folder-open"></span></a>
{{#if $eq status 'READY'}}
{{#if $neq status 'BUILDING'}}
<a onclick="trackLink('rebuild image')" href="#" class="btn-rebuild" data-toggle="tooltip" data-placement="bottom" title="Rebuild" data-container="body"><span class="typcn typcn-refresh-outline"></span></a>
{{/if}}
</span>

View File

@ -2,6 +2,5 @@
"dockerode": "2.0.3",
"tar": "0.1.20",
"ansi-to-html": "0.2.0",
"async": "0.9.0",
"chokidar": "0.8.4"
"async": "0.9.0"
}

View File

@ -1,100 +1,9 @@
var watchers = {};
removeBindFolder = function (name, callback) {
exec(path.join(getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function(err, stdout) {
callback(err, stdout);
});
};
removeAppWatcher = function (id) {
if (watchers[id]) {
watchers[id].watcher.close();
delete watchers[id];
}
};
addAppWatcher = function (app) {
removeAppWatcher(app._id);
var appPath = path.join(KITE_PATH, 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: /[\/\\]\./});
var syncFunc = function () {
// Make sure that if they delete the app_name folder under ~/Kitematic, we don't delete all the volumes.
// Deleting files inside the app_name folder _will_ delete the volumes.
var rootMissing = '';
if (!fs.existsSync(appPath)) {
rootMissing = '-ignorearchives';
console.log('Created Kite ' + app.name + ' directory.');
fs.mkdirSync(appPath, function (err) {
if (err) { throw err; }
});
}
var errorPattern = /The\sfile\s(.*)\son\shost/g;
var archiveErrorPattern = /Archive\s(.*)\son\shost\s.*\sshould\sbe\sDELETED/g;
var command = path.join(getBinDir(), 'unison') + ' ' + vmPath + ' ' + appPath + ' -prefer ' + vmPath + ' ' + rootMissing + ' -servercmd "sudo unison" -batch -log=false -confirmbigdel=false -ignore "Name {*.tmp,*.unison,*.swp,*.pyc,.DS_STORE}" -auto -sshargs "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i ' + path.join(getHomePath(), '.ssh/id_boot2docker') + '"';
exec(command, function (err) {
if (err) {
var results;
var location;
console.error(err);
try {
if (err.message.indexOf('the archives are locked.') !== -1) {
results = errorPattern.exec(err.message);
location = results[1].replace(' ', '\\ ');
exec('/bin/rm -rf ' + location, function () {
console.log('Removed unison file.');
console.log(location);
});
}
if (err.message.indexOf('The archive file is missing on some hosts') !== -1) {
results = archiveErrorPattern.exec(err.message);
location = results[1].replace(' ', '\\ ');
var fullLocation = path.join(getHomePath(), 'Library/Application\\ Support/Unison', location);
var cmd = '/bin/rm -rf ' + fullLocation;
exec(cmd, function () {});
}
} catch (e) {
console.error(e);
}
}
});
};
watchers[app._id] = {
watcher: watcher,
sync: syncFunc
};
watcher.on('all', syncFunc);
};
resolveWatchers = function (callback) {
var apps = Apps.find({}).fetch();
var ids = _.map(apps, function(app) {
return app._id;
});
var watcherKeys = _.keys(watchers);
var toAdd = _.difference(ids, watcherKeys);
var toRemove = _.difference(watcherKeys, ids);
_.each(toAdd, function (id) {
addAppWatcher(Apps.findOne(id), function () {});
});
_.each(toRemove, function (id) {
removeAppWatcher(id);
});
// Run a sync for 'pulling' changes in the volumes.
_.each(watchers, function (watcher) {
watcher.sync();
});
callback();
};
recoverApps = function (callback) {
var apps = Apps.find({}).fetch();
_.each(apps, function (app) {
@ -143,7 +52,6 @@ Meteor.methods({
if (err) { console.error(err); }
var appPath = path.join(KITE_PATH, app.name);
deleteFolder(appPath);
removeAppWatcher(app._id);
removeBindFolder(app.name, function () {
console.log('Deleted Kite ' + app.name + ' directory.');
Fiber(function () {

View File

@ -94,9 +94,7 @@ runContainer = function (app, image, callback) {
if (err) { callback(err, null); return; }
console.log('Started container: ' + container.id);
// Use dig to refresh the DNS
exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out, code) {
console.log(out);
});
exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out, code) {});
callback(null, container);
});
});
@ -147,9 +145,7 @@ restartApp = function (app, callback) {
callback(null);
// Use dig to refresh the DNS
exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out, code) {
console.log(out);
});
exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out, code) {});
} else {
callback(null);
}
@ -582,20 +578,29 @@ buildImage = function (image, callback) {
console.error(e);
}
Fiber(function () {
var imageData = getImageDataSync(image._id);
var oldImageId = null;
try {
var imageData = getImageDataSync(image._id);
var oldImageId = null;
Images.update(image._id, {
$set: {
docker: imageData,
status: 'READY'
}
});
} catch (e) {
console.log(e);
Images.update(image._id, {
$set: {
status: 'ERROR'
}
});
}
if (image.docker && image.docker.Id) {
oldImageId = image.docker.Id;
}
if (oldImageId && oldImageId !== imageData.Id) {
removeImageSync(oldImageId);
}
Images.update(image._id, {
$set: {
docker: imageData,
status: 'READY'
}
});
}).run();
callback(null);
});

View File

@ -137,9 +137,15 @@ Meteor.methods({
}
var app = Apps.findOne({imageId: imageId});
if (!app) {
deleteImageSync(image);
deleteFolder(image.path);
Images.remove({_id: image._id});
console.log('here');
try {
deleteImageSync(image);
deleteFolder(image.path);
} catch (e) {
console.log(e);
} finally {
Images.remove({_id: image._id});
}
} else {
throw new Meteor.Error(400, 'This image is currently being used by <a href="/apps/' + app.name + '">' + app.name + "</a>.");
}

View File

@ -6,4 +6,4 @@ path = Meteor.require('path');
exec = Meteor.require('child_process').exec;
async = Meteor.require('async');
Fiber = Meteor.require('fibers');
chokidar = Meteor.require('chokidar');
child_process = Meteor.require('child_process');

View File

@ -6,7 +6,7 @@ getBinDir = function () {
if (process.env.NODE_ENV === 'development') {
return path.join(path.join(process.env.PWD, '..'), 'resources');
} else {
return path.join(path.join(process.cwd(), '../../..'), 'resources');
return path.join(process.cwd(), '../../../resources');
}
};

70
npm-debug.log Normal file
View File

@ -0,0 +1,70 @@
0 info it worked if it ends with ok
1 verbose cli [ '/Users/jmorgan/workspace/kite-desktop/script/../cache/node/bin/node',
1 verbose cli '/Users/jmorgan/workspace/kite-desktop/cache/node/bin/npm',
1 verbose cli 'install',
1 verbose cli '--quiet' ]
2 info using npm@1.4.14
3 info using node@v0.10.29
4 verbose node symlink /Users/jmorgan/workspace/kite-desktop/script/../cache/node/bin/node
5 warn package.json Kitematic@0.1.0 No repository field.
6 verbose readDependencies using package.json deps
7 verbose install where, deps [ '/Users/jmorgan/workspace/kite-desktop',
7 verbose install [ 'async', 'chokidar', 'exec', 'moment', 'open' ] ]
8 info preinstall Kitematic@0.1.0
9 verbose readDependencies using package.json deps
10 verbose already installed skipping moment@2.8.1 /Users/jmorgan/workspace/kite-desktop
11 verbose already installed skipping open@0.0.5 /Users/jmorgan/workspace/kite-desktop
12 verbose already installed skipping exec@^0.1.2 /Users/jmorgan/workspace/kite-desktop
13 verbose already installed skipping async@^0.9.0 /Users/jmorgan/workspace/kite-desktop
14 verbose cache add [ 'chokidar@git+https://github.com/usekite/chokidar.git', null ]
15 verbose cache add name=undefined spec="chokidar@git+https://github.com/usekite/chokidar.git" args=["chokidar@git+https://github.com/usekite/chokidar.git",null]
16 verbose parsed url { protocol: null,
16 verbose parsed url slashes: null,
16 verbose parsed url auth: null,
16 verbose parsed url host: null,
16 verbose parsed url port: null,
16 verbose parsed url hostname: null,
16 verbose parsed url hash: null,
16 verbose parsed url search: null,
16 verbose parsed url query: null,
16 verbose parsed url pathname: 'chokidar@git+https://github.com/usekite/chokidar.git',
16 verbose parsed url path: 'chokidar@git+https://github.com/usekite/chokidar.git',
16 verbose parsed url href: 'chokidar@git+https://github.com/usekite/chokidar.git' }
17 verbose cache add name="chokidar" spec="git+https://github.com/usekite/chokidar.git" args=["chokidar","git+https://github.com/usekite/chokidar.git"]
18 verbose parsed url { protocol: 'git+https:',
18 verbose parsed url slashes: true,
18 verbose parsed url auth: null,
18 verbose parsed url host: 'github.com',
18 verbose parsed url port: null,
18 verbose parsed url hostname: 'github.com',
18 verbose parsed url hash: null,
18 verbose parsed url search: null,
18 verbose parsed url query: null,
18 verbose parsed url pathname: '/usekite/chokidar.git',
18 verbose parsed url path: '/usekite/chokidar.git',
18 verbose parsed url href: 'git+https://github.com/usekite/chokidar.git' }
19 silly lockFile 9e9e81d6--github-com-usekite-chokidar-git https://github.com/usekite/chokidar.git
20 verbose lock https://github.com/usekite/chokidar.git /Users/jmorgan/.npm/9e9e81d6--github-com-usekite-chokidar-git.lock
21 verbose addRemoteGit [ 'https://github.com/usekite/chokidar.git', 'master' ]
22 verbose git remote.origin.url https://github.com/usekite/chokidar.git
23 error git fetch -a origin (https://github.com/usekite/chokidar.git) fatal: unable to access 'https://github.com/usekite/chokidar.git/': Could not resolve host: github.com
24 silly lockFile 9e9e81d6--github-com-usekite-chokidar-git https://github.com/usekite/chokidar.git
25 silly lockFile 9e9e81d6--github-com-usekite-chokidar-git https://github.com/usekite/chokidar.git
26 error Error: Command failed: fatal: unable to access 'https://github.com/usekite/chokidar.git/': Could not resolve host: github.com
26 error
26 error at ChildProcess.exithandler (child_process.js:647:15)
26 error at ChildProcess.emit (events.js:98:17)
26 error at maybeClose (child_process.js:755:16)
26 error at Socket.<anonymous> (child_process.js:968:11)
26 error at Socket.emit (events.js:95:17)
26 error at Pipe.close (net.js:465:12)
27 error If you need help, you may report this *entire* log,
27 error including the npm and node versions, at:
27 error <http://github.com/npm/npm/issues>
28 error System Darwin 13.3.0
29 error command "/Users/jmorgan/workspace/kite-desktop/script/../cache/node/bin/node" "/Users/jmorgan/workspace/kite-desktop/cache/node/bin/npm" "install" "--quiet"
30 error cwd /Users/jmorgan/workspace/kite-desktop
31 error node -v v0.10.29
32 error npm -v 1.4.14
33 error code 128
34 verbose exit [ 1, true ]

View File

@ -16,6 +16,7 @@
},
"dependencies": {
"async": "^0.9.0",
"chokidar": "git+https://github.com/usekite/chokidar.git",
"exec": "^0.1.2",
"moment": "2.8.1",
"open": "0.0.5"

View File

@ -40,7 +40,7 @@ echo '<?xml version="1.0" encoding="UTF-8"?>
DIR=$(dirname "$0")
USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'`
sudo -u $USER $DIR/boot2docker init
sudo -u $USER $DIR/boot2docker init --lowerip=192.168.59.103 --upperip=192.168.59.103
# Add entries to routing table for Kitematic VM
/sbin/route delete 172.17.0.0/16 192.168.59.103

View File

@ -7,8 +7,6 @@ export ROOT_URL=https://localhost:3000
export DOCKER_HOST=http://192.168.59.103
export DOCKER_PORT=2375
$BASE/script/setup.sh
#export METEOR_SETTINGS=`cat $BASE/meteor/settings_dev.json`
#echo $METEOR_SETTINGS