From ff87fbdc29ed8c7400d86059cca744699f51f38a Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 27 Aug 2014 18:18:59 -0700 Subject: [PATCH 01/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 750c0d812a..10325b3aa8 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Kitematic](https://kitematic.com) -![Kitematic Screenshot](http://kitematic.com/img/screenshot.0c17.png) +![Kitematic Screenshot](https://s3.amazonaws.com/kite-installer/screenshot.5843.png) ## Table of Contents From d0c6939ad62fb425f5c32aa7fe4936b1f677b66b Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 28 Aug 2014 00:39:33 -0700 Subject: [PATCH 02/58] Does not run pulling again if image already exists. --- meteor/server/docker.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 2127ed8be4..3e5228dead 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -502,7 +502,13 @@ killAndRemoveContainers = function (names, callback) { pullImageFromDockerfile = function (dockerfile, imageId, callback) { var fromImage = getFromImage(dockerfile); console.log('From image: ' + fromImage); - if (fromImage) { + var installedImage = null; + try { + installedImage = getImageDataSync(fromImage); + } catch (e) { + console.error(e); + } + if (fromImage && !installedImage) { Fiber(function () { Images.update(imageId, { $set: { @@ -542,6 +548,8 @@ pullImageFromDockerfile = function (dockerfile, imageId, callback) { callback(null); }); }); + } else { + callback(null); } }; From 188e66209d4c9fb368d0312f76c07ec94453dc75 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 28 Aug 2014 13:38:18 -0700 Subject: [PATCH 03/58] Added code to check if web port is disabled. --- meteor/collections/apps.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 9ac599e4ce..7c4373aba0 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -64,6 +64,8 @@ Apps.helpers({ var image = Images.findOne(app.imageId); if (image && image.meta.app && image.meta.app.webPort) { return 'http://' + app.name + '.dev:' + image.meta.app.webPort; + } else if (image && image.meta.app && image.meta.app.webPort === false) { + return null; } else { // Picks the best port if (app.docker && app.docker.NetworkSettings.Ports) { From 937e29fc125cc7b2eec1ca626e039a27feae7d3e Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 28 Aug 2014 19:27:56 -0700 Subject: [PATCH 04/58] Displays host url and ports properly in app settings. --- .../dashboard/apps/dashboard-apps-settings.html | 12 +++++++++++- meteor/collections/apps.js | 15 +++++++++++++++ meteor/server/docker.js | 4 ++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/meteor/client/views/dashboard/apps/dashboard-apps-settings.html b/meteor/client/views/dashboard/apps/dashboard-apps-settings.html index fff224a04e..35acdf1dcb 100755 --- a/meteor/client/views/dashboard/apps/dashboard-apps-settings.html +++ b/meteor/client/views/dashboard/apps/dashboard-apps-settings.html @@ -10,9 +10,19 @@
- {{url}} + {{hostUrl}}
+ {{#if ports}} +
+
+ +
+
+ {{ports}} +
+
+ {{/if}}
diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 7c4373aba0..3b0ff8b147 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -59,6 +59,21 @@ Apps.helpers({ image: function () { return Images.findOne(this.imageId); }, + hostUrl: function () { + return this.name + '.dev'; + }, + ports: function () { + var app = this; + if (app.docker && app.docker.NetworkSettings.Ports) { + var ports = _.map(_.keys(app.docker.NetworkSettings.Ports), function (portObj) { + var port = parseInt(portObj.split('/')[0], 10); + return port; + }); + return ports.join(', '); + } else { + return null; + } + }, url: function () { var app = this; var image = Images.findOne(app.imageId); diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 3e5228dead..06d52b63c2 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -94,7 +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) { + exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out) { console.log(out); }); callback(null, container); @@ -147,7 +147,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) { + exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out) { console.log(out); }); } else { From 5038b2e5e893214100860a863ad12a91cca92eb3 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 28 Aug 2014 19:47:13 -0700 Subject: [PATCH 05/58] Fixed crash when deleting old images. --- meteor/server/docker.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 06d52b63c2..67536837ff 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -596,7 +596,11 @@ buildImage = function (image, callback) { oldImageId = image.docker.Id; } if (oldImageId && oldImageId !== imageData.Id) { - removeImageSync(oldImageId); + try { + removeImageSync(oldImageId); + } catch (e) { + console.error(e); + } } Images.update(image._id, { $set: { From 06c4afec936e2ebefa10ba17b5c73e6f97a99c9b Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 28 Aug 2014 19:52:58 -0700 Subject: [PATCH 06/58] Fixed crash when restarting app. --- meteor/server/docker.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 67536837ff..2394434c16 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -136,7 +136,11 @@ var getFromImage = function (dockerfile) { restartApp = function (app, callback) { if (app.docker && app.docker.Id) { - restartContainerSync(app.docker.Id); + try { + restartContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } var containerData = getContainerDataSync(app.docker.Id); Fiber(function () { Apps.update(app._id, {$set: { @@ -145,7 +149,6 @@ restartApp = function (app, callback) { }}); }).run(); callback(null); - // Use dig to refresh the DNS exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function(err, out) { console.log(out); From e05baa74bdcc81a2229240e00155d16fb138b2be Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Thu, 28 Aug 2014 22:21:34 -0700 Subject: [PATCH 07/58] Fixing sync problems --- meteor/client/lib/sync.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/meteor/client/lib/sync.js b/meteor/client/lib/sync.js index 19d24ffa9e..4dbbcace5d 100644 --- a/meteor/client/lib/sync.js +++ b/meteor/client/lib/sync.js @@ -20,15 +20,14 @@ addAppWatcher = function (app) { var vmPath = 'ssh://docker@localhost:2022/' + vmDir; var watcher = chokidar.watch(appPath, {ignored: /.*\.DS_Store/}); var syncing = false; + var willSyncAgain = false; var syncFunc = function (event, changedPath) { if (syncing) { + willSyncAgain = true; 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'); @@ -79,6 +78,10 @@ addAppWatcher = function (app) { // console.error(e); } syncing = false; + if (willSyncAgain) { + syncFunc(); + willSyncAgain = false; + } }); }; @@ -110,9 +113,9 @@ resolveWatchers = function (callback) { }); // Run a sync for 'pulling' changes in the volumes. - /*_.each(watchers, function (watcher) { + _.each(watchers, function (watcher) { watcher.sync(); - });*/ + }); callback(); }; \ No newline at end of file From b0d03ccbd3768ad541938aa6b352870946c9a5d9 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Thu, 28 Aug 2014 22:35:15 -0700 Subject: [PATCH 08/58] Fixing readme uninstall instructions --- README.md | 2 +- npm-debug.log | 70 --------------------------------------------------- 2 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 npm-debug.log diff --git a/README.md b/README.md index 10325b3aa8..79c6e2a3ea 100755 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ - Remove VirtualBox - rm /usr/local/bin/boot2docker - sudo route delete 172.17.0.0/16 192.168.59.103 (disable routing to containers through VM) -- rm -rf ~/Application\ Support/Kitematic (remove app data) +- rm -rf ~/Library/Application\ Support/Kitematic (remove app data) - rm /Library/LaunchAgents/com.kitematic.route.plist (remove launch job that sets up routing to the containers) ## Bugs and Feature Requests diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index e7412a0033..0000000000 --- a/npm-debug.log +++ /dev/null @@ -1,70 +0,0 @@ -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. (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 -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 ] From 548d8f33ba08eaf08d369400f7285a3dbe2f6791 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Fri, 29 Aug 2014 00:59:30 -0700 Subject: [PATCH 09/58] fixing installer --- meteor/client/lib/virtualbox.js | 32 ++++++----- .../views/dashboard/setup/setup-install.html | 9 +++- .../views/dashboard/setup/setup-install.js | 51 ++++++++++++------ meteor/public/step_failed.png | Bin 0 -> 4112 bytes meteor/public/step_failed@2x.png | Bin 0 -> 5829 bytes resources/install | 9 +--- 6 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 meteor/public/step_failed.png create mode 100644 meteor/public/step_failed@2x.png diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js index 4000289978..6d362492a9 100644 --- a/meteor/client/lib/virtualbox.js +++ b/meteor/client/lib/virtualbox.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var exec = require('exec'); +var child_process = require('child_process'); var path = require('path'); isVirtualBoxInstalled = function (callback) { @@ -24,22 +24,15 @@ isResolverSetup = function (callback) { }); }; -setupVirtualBoxAndResolver = function (skipVirtualBox, callback) { +setupResolver = function (callback) { var installFile = path.join(getBinDir(), 'install'); var cocoaSudo = path.join(getBinDir(), 'cocoasudo'); var execCommand = cocoaSudo + ' --prompt="Kitematic Setup wants to make changes. Type your password to allow this." ' + installFile; - console.log(execCommand); - var env = { - VIRTUALBOX_PKG_PATH: path.join(getBinDir(), 'virtualbox-4.3.12.pkg') - }; - if (!skipVirtualBox) { - env.INSTALL_VIRTUALBOX = true; - } - exec(execCommand, {env: env}, function (err, stdout) { + child_process.exec(execCommand, function (error, stdout, stderr) { console.log(stdout); - if (err) { - console.log(err); - callback(err); + if (error) { + console.log(error); + callback(error); return; } console.log('Virtualbox Installation & Resolver config complete.'); @@ -47,3 +40,16 @@ setupVirtualBoxAndResolver = function (skipVirtualBox, callback) { }); }; +setupVirtualBox = function (callback) { + child_process.exec('open -W ' + path.join(getBinDir(), 'virtualbox-4.3.12.pkg'), function (error, stdout, stderr) { + console.log(stdout); + if (error) { + console.log(error); + callback(error); + return; + } + console.log('Virtualbox Installation running.'); + callback(); + }); +}; + diff --git a/meteor/client/views/dashboard/setup/setup-install.html b/meteor/client/views/dashboard/setup/setup-install.html index 62f666f6fa..b179a4f7d2 100644 --- a/meteor/client/views/dashboard/setup/setup-install.html +++ b/meteor/client/views/dashboard/setup/setup-install.html @@ -21,10 +21,17 @@ {{/if}} {{#if $eq this.index currentInstallStep}} - {{> spinner}} + {{#if $eq this.index failedStep}} + + {{else}} + {{> spinner}} + {{/if}}
{{this.message}} + {{#if $eq this.index failedStep}} +

{{failedError}}

+ {{/if}}
{{/if}}
diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js index 5ff517fa80..af562d2b91 100644 --- a/meteor/client/views/dashboard/setup/setup-install.js +++ b/meteor/client/views/dashboard/setup/setup-install.js @@ -8,13 +8,18 @@ var async = require('async'); // - imperativeMessage: Message to show before running var steps = [ - // Step 0, set up VirtualBox + // Set up VirtualBox { install: function (callback) { isVirtualBoxInstalled(function (err, virtualBoxInstalled) { - setupVirtualBoxAndResolver(virtualBoxInstalled, function () { + var installedYet = false; + if (!virtualBoxInstalled) { + setupVirtualBox(function (err) { + callback(err); + }); + } else { callback(); - }); + } }); }, pastMessage: 'VirtualBox installed', @@ -22,7 +27,19 @@ var steps = [ imperativeMessage: 'Install VirtualBox if necessary' }, - // Step 1: Set up the VM for running Kitematic apps + // Set up the routing. + { + install: function (callback) { + setupResolver(function (err) { + callback(err); + }); + }, + pastMessage: 'Container routing set up (root required).', + message: 'Setting up container routing (root required).', + imperativeMessage: 'Set up container routing to VM (root required).' + }, + + // Set up the VM for running Kitematic apps { install: function (callback) { console.log('Checking if vm exists...'); @@ -49,7 +66,7 @@ var steps = [ imperativeMessage: 'Set up the Kitematic VM' }, - // Step 2: Start the Kitematic VM + // Start the Kitematic VM { install: function (callback) { startBoot2Docker(function (err) { @@ -61,7 +78,7 @@ var steps = [ imperativeMessage: 'Start the Kitematic VM' }, - // Step 3: Set up the default Kitematic images + // Set up the default Kitematic images { install: function (callback) { Meteor.call('reloadDefaultContainers', function (err) { @@ -93,8 +110,10 @@ runSetup = function (callback) { }, function (err) { if (err) { // if any of the steps fail - console.log('Kitematic setup failed at step' + currentStep); + console.log('Kitematic setup failed at step ' + currentStep); console.log(err); + Session.set('failedStep', currentStep); + Session.set('failedError', err); callback(err); } else { // Setup Finished @@ -121,10 +140,6 @@ Template.setup_install.rendered = function() { } }; -Template.setup_install.events({ - -}); - Template.setup_install.steps = function () { return steps.map(function (step, index) { step.index = index; @@ -135,11 +150,15 @@ Template.setup_install.steps = function () { Template.setup_install.helpers({ currentInstallStep: function () { return Session.get('currentInstallStep'); + }, + installComplete: function () { + return Session.get('currentInstallStep') === steps.length; + }, + failedStep: function () { + return Session.get('failedStep'); + }, + failedError: function () { + return Session.get('failedError'); } }); -Template.setup_install.helpers({ - installComplete: function () { - return Session.get('currentInstallStep') === steps.length; - } -}); diff --git a/meteor/public/step_failed.png b/meteor/public/step_failed.png new file mode 100644 index 0000000000000000000000000000000000000000..d8b8fb64bbe6ee010647d1a3ff44fc8c6908e836 GIT binary patch literal 4112 zcmV+r5by7aP)X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@h&RTw>YcDrq9X$ytY ziWRA%6i{sOfm*GJ0U8w)G*F28htz;28WN4##2Cd`Bg6+%AsC`!g2gC*s?uou;VYUL z3Tl-mM51CS<(0lj+u7Z@erJ~1PNy^7Ey79e+&lL>-}jxluel22fL{Daj#-djgAndj z8f99k5(Rx3LujbuO6yLoa1?QT9_j9BRbPI1F5U1d9pbqak5z^^8U-WEaZ=LZ zhSu7Cu8>+dl#Ld}N8L`OhS@}S@pWsi>3mO`Hujn`&<-1LPXUp{s; zqO}{I3WY;0A*hLld^yW_MKCIT9Dl}QOvWCa(Pu<#qG4=y0{A&yXrINAkpjzlI-_~p z7I&NxQ^V;9WrSJ`D1$;S1#B_URy-PC>Yk=#HZIJV5pI_?ksBx?GXe~m>FFDTYl~6n z4_mQ$&H_%Z5i>jc^)W>(tN^w&0GAi17D5iW^znf`?ef9Q>6q;iF&|u4VPr-Q2!U{H z!}U$q1NDyrp%Bp554^e^_~-br-QBuE&bMdR0~2$BcpPZj4gCD8n_;)Pqhw^XHDoS` ze7fUcVnin8ZBA(L5m7N0c>KHwhG2q*dSGVhsNi-VZR8LXPG83GfFEps_2m55Xfk+g z8-vkm9|>X=ovW7tVF~C)&Vyt1$`NVu{^LxMRMC3q8ne@A3Axa##e~rSdf>P zj*=p$XSg<_(w4I!B0I{9&Xd3^O+a_A*OGkaT8S4GwsL)zCwqe$4D|Bbz`vcN8IcTs z5#-Wm3=vK+YB1EGSuo~kIP6~8E&RHm-Qw_AlS3|jQh>Rs!N9;kP73a@xlgVaOwe_D zxX;__k~KjNx#>bQa4Gw4n7|lwW&wqh0?n8*86anTP=@SzuD0~RlX8Qc3*PPQ_U73^ zPQa<^rY75>!9Z?ydSh61Be4Eq-}4|D&}s0@AeTPrKr|T0&pVrvTfGpdTkS96k0St! z7ZgJu`lf}|Kg8yW`=95J^l>X+>%-Z-6)jYu|!BN=45WV z*h1JqS?MNKx0?Gf#pl}W|NGE(F3ITxN(vEWow%i}#DSHks^;3XK&tU26G~2Ig`{9wCGVA-peQfa%3A;N3l?7&PTDxd_OL z#8J2A7;c{bB6D~n27a-KsY2(et=QXAh6BIJ743pYM`OWty;!yMC`zV2;T;J_P@oVJ zL`;cPPvXd556M(bMaQ4Hh{clY$_e6I^R%9Gbw3_hatgDq{6si!i3p!7Uc!4!Ax;7j zG+Wy1X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@2%-9=dB zjRZk~6pxYxG%A#pM8QWAQWX~Rs6;UcieQBhp%U^ZDWL)tqY;Ee3=mC4VK6Bv3(7|z z1fqtQO0<9|No8U&StO;fHDC7qN=m6)p1GDDdcOp)_q%4bT+N2uGUOmlxQYQCky%XnRM zfjz|$^R8K?k&Z3j5ToR4rl_w#Q6D&&A_}ZH#RMB}0eP(bREfDsV$oMZ^?~(jLftdIf0$+&Uz;Sx?U5(Yu(N|Co!Rx*vx!mO*%WZ4Q(FH z+S*Vg&|^OC>v^J7DQAQRqoewqAekI*Q$dD^|EM3URIV@Ef&sYyFs;w2d~>R=>J@5z;*Q;UBAj zu@w}@p(9GU0sLEN{IjxDHP zT>8S&d0uSvRP4EGVr@X?rI_JDNfYmLIbvHmNkcY@P1oK4v6@ODiKa1L&1V zhITX!DfPh^whI}SJ;+e{y<0-v~tqL0pg`aCTmE!ctD z`wR!5-<70ScMjHi3U?$Xv3*E_l=y8=-{@mHh!jHjYywZ_*=*WzLj$G*NLzP(KapR| z7J2Y{g(Zb#5R=%{;X~+y(eTSkgJ z`~z$yQ`BSuNz+UWViA+r)InW7B*`cEX!+_IpJb`^2rp{9#iXmFr^_$&5t;j-NO@Un z_nU4InRTB?Nl9y+-!qOS2C=kI2X#@WPtpDv@RdG0hbIwYgaUYa(RQ%_Hj*5 zk$Et%qCBMnj#X&ffSD6SYo(^x~P-7J&MyedP$$1Lv7Dd6PY}%MwOK9vO4j8 zi<9E)objANZvIS>j&^D^W50jz6M15#NON<>E=di+pvdg|MTQMds!qA6zaX+;smRe2 zDNUIwxE$LXHlz}wjUz*Ap4rX5tusW0rjra`L=d4zSCOC2$W8|P(y=*HMM7{io3Ic2 zW+Q_*{32)rZSf)W)tV9TT@SOanGkNNHcleLZEwQ7&W?%>A`2fCsqUolydC!=P4Vj$ zB8?gH#0;80c|c^)0B?A@*O|{i%wmyC4Z5byV`7#lRUbGL>&C>jHA9#kCmFtH$Hhw` zfBR>fO4BydQ3gah?iSXu%`X|mp)DTC&}yw&0^hAS1gNwKQGwF0vqMg|%=$M(URba3 zy&t)c;56^x2*NyPozFdnrIa?%79WzhWiUo_5GTaA)A#IfD&DYJ3MIf_M0z_^pc6?bdVVBkZ|R$jz>^;VI4zs1Px&$J`Q?f$)c_#4M0 z^B-Tz?Y50;VjQt@CODQfw_G3K>qWiPP|Nj&F6QT*#H{2;zirw%)Rn!AsN(>CmGs^gzud@k2X{wSd;h6(8&6@ zZHLJ6KNW`*l0j_hD4>u76%33Fs7M5ntqAh=J0ejSKjZtkZQ0YA#M^P%^CCOmOV^rr z9l0=YFIMG^lnH=_FIkPa(vqo%_j+Owt4L&6hJXtLL9oobRXOzB{l3VPEAjl22Pqs& z3}O{iz=eSatzb?YGCzjBw-@^qERS3!{R+oChq1&cl34MB*x&*yZX(7ONgZJO_7bVR z$N&C>7I9BA31f*-biu0@DGed=6;b;2v3H|YSyBUSnZU8!#S*KS0xk?y7DDJQk__k@ z-hbB!!qo5^{kVQkF-wYs0dE*CLzsQ1ZiD8{IuM@!^k{#4d(_3qz_qBHdJA z8H~q!CV`!~)k3KtCb6lb&;l+Dxw`tSez++J{?LIicv|{gM?n+-#LBuu2Xz%d^aGw; z-Q}#IsylyD08ATV_+aEfry@ld@&{ReUW9p3KSCYUMV$o_Ro&?mLk+riM?vsL41s|^ z^p-DmDrDO4mrtSmR)pt%gn3u$r0xQU8q|NMfh6t@xvI)z8a#v6(ZfX^nSy7QK!y$( zs!yL0d2Ff3iPLuBIqUogby7EN$VbS*utD3I3fTi@F6NB&r4jZUHNWp%PJ#pyaH_6V zHP2bcHa|k$e5*%Wa+Ej+d+!099+sDGQZ+;Cz3NWt_vS8J-s8a0a~@Q&5BqXV zHsJ#xZJ;eVh~+yt;82?okd)gHe%D8cufC`zDhoRe8_g}LJqN;V@L`y(g}5L zek)x|);h~q_)OmsKdhBj&kzm{t8b3?8OdBaU*9V7=a-W!KRPJ#tL47ms3n2$Z5%O( zj~Q*}Kbs<+hj{8(iY+@gVcT|k z>Yk|+etR2J3+5h>-rc_qLU!wZNlsf0y}NIcabxRpjwKqIuL%lG2C<2mFBx2w;3LS` zx0y>fVVbuT)Yr8I}+9#rBErrIn<8NZKB#1n6uWrpn8&Rh!`r-2v)p zTqeBBRS&yO>o%D%?hJH33$vfdrrWb)2~xK0tTI=v?WH2kCE36~&vr8uj9LM7%qS#m zy^%qCJrlytAO&eDnJY(6hRml{^-)KTm$SaTiP^hHgSE8w>c(v{ZL(wF2!2sMBdyaSL2;w2>S|EC;5 zx9epmhD1ylcM4DH$Pox{krgZ;gT(ljoC(pIKulkp{|sfURrle+GI?WLXZ*OOBYwJF zoWP45s4(Su`Qf{!8XH@RO_a=N@GGfSix$%Yz_yd7{q#zM|E=fGX=dQ%(D&s{Fi1{M=)uP;m(lIfH zv1b2)4)_a42(K7Q< /etc/resolver/dev -# Install virtualbox -if [ -n "$INSTALL_VIRTUALBOX" ]; then - /usr/sbin/installer -pkg $VIRTUALBOX_PKG_PATH -target / -fi +DIR=$(dirname "$0") +USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'` /bin/rm -rf /Library/LaunchAgents/com.kitematic.route.plist @@ -37,9 +35,6 @@ echo ' ' > /Library/LaunchAgents/com.kitematic.route.plist -DIR=$(dirname "$0") -USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'` - sudo -u $USER $DIR/boot2docker init --lowerip=192.168.59.103 --upperip=192.168.59.103 # Add entries to routing table for Kitematic VM From 6a6f21fa51249a40ee6c3f8842fabe9f072af129 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Fri, 29 Aug 2014 10:49:26 -0700 Subject: [PATCH 10/58] Fixing installer & mongodb startup bug. --- index.js | 42 ++++++++++++------- .../views/dashboard/setup/setup-install.html | 6 ++- .../views/dashboard/setup/setup-install.js | 14 ++++--- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index 50d313a2f0..3a3d3a4989 100644 --- a/index.js +++ b/index.js @@ -41,10 +41,15 @@ var start = function (callback) { // One for meteor, one for mongo freeport(function (err, webPort) { freeport(function(err, mongoPort) { - child_process.exec('kill $(ps aux -e | grep \'PURPOSE=KITEMATIC\' | awk \'{print $2}\')', function (error, stdout, stderr) { + console.log('MongoDB: ' + mongoPort); + console.log('webPort: ' + webPort); + child_process.exec('kill $(ps aux -e | grep PURPOSE=KITEMATIC | awk \'{print $2}\')', function (error, stdout, stderr) { + console.log(error); + console.log(stdout); + console.log(stderr); var mongoChild = child_process.spawn(path.join(process.cwd(), 'resources', 'mongod'), ['--bind_ip', '127.0.0.1', '--dbpath', dataPath, '--port', mongoPort, '--unixSocketPrefix', dataPath], { env: { - DB_PURPOSE: 'KITEMATIC' + PURPOSE: 'KITEMATIC' } }); var started = false; @@ -70,17 +75,6 @@ var start = function (callback) { env: user_env }); - var cleanUpChildren = function () { - console.log('Cleaning up children.') - mongoChild.kill(); - nodeChild.kill(); - }; - - process.on('exit', cleanUpChildren); - process.on('uncaughtException', cleanUpChildren); - process.on('SIGINT', cleanUpChildren); - process.on('SIGTERM', cleanUpChildren); - var opened = false; nodeChild.stdout.setEncoding('utf8'); nodeChild.stdout.on('data', function (data) { @@ -91,7 +85,7 @@ var start = function (callback) { } else { return; } - callback(rootURL); + callback(rootURL, nodeChild, mongoChild); } }); } @@ -102,7 +96,19 @@ var start = function (callback) { } }; -start(function (url) { +start(function (url, nodeChild, mongoChild) { + var cleanUpChildren = function () { + console.log('Cleaning up children.') + mongoChild.kill(); + nodeChild.kill(); + }; + if (nodeChild && mongoChild) { + process.on('exit', cleanUpChildren); + process.on('uncaughtException', cleanUpChildren); + process.on('SIGINT', cleanUpChildren); + process.on('SIGTERM', cleanUpChildren); + } + var gui = require('nw.gui'); var mainWindow = gui.Window.get(); gui.App.on('reopen', function () { @@ -117,7 +123,11 @@ start(function (url) { mainWindow.on('close', function (type) { this.hide(); if (type === 'quit') { - this.close(false); + console.log('here'); + if (nodeChild && mongoChild) { + cleanUpChildren(); + } + this.close(true); } console.log('Window Closed.'); }); diff --git a/meteor/client/views/dashboard/setup/setup-install.html b/meteor/client/views/dashboard/setup/setup-install.html index b179a4f7d2..a58b7c88b9 100644 --- a/meteor/client/views/dashboard/setup/setup-install.html +++ b/meteor/client/views/dashboard/setup/setup-install.html @@ -8,7 +8,7 @@
- {{this.imperativeMessage}} + {{this.futureMessage}}
{{/if}} {{#if $lt this.index currentInstallStep}} @@ -31,6 +31,10 @@ {{this.message}} {{#if $eq this.index failedStep}}

{{failedError}}

+ {{else}} + {{#if this.subMessage}} +

{{this.subMessage}}

+ {{/if}} {{/if}}
{{/if}} diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js index af562d2b91..38ab389d33 100644 --- a/meteor/client/views/dashboard/setup/setup-install.js +++ b/meteor/client/views/dashboard/setup/setup-install.js @@ -24,7 +24,7 @@ var steps = [ }, pastMessage: 'VirtualBox installed', message: 'Installing VirtualBox', - imperativeMessage: 'Install VirtualBox if necessary' + futureMessage: 'Install VirtualBox if necessary' }, // Set up the routing. @@ -36,7 +36,7 @@ var steps = [ }, pastMessage: 'Container routing set up (root required).', message: 'Setting up container routing (root required).', - imperativeMessage: 'Set up container routing to VM (root required).' + futureMessage: 'Set up container routing to VM (root required).' }, // Set up the VM for running Kitematic apps @@ -63,7 +63,7 @@ var steps = [ }, pastMessage: 'Set up the Kitematic VM', message: 'Setting up the Kitematic VM...', - imperativeMessage: 'Set up the Kitematic VM' + futureMessage: 'Set up the Kitematic VM' }, // Start the Kitematic VM @@ -74,8 +74,9 @@ var steps = [ }); }, pastMessage: 'Started the Kitematic VM', - message: 'Starting the Kitematic VM...', - imperativeMessage: 'Start the Kitematic VM' + message: 'Starting the Kitematic VM', + subMessage: '(This may take a few minutes)', + futureMessage: 'Start the Kitematic VM', }, // Set up the default Kitematic images @@ -87,7 +88,8 @@ var steps = [ }, pastMessage: 'Started the Kitematic VM', message: 'Setting up the default Kitematic images...', - imperativeMessage: 'Set up the default Kitematic images' + subMessage: '(This may take a few minutes)', + futureMessage: 'Set up the default Kitematic images', } ]; From 192436c1d6debf7e84cd5f68ff82f855af57a202 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Fri, 29 Aug 2014 10:52:40 -0700 Subject: [PATCH 11/58] JSHint. --- meteor/server/docker.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 37514ab905..59349a9794 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -589,9 +589,9 @@ buildImage = function (image, callback) { console.error(e); } Fiber(function () { + var imageData = null; try { - var imageData = getImageDataSync(image._id); - var oldImageId = null; + imageData = getImageDataSync(image._id); Images.update(image._id, { $set: { docker: imageData, @@ -606,10 +606,11 @@ buildImage = function (image, callback) { } }); } + var oldImageId = null; if (image.docker && image.docker.Id) { oldImageId = image.docker.Id; } - if (oldImageId && oldImageId !== imageData.Id) { + if (oldImageId && imageData && oldImageId !== imageData.Id) { try { removeImageSync(oldImageId); } catch (e) { From 3817e346df9e2807339e8d5e017854ea22eb45f8 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Fri, 29 Aug 2014 14:08:13 -0700 Subject: [PATCH 12/58] Fixed bug with loading default containers. --- meteor/server/docker.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 59349a9794..d71b12688b 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -370,12 +370,14 @@ reloadDefaultContainers = function (callback) { return container.name; }); - var results; + var ready = false; async.until(function () { - return !!results; + return ready; }, function (callback) { docker.listContainers(function (err, containers) { - results = containers; + if (!err) { + ready = true; + } callback(); }); }, function (err) { From 4c7f6affb90d96f0fe77b0315dfffc5f5179b99f Mon Sep 17 00:00:00 2001 From: Sean Li Date: Fri, 29 Aug 2014 14:19:25 -0700 Subject: [PATCH 13/58] Added sub message for routing step during setup. --- meteor/client/views/dashboard/setup/setup-install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js index 38ab389d33..004ac755cf 100644 --- a/meteor/client/views/dashboard/setup/setup-install.js +++ b/meteor/client/views/dashboard/setup/setup-install.js @@ -36,6 +36,7 @@ var steps = [ }, pastMessage: 'Container routing set up (root required).', message: 'Setting up container routing (root required).', + subMessage: '(This may take a few minutes)', futureMessage: 'Set up container routing to VM (root required).' }, @@ -163,4 +164,3 @@ Template.setup_install.helpers({ return Session.get('failedError'); } }); - From 2e3fc3d627c742501948f40b198ea79d7ea5fd07 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Sat, 30 Aug 2014 02:55:49 -0700 Subject: [PATCH 14/58] Fixing startup bug if boot2docker-vm already exists. --- resources/install | 2 ++ script/dist.sh | 6 ++++-- script/versions.sh | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/resources/install b/resources/install index ad6951aebf..f3535ec9fd 100755 --- a/resources/install +++ b/resources/install @@ -36,6 +36,8 @@ echo ' ' > /Library/LaunchAgents/com.kitematic.route.plist sudo -u $USER $DIR/boot2docker init --lowerip=192.168.59.103 --upperip=192.168.59.103 +VBoxManage modifyvm boot2docker-vm --nic2 hostonly --nictype2 virtio --cableconnected2 on --hostonlyadapter2 vboxnet0 +VBoxManage dhcpserver add --netname=vboxnet0 --ip=192.168.59.99 --netmask=255.255.255.0 --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 diff --git a/script/dist.sh b/script/dist.sh index 56c145a351..1aecd569a3 100755 --- a/script/dist.sh +++ b/script/dist.sh @@ -6,6 +6,7 @@ source $DIR/colors.sh source $DIR/versions.sh BASE=$DIR/.. + pushd $BASE if [ ! -d bundle ]; then @@ -18,6 +19,7 @@ rm -rf dist/osx/Kitematic.zip mkdir -p dist/osx/ cecho "-----> Creating Kitematic.app..." $blue +find cache/node-webkit -name "debug\.log" -print0 | xargs -0 rm -rf cp -R cache/node-webkit/node-webkit.app dist/osx/ mv dist/osx/node-webkit.app dist/osx/Kitematic.app mkdir -p dist/osx/Kitematic.app/Contents/Resources/app.nw @@ -50,8 +52,8 @@ if [ -f $DIR/sign.sh ]; then fi pushd dist/osx -cecho "-----> Creating disributable zip file...." $blue -zip -rq --display-dots Kitematic.zip Kitematic.app + cecho "-----> Creating disributable zip file...." $blue + ditto -c -k --sequesterRsrc --keepParent Kitematic.app Kitematic.zip popd cecho "Done." $green diff --git a/script/versions.sh b/script/versions.sh index 0c12c7cd49..f1f7853a2f 100644 --- a/script/versions.sh +++ b/script/versions.sh @@ -4,8 +4,8 @@ BASE_IMAGE_FILE=base-images.tar.gz VIRTUALBOX_FILE=virtualbox-4.3.12.pkg -BOOT2DOCKER_CLI_VERSION=1.1.2 +BOOT2DOCKER_CLI_VERSION=1.2.0 BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION BOOT2DOCKER_CLI_FILE=boot2docker -COCOASUDO_FILE=cocoasudo \ No newline at end of file +COCOASUDO_FILE=cocoasudo From 63705c33c224f13b49e99c706c0a2c252139d69e Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 30 Aug 2014 04:25:29 -0700 Subject: [PATCH 15/58] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 79c6e2a3ea..546b1abd03 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # [Kitematic](https://kitematic.com) +# [Download the latest version (Mac OS X 64-bit).](https://s3.amazonaws.com/kite-installer/Kitematic.zip) + ![Kitematic Screenshot](https://s3.amazonaws.com/kite-installer/screenshot.5843.png) From 1c9edf1512ba9e1ec0080dc9be928a25b11668d0 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 30 Aug 2014 04:25:40 -0700 Subject: [PATCH 16/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 546b1abd03..8c4cc6dac8 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Kitematic](https://kitematic.com) -# [Download the latest version (Mac OS X 64-bit).](https://s3.amazonaws.com/kite-installer/Kitematic.zip) +### [Download the latest version (Mac OS X 64-bit).](https://s3.amazonaws.com/kite-installer/Kitematic.zip) ![Kitematic Screenshot](https://s3.amazonaws.com/kite-installer/screenshot.5843.png) From 1e57a4c93f6a0879923ec5eb2d7443c087b1276f Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 30 Aug 2014 04:31:55 -0700 Subject: [PATCH 17/58] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 8c4cc6dac8..79c6e2a3ea 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # [Kitematic](https://kitematic.com) -### [Download the latest version (Mac OS X 64-bit).](https://s3.amazonaws.com/kite-installer/Kitematic.zip) - ![Kitematic Screenshot](https://s3.amazonaws.com/kite-installer/screenshot.5843.png) From 66a8932d980071abc82a4e28e7d642e39c90842d Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Sat, 30 Aug 2014 06:15:42 -0700 Subject: [PATCH 18/58] Fixing setup for boot2docker CLI --- meteor/client/lib/boot2docker.js | 4 ++-- resources/install | 2 +- script/setup.sh | 8 +++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 65333e8240..96015f50b7 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -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 ' + command, function(err, stdout) { + exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout) { callback(err, stdout); }); }; @@ -195,7 +195,7 @@ startBoot2Docker = function (callback) { console.log(err); console.log(stdout); if (err) { - if (err.indexOf('Waiting for VM to be started') !== -1) { + if (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1) { installBoot2DockerAddons(function (err) { callback(err); }); diff --git a/resources/install b/resources/install index f3535ec9fd..ca0698dbe8 100755 --- a/resources/install +++ b/resources/install @@ -35,7 +35,7 @@ echo ' ' > /Library/LaunchAgents/com.kitematic.route.plist -sudo -u $USER $DIR/boot2docker init --lowerip=192.168.59.103 --upperip=192.168.59.103 +sudo -u $USER $DIR/boot2docker init VBoxManage modifyvm boot2docker-vm --nic2 hostonly --nictype2 virtio --cableconnected2 on --hostonlyadapter2 vboxnet0 VBoxManage dhcpserver add --netname=vboxnet0 --ip=192.168.59.99 --netmask=255.255.255.0 --lowerip=192.168.59.103 --upperip=192.168.59.103 diff --git a/script/setup.sh b/script/setup.sh index 064d7be76b..c40c686e78 100755 --- a/script/setup.sh +++ b/script/setup.sh @@ -62,13 +62,11 @@ fi if [ ! -f $BASE_IMAGE_FILE ]; then - cp cache/$BASE_IMAGE_VERSION_FILE $BASE_IMAGE_FILE + cp ../cache/$BASE_IMAGE_VERSION_FILE $BASE_IMAGE_FILE fi -if [ ! -f $BOOT2DOCKER_CLI_FILE ]; then - cp cache/$BOOT2DOCKER_CLI_VERSION_FILE $BOOT2DOCKER_CLI_FILE - chmod +x $BOOT2DOCKER_CLI_FILE -fi +cp ../cache/$BOOT2DOCKER_CLI_VERSION_FILE $BOOT2DOCKER_CLI_FILE +chmod +x $BOOT2DOCKER_CLI_FILE popd From eded386889716a9475b0d15e96175b9a01961d7a Mon Sep 17 00:00:00 2001 From: Siddique Hameed Date: Sat, 30 Aug 2014 10:21:37 -0500 Subject: [PATCH 19/58] Fixing typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 79c6e2a3ea..a0a4f1c9af 100755 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ - Install demeteorizer `npm install demeteorizer -g` - Run ./script/setup.sh to download the binary requirements (things like virtualbox). -### Running the develoment Server +### Running the development Server - ./script/run.sh From 6d9b7705c4c447ac37c9e51fa796f451e3f33aad Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sat, 30 Aug 2014 09:44:31 -0700 Subject: [PATCH 20/58] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a0a4f1c9af..26c362617f 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # [Kitematic](https://kitematic.com) -![Kitematic Screenshot](https://s3.amazonaws.com/kite-installer/screenshot.5843.png) +Kitematic is still in Beta. Any effort in helping us find issues and improving the experience is greatly appreciated! +[Fixes for Known Issues](http://kitematic.com/docs/known-issue-fixes) + +![Kitematic Screenshot](https://s3.amazonaws.com/kite-installer/screenshot.5843.png) ## Table of Contents From ae6832d0b730213c43602812ae9fe488fa91eea1 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Sat, 30 Aug 2014 11:09:27 -0700 Subject: [PATCH 21/58] Fixing critical install bug --- meteor/client/lib/boot2docker.js | 2 +- resources/install | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 96015f50b7..8e939a2dc3 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -191,7 +191,7 @@ startBoot2Docker = function (callback) { if (installed) { boot2DockerVMExists(function (err, exists) { if (exists) { - boot2dockerexec('up', function (err, stdout) { + boot2dockerexec('up -v', function (err, stdout) { console.log(err); console.log(stdout); if (err) { diff --git a/resources/install b/resources/install index ca0698dbe8..723ff280d1 100755 --- a/resources/install +++ b/resources/install @@ -1,6 +1,7 @@ #!/bin/sh # Lookup the Kitematic VM resolver for .dev domains +mkdir -p /etc/resolver echo "nameserver 172.17.42.1" > /etc/resolver/dev DIR=$(dirname "$0") From a656151a3fa2e5858e82e00da722ee995b930f39 Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Sat, 30 Aug 2014 11:19:28 -0700 Subject: [PATCH 22/58] Small text change in settings --- meteor/client/views/dashboard/settings/dashboard-settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor/client/views/dashboard/settings/dashboard-settings.html b/meteor/client/views/dashboard/settings/dashboard-settings.html index 9b3f6adc3f..cf615b4d5b 100644 --- a/meteor/client/views/dashboard/settings/dashboard-settings.html +++ b/meteor/client/views/dashboard/settings/dashboard-settings.html @@ -8,7 +8,7 @@ {{#if $.Session.equals 'boot2dockerState' 'poweroff'}}

Please start Boot2Docker for Kitematic to work properly!

{{else}} -

All apps run in a Linux VM included with Kitematic. It needs to be turned to run apps.

+

All apps run in a Linux VM included with Kitematic. It needs to be turned on to run apps.

{{/if}}
From bff96466971f49fc70b7241239798462087d1c42 Mon Sep 17 00:00:00 2001 From: Michael Chiang Date: Sat, 30 Aug 2014 16:26:05 -0700 Subject: [PATCH 23/58] Emergency log submission update. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 26c362617f..1e4205d50b 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # [Kitematic](https://kitematic.com) +**Note:** If the installer gets stuck at any step for more than 1 minute, there is probably an error. Please help us troubleshoot by running it from the command line, and submit the logs to [contact@kitematic.com](mailto:contact@kitematic.com). + +1. `cd ` +2. Run `./Kitematic.app/Contents/MacOS/node-webkit` + Kitematic is still in Beta. Any effort in helping us find issues and improving the experience is greatly appreciated! [Fixes for Known Issues](http://kitematic.com/docs/known-issue-fixes) From 208b2d3f12644b2a340bfc36f2cc1ab2e360d482 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Sat, 30 Aug 2014 16:51:51 -0700 Subject: [PATCH 24/58] Hotfix for hanging on the last step of the installer. --- meteor/client/lib/boot2docker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 8e939a2dc3..281d79c0c0 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -178,6 +178,7 @@ installBoot2DockerAddons = function (callback) { console.log(stdout); callback(err); }); + exec('VBoxManage modifyvm boot2docker-vm --nic2 hostonly --nictype2 virtio --cableconnected2 on --hostonlyadapter2 vboxnet0', function (err, stdout) {}); 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) {}); }; From 8955d6bcda8e94a3c3839189ae124ad612a8db3b Mon Sep 17 00:00:00 2001 From: Michael Chiang Date: Sat, 30 Aug 2014 17:05:47 -0700 Subject: [PATCH 25/58] Updated contact information. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1e4205d50b..c0559fd0ba 100755 --- a/README.md +++ b/README.md @@ -78,20 +78,28 @@ For transparency into our release cycle and in striving to maintain backward com ## Creators +Team E-mail: [contact@kitematic.com](mailto:contact@kitematic.com) + **Sean Li** - - +- Email: [sean@kitematic.com](mailto:sean@kitematic.com) +- [LinkedIn](https://www.linkedin.com/in/lishang) **Jeffrey Morgan** - - +- Email: [jeff@kitematic.com](mailto:jeff@kitematic.com) +- [LinkedIn](https://www.linkedin.com/in/jeffdmorgan) **Michael Chiang** - - +- Email: [mike@kitematic.com](mailto:mike@kitematic.com) +- [LinkedIn](https://www.linkedin.com/in/mchiang0610) ## Copyright and License From 9d5fa7ad6963b0fce06d31bf2e11f4ede9225cf7 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sun, 31 Aug 2014 19:18:11 -0700 Subject: [PATCH 26/58] Moved basic Docker operations to namespace. --- meteor/.jshintrc | 16 ++--------- meteor/server/docker.js | 64 +++++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/meteor/.jshintrc b/meteor/.jshintrc index e2c8f682e4..2f77f05ffa 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -128,7 +128,7 @@ // Packages "Fiber": true, "moment": true, - "Docker": true, + "Dockerode": true, "byline": true, "fs": true, "zlib": true, @@ -142,6 +142,7 @@ "chokidar": true, "docker": true, "async": true, + "child_process": true, // Collections "SimpleSchema": false, @@ -161,6 +162,7 @@ "SetupController": true, // Server and Client + "Docker": true, "boot2dockerexec": true, "getBinDir": true, "getBoot2DockerIp": true, @@ -202,16 +204,8 @@ "loadKiteVolumes": true, "getAppLogs": true, "hasDockerfile": true, - "runContainer": true, - "runContainerSync": true, - "restartContainer": true, - "restartContainerSync": true, "createTarFile": true, "createTarFileSync": true, - "getImageData": true, - "getImageDataSync": true, - "removeImage": true, - "removeImageSync": true, "deleteImage": true, "checkDefaultImages": true, "resolveDefaultImages": true, @@ -239,11 +233,7 @@ "FormSchema": true, "showFormSuccess": true, "resetForm": true, - "removeContainer": true, - "removeContainerSync": true, "deleteAppSync": true, - "getContainerData": true, - "getContainerDataSync": true, // Testing "require": false, diff --git a/meteor/server/docker.js b/meteor/server/docker.js index d71b12688b..f5a3a022fe 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -1,16 +1,18 @@ -Docker = Meteor.require('dockerode'); +Dockerode = Meteor.require('dockerode'); var Convert = Meteor.require('ansi-to-html'); var convert = new Convert(); var DOCKER_HOST='192.168.59.103'; -docker = new Docker({host: '192.168.59.103', port: '2375'}); +docker = new Dockerode({host: '192.168.59.103', port: '2375'}); + +Docker = {}; hasDockerfile = function (directory) { return fs.existsSync(path.join(directory, 'Dockerfile')); }; -removeContainer = function (containerId, callback) { +Docker.removeContainer = function (containerId, callback) { var container = docker.getContainer(containerId); container.kill(function (err) { if (err) { callback(err); return; } @@ -22,8 +24,8 @@ removeContainer = function (containerId, callback) { }); }; -removeContainerSync = function (containerId) { - return Meteor._wrapAsync(removeContainer)(containerId); +Docker.removeContainerSync = function (containerId) { + return Meteor._wrapAsync(Docker.removeContainer)(containerId); }; deleteApp = function (app, callback) { @@ -32,7 +34,7 @@ deleteApp = function (app, callback) { return; } try { - removeContainerSync(app.docker.Id); + Docker.removeContainerSync(app.docker.Id); } catch (e) { console.error(e); } @@ -43,7 +45,7 @@ deleteAppSync = function (app) { return Meteor._wrapAsync(deleteApp)(app); }; -getContainerData = function (containerId, callback) { +Docker.getContainerData = function (containerId, callback) { var container = docker.getContainer(containerId); container.inspect(function (err, data) { if (err) { @@ -59,11 +61,11 @@ getContainerData = function (containerId, callback) { }); }; -getContainerDataSync = function (containerId) { - return Meteor._wrapAsync(getContainerData)(containerId); +Docker.getContainerDataSync = function (containerId) { + return Meteor._wrapAsync(Docker.getContainerData)(containerId); }; -runContainer = function (app, image, callback) { +Docker.runContainer = function (app, image, callback) { var envParam = []; _.each(_.keys(app.config), function (key) { var builtStr = key + '=' + app.config[key]; @@ -100,11 +102,11 @@ runContainer = function (app, image, callback) { }); }; -runContainerSync = function (app, image) { - return Meteor._wrapAsync(runContainer)(app, image); +Docker.runContainerSync = function (app, image) { + return Meteor._wrapAsync(Docker.runContainer)(app, image); }; -restartContainer = function (containerId, callback) { +Docker.restartContainer = function (containerId, callback) { var container = docker.getContainer(containerId); container.restart(function (err) { if (err) { @@ -117,8 +119,8 @@ restartContainer = function (containerId, callback) { }); }; -restartContainerSync = function (containerId) { - return Meteor._wrapAsync(restartContainer)(containerId); +Docker.restartContainerSync = function (containerId) { + return Meteor._wrapAsync(Docker.restartContainer)(containerId); }; var getFromImage = function (dockerfile) { @@ -135,11 +137,11 @@ var getFromImage = function (dockerfile) { restartApp = function (app, callback) { if (app.docker && app.docker.Id) { try { - restartContainerSync(app.docker.Id); + Docker.restartContainerSync(app.docker.Id); } catch (e) { console.error(e); } - var containerData = getContainerDataSync(app.docker.Id); + var containerData = Docker.getContainerDataSync(app.docker.Id); Fiber(function () { Apps.update(app._id, {$set: { status: 'READY', @@ -209,7 +211,7 @@ var convertVolumeObjToArray = function (obj) { return result; }; -getImageData = function (imageId, callback) { +Docker.getImageData = function (imageId, callback) { var image = docker.getImage(imageId.toLowerCase()); image.inspect(function (err, data) { if (err) { @@ -224,11 +226,11 @@ getImageData = function (imageId, callback) { }); }; -getImageDataSync = function (imageId) { - return Meteor._wrapAsync(getImageData)(imageId); +Docker.getImageDataSync = function (imageId) { + return Meteor._wrapAsync(Docker.getImageData)(imageId); }; -removeImage = function (imageId, callback) { +Docker.removeImage = function (imageId, callback) { var image = docker.getImage(imageId.toLowerCase()); image.remove({force: true}, function (err) { if (err) { callback(err); return; } @@ -237,8 +239,8 @@ removeImage = function (imageId, callback) { }); }; -removeImageSync = function (imageId) { - return Meteor._wrapAsync(removeImage)(imageId); +Docker.removeImageSync = function (imageId) { + return Meteor._wrapAsync(Docker.removeImage)(imageId); }; deleteImage = function (image, callback) { @@ -247,7 +249,7 @@ deleteImage = function (image, callback) { return; } try { - removeImageSync(image.docker.Id); + Docker.removeImageSync(image.docker.Id); } catch (e) { console.error(e); } @@ -374,7 +376,7 @@ reloadDefaultContainers = function (callback) { async.until(function () { return ready; }, function (callback) { - docker.listContainers(function (err, containers) { + docker.listContainers(function (err) { if (!err) { ready = true; } @@ -505,7 +507,7 @@ pullImageFromDockerfile = function (dockerfile, imageId, callback) { console.log('From image: ' + fromImage); var installedImage = null; try { - installedImage = getImageDataSync(fromImage); + installedImage = Docker.getImageDataSync(fromImage); } catch (e) { console.error(e); } @@ -593,7 +595,7 @@ buildImage = function (image, callback) { Fiber(function () { var imageData = null; try { - imageData = getImageDataSync(image._id); + imageData = Docker.getImageDataSync(image._id); Images.update(image._id, { $set: { docker: imageData, @@ -614,7 +616,7 @@ buildImage = function (image, callback) { } if (oldImageId && imageData && oldImageId !== imageData.Id) { try { - removeImageSync(oldImageId); + Docker.removeImageSync(oldImageId); } catch (e) { console.error(e); } @@ -631,11 +633,11 @@ Meteor.methods({ this.unblock(); var image = Images.findOne({_id: app.imageId}); try { - removeContainerSync(app.name); + Docker.removeContainerSync(app.name); } catch (e) {} try { - var container = runContainerSync(app, image); - var containerData = getContainerDataSync(container.id); + var container = Docker.runContainerSync(app, image); + var containerData = Docker.getContainerDataSync(container.id); Meteor.setTimeout(function () { Apps.update(app._id, {$set: { docker: containerData, From e59c03d620be56233f6609bc6348586d64c2e185 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sun, 31 Aug 2014 19:56:18 -0700 Subject: [PATCH 27/58] Moved kite functions to utilities. --- meteor/collections/apps.js | 11 +++++++ meteor/server/apps.js | 15 +++------ meteor/server/constants.js | 24 ++++++++++++++ meteor/server/docker.js | 7 ++++ meteor/server/images.js | 1 + meteor/server/kite.js | 59 ---------------------------------- meteor/server/lib/utilities.js | 35 ++++++++++++++++++++ 7 files changed, 82 insertions(+), 70 deletions(-) create mode 100644 meteor/server/constants.js delete mode 100755 meteor/server/kite.js diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 3b0ff8b147..05006eb8e2 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -116,3 +116,14 @@ Apps.helpers({ }); Apps.attachSchema(schemaApps); + +Apps.after.remove(function (userId, app) { + deleteApp(app, function (err) { + if (err) { console.error(err); } + var appPath = path.join(KITE_PATH, app.name); + deleteFolder(appPath); + removeBindFolder(app.name, function () { + console.log('Deleted Kite ' + app.name + ' directory.'); + }); + }); +}); diff --git a/meteor/server/apps.js b/meteor/server/apps.js index af9383afa3..07a51cfa58 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -29,6 +29,7 @@ recoverApps = function (callback) { Meteor.methods({ recoverApps: function () { + this.unblock(); return Meteor._wrapAsync(recoverApps)(); }, configVar: function (appId, configVars) { @@ -48,19 +49,10 @@ Meteor.methods({ if (!app) { throw new Meteor.Error(403, 'No app found with this ID'); } - deleteApp(app, function (err) { - if (err) { console.error(err); } - var appPath = path.join(KITE_PATH, app.name); - deleteFolder(appPath); - removeBindFolder(app.name, function () { - console.log('Deleted Kite ' + app.name + ' directory.'); - Fiber(function () { - Apps.remove({_id: app._id}); - }).run(); - }); - }); + Apps.remove({_id: app._id}); }, createApp: function (formData) { + this.unblock(); var validationResult = formValidate(formData, FormSchema.formCreateApp); if (validationResult.errors) { throw new Meteor.Error(400, 'Validation Failed.', validationResult.errors); @@ -123,6 +115,7 @@ Meteor.methods({ } }, resolveWatchers: function () { + this.unblock(); return Meteor._wrapAsync(resolveWatchers)(); } }); diff --git a/meteor/server/constants.js b/meteor/server/constants.js new file mode 100644 index 0000000000..e3fd37c46e --- /dev/null +++ b/meteor/server/constants.js @@ -0,0 +1,24 @@ +KITE_PATH = path.join(getHomePath(), 'Kitematic'); +KITE_TAR_PATH = path.join(KITE_PATH, '.tar'); +KITE_IMAGES_PATH = path.join(KITE_PATH, '.images'); + +if (!fs.existsSync(KITE_PATH)) { + console.log('Created Kitematic directory.'); + fs.mkdirSync(KITE_PATH, function (err) { + if (err) { throw err; } + }); +} + +if (!fs.existsSync(KITE_TAR_PATH)) { + console.log('Created Kitematic .tar directory.'); + fs.mkdirSync(KITE_TAR_PATH, function (err) { + if (err) { throw err; } + }); +} + +if (!fs.existsSync(KITE_IMAGES_PATH)) { + console.log('Created Kitematic .images directory.'); + fs.mkdirSync(KITE_IMAGES_PATH, function (err) { + if (err) { throw err; } + }); +} diff --git a/meteor/server/docker.js b/meteor/server/docker.js index f5a3a022fe..c46ec6de30 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -632,12 +632,14 @@ 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, @@ -652,18 +654,23 @@ Meteor.methods({ return DOCKER_HOST; }, reloadDefaultContainers: function () { + this.unblock(); return Meteor._wrapAsync(reloadDefaultContainers)(); }, checkDefaultImages: function () { + this.unblock(); return Meteor._wrapAsync(checkDefaultImages)(); }, resolveDefaultImages: function () { + this.unblock(); return Meteor._wrapAsync(resolveDefaultImages)(); }, checkDefaultContainers: function () { + this.unblock(); return Meteor._wrapAsync(checkDefaultContainers)(); }, resolveDefaultContainers: function () { + this.unblock(); return Meteor._wrapAsync(resolveDefaultContainers)(); } }); diff --git a/meteor/server/images.js b/meteor/server/images.js index 5429dd53b1..f83f21da7f 100755 --- a/meteor/server/images.js +++ b/meteor/server/images.js @@ -125,6 +125,7 @@ Meteor.methods({ }); }, validateDirectory: function (directory) { + this.unblock(); if (!hasDockerfile(directory)) { throw new Meteor.Error(400, "Only directories with Dockerfiles are supported now."); } diff --git a/meteor/server/kite.js b/meteor/server/kite.js deleted file mode 100755 index 712635616a..0000000000 --- a/meteor/server/kite.js +++ /dev/null @@ -1,59 +0,0 @@ -KITE_PATH = path.join(getHomePath(), 'Kitematic'); -KITE_TAR_PATH = path.join(KITE_PATH, '.tar'); -KITE_IMAGES_PATH = path.join(KITE_PATH, '.images'); - -if (!fs.existsSync(KITE_PATH)) { - console.log('Created Kitematic directory.'); - fs.mkdirSync(KITE_PATH, function (err) { - if (err) { throw err; } - }); -} - -if (!fs.existsSync(KITE_TAR_PATH)) { - console.log('Created Kitematic .tar directory.'); - fs.mkdirSync(KITE_TAR_PATH, function (err) { - if (err) { throw err; } - }); -} - -if (!fs.existsSync(KITE_IMAGES_PATH)) { - console.log('Created Kitematic .images directory.'); - fs.mkdirSync(KITE_IMAGES_PATH, function (err) { - if (err) { throw err; } - }); -} - -getImageJSON = function (directory) { - var KITE_JSON_PATH = path.join(directory, 'image.json'); - if (fs.existsSync(KITE_JSON_PATH)) { - var data = fs.readFileSync(KITE_JSON_PATH, 'utf8'); - return JSON.parse(data); - } else { - return null; - } -}; - -loadKiteVolumes = function (directory, appName) { - var KITE_VOLUMES_PATH = path.join(directory, 'volumes'); - if (fs.existsSync(KITE_VOLUMES_PATH)) { - var destinationPath = path.join(KITE_PATH, appName); - copyFolder(KITE_VOLUMES_PATH, destinationPath); - console.log('Copied volumes for: ' + appName); - } -}; - -saveImageFolder = function (directory, imageId, callback) { - var destinationPath = path.join(KITE_IMAGES_PATH, imageId); - if (!fs.existsSync(destinationPath)) { - fs.mkdirSync(destinationPath, function (err) { - if (err) { callback(err); return; } - }); - copyFolder(directory, destinationPath); - console.log('Copied image folder for: ' + imageId); - callback(null); - } -}; - -saveImageFolderSync = function (directory, imageId) { - return Meteor._wrapAsync(saveImageFolder)(directory, imageId); -}; diff --git a/meteor/server/lib/utilities.js b/meteor/server/lib/utilities.js index 6b4d68df1a..4be5a0edaa 100755 --- a/meteor/server/lib/utilities.js +++ b/meteor/server/lib/utilities.js @@ -51,3 +51,38 @@ copyFolder = function (src, dest) { } } }; + +getImageJSON = function (directory) { + var KITE_JSON_PATH = path.join(directory, 'image.json'); + if (fs.existsSync(KITE_JSON_PATH)) { + var data = fs.readFileSync(KITE_JSON_PATH, 'utf8'); + return JSON.parse(data); + } else { + return null; + } +}; + +loadKiteVolumes = function (directory, appName) { + var KITE_VOLUMES_PATH = path.join(directory, 'volumes'); + if (fs.existsSync(KITE_VOLUMES_PATH)) { + var destinationPath = path.join(KITE_PATH, appName); + copyFolder(KITE_VOLUMES_PATH, destinationPath); + console.log('Copied volumes for: ' + appName); + } +}; + +saveImageFolder = function (directory, imageId, callback) { + var destinationPath = path.join(KITE_IMAGES_PATH, imageId); + if (!fs.existsSync(destinationPath)) { + fs.mkdirSync(destinationPath, function (err) { + if (err) { callback(err); return; } + }); + copyFolder(directory, destinationPath); + console.log('Copied image folder for: ' + imageId); + callback(null); + } +}; + +saveImageFolderSync = function (directory, imageId) { + return Meteor._wrapAsync(saveImageFolder)(directory, imageId); +}; From eb46a9dee4a04b6accd7c2ad836ce6f3ca4b81fb Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sun, 31 Aug 2014 20:27:49 -0700 Subject: [PATCH 28/58] Refactored app code into hooks. --- meteor/.jshintrc | 2 +- meteor/collections/apps.js | 23 ++++++++++++++++++++ meteor/server/apps.js | 39 ++++++++++------------------------ meteor/server/lib/utilities.js | 2 +- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/meteor/.jshintrc b/meteor/.jshintrc index 2f77f05ffa..61e603f116 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -201,7 +201,7 @@ "restartApp": true, "deleteApp": true, "deleteFolder": true, - "loadKiteVolumes": true, + "copyVolumes": true, "getAppLogs": true, "hasDockerfile": true, "createTarFile": true, diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 05006eb8e2..5bbc9ef2a9 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -117,6 +117,29 @@ 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); + copyVolumes(image.path, app.name); + app = Apps.findOne(appId); + removeBindFolder(app.name, function (err) { + if (err) { + console.error(err); + } + Fiber(function () { + Meteor.call('runApp', app, function (err) { + if (err) { throw err; } + }); + }).run(); + }); +}); + Apps.after.remove(function (userId, app) { deleteApp(app, function (err) { if (err) { console.error(err); } diff --git a/meteor/server/apps.js b/meteor/server/apps.js index 07a51cfa58..a52495810f 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -58,39 +58,22 @@ Meteor.methods({ throw new Meteor.Error(400, 'Validation Failed.', validationResult.errors); } else { var cleaned = validationResult.cleaned; - var appObj = { - name: cleaned.name, - imageId: cleaned.imageId, - status: 'STARTING', - config: {} - }; - var appId = Apps.insert(appObj); - var appPath = path.join(KITE_PATH, appObj.name); + var appName = cleaned.name; + var appPath = path.join(KITE_PATH, appName); if (!fs.existsSync(appPath)) { - console.log('Created Kite ' + appObj.name + ' directory.'); + console.log('Created Kite ' + appName + ' directory.'); fs.mkdirSync(appPath, function (err) { if (err) { throw err; } }); } - Apps.update(appId, { - $set: { - 'config.APP_ID': appId, - path: appPath - } - }); - var image = Images.findOne(appObj.imageId); - loadKiteVolumes(image.path, appObj.name); - var app = Apps.findOne(appId); - removeBindFolder(app.name, function (err) { - if (err) { - console.error(err); - } - Fiber(function () { - Meteor.call('runApp', app, function (err) { - if (err) { throw err; } - }); - }).run(); - }); + var appObj = { + name: appName, + imageId: cleaned.imageId, + status: 'STARTING', + config: {}, + path: appPath + }; + Apps.insert(appObj); } }, getAppLogs: function (appId) { diff --git a/meteor/server/lib/utilities.js b/meteor/server/lib/utilities.js index 4be5a0edaa..a0446ad702 100755 --- a/meteor/server/lib/utilities.js +++ b/meteor/server/lib/utilities.js @@ -62,7 +62,7 @@ getImageJSON = function (directory) { } }; -loadKiteVolumes = function (directory, appName) { +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); From b3cc727dbbaa3906bf7d9b37d3f31ce759549b27 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sun, 31 Aug 2014 21:07:22 -0700 Subject: [PATCH 29/58] Refactored utilities. --- meteor/.jshintrc | 6 +-- meteor/client/lib/boot2docker.js | 6 +-- meteor/client/lib/requires.js | 2 + meteor/client/lib/sync.js | 10 ++--- meteor/client/lib/utilities.js | 18 +------- meteor/client/lib/virtualbox.js | 7 ++- .../dashboard/apps/dashboard-single-app.js | 4 +- meteor/collections/apps.js | 4 +- meteor/{server => }/lib/utilities.js | 44 +++++-------------- meteor/server/apps.js | 2 +- meteor/server/constants.js | 2 +- meteor/server/docker.js | 4 +- meteor/server/images.js | 4 +- 13 files changed, 36 insertions(+), 77 deletions(-) create mode 100644 meteor/client/lib/requires.js rename meteor/{server => }/lib/utilities.js (54%) mode change 100755 => 100644 diff --git a/meteor/.jshintrc b/meteor/.jshintrc index 61e603f116..19c2e2f11c 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -163,8 +163,8 @@ // Server and Client "Docker": true, + "Util": true, "boot2dockerexec": true, - "getBinDir": true, "getBoot2DockerIp": true, "getBoot2DockerState": true, "getBoot2DockerDiskUsage": true, @@ -172,7 +172,6 @@ "getBoot2DockerInfo": true, "boot2DockerVMExists": true, "eraseBoot2DockerVMFiles": true, - "getHomePath": true, "initBoot2Docker": true, "isVirtualBoxInstalled": true, "upgradeBoot2Docker": true, @@ -200,8 +199,6 @@ "recoverApps": true, "restartApp": true, "deleteApp": true, - "deleteFolder": true, - "copyVolumes": true, "getAppLogs": true, "hasDockerfile": true, "createTarFile": true, @@ -224,7 +221,6 @@ "saveImageFolderSync": true, "rebuildImageSync": true, "saveImageFolder": true, - "copyFolder": true, // Forms "showFormErrors": true, diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 8e939a2dc3..edeec4e5a8 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -2,7 +2,7 @@ var exec = require('exec'); var path = require('path'); boot2dockerexec = function (command, callback) { - exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout) { + exec(path.join(Util.getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout) { callback(err, stdout); }); }; @@ -131,7 +131,7 @@ boot2DockerVMExists = function (callback) { }; eraseBoot2DockerVMFiles = function (callback) { - var VMFileLocation = path.join(getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm'); + var VMFileLocation = path.join(Util.getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm'); exec('rm -rf ' + VMFileLocation, function (err) { callback(err); }); @@ -174,7 +174,7 @@ upgradeBoot2Docker = function (callback) { }; installBoot2DockerAddons = function (callback) { - exec('/bin/cat ' + path.join(getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) { + exec('/bin/cat ' + path.join(Util.getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(Util.getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) { console.log(stdout); callback(err); }); diff --git a/meteor/client/lib/requires.js b/meteor/client/lib/requires.js new file mode 100644 index 0000000000..2b261be406 --- /dev/null +++ b/meteor/client/lib/requires.js @@ -0,0 +1,2 @@ +path = require('path'); +fs = require('fs'); diff --git a/meteor/client/lib/sync.js b/meteor/client/lib/sync.js index 4dbbcace5d..9cf3fe0e4b 100644 --- a/meteor/client/lib/sync.js +++ b/meteor/client/lib/sync.js @@ -15,7 +15,7 @@ removeAppWatcher = function (id) { addAppWatcher = function (app) { removeAppWatcher(app._id); - var appPath = path.join(path.join(getHomePath(), 'Kitematic'), app.name); + var appPath = path.join(path.join(Util.getHomePath(), 'Kitematic'), app.name); var vmDir = path.join('/var/lib/docker/binds', app.name); var vmPath = 'ssh://docker@localhost:2022/' + vmDir; var watcher = chokidar.watch(appPath, {ignored: /.*\.DS_Store/}); @@ -30,7 +30,7 @@ addAppWatcher = function (app) { syncing = true; var errorPattern = /The\sfile\s(.*)\son\shost/g; var archiveErrorPattern = /Archive\s(.*)\son\shost\s.*\sshould\sbe\sDELETED/g; - var cmd = path.join(getBinDir(), 'unison'); + var cmd = path.join(Util.getBinDir(), 'unison'); var args = [ cmd, vmPath, @@ -46,7 +46,7 @@ addAppWatcher = function (app) { 'Name\ {*.tmp,*.unison,*.swp,*.pyc,.DS_Store}', '-auto', '-sshargs', - '-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(getHomePath(), '.ssh/id_boot2docker') + '-o\ UserKnownHostsFile=/dev/null\ -o\ StrictHostKeyChecking=no\ -o PreferredAuthentications=publickey\ -i\ ' + path.join(Util.getHomePath(), '.ssh/id_boot2docker') ]; if (!fs.existsSync(appPath)) { @@ -70,7 +70,7 @@ addAppWatcher = function (app) { if (err.indexOf('The archive file is missing on some hosts') !== -1) { var results = archiveErrorPattern.exec(err); var location = results[1].replace(' ', '\\ '); - var fullLocation = path.join(getHomePath(), 'Library/Application\\ Support/Unison', location); + var fullLocation = path.join(Util.getHomePath(), 'Library/Application\\ Support/Unison', location); var cmd = '/bin/rm -rf ' + fullLocation; exec(cmd, function () {}); } @@ -118,4 +118,4 @@ resolveWatchers = function (callback) { }); callback(); -}; \ No newline at end of file +}; diff --git a/meteor/client/lib/utilities.js b/meteor/client/lib/utilities.js index c3fe268798..3315ad4040 100755 --- a/meteor/client/lib/utilities.js +++ b/meteor/client/lib/utilities.js @@ -1,17 +1,3 @@ -var path = require('path'); - -getBinDir = function () { - if (process.env.NODE_ENV === 'development') { - return path.join(path.join(process.env.PWD, '..'), 'resources'); - } else { - return path.join(process.cwd(), 'resources'); - } -}; - -getHomePath = function () { - return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; -}; - showFormErrors = function ($form, errors) { for (var name in errors) { if (errors.hasOwnProperty(name)) { @@ -31,7 +17,7 @@ clearFormErrors = function ($form) { $form.find('.form-group.has-error .help-block.error').remove(); $form.find('.form-group.has-error').removeClass('has-error'); }; - + resetForm = function ($form) { $form.find('input').val(''); }; @@ -41,4 +27,4 @@ trackLink = function (trackLabel) { console.log(trackLabel); ga('send', 'event', 'link', 'click', trackLabel); } -}; \ No newline at end of file +}; diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js index 6d362492a9..9022d22430 100644 --- a/meteor/client/lib/virtualbox.js +++ b/meteor/client/lib/virtualbox.js @@ -25,8 +25,8 @@ isResolverSetup = function (callback) { }; setupResolver = function (callback) { - var installFile = path.join(getBinDir(), 'install'); - var cocoaSudo = path.join(getBinDir(), 'cocoasudo'); + var installFile = path.join(Util.getBinDir(), 'install'); + var cocoaSudo = path.join(Util.getBinDir(), 'cocoasudo'); var execCommand = cocoaSudo + ' --prompt="Kitematic Setup wants to make changes. Type your password to allow this." ' + installFile; child_process.exec(execCommand, function (error, stdout, stderr) { console.log(stdout); @@ -41,7 +41,7 @@ setupResolver = function (callback) { }; setupVirtualBox = function (callback) { - child_process.exec('open -W ' + path.join(getBinDir(), 'virtualbox-4.3.12.pkg'), function (error, stdout, stderr) { + child_process.exec('open -W ' + path.join(Util.getBinDir(), 'virtualbox-4.3.12.pkg'), function (error, stdout, stderr) { console.log(stdout); if (error) { console.log(error); @@ -52,4 +52,3 @@ setupVirtualBox = function (callback) { callback(); }); }; - diff --git a/meteor/client/views/dashboard/apps/dashboard-single-app.js b/meteor/client/views/dashboard/apps/dashboard-single-app.js index acbd198e79..5d32c4818f 100755 --- a/meteor/client/views/dashboard/apps/dashboard-single-app.js +++ b/meteor/client/views/dashboard/apps/dashboard-single-app.js @@ -21,8 +21,8 @@ Template.dashboard_single_app.events({ }, 'click .btn-terminal': function () { var app = this; - var cmd = path.join(getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"'; - var terminalCmd = path.join(getBinDir(), 'terminal') + ' ' + cmd; + var cmd = path.join(Util.getBinDir(), 'boot2docker') + ' ssh -t "sudo docker-enter ' + app.docker.Id + '"'; + var terminalCmd = path.join(Util.getBinDir(), 'terminal') + ' ' + cmd; var exec = require('child_process').exec; console.log(terminalCmd); exec(terminalCmd, function (err, stdout) { diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 5bbc9ef2a9..1b7ee7ce97 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -126,7 +126,7 @@ Apps.after.insert(function (userId, app) { } }); var image = Images.findOne(app.imageId); - copyVolumes(image.path, app.name); + Util.copyVolumes(image.path, app.name); app = Apps.findOne(appId); removeBindFolder(app.name, function (err) { if (err) { @@ -144,7 +144,7 @@ Apps.after.remove(function (userId, app) { deleteApp(app, function (err) { if (err) { console.error(err); } var appPath = path.join(KITE_PATH, app.name); - deleteFolder(appPath); + Util.deleteFolder(appPath); removeBindFolder(app.name, function () { console.log('Deleted Kite ' + app.name + ' directory.'); }); diff --git a/meteor/server/lib/utilities.js b/meteor/lib/utilities.js old mode 100755 new mode 100644 similarity index 54% rename from meteor/server/lib/utilities.js rename to meteor/lib/utilities.js index a0446ad702..12c03f9298 --- a/meteor/server/lib/utilities.js +++ b/meteor/lib/utilities.js @@ -1,8 +1,10 @@ -getHomePath = function () { +Util = {}; + +Util.getHomePath = function () { return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; }; -getBinDir = function () { +Util.getBinDir = function () { if (process.env.NODE_ENV === 'development') { return path.join(path.join(process.env.PWD, '..'), 'resources'); } else { @@ -10,13 +12,13 @@ getBinDir = function () { } }; -deleteFolder = function (directory) { +Util.deleteFolder = function (directory) { if (fs.existsSync(directory)) { fs.readdirSync(directory).forEach(function (file) { var curDirectory = directory + '/' + file; if (fs.lstatSync(curDirectory).isDirectory()) { // Recurse - deleteFolder(curDirectory); + Util.deleteFolder(curDirectory); } else { // Delete File try { @@ -30,7 +32,7 @@ deleteFolder = function (directory) { } }; -copyFolder = function (src, dest) { +Util.copyFolder = function (src, dest) { var exists = fs.existsSync(src); var stats = exists && fs.statSync(src); var isDirectory = exists && stats.isDirectory(); @@ -41,7 +43,7 @@ copyFolder = function (src, dest) { console.error(e); } fs.readdirSync(src).forEach(function (childItemName) { - copyFolder(path.join(src, childItemName), path.join(dest, childItemName)); + Util.copyFolder(path.join(src, childItemName), path.join(dest, childItemName)); }); } else { try { @@ -52,37 +54,11 @@ copyFolder = function (src, dest) { } }; -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; - } -}; - -copyVolumes = function (directory, appName) { +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); - copyFolder(KITE_VOLUMES_PATH, destinationPath); + Util.copyFolder(KITE_VOLUMES_PATH, destinationPath); console.log('Copied volumes for: ' + appName); } }; - -saveImageFolder = function (directory, imageId, callback) { - var destinationPath = path.join(KITE_IMAGES_PATH, imageId); - if (!fs.existsSync(destinationPath)) { - fs.mkdirSync(destinationPath, function (err) { - if (err) { callback(err); return; } - }); - copyFolder(directory, destinationPath); - console.log('Copied image folder for: ' + imageId); - callback(null); - } -}; - -saveImageFolderSync = function (directory, imageId) { - return Meteor._wrapAsync(saveImageFolder)(directory, imageId); -}; diff --git a/meteor/server/apps.js b/meteor/server/apps.js index a52495810f..a96174beeb 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -1,5 +1,5 @@ removeBindFolder = function (name, callback) { - exec(path.join(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); }); }; diff --git a/meteor/server/constants.js b/meteor/server/constants.js index e3fd37c46e..cb35d90451 100644 --- a/meteor/server/constants.js +++ b/meteor/server/constants.js @@ -1,4 +1,4 @@ -KITE_PATH = path.join(getHomePath(), 'Kitematic'); +KITE_PATH = path.join(Util.getHomePath(), 'Kitematic'); KITE_TAR_PATH = path.join(KITE_PATH, '.tar'); KITE_IMAGES_PATH = path.join(KITE_PATH, '.images'); diff --git a/meteor/server/docker.js b/meteor/server/docker.js index c46ec6de30..0e22efb2ff 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -306,7 +306,7 @@ resolveDefaultImages = function () { image.inspect(function (err) { if (err) { if (err.reason === 'no such image') { - docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) { + docker.loadImage(path.join(Util.getBinDir(), 'base-images.tar.gz'), {}, function (err) { if (err) { innerCallback(err); return; @@ -393,7 +393,7 @@ reloadDefaultContainers = function (callback) { return; } console.log('Loading new Kitematic default images.'); - docker.loadImage(path.join(getBinDir(), 'base-images.tar.gz'), {}, function (err) { + docker.loadImage(path.join(Util.getBinDir(), 'base-images.tar.gz'), {}, function (err) { if (err) { callback(err); return; diff --git a/meteor/server/images.js b/meteor/server/images.js index f83f21da7f..8663f92cc4 100755 --- a/meteor/server/images.js +++ b/meteor/server/images.js @@ -13,7 +13,7 @@ getImageMetaData = function (directory) { }; rebuildImage = function (image, callback) { - deleteFolder(image.path); + Util.deleteFolder(image.path); var imageMetaData = getImageMetaData(image.originPath); if (imageMetaData.logo) { Images.update(image._id, { @@ -141,7 +141,7 @@ Meteor.methods({ console.log('here'); try { deleteImageSync(image); - deleteFolder(image.path); + Util.deleteFolder(image.path); } catch (e) { console.log(e); } finally { From aa6ac2a4d5cf7550e4eebc7f9c56537998e4bdf4 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sun, 31 Aug 2014 21:08:21 -0700 Subject: [PATCH 30/58] Moved server utilities. --- meteor/server/images.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/meteor/server/images.js b/meteor/server/images.js index 8663f92cc4..d8985a8384 100755 --- a/meteor/server/images.js +++ b/meteor/server/images.js @@ -1,3 +1,29 @@ +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; + } +}; + +saveImageFolder = function (directory, imageId, callback) { + var destinationPath = path.join(KITE_IMAGES_PATH, imageId); + if (!fs.existsSync(destinationPath)) { + fs.mkdirSync(destinationPath, function (err) { + if (err) { callback(err); return; } + }); + Util.copyFolder(directory, destinationPath); + console.log('Copied image folder for: ' + imageId); + callback(null); + } +}; + +saveImageFolderSync = function (directory, imageId) { + return Meteor._wrapAsync(saveImageFolder)(directory, imageId); +}; + getImageMetaData = function (directory) { var kiteJSON = getImageJSON(directory); if (kiteJSON) { From dc8cb18fa615b193523df2988818255b3394cf23 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Sun, 31 Aug 2014 22:10:32 -0700 Subject: [PATCH 31/58] Refactored code and fixed bin dir path issue. --- meteor/.jshintrc | 3 + meteor/client/lib/sync.js | 2 +- meteor/lib/utilities.js | 6 +- meteor/server/apps.js | 68 ++++++++++ meteor/server/docker.js | 225 ---------------------------------- meteor/server/images.js | 154 +++++++++++++++++++++++ meteor/server/lib/requires.js | 4 +- 7 files changed, 234 insertions(+), 228 deletions(-) diff --git a/meteor/.jshintrc b/meteor/.jshintrc index 19c2e2f11c..300055089f 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -143,6 +143,8 @@ "docker": true, "async": true, "child_process": true, + "convert": true, + "Convert": true, // Collections "SimpleSchema": false, @@ -221,6 +223,7 @@ "saveImageFolderSync": true, "rebuildImageSync": true, "saveImageFolder": true, + "getFromImage": true, // Forms "showFormErrors": true, diff --git a/meteor/client/lib/sync.js b/meteor/client/lib/sync.js index 9cf3fe0e4b..cd2468ef2d 100644 --- a/meteor/client/lib/sync.js +++ b/meteor/client/lib/sync.js @@ -75,7 +75,7 @@ addAppWatcher = function (app) { exec(cmd, function () {}); } } catch (e) { - // console.error(e); + //console.error(e); } syncing = false; if (willSyncAgain) { diff --git a/meteor/lib/utilities.js b/meteor/lib/utilities.js index 12c03f9298..73aea70f5f 100644 --- a/meteor/lib/utilities.js +++ b/meteor/lib/utilities.js @@ -8,7 +8,11 @@ Util.getBinDir = function () { if (process.env.NODE_ENV === 'development') { return path.join(path.join(process.env.PWD, '..'), 'resources'); } else { - return path.join(process.cwd(), '../../../resources'); + if (Meteor.isClient) { + return path.join(process.cwd(), 'resources'); + } else { + return path.join(process.cwd(), '../../../resources'); + } } }; diff --git a/meteor/server/apps.js b/meteor/server/apps.js index a96174beeb..6cc531bc13 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -1,3 +1,71 @@ +deleteApp = function (app, callback) { + if (!app.docker) { + callback(null); + return; + } + try { + Docker.removeContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } + callback(null); +}; + +deleteAppSync = function (app) { + return Meteor._wrapAsync(deleteApp)(app); +}; + +restartApp = function (app, callback) { + if (app.docker && app.docker.Id) { + try { + Docker.restartContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } + var containerData = Docker.getContainerDataSync(app.docker.Id); + Fiber(function () { + Apps.update(app._id, {$set: { + status: 'READY', + docker: containerData + }}); + }).run(); + callback(null); + // Use dig to refresh the DNS + exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {}); + } else { + callback(null); + } +}; + +getAppLogs = function (app) { + if (app.docker && app.docker.Id) { + var container = docker.getContainer(app.docker.Id); + container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) { + if (err) { throw err; } + Fiber(function () { + Apps.update(app._id, { + $set: { + logs: [] + } + }); + }).run(); + var logs = []; + response.setEncoding('utf8'); + response.on('data', function (line) { + logs.push(convert.toHtml(line.slice(8))); + Fiber(function () { + Apps.update(app._id, { + $set: { + logs: logs + } + }); + }).run(); + }); + response.on('end', function () {}); + }); + } +}; + removeBindFolder = function (name, callback) { exec(path.join(Util.getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function(err, stdout) { callback(err, stdout); diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 0e22efb2ff..447359cf2d 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -1,8 +1,5 @@ Dockerode = Meteor.require('dockerode'); -var Convert = Meteor.require('ansi-to-html'); -var convert = new Convert(); - var DOCKER_HOST='192.168.59.103'; docker = new Dockerode({host: '192.168.59.103', port: '2375'}); @@ -28,23 +25,6 @@ Docker.removeContainerSync = function (containerId) { return Meteor._wrapAsync(Docker.removeContainer)(containerId); }; -deleteApp = function (app, callback) { - if (!app.docker) { - callback(null); - return; - } - try { - Docker.removeContainerSync(app.docker.Id); - } catch (e) { - console.error(e); - } - callback(null); -}; - -deleteAppSync = function (app) { - return Meteor._wrapAsync(deleteApp)(app); -}; - Docker.getContainerData = function (containerId, callback) { var container = docker.getContainer(containerId); container.inspect(function (err, data) { @@ -123,68 +103,6 @@ Docker.restartContainerSync = function (containerId) { return Meteor._wrapAsync(Docker.restartContainer)(containerId); }; -var getFromImage = function (dockerfile) { - var patternString = "(FROM)(.*)"; - var regex = new RegExp(patternString, "g"); - var fromInstruction = dockerfile.match(regex); - if (fromInstruction && fromInstruction.length > 0) { - return fromInstruction[0].replace('FROM', '').trim(); - } else { - return null; - } -}; - -restartApp = function (app, callback) { - if (app.docker && app.docker.Id) { - try { - Docker.restartContainerSync(app.docker.Id); - } catch (e) { - console.error(e); - } - var containerData = Docker.getContainerDataSync(app.docker.Id); - Fiber(function () { - Apps.update(app._id, {$set: { - status: 'READY', - docker: containerData - }}); - }).run(); - callback(null); - // Use dig to refresh the DNS - exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {}); - } else { - callback(null); - } -}; - -getAppLogs = function (app) { - if (app.docker && app.docker.Id) { - var container = docker.getContainer(app.docker.Id); - container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) { - if (err) { throw err; } - Fiber(function () { - Apps.update(app._id, { - $set: { - logs: [] - } - }); - }).run(); - var logs = []; - response.setEncoding('utf8'); - response.on('data', function (line) { - logs.push(convert.toHtml(line.slice(8))); - Fiber(function () { - Apps.update(app._id, { - $set: { - logs: logs - } - }); - }).run(); - }); - response.on('end', function () {}); - }); - } -}; - createTarFile = function (image, callback) { var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar'); exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) { @@ -243,23 +161,6 @@ Docker.removeImageSync = function (imageId) { return Meteor._wrapAsync(Docker.removeImage)(imageId); }; -deleteImage = function (image, callback) { - if (!image.docker) { - callback(null, {}); - return; - } - try { - Docker.removeImageSync(image.docker.Id); - } catch (e) { - console.error(e); - } - callback(null); -}; - -deleteImageSync = function (image) { - return Meteor._wrapAsync(deleteImage)(image); -}; - var defaultContainerOptions = function () { return [ { @@ -502,132 +403,6 @@ killAndRemoveContainers = function (names, callback) { }); }; -pullImageFromDockerfile = 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); - } -}; - -buildImage = function (image, callback) { - Fiber(function () { - var tarFilePath = createTarFileSync(image); - Images.update(image._id, { - $set: { - buildLogs: [] - } - }); - docker.buildImage(tarFilePath, {t: image._id.toLowerCase()}, function (err, response) { - if (err) { callback(err); } - console.log('Building Docker image...'); - var logs = []; - response.setEncoding('utf8'); - response.on('data', function (data) { - try { - var line = JSON.parse(data).stream; - logs.push(convert.toHtml(line)); - Fiber(function () { - Images.update(image._id, { - $set: { - buildLogs: logs - } - }); - }).run(); - } catch (e) { - console.error(e); - } - }); - response.on('end', function () { - console.log('Finished building Docker image.'); - try { - fs.unlinkSync(tarFilePath); - console.log('Cleaned up tar file.'); - } catch (e) { - console.error(e); - } - Fiber(function () { - var imageData = null; - try { - imageData = 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({ runApp: function (app) { this.unblock(); diff --git a/meteor/server/images.js b/meteor/server/images.js index d8985a8384..a2c91c538b 100755 --- a/meteor/server/images.js +++ b/meteor/server/images.js @@ -1,3 +1,14 @@ +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; + } +}; + getImageJSON = function (directory) { var KITE_JSON_PATH = path.join(directory, 'image.json'); if (fs.existsSync(KITE_JSON_PATH)) { @@ -38,6 +49,23 @@ getImageMetaData = function (directory) { return kiteJSON; }; +deleteImage = function (image, callback) { + if (!image.docker) { + callback(null, {}); + return; + } + try { + Docker.removeImageSync(image.docker.Id); + } catch (e) { + console.error(e); + } + callback(null); +}; + +deleteImageSync = function (image) { + return Meteor._wrapAsync(deleteImage)(image); +}; + rebuildImage = function (image, callback) { Util.deleteFolder(image.path); var imageMetaData = getImageMetaData(image.originPath); @@ -75,6 +103,132 @@ rebuildImageSync = function (image) { return Meteor._wrapAsync(rebuildImage)(image); }; +pullImageFromDockerfile = 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); + } +}; + +buildImage = function (image, callback) { + Fiber(function () { + var tarFilePath = createTarFileSync(image); + Images.update(image._id, { + $set: { + buildLogs: [] + } + }); + docker.buildImage(tarFilePath, {t: image._id.toLowerCase()}, function (err, response) { + if (err) { callback(err); } + console.log('Building Docker image...'); + var logs = []; + response.setEncoding('utf8'); + response.on('data', function (data) { + try { + var line = JSON.parse(data).stream; + logs.push(convert.toHtml(line)); + Fiber(function () { + Images.update(image._id, { + $set: { + buildLogs: logs + } + }); + }).run(); + } catch (e) { + console.error(e); + } + }); + response.on('end', function () { + console.log('Finished building Docker image.'); + try { + fs.unlinkSync(tarFilePath); + console.log('Cleaned up tar file.'); + } catch (e) { + console.error(e); + } + Fiber(function () { + var imageData = null; + try { + imageData = 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(); diff --git a/meteor/server/lib/requires.js b/meteor/server/lib/requires.js index cac98d255a..6174cd0202 100755 --- a/meteor/server/lib/requires.js +++ b/meteor/server/lib/requires.js @@ -6,4 +6,6 @@ path = Meteor.require('path'); exec = Meteor.require('child_process').exec; async = Meteor.require('async'); Fiber = Meteor.require('fibers'); -child_process = Meteor.require('child_process'); \ No newline at end of file +child_process = Meteor.require('child_process'); +Convert = Meteor.require('ansi-to-html'); +convert = new Convert(); From 50d3629758654d4ed6895d22c78bbdeab57bdf9e Mon Sep 17 00:00:00 2001 From: Sean Li Date: Mon, 1 Sep 2014 00:59:32 -0700 Subject: [PATCH 32/58] Refactored app code. --- meteor/.jshintrc | 7 ------- meteor/client/main.js | 4 ++-- meteor/collections/apps.js | 20 ++++++++++++-------- meteor/lib/utilities.js | 4 ++++ meteor/server/apps.js | 37 +++++++------------------------------ meteor/server/docker.js | 23 ++++++----------------- meteor/server/images.js | 23 +++++++++++++++++++++-- 7 files changed, 52 insertions(+), 66 deletions(-) diff --git a/meteor/.jshintrc b/meteor/.jshintrc index 300055089f..139808b8bf 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -194,15 +194,9 @@ "fixInterval": true, "stopFixInterval": true, "runSetup": true, - "removeBindFolder": true, "removeAppWatcher": true, "addAppWatcher": true, "resolveWatchers": true, - "recoverApps": true, - "restartApp": true, - "deleteApp": true, - "getAppLogs": true, - "hasDockerfile": true, "createTarFile": true, "createTarFileSync": true, "deleteImage": true, @@ -232,7 +226,6 @@ "FormSchema": true, "showFormSuccess": true, "resetForm": true, - "deleteAppSync": true, // Testing "require": false, diff --git a/meteor/client/main.js b/meteor/client/main.js index 7abe7d59a1..4b47333ece 100755 --- a/meteor/client/main.js +++ b/meteor/client/main.js @@ -148,7 +148,7 @@ startFixInterval = function () { resolveWatchers(function () {}); fixBoot2DockerVM(function (err) { if (err) { console.log(err); return; } - // 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; -}; \ No newline at end of file +}; diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 1b7ee7ce97..ba40c9b2e9 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -128,7 +128,7 @@ Apps.after.insert(function (userId, app) { var image = Images.findOne(app.imageId); Util.copyVolumes(image.path, app.name); app = Apps.findOne(appId); - removeBindFolder(app.name, function (err) { + Docker.removeBindFolder(app.name, function (err) { if (err) { console.error(err); } @@ -141,12 +141,16 @@ Apps.after.insert(function (userId, app) { }); Apps.after.remove(function (userId, app) { - deleteApp(app, function (err) { - if (err) { console.error(err); } - var appPath = path.join(KITE_PATH, app.name); - Util.deleteFolder(appPath); - removeBindFolder(app.name, function () { - console.log('Deleted Kite ' + app.name + ' directory.'); - }); + if (app.docker) { + try { + Docker.removeContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } + } + var appPath = path.join(KITE_PATH, app.name); + Util.deleteFolder(appPath); + Docker.removeBindFolder(app.name, function () { + console.log('Deleted Kite ' + app.name + ' directory.'); }); }); diff --git a/meteor/lib/utilities.js b/meteor/lib/utilities.js index 73aea70f5f..2497df3b53 100644 --- a/meteor/lib/utilities.js +++ b/meteor/lib/utilities.js @@ -66,3 +66,7 @@ Util.copyVolumes = function (directory, appName) { console.log('Copied volumes for: ' + appName); } }; + +Util.hasDockerfile = function (directory) { + return fs.existsSync(path.join(directory, 'Dockerfile')); +}; diff --git a/meteor/server/apps.js b/meteor/server/apps.js index 6cc531bc13..a8e7c8dce7 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -1,21 +1,4 @@ -deleteApp = function (app, callback) { - if (!app.docker) { - callback(null); - return; - } - try { - Docker.removeContainerSync(app.docker.Id); - } catch (e) { - console.error(e); - } - callback(null); -}; - -deleteAppSync = function (app) { - return Meteor._wrapAsync(deleteApp)(app); -}; - -restartApp = function (app, callback) { +Apps.restart = function (app, callback) { if (app.docker && app.docker.Id) { try { Docker.restartContainerSync(app.docker.Id); @@ -37,7 +20,7 @@ restartApp = function (app, callback) { } }; -getAppLogs = function (app) { +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) { @@ -66,13 +49,7 @@ getAppLogs = function (app) { } }; -removeBindFolder = function (name, callback) { - exec(path.join(Util.getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function(err, stdout) { - callback(err, stdout); - }); -}; - -recoverApps = function (callback) { +Apps.recover = function (callback) { var apps = Apps.find({}).fetch(); _.each(apps, function (app) { // Update the app with the latest container info @@ -85,7 +62,7 @@ recoverApps = function (callback) { console.log('restarting: ' + app.name); console.log(app.docker.Id); Fiber(function () { - restartApp(app, function (err) { + Apps.restart(app, function (err) { if (err) { console.error(err); } }); }).run(); @@ -98,7 +75,7 @@ recoverApps = function (callback) { Meteor.methods({ recoverApps: function () { this.unblock(); - return Meteor._wrapAsync(recoverApps)(); + return Meteor._wrapAsync(Apps.recover)(); }, configVar: function (appId, configVars) { this.unblock(); @@ -148,7 +125,7 @@ Meteor.methods({ this.unblock(); var app = Apps.findOne(appId); if (app) { - getAppLogs(app, function (err) { + Apps.logs(app, function (err) { if (err) { throw err; } }); } @@ -160,7 +137,7 @@ Meteor.methods({ Apps.update(app._id, {$set: { status: 'STARTING' }}); - restartApp(app, function (err) { + Apps.restart(app, function (err) { if (err) { console.error(err); } }); } diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 447359cf2d..ae1e759985 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -5,10 +5,6 @@ docker = new Dockerode({host: '192.168.59.103', port: '2375'}); Docker = {}; -hasDockerfile = function (directory) { - return fs.existsSync(path.join(directory, 'Dockerfile')); -}; - Docker.removeContainer = function (containerId, callback) { var container = docker.getContainer(containerId); container.kill(function (err) { @@ -103,19 +99,6 @@ Docker.restartContainerSync = function (containerId) { return Meteor._wrapAsync(Docker.restartContainer)(containerId); }; -createTarFile = function (image, callback) { - var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar'); - exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) { - if (err) { callback(err, null); return; } - console.log('Created tar file: ' + TAR_PATH); - callback(null, TAR_PATH); - }); -}; - -createTarFileSync = function (image) { - return Meteor._wrapAsync(createTarFile)(image); -}; - var convertVolumeObjToArray = function (obj) { var result = []; if (obj !== null && typeof obj === 'object') { @@ -161,6 +144,12 @@ 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) { + callback(err, stdout); + }); +}; + var defaultContainerOptions = function () { return [ { diff --git a/meteor/server/images.js b/meteor/server/images.js index a2c91c538b..3befdd5e2b 100755 --- a/meteor/server/images.js +++ b/meteor/server/images.js @@ -1,3 +1,16 @@ +createTarFile = function (image, callback) { + var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar'); + exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) { + if (err) { callback(err, null); return; } + console.log('Created tar file: ' + TAR_PATH); + callback(null, TAR_PATH); + }); +}; + +createTarFileSync = function (image) { + return Meteor._wrapAsync(createTarFile)(image); +}; + getFromImage = function (dockerfile) { var patternString = "(FROM)(.*)"; var regex = new RegExp(patternString, "g"); @@ -272,7 +285,13 @@ Meteor.methods({ if (apps.length > 0) { _.each(apps, function (app) { console.log('Updating app: ' + app.name); - deleteAppSync(app); + if (app.docker) { + try { + Docker.removeContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } + } Apps.update(app._id, { $set: { 'docker.Id': null, @@ -306,7 +325,7 @@ Meteor.methods({ }, validateDirectory: function (directory) { this.unblock(); - if (!hasDockerfile(directory)) { + if (!Util.hasDockerfile(directory)) { throw new Meteor.Error(400, "Only directories with Dockerfiles are supported now."); } }, From 63148c2d9cc4246aea5e0e1f0dc2d19662db0a11 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Mon, 1 Sep 2014 02:15:00 -0700 Subject: [PATCH 33/58] Refactored image code. --- meteor/.jshintrc | 13 ----- meteor/collections/images.js | 40 ++++++++++++++ meteor/server/images.js | 103 ++++++++++------------------------- 3 files changed, 68 insertions(+), 88 deletions(-) diff --git a/meteor/.jshintrc b/meteor/.jshintrc index 139808b8bf..35c1c7df9b 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -197,27 +197,14 @@ "removeAppWatcher": true, "addAppWatcher": true, "resolveWatchers": true, - "createTarFile": true, - "createTarFileSync": true, - "deleteImage": true, "checkDefaultImages": true, "resolveDefaultImages": true, "checkDefaultContainers": true, "resolveDefaultContainers": true, "killAndRemoveContainers": true, - "deleteImageSync": true, "upContainers": true, "reloadDefaultContainers": true, "removeImages": true, - "pullImageFromDockerfile": true, - "buildImage": true, - "getImageMetaData": true, - "getImageJSON": true, - "rebuildImage": true, - "saveImageFolderSync": true, - "rebuildImageSync": true, - "saveImageFolder": true, - "getFromImage": true, // Forms "showFormErrors": true, diff --git a/meteor/collections/images.js b/meteor/collections/images.js index 15055ad021..5a4de5b4f8 100755 --- a/meteor/collections/images.js +++ b/meteor/collections/images.js @@ -91,3 +91,43 @@ Images.allow({ }); Images.attachSchema(schemaImages); + +Images.after.insert(function (userId, image) { + var imageId = this._id; + var imagePath = path.join(KITE_IMAGES_PATH, imageId); + Images.update(imageId, { + $set: { + path: imagePath + } + }); + if (image.meta.logo) { + Images.update(imageId, { + $set: { + logoPath: path.join(imagePath, image.meta.logo) + } + }); + } + image = Images.findOne(imageId); + Images.saveFolderSync(image.originPath, imageId); + Images.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) { + if (err) { throw err; } + Images.build(image, function (err) { + if (err) { console.error(err); } + }); + }); +}); + +Images.after.remove(function (userId, image) { + if (image.docker) { + try { + Docker.removeImageSync(image.docker.Id); + } catch (e) { + console.error(e); + } + } + try { + Util.deleteFolder(image.path); + } catch (e) { + console.error(e); + } +}); diff --git a/meteor/server/images.js b/meteor/server/images.js index 3befdd5e2b..6ad43fe7d0 100755 --- a/meteor/server/images.js +++ b/meteor/server/images.js @@ -1,4 +1,4 @@ -createTarFile = function (image, callback) { +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; } @@ -7,11 +7,11 @@ createTarFile = function (image, callback) { }); }; -createTarFileSync = function (image) { +var createTarFileSync = function (image) { return Meteor._wrapAsync(createTarFile)(image); }; -getFromImage = function (dockerfile) { +var getFromImage = function (dockerfile) { var patternString = "(FROM)(.*)"; var regex = new RegExp(patternString, "g"); var fromInstruction = dockerfile.match(regex); @@ -22,7 +22,7 @@ getFromImage = function (dockerfile) { } }; -getImageJSON = function (directory) { +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'); @@ -32,23 +32,7 @@ getImageJSON = function (directory) { } }; -saveImageFolder = function (directory, imageId, callback) { - var destinationPath = path.join(KITE_IMAGES_PATH, imageId); - if (!fs.existsSync(destinationPath)) { - fs.mkdirSync(destinationPath, function (err) { - if (err) { callback(err); return; } - }); - Util.copyFolder(directory, destinationPath); - console.log('Copied image folder for: ' + imageId); - callback(null); - } -}; - -saveImageFolderSync = function (directory, imageId) { - return Meteor._wrapAsync(saveImageFolder)(directory, imageId); -}; - -getImageMetaData = function (directory) { +var getImageMetaData = function (directory) { var kiteJSON = getImageJSON(directory); if (kiteJSON) { if (!kiteJSON.name) { @@ -62,24 +46,23 @@ getImageMetaData = function (directory) { return kiteJSON; }; -deleteImage = function (image, callback) { - if (!image.docker) { - callback(null, {}); - return; +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); } - try { - Docker.removeImageSync(image.docker.Id); - } catch (e) { - console.error(e); - } - callback(null); }; -deleteImageSync = function (image) { - return Meteor._wrapAsync(deleteImage)(image); +Images.saveFolderSync = function (directory, imageId) { + return Meteor._wrapAsync(Images.saveFolder)(directory, imageId); }; -rebuildImage = function (image, callback) { +Images.rebuild = function (image, callback) { Util.deleteFolder(image.path); var imageMetaData = getImageMetaData(image.originPath); if (imageMetaData.logo) { @@ -102,21 +85,21 @@ rebuildImage = function (image, callback) { } }); image = Images.findOne(image._id); - saveImageFolderSync(image.originPath, image._id); - pullImageFromDockerfile(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) { + Images.saveFolderSync(image.originPath, image._id); + Images.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) { if (err) { callback(err, null); return; } - buildImage(image, function (err) { + Images.build(image, function (err) { if (err) { console.error(err); } callback(null, null); }); }); }; -rebuildImageSync = function (image) { - return Meteor._wrapAsync(rebuildImage)(image); +Images.rebuildSync = function (image) { + return Meteor._wrapAsync(Images.rebuild)(image); }; -pullImageFromDockerfile = function (dockerfile, imageId, callback) { +Images.pull = function (dockerfile, imageId, callback) { var fromImage = getFromImage(dockerfile); console.log('From image: ' + fromImage); var installedImage = null; @@ -170,7 +153,7 @@ pullImageFromDockerfile = function (dockerfile, imageId, callback) { } }; -buildImage = function (image, callback) { +Images.build = function (image, callback) { Fiber(function () { var tarFilePath = createTarFileSync(image); Images.update(image._id, { @@ -251,29 +234,7 @@ Meteor.methods({ }; var imageMetaData = getImageMetaData(directory); imageObj.meta = imageMetaData; - var imageId = Images.insert(imageObj); - var imagePath = path.join(KITE_IMAGES_PATH, imageId); - Images.update(imageId, { - $set: { - path: imagePath - } - }); - if (imageObj.meta.logo) { - Images.update(imageId, { - $set: { - logoPath: path.join(imagePath, imageObj.meta.logo) - } - }); - } - var image = Images.findOne(imageId); - saveImageFolderSync(directory, imageId); - console.log('Saved folder sync'); - pullImageFromDockerfile(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) { - if (err) { throw err; } - buildImage(image, function (err) { - if (err) { console.error(err); } - }); - }); + Images.insert(imageObj); }, rebuildImage: function (imageId) { this.unblock(); @@ -300,7 +261,7 @@ Meteor.methods({ } }); }); - rebuildImageSync(image); + Images.rebuildSync(image); _.each(apps, function (app) { app = Apps.findOne(app._id); Meteor.call('runApp', app, function (err) { @@ -308,7 +269,7 @@ Meteor.methods({ }); }); } else { - rebuildImageSync(image); + Images.rebuildSync(image); } }, changeDirectory: function (imageId, directory) { @@ -337,15 +298,7 @@ Meteor.methods({ } var app = Apps.findOne({imageId: imageId}); if (!app) { - console.log('here'); - try { - deleteImageSync(image); - Util.deleteFolder(image.path); - } catch (e) { - console.log(e); - } finally { - Images.remove({_id: image._id}); - } + Images.remove({_id: image._id}); } else { throw new Meteor.Error(400, 'This image is currently being used by ' + app.name + "."); } From c00fe7b6306e52df06ffaf4c44bbd26c1e94a43b Mon Sep 17 00:00:00 2001 From: Sean Li Date: Mon, 1 Sep 2014 02:23:18 -0700 Subject: [PATCH 34/58] JSHint. --- meteor/client/views/dashboard/setup/setup-install.js | 5 ++--- meteor/server/publications.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js index 004ac755cf..dfb9ec9fd8 100644 --- a/meteor/client/views/dashboard/setup/setup-install.js +++ b/meteor/client/views/dashboard/setup/setup-install.js @@ -12,7 +12,6 @@ var steps = [ { install: function (callback) { isVirtualBoxInstalled(function (err, virtualBoxInstalled) { - var installedYet = false; if (!virtualBoxInstalled) { setupVirtualBox(function (err) { callback(err); @@ -77,7 +76,7 @@ var steps = [ pastMessage: 'Started the Kitematic VM', message: 'Starting the Kitematic VM', subMessage: '(This may take a few minutes)', - futureMessage: 'Start the Kitematic VM', + futureMessage: 'Start the Kitematic VM' }, // Set up the default Kitematic images @@ -90,7 +89,7 @@ var steps = [ pastMessage: 'Started the Kitematic 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' } ]; diff --git a/meteor/server/publications.js b/meteor/server/publications.js index fe75a8a9ba..6e3540fc61 100755 --- a/meteor/server/publications.js +++ b/meteor/server/publications.js @@ -7,5 +7,5 @@ Meteor.publish('images', function () { }); Meteor.publish('installs', function () { - return Installs.find({}, {sort: {createdAt: -1}}); + return Installs.find({}); }); From 0bedb1902ef7f298ef8b95f6225fe9d8048c4f8d Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Mon, 1 Sep 2014 03:25:31 -0700 Subject: [PATCH 35/58] Fixing common installer bugs --- meteor/client/lib/boot2docker.js | 289 +++++++++--------- meteor/client/lib/installer.js | 146 +++++++++ meteor/client/lib/utilities.js | 75 +++++ meteor/client/lib/virtualbox.js | 181 ++++++++--- meteor/client/main.js | 33 +- .../dashboard/settings/dashboard-settings.js | 2 +- .../views/dashboard/setup/setup-install.js | 134 +------- meteor/server/docker.js | 4 +- resources/install | 39 +-- 9 files changed, 562 insertions(+), 341 deletions(-) create mode 100644 meteor/client/lib/installer.js diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 281d79c0c0..879fb42a76 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -1,14 +1,54 @@ var exec = require('exec'); var path = require('path'); -boot2dockerexec = function (command, callback) { - exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout) { - callback(err, stdout); +Boot2Docker = {}; + +Boot2Docker.REQUIRED_IP = '192.168.60.103'; + +Boot2Docker.exec = function (command, callback) { + exec(path.join(getBinDir(), 'boot2docker') + ' ' + command, function(err, stdout, stderr) { + callback(err, stdout, stderr); }); }; -getBoot2DockerIp = function (callback) { - boot2dockerexec('ip', function (err, stdout) { +Boot2Docker.exists = function (callback) { + this.exec('info', function (err) { + if (err) { + callback(null, false); + } else { + callback(null, true); + } + }); +}; + +Boot2Docker.stop = function (callback) { + this.exec('stop', function (err, stdout) { + if (err) { + callback(err); + } else { + callback(null); + } + }); +}; + +Boot2Docker.erase = function (callback) { + var VMFileLocation = path.join(getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm'); + exec('rm -rf ' + VMFileLocation, function (err) { + callback(err); + }); +}; + +Boot2Docker.upgrade = function (callback) { + var self = this; + self.stop(function (err) { + self.exec('upgrade', function (err, stdout) { + callback(err); + }); + }); +}; + +Boot2Docker.ip = function (callback) { + this.exec('ip', function (err, stdout) { if (err) { callback(err, null); } else { @@ -17,8 +57,51 @@ getBoot2DockerIp = function (callback) { }); }; -getBoot2DockerState = function (callback) { - boot2dockerexec(' info', function (err, stdout) { +Boot2Docker.setIp = function (ifname, ip, callback) { + this.exec('ssh "sudo ifconfig ' + ifname + ' ' + ip + ' netmask 255.255.255.0"', function (err, stdout) { + callback(err); + }); +}; + +Boot2Docker.init = function (callback) { + this.exec('init', function (err) { + callback(err); + }); +}; + +Boot2Docker.start = function (callback) { + var self = this; + self.exists(function (err, exists) { + if (!exists) { + callback('Cannot start if the boot2docker VM doesn\'t exist'); + return; + } + self.exec('up -v', function (err, stdout) { + console.log('here0'); + console.log('here1'); + // Sometimes boot2docker returns an error code even though it's working / waiting, so treat that as + // Success as well + if (!err || (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1)) { + Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) { + console.log('here1'); + if (err) { callback(err); return; } + VirtualBox.removeDHCP(function (err) { + console.log('here2'); + self.injectUtilities(function (err) { + console.log('here3'); + callback(err); + }); + }); + }); + } else { + callback(err); + } + }); + }); +}; + +Boot2Docker.state = function (callback) { + this.exec('info', function (err, stdout) { if (err) { callback(err, null); return; @@ -32,8 +115,8 @@ getBoot2DockerState = function (callback) { }); }; -getBoot2DockerDiskUsage = function (callback) { - boot2dockerexec('ssh "df"', function (err, stdout) { +Boot2Docker.diskUsage = function (callback) { + this.exec('ssh "df"', function (err, stdout) { if (err) { callback(err, null); return; @@ -61,8 +144,8 @@ getBoot2DockerDiskUsage = function (callback) { }); }; -getBoot2DockerMemoryUsage = function (callback) { - boot2dockerexec('ssh "free -m"', function (err, stdout) { +Boot2Docker.memoryUsage = function (callback) { + this.exec('ssh "free -m"', function (err, stdout) { if (err) { callback(err, null); return; @@ -92,152 +175,80 @@ getBoot2DockerMemoryUsage = function (callback) { }); }; -getBoot2DockerInfo = function (callback) { - 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) { +Boot2Docker.stats = function (callback) { + this.state(function (err, state) { + if (err) { + callback(err, null); + return; + } + if (state === 'poweroff') { + callback(null, {state: state}); + return; + } + this.memoryUsage(function (err, mem) { if (err) { - callback(err, null); + callback(null, {state: state}); return; } - if (state === 'poweroff') { - callback(null, {state: state}); - } else { - getBoot2DockerMemoryUsage(function (err, mem) { - if (err) { callback(null, {state: state}); } - getBoot2DockerDiskUsage(function (err, disk) { - if (err) { callback(null, {state: state}); } - callback(null, { - state: state, - memory: mem, - disk: disk - }); - }); - }); - } - }); - }); - }); -}; - -boot2DockerVMExists = function (callback) { - boot2dockerexec('info', function (err) { - if (err) { - callback(null, false); - } else { - callback(null, true); - } - }); -}; - -eraseBoot2DockerVMFiles = function (callback) { - var VMFileLocation = path.join(getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm'); - exec('rm -rf ' + VMFileLocation, function (err) { - callback(err); - }); -}; - -initBoot2Docker = function (callback) { - isVirtualBoxInstalled(function (err, installed) { - if (err) { - callback(err); - return; - } - if (installed) { - boot2dockerexec('init', function (err) { - console.log(err); + this.diskUsage(function (err, disk) { if (err) { - if (err.indexOf('exit status 1') !== -1) { - eraseBoot2DockerVMFiles(function () { - boot2dockerexec('init', function (err) { - callback(err); - }); - }); - } else { - callback(err); - } - } else { - callback(); + callback(null, {state: state}); + return; } + callback(null, { + state: state, + memory: mem, + disk: disk + }); }); + }); + }); +}; + +/** + * Get the VM's version. + * Node that this only works if the VM is up and running. + */ +Boot2Docker.vmVersion = function (callback) { + this.exec('ssh "cat /etc/version', function (err, stdout, stderr) { + if (err) { + callback(err); + return; } else { - callback(new Error('initBoot2Docker called but VirtualBox isn\'t installed.')); + callback(null, stdout); } }); }; -upgradeBoot2Docker = function (callback) { - boot2dockerexec('upgrade', function (err, stdout) { - console.log(stdout); - callback(err); +Boot2Docker.version = function (callback) { + this.exec('version', function (err, stdout, stderr) { + if (err) { + callback(err); + return; + } + var match = stdout.match(/Client version: v(\d\.\d\.\d)/); + if (!match || match.length < 2) { + callback('Could not parse the boot2docker cli version.') + } else { + callback(null, match[1]); + } }); }; -installBoot2DockerAddons = function (callback) { +Boot2Docker.injectUtilities = function (callback) { exec('/bin/cat ' + path.join(getBinDir(), 'kite-binaries.tar.gz') + ' | ' + path.join(getBinDir(), 'boot2docker') + ' ssh "tar zx -C /usr/local/bin"', function (err, stdout) { - console.log(stdout); callback(err); }); - exec('VBoxManage modifyvm boot2docker-vm --nic2 hostonly --nictype2 virtio --cableconnected2 on --hostonlyadapter2 vboxnet0', function (err, stdout) {}); - 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) { - isVirtualBoxInstalled(function (err, installed) { - if (err) { - callback(err); - return; - } - if (installed) { - boot2DockerVMExists(function (err, exists) { - if (exists) { - boot2dockerexec('up -v', function (err, stdout) { - console.log(err); - console.log(stdout); - if (err) { - if (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1) { - installBoot2DockerAddons(function (err) { - callback(err); - }); - } else { - callback(err); - } - } else { - installBoot2DockerAddons(function (err) { - callback(err); - }); - } - }); - } else { - callback(new Error('startBoot2Docker called but boot2docker-vm doesn\'t exist.')); - } - }); - } else { - callback(new Error('startBoot2Docker called but VirtualBox isn\'t installed.')); - } - }); -}; - -stopBoot2Docker = function (callback) { - boot2dockerexec('stop', function (err, stdout) { - console.log(stdout); - console.log(err); - if (err) { - callback(err); - return; - } - callback(null); - }); -}; - -checkBoot2DockerVM = function (callback) { - boot2DockerVMExists(function (err) { +Boot2Docker.check = function (callback) { + var self = this; + self.exists(function (err) { if (err) { callback(err); return; } else { - getBoot2DockerState(function (err, state) { + self.state(function (err, state) { if (state !== 'running') { callback('boot2docker not running'); } else { @@ -248,10 +259,9 @@ checkBoot2DockerVM = function (callback) { }); }; -// Make sure the VM exists, is up and is running. -resolveBoot2DockerVM = function (callback) { - boot2DockerVMExists(function (err, exists) { - +Boot2Docker.resolve = function (callback) { + var self = this; + self.exists(function (err, exists) { // If somehow the boot2docker VM doesn't exist anymor then re-create it. if (!exists) { initBoot2Docker(function () { @@ -260,9 +270,8 @@ resolveBoot2DockerVM = function (callback) { }); }); } else { - // If it exists but it's not running.. restart it. - getBoot2DockerState(function (err, state) { + self.state(function (err, state) { if (state !== 'running') { startBoot2Docker(function (err) { callback(err); diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js new file mode 100644 index 0000000000..39e043c2b8 --- /dev/null +++ b/meteor/client/lib/installer.js @@ -0,0 +1,146 @@ +var async = require('async'); + +Installer = {}; + +/** + * Install steps. A step is a function that accepts a function (err) callback and returns once that step is complete.keys: + * - run: Function that runs the installation step and calls the callback with an error if failed. + * - pastMessage: Message to show after step completion + * - message: Message to show while step is running + * - imperativeMessage: Message to show before running + */ +Installer.steps = [ + { + run: function (callback) { + var installed = VirtualBox.installed(); + if (!installed) { + VirtualBox.install(function (err) { + callback(err); + }); + } else { + // Version 4.3.12 is required. + VirtualBox.version(function (err, installedVersion) { + if (err) { + callback(err); + return; + } + var needsUpdate = Utilities.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0; + if (needsUpdate) { + VirtualBox.install(function (err) { + callback(err); + }); + } else { + callback(); + } + }); + } + }, + pastMessage: 'VirtualBox installed', + message: 'Installing VirtualBox', + futureMessage: 'Install VirtualBox if necessary' + }, + + // Initialize Boot2Docker if necessary. + { + run: function (callback) { + Boot2Docker.exists(function (err, exists) { + if (err) { + callback(err); + return; + } + if (!exists) { + Boot2Docker.init(function (err) { + callback(err); + }); + } else { + Boot2Docker.stop(function (err) { + Boot2Docker.upgrade(function (err) { + callback(err); + }); + }); + } + }); + }, + pastMessage: 'Setup the Boot2Docker VM (if required)', + message: 'Setting up the Boot2Docker VM', + futureMessage: 'Set up the Boot2Docker VM(if required)' + }, + + // Set up the routing. + { + run: function (callback) { + VirtualBox.setupRouting('boot2docker-vm', function (err, ifname) { + callback(err); + }); + }, + pastMessage: 'Container routing set up.', + message: 'Setting up container routing (root required).', + subMessage: '(This may take a few minutes)', + futureMessage: 'Set up container routing to VM (root required).' + }, + + // Start the Kitematic VM + { + run: function (callback) { + Boot2Docker.state(function (err, state) { + if (state !== 'running') { + Boot2Docker.start(function (err) { + callback(err); + }); + } else { + Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) { + callback(err); + }); + } + }); + }, + pastMessage: 'Started the Boot2Docker VM', + message: 'Starting the Boot2Docker VM', + subMessage: '(This may take a few minutes)', + futureMessage: 'Start the Kitematic VM', + }, + + // Set up the default Kitematic images + { + run: function (callback) { + Meteor.call('reloadDefaultContainers', function (err) { + callback(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', + } +]; + +Installer.run = function (callback) { + var currentStep = 0; + Session.set('currentInstallStep', currentStep); + Session.set('numberOfInstallSteps', this.steps.length); + async.eachSeries(this.steps, function (step, callback) { + console.log('Performing step ' + currentStep); + step.run(function (err) { + if (err) { + callback(err); + } else { + currentStep += 1; + Session.set('currentInstallStep', currentStep); + callback(); + } + }); + }, function (err) { + if (err) { + // if any of the steps fail + console.log('Kitematic setup failed at step ' + currentStep); + console.log(err); + Session.set('failedStep', currentStep); + Session.set('failedError', err); + callback(err); + } else { + // Setup Finished + console.log('Setup finished.'); + callback(); + } + }); +}; diff --git a/meteor/client/lib/utilities.js b/meteor/client/lib/utilities.js index c3fe268798..8915f19d46 100755 --- a/meteor/client/lib/utilities.js +++ b/meteor/client/lib/utilities.js @@ -1,5 +1,80 @@ var path = require('path'); +Utilities = {}; + +/** + * Compares two software version numbers (e.g. "1.7.1" or "1.2b"). + * + * @param {string} v1 The first version to be compared. + * @param {string} v2 The second version to be compared. + * @param {object} [options] Optional flags that affect comparison behavior: + *
    + *
  • + * lexicographical: true compares each part of the version strings lexicographically instead of + * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than + * "1.2". + *
  • + *
  • + * zeroExtend: true changes the result if one version string has less parts than the other. In + * this case the shorter string will be padded with "zero" parts instead of being considered smaller. + *
  • + *
+ * @returns {number|NaN} + *
    + *
  • 0 if the versions are equal
  • + *
  • a negative integer iff v1 < v2
  • + *
  • a positive integer iff v1 > v2
  • + *
  • NaN if either version string is in the wrong format
  • + *
+ * + */ +Utilities.compareVersions = function (v1, v2, options) { + var lexicographical = options && options.lexicographical, + zeroExtend = options && options.zeroExtend, + v1parts = v1.split('.'), + v2parts = v2.split('.'); + + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + + if (zeroExtend) { + while (v1parts.length < v2parts.length) v1parts.push('0'); + while (v2parts.length < v1parts.length) v2parts.push('0'); + } + + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length == i) { + return 1; + } + + if (v1parts[i] == v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + return 1; + } + else { + return -1; + } + } + + if (v1parts.length != v2parts.length) { + return -1; + } + + return 0; +}; + getBinDir = function () { if (process.env.NODE_ENV === 'development') { return path.join(path.join(process.env.PWD, '..'), 'resources'); diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js index 6d362492a9..cbf3290293 100644 --- a/meteor/client/lib/virtualbox.js +++ b/meteor/client/lib/virtualbox.js @@ -1,55 +1,168 @@ var fs = require('fs'); -var child_process = require('child_process'); +var exec = require('exec'); var path = require('path'); -isVirtualBoxInstalled = function (callback) { - fs.exists('/usr/bin/VBoxManage', function (exists) { - callback(null, exists); +VirtualBox = {}; + +VirtualBox.REQUIRED_VERSION = '4.3.12'; +VirtualBox.INCLUDED_VERSION = '4.3.12'; +VirtualBox.INSTALLER_FILENAME = 'virtualbox-4.3.12.pkg'; + +// Info for the hostonly interface we add to the VM. +VirtualBox.HOSTONLY_HOSTIP = '192.168.60.3'; +VirtualBox.HOSTONLY_NETWORKMASK = '255.255.255.0'; + +VirtualBox.installed = function () { + return fs.existsSync('/usr/bin/VBoxManage'); +}; + +VirtualBox.exec = function (command, callback) { + exec('/usr/bin/VBoxManage ' + command, function (error, stdout, stderr) { + callback(error, stdout, stderr); }); }; -isResolverSetup = function (callback) { - fs.readFile('/etc/resolver/dev', { - encoding: 'utf8' - }, function (err, data) { +VirtualBox.install = function (callback) { + // -W waits for the process to close before finishing. + exec('open -W ' + path.join(getBinDir(), this.INSTALLER_FILENAME), function (error, stdout, stderr) { + if (error) { + callback(error); + return; + } + callback(null); + }); +} + +VirtualBox.version = function (callback) { + if (!this.installed()) { + callback('VirtualBox not installed.'); + return; + } + this.exec('-v', function (err, stdout, stderr) { if (err) { - callback(err, false); - } else { - if (data.indexOf('nameserver 172.17.42.1') !== -1) { - callback(null, true); - } else { - callback(null, false); + callback(err); + return; + } + // Output is x.x.xryyyyyy + var match = stdout.match(/(\d+\.\d+\.\d+).*/); + if (!match || match.length < 2) { + callback('VBoxManage -v output format not recognized.'); + return; + } + callback(null, match[1]); + }); +}; + +VirtualBox.hostOnlyIfs = function (callback) { + this.exec('list hostonlyifs', function (err, stdout, stderr) { + if (err) { + callback(err); + return; + } + var lines = stdout.split('\n'); + var hostOnlyIfs = {}; + var currentIf = null; + _.each(lines, function (line) { + if (!line.length) return; + var pieces = line.split(':'); + var key = pieces[0].trim(); + var value = pieces[1] ? pieces[1].trim() : null; + if (key === 'Name') { + currentIf = value; + hostOnlyIfs[value] = {}; } - } + hostOnlyIfs[currentIf][key] = value; + }); + callback(null, hostOnlyIfs); }); }; -setupResolver = function (callback) { - var installFile = path.join(getBinDir(), 'install'); - var cocoaSudo = path.join(getBinDir(), 'cocoasudo'); - var execCommand = cocoaSudo + ' --prompt="Kitematic Setup wants to make changes. Type your password to allow this." ' + installFile; - child_process.exec(execCommand, function (error, stdout, stderr) { - console.log(stdout); - if (error) { - console.log(error); - callback(error); +VirtualBox.hostOnlyAdapters = function (vm, callback) { + this.exec('showvminfo ' + vm + ' --machinereadable', function (err, stdout, stderr) { + if (err) { + callback(err); return; } - console.log('Virtualbox Installation & Resolver config complete.'); - callback(); + var matches = stdout.match(/(hostonlyadapter\d+)="(vboxnet\d+)"/g); + if (!matches.length) { + callback(null, {}); + } else { + var objs = {}; + _.each(matches, function (match) { + var pieces = match.split('='); + objs[pieces[0]] = pieces[1].replace(/"/g, ''); + }); + callback(null, objs); + } }); }; -setupVirtualBox = function (callback) { - child_process.exec('open -W ' + path.join(getBinDir(), 'virtualbox-4.3.12.pkg'), function (error, stdout, stderr) { - console.log(stdout); - if (error) { - console.log(error); - callback(error); +VirtualBox.hostOnlyAdapter = function (callback) { + var self = this; + self.hostOnlyIfs(function (err, ifs) { + var iface = _.findWhere(_.toArray(ifs), {IPAddress: VirtualBox.HOSTONLY_HOSTIP}); + if (!iface) { + self.exec('hostonlyif create', function (err, stdout, stderr) { + var match = stdout.match(/Interface '(vboxnet\d+)' was successfully created/); + console.log(match); + if (!match) { + callback('Could not parse output of hostonlyif create'); + return; + } + self.exec('hostonlyif ipconfig ' + match[1] + ' --ip ' + VirtualBox.HOSTONLY_HOSTIP + ' --netmask ' + VirtualBox.HOSTONLY_NETWORKMASK, function(err, stdout, stderr) { + if (err) { + callback(err); + return; + } + callback(null, match[1]); + }); + }); + } else { + callback(null, iface.Name); + } + }); +}; + +VirtualBox.addCustomHostAdapter = function (vm, callback) { + var self = this; + self.hostOnlyAdapter(function (err, ifname) { + if (err) { + callback(err); return; } - console.log('Virtualbox Installation running.'); - callback(); + self.exec('modifyvm ' + vm + ' --nic3 hostonly --nictype3 virtio --cableconnected3 on --hostonlyadapter3 ' + ifname, function (err, stdout, stderr) { + callback(err, ifname); + }); }); }; +VirtualBox.setupRouting = function (vm, callback) { + // Get the host only adapter or create it if it doesn't exist + this.addCustomHostAdapter(vm, function (err, ifname) { + var installFile = path.join(getBinDir(), 'install'); + var cocoaSudo = path.join(getBinDir(), 'cocoasudo'); + var execCommand = cocoaSudo + ' --prompt="Kitematic needs your password to allow routing *.dev requests to containers." ' + installFile; + exec(execCommand, {env: {IFNAME: ifname, GATEWAY: Boot2Docker.REQUIRED_IP}}, function (error, stdout, stderr) { + if (error) { + callback(error); + return; + } + callback(); + }); + }); +}; + + +VirtualBox.removeDHCP = function (callback) { + var self = this; + self.hostOnlyAdapter(function (err, ifname) { + if (err) { callback(err); return; } + console.log(ifname); + self.exec('dhcpserver remove --ifname ' + ifname, function (err, stdout, stderr) { + callback(err); + }); + }); +}; + + + diff --git a/meteor/client/main.js b/meteor/client/main.js index 7abe7d59a1..b021d84992 100755 --- a/meteor/client/main.js +++ b/meteor/client/main.js @@ -68,19 +68,6 @@ Meteor.call('getDockerHost', function (err, host) { Session.set('dockerHost', host); }); -updateBoot2DockerInfo = function () { - getBoot2DockerInfo(function (err, info) { - if (err) { - return; - } - Session.set('boot2dockerState', info.state); - if (info.state !== 'poweroff' && info.memory && info.disk) { - Session.set('boot2dockerMemoryUsage', info.memory); - Session.set('boot2dockerDiskUsage', info.disk); - } - }); -}; - fixBoot2DockerVM = function (callback) { checkBoot2DockerVM(function (err) { if (err) { @@ -138,7 +125,24 @@ fixDefaultContainers = function (callback) { }; Meteor.setInterval(function () { - updateBoot2DockerInfo(); + Boot2Docker.exists(function (err, exists) { + if (err) { return; } + if (exists) { + Boot2Docker.state(function (err, state) { + if (err) { return; } + if (state === 'running') { + Boot2Docker.info(function (err, info) { + if (err) { return; } + Session.set('boot2dockerState', info.state); + if (info.state !== 'poweroff' && info.memory && info.disk) { + Session.set('boot2dockerMemoryUsage', info.memory); + Session.set('boot2dockerDiskUsage', info.disk); + } + }); + } + }); + } + }); }, 5000); fixInterval = null; @@ -148,7 +152,6 @@ startFixInterval = function () { resolveWatchers(function () {}); fixBoot2DockerVM(function (err) { if (err) { console.log(err); return; } - // Meteor.call('recoverApps'); fixDefaultImages(function (err) { if (err) { console.log(err); return; } fixDefaultContainers(function (err) { diff --git a/meteor/client/views/dashboard/settings/dashboard-settings.js b/meteor/client/views/dashboard/settings/dashboard-settings.js index cf12f91c27..5ee50c0778 100644 --- a/meteor/client/views/dashboard/settings/dashboard-settings.js +++ b/meteor/client/views/dashboard/settings/dashboard-settings.js @@ -3,7 +3,7 @@ Template.dashboard_settings.events({ var $btn = $(e.currentTarget); $btn.html('Starting Boot2Docker...'); $btn.attr("disabled", "disabled"); - startFixInterval(); + //startFixInterval(); startBoot2Docker(function (err) { if (err) { console.error(err); } }); diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js index 004ac755cf..020e86413b 100644 --- a/meteor/client/views/dashboard/setup/setup-install.js +++ b/meteor/client/views/dashboard/setup/setup-install.js @@ -1,136 +1,8 @@ -var async = require('async'); - -// Install steps. A step is a function that accepts a function (err) callback and returns once that step is complete. -// keys: -// - install: Function that runs the installation step and calls the callback with an error if failed. -// - pastMessage: Message to show after step completion -// - message: Message to show while step is running -// - imperativeMessage: Message to show before running -var steps = [ - - // Set up VirtualBox - { - install: function (callback) { - isVirtualBoxInstalled(function (err, virtualBoxInstalled) { - var installedYet = false; - if (!virtualBoxInstalled) { - setupVirtualBox(function (err) { - callback(err); - }); - } else { - callback(); - } - }); - }, - pastMessage: 'VirtualBox installed', - message: 'Installing VirtualBox', - futureMessage: 'Install VirtualBox if necessary' - }, - - // Set up the routing. - { - install: function (callback) { - setupResolver(function (err) { - callback(err); - }); - }, - pastMessage: 'Container routing set up (root required).', - message: 'Setting up container routing (root required).', - subMessage: '(This may take a few minutes)', - futureMessage: 'Set up container routing to VM (root required).' - }, - - // Set up the VM for running Kitematic apps - { - install: function (callback) { - console.log('Checking if vm exists...'); - boot2DockerVMExists(function (err, exists) { - console.log('VM exists: ' + exists); - if (exists) { - console.log('Stopping vm'); - stopBoot2Docker(function () { - console.log('Upgrading vm'); - upgradeBoot2Docker(function () { - callback(); - }); - }); - } else { - console.log('init VM'); - initBoot2Docker(function () { - callback(); - }); - } - }); - }, - pastMessage: 'Set up the Kitematic VM', - message: 'Setting up the Kitematic VM...', - futureMessage: 'Set up the Kitematic VM' - }, - - // Start the Kitematic VM - { - install: function (callback) { - startBoot2Docker(function (err) { - callback(err); - }); - }, - pastMessage: 'Started the Kitematic VM', - message: 'Starting the Kitematic VM', - subMessage: '(This may take a few minutes)', - futureMessage: 'Start the Kitematic VM', - }, - - // Set up the default Kitematic images - { - install: function (callback) { - Meteor.call('reloadDefaultContainers', function (err) { - callback(err); - }); - }, - pastMessage: 'Started the Kitematic VM', - message: 'Setting up the default Kitematic images...', - subMessage: '(This may take a few minutes)', - futureMessage: 'Set up the default Kitematic images', - } -]; - -runSetup = function (callback) { - // Run through the Kitematic installation, skipping steps if required. - var currentStep = 0; - Session.set('currentInstallStep', currentStep); - Session.set('numberOfInstallSteps', steps.length); - async.eachSeries(steps, function (step, callback) { - console.log('Performing step ' + currentStep); - step.install(function (err) { - if (err) { - callback(err); - } else { - currentStep += 1; - Session.set('currentInstallStep', currentStep); - callback(); - } - }); - }, function (err) { - if (err) { - // if any of the steps fail - console.log('Kitematic setup failed at step ' + currentStep); - console.log(err); - Session.set('failedStep', currentStep); - Session.set('failedError', err); - callback(err); - } else { - // Setup Finished - console.log('Setup finished.'); - callback(); - } - }); -}; - var installStarted = false; Template.setup_install.rendered = function() { if(!installStarted) { installStarted = true; - runSetup(function (err) { + Installer.run(function (err) { if (err) { console.log('Setup failed.'); console.log(err); @@ -144,7 +16,7 @@ Template.setup_install.rendered = function() { }; Template.setup_install.steps = function () { - return steps.map(function (step, index) { + return Installer.steps.map(function (step, index) { step.index = index; return step; }); @@ -155,7 +27,7 @@ Template.setup_install.helpers({ return Session.get('currentInstallStep'); }, installComplete: function () { - return Session.get('currentInstallStep') === steps.length; + return Session.get('currentInstallStep') === Installer.steps.length; }, failedStep: function () { return Session.get('failedStep'); diff --git a/meteor/server/docker.js b/meteor/server/docker.js index d71b12688b..6aded68a47 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -3,8 +3,8 @@ Docker = Meteor.require('dockerode'); var Convert = Meteor.require('ansi-to-html'); var convert = new Convert(); -var DOCKER_HOST='192.168.59.103'; -docker = new Docker({host: '192.168.59.103', port: '2375'}); +var DOCKER_HOST='192.168.60.103'; +docker = new Docker({host: DOCKER_HOST, port: '2375'}); hasDockerfile = function (directory) { return fs.existsSync(path.join(directory, 'Dockerfile')); diff --git a/resources/install b/resources/install index 723ff280d1..b0791ac3bc 100755 --- a/resources/install +++ b/resources/install @@ -1,6 +1,11 @@ #!/bin/sh -# Lookup the Kitematic VM resolver for .dev domains +# This script must be run as root and sets up Mac OS X to route all .dev domains to the virtual box VM with the name +# 'boot2docker-vm'. It does the following: +# 1) Adds a file under /etc/resolver/dev +# 2) Sets up a LaunchAgent for adding entries to the route table to route all requests to the Docker subnet (172.17.0.0/16) +# And expects the $IFNAME variable to contain the interface on which to send traffic to the boot2docker VM. + mkdir -p /etc/resolver echo "nameserver 172.17.42.1" > /etc/resolver/dev @@ -8,10 +13,9 @@ DIR=$(dirname "$0") USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'` /bin/rm -rf /Library/LaunchAgents/com.kitematic.route.plist - -echo ' - - +echo " + + Label com.kitematic.route @@ -20,26 +24,25 @@ echo ' /sbin/route -n add - 172.17.0.0/16 - 192.168.59.103 + -net + 172.17.0.0 + -netmask + 255.255.0.0 + -iface + $IFNAME + 255.255.0.0 + -gateway + $GATEWAY KeepAlive RunAtLoad - ServiceIPC - - UserName - root LaunchOnlyOnce -' > /Library/LaunchAgents/com.kitematic.route.plist - -sudo -u $USER $DIR/boot2docker init -VBoxManage modifyvm boot2docker-vm --nic2 hostonly --nictype2 virtio --cableconnected2 on --hostonlyadapter2 vboxnet0 -VBoxManage dhcpserver add --netname=vboxnet0 --ip=192.168.59.99 --netmask=255.255.255.0 --lowerip=192.168.59.103 --upperip=192.168.59.103 +" > /Library/LaunchAgents/com.kitematic.route.plist # Add entries to routing table for Kitematic VM -/sbin/route delete 172.17.0.0/16 192.168.59.103 -/sbin/route -n add 172.17.0.0/16 192.168.59.103 \ No newline at end of file +/sbin/route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY +/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -iface $IFNAME -gateway $GATEWAY \ No newline at end of file From 3ac3c9aa9eb43194c1fa13bb4e7d0448dae7be48 Mon Sep 17 00:00:00 2001 From: Liban Mohamed Date: Mon, 1 Sep 2014 14:17:35 -0400 Subject: [PATCH 36/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0559fd0ba..c8cf734076 100755 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Keep track of development and community news. ## Versioning -For transparency into our release cycle and in striving to maintain backward compatibility, Kitematic is maintained under the [Semantic Versioning Guidelines](http://semver.org/). We'll try very hard adhere to those rules whenever possible. +For transparency into our release cycle and in striving to maintain backward compatibility, Kitematic is maintained under the [Semantic Versioning Guidelines](http://semver.org/). We'll try very hard to adhere to those rules whenever possible. ## Creators From c370a4cf898ffa064145e7821417511c81845c3b Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Mon, 1 Sep 2014 15:42:15 -0700 Subject: [PATCH 37/58] Fixing remaining installer bugs --- meteor/client/lib/boot2docker.js | 24 ++++++-------- meteor/client/lib/installer.js | 32 ++++++++++++------- meteor/client/lib/virtualbox.js | 13 ++------ meteor/client/main.js | 32 ++++++++++--------- .../dashboard/settings/dashboard-settings.js | 16 ++++++---- resources/install | 6 ++-- 6 files changed, 63 insertions(+), 60 deletions(-) diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 714f901902..b1cb48837f 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -81,6 +81,7 @@ Boot2Docker.start = function (callback) { // Success as well if (!err || (err.indexOf('Waiting for VM to be started') !== -1 || err.indexOf('..........') !== -1)) { self.correct(function (err) { + if (err) { callback(err); return; } self.injectUtilities(function (err) { callback(err); }); @@ -96,17 +97,14 @@ Boot2Docker.correct = function (callback) { Boot2Docker.setIp('eth2', Boot2Docker.REQUIRED_IP, function(err) { if (err) { callback(err); return; } VirtualBox.removeDHCP(function (err) { - callback(err); + callback(); }); }); }; Boot2Docker.state = function (callback) { - this.exec('info', function (err, stdout) { - if (err) { - callback(err, null); - return; - } + this.exec('info', function (err, stdout, stderr) { + if (err) { callback(err, null); return; } try { var info = JSON.parse(stdout); callback(null, info.State); @@ -177,23 +175,21 @@ Boot2Docker.memoryUsage = function (callback) { }; Boot2Docker.stats = function (callback) { - this.state(function (err, state) { - if (err) { - callback(err, null); - return; - } + var self = this; + self.state(function (err, state) { + if (err) { callback(err, null); return; } if (state === 'poweroff') { callback(null, {state: state}); return; } - this.memoryUsage(function (err, mem) { + self.memoryUsage(function (err, mem) { if (err) { callback(null, {state: state}); return; } - this.diskUsage(function (err, disk) { + self.diskUsage(function (err, disk) { if (err) { - callback(null, {state: state}); + callback(null, {state: state, memory: mem}); return; } callback(null, { diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js index 39e043c2b8..76a3465e0f 100644 --- a/meteor/client/lib/installer.js +++ b/meteor/client/lib/installer.js @@ -24,7 +24,7 @@ Installer.steps = [ callback(err); return; } - var needsUpdate = Utilities.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0; + var needsUpdate = Util.compareVersions(installedVersion, VirtualBox.REQUIRED_VERSION) < 0; if (needsUpdate) { VirtualBox.install(function (err) { callback(err); @@ -44,16 +44,14 @@ Installer.steps = [ { run: function (callback) { Boot2Docker.exists(function (err, exists) { - if (err) { - callback(err); - return; - } + if (err) { callback(err); return; } if (!exists) { Boot2Docker.init(function (err) { callback(err); }); } else { Boot2Docker.stop(function (err) { + if (err) { callback(err); return; } Boot2Docker.upgrade(function (err) { callback(err); }); @@ -66,24 +64,24 @@ Installer.steps = [ futureMessage: 'Set up the Boot2Docker VM(if required)' }, - // Set up the routing. { run: function (callback) { - VirtualBox.setupRouting('boot2docker-vm', function (err, ifname) { + VirtualBox.addCustomHostAdapter('boot2docker-vm', function (err, ifname) { callback(err); }); }, - pastMessage: 'Container routing set up.', - message: 'Setting up container routing (root required).', - subMessage: '(This may take a few minutes)', - futureMessage: 'Set up container routing to VM (root required).' + pastMessage: 'Added custom host adapter to the Boot2Docker VM', + message: 'Adding custom host adapter to the Boot2Docker VM', + futureMessage: 'Add custom host adapter to the Boot2Docker VM' }, // Start the Kitematic VM { run: function (callback) { Boot2Docker.state(function (err, state) { + if (err) { callback(err); return; } if (state !== 'running') { + console.log('starting'); Boot2Docker.start(function (err) { callback(err); }); @@ -96,10 +94,20 @@ Installer.steps = [ }, pastMessage: 'Started the Boot2Docker VM', message: 'Starting the Boot2Docker VM', - subMessage: '(This may take a few minutes)', futureMessage: 'Start the Kitematic VM', }, + { + run: function (callback) { + VirtualBox.setupRouting('boot2docker-vm', function (err, ifname) { + callback(err); + }); + }, + pastMessage: 'Container routing set up', + message: 'Setting up container routing (root required)', + futureMessage: 'Set up container routing to VM (root required)' + }, + // Set up the default Kitematic images { run: function (callback) { diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js index 73a23c27b8..4368f5c72f 100644 --- a/meteor/client/lib/virtualbox.js +++ b/meteor/client/lib/virtualbox.js @@ -106,16 +106,12 @@ VirtualBox.hostOnlyAdapter = function (callback) { if (!iface) { self.exec('hostonlyif create', function (err, stdout, stderr) { var match = stdout.match(/Interface '(vboxnet\d+)' was successfully created/); - console.log(match); if (!match) { callback('Could not parse output of hostonlyif create'); return; } self.exec('hostonlyif ipconfig ' + match[1] + ' --ip ' + VirtualBox.HOSTONLY_HOSTIP + ' --netmask ' + VirtualBox.HOSTONLY_NETWORKMASK, function(err, stdout, stderr) { - if (err) { - callback(err); - return; - } + if (err) { callback(err); return; } callback(null, match[1]); }); }); @@ -128,10 +124,7 @@ VirtualBox.hostOnlyAdapter = function (callback) { VirtualBox.addCustomHostAdapter = function (vm, callback) { var self = this; self.hostOnlyAdapter(function (err, ifname) { - if (err) { - callback(err); - return; - } + if (err) { callback(err); return; } self.exec('modifyvm ' + vm + ' --nic3 hostonly --nictype3 virtio --cableconnected3 on --hostonlyadapter3 ' + ifname, function (err, stdout, stderr) { callback(err, ifname); }); @@ -143,7 +136,7 @@ VirtualBox.setupRouting = function (vm, callback) { this.addCustomHostAdapter(vm, function (err, ifname) { var installFile = path.join(Util.getBinDir(), 'install'); var cocoaSudo = path.join(Util.getBinDir(), 'cocoasudo'); - var execCommand = cocoaSudo + ' --prompt="Kitematic needs your password to allow routing *.dev requests to containers." ' + installFile; + 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) { callback(error); diff --git a/meteor/client/main.js b/meteor/client/main.js index 8d852723bf..0033c360b4 100755 --- a/meteor/client/main.js +++ b/meteor/client/main.js @@ -126,17 +126,17 @@ fixDefaultContainers = function (callback) { Meteor.setInterval(function () { Boot2Docker.exists(function (err, exists) { - if (err) { return; } + if (err) { console.log(err); return; } if (exists) { Boot2Docker.state(function (err, state) { - if (err) { return; } + if (err) { console.log(err); return; } + Session.set('boot2dockerState', state); if (state === 'running') { - Boot2Docker.info(function (err, info) { - if (err) { return; } - Session.set('boot2dockerState', info.state); - if (info.state !== 'poweroff' && info.memory && info.disk) { - Session.set('boot2dockerMemoryUsage', info.memory); - Session.set('boot2dockerDiskUsage', info.disk); + Boot2Docker.stats(function (err, stats) { + if (err) { console.log(err); return; } + if (stats.state !== 'poweroff' && stats.memory && stats.disk) { + Session.set('boot2dockerMemoryUsage', stats.memory); + Session.set('boot2dockerDiskUsage', stats.disk); } }); } @@ -148,16 +148,18 @@ Meteor.setInterval(function () { Meteor.setInterval(function () { if (Installs.findOne()) { resolveWatchers(function () {}); - fixBoot2DockerVM(function (err) { - if (err) { console.log(err); return; } - Meteor.call('recoverApps'); - fixDefaultImages(function (err) { + if (!Session.get('boot2dockerOff')) { + fixBoot2DockerVM(function (err) { if (err) { console.log(err); return; } - fixDefaultContainers(function (err) { - if (err) { console.log(err); } + Meteor.call('recoverApps'); + fixDefaultImages(function (err) { + if (err) { console.log(err); return; } + fixDefaultContainers(function (err) { + if (err) { console.log(err); } + }); }); }); - }); + } } }, 5000); diff --git a/meteor/client/views/dashboard/settings/dashboard-settings.js b/meteor/client/views/dashboard/settings/dashboard-settings.js index 5ee50c0778..1a2ef4b7bd 100644 --- a/meteor/client/views/dashboard/settings/dashboard-settings.js +++ b/meteor/client/views/dashboard/settings/dashboard-settings.js @@ -3,18 +3,22 @@ Template.dashboard_settings.events({ var $btn = $(e.currentTarget); $btn.html('Starting Boot2Docker...'); $btn.attr("disabled", "disabled"); - //startFixInterval(); - startBoot2Docker(function (err) { - if (err) { console.error(err); } + Session.set('boot2dockerOff', false); + Boot2Docker.start(function (err) { + if (err) { + console.log(err); + } }); }, 'click .btn-stop-boot2docker': function (e) { var $btn = $(e.currentTarget); $btn.html('Stopping Boot2Docker...'); $btn.attr("disabled", "disabled"); - stopFixInterval(); - stopBoot2Docker(function (err) { - if (err) { console.error(err); } + Session.set('boot2dockerOff', true); + Boot2Docker.stop(function (err) { + if (err) { + console.log(err); + } }); } }); diff --git a/resources/install b/resources/install index afde39e80b..f558efe007 100755 --- a/resources/install +++ b/resources/install @@ -1,13 +1,13 @@ #!/bin/sh -# This script must be run as root and sets up Mac OS X to route all .dev domains to the virtual box VM with the name +# This script must be run as root and sets up Mac OS X to route all .kite domains to the virtual box VM with the name # 'boot2docker-vm'. It does the following: -# 1) Adds a file under /etc/resolver/dev +# 1) Adds a file under /etc/resolver/kite # 2) Sets up a LaunchAgent for adding entries to the route table to route all requests to the Docker subnet (172.17.0.0/16) # And expects the $IFNAME variable to contain the interface on which to send traffic to the boot2docker VM. mkdir -p /etc/resolver -echo "nameserver 172.17.42.1" > /etc/resolver/dev +echo "nameserver 172.17.42.1" > /etc/resolver/kite DIR=$(dirname "$0") USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'` From 21f4ca056525311f59f2c108494b508eff92a7ba Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Mon, 1 Sep 2014 15:55:13 -0700 Subject: [PATCH 38/58] Remove mongod.lock upon restart --- index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/index.js b/index.js index 3a3d3a4989..9fe8b62b3e 100644 --- a/index.js +++ b/index.js @@ -43,10 +43,7 @@ var start = function (callback) { freeport(function(err, mongoPort) { console.log('MongoDB: ' + mongoPort); console.log('webPort: ' + webPort); - child_process.exec('kill $(ps aux -e | grep PURPOSE=KITEMATIC | awk \'{print $2}\')', function (error, stdout, stderr) { - console.log(error); - console.log(stdout); - console.log(stderr); + child_process.exec('kill $(ps aux -e | grep PURPOSE=KITEMATIC | awk \'{print $2}\') && rm ' + path.join(dataPath, 'mongod.lock'), function (error, stdout, stderr) { var mongoChild = child_process.spawn(path.join(process.cwd(), 'resources', 'mongod'), ['--bind_ip', '127.0.0.1', '--dbpath', dataPath, '--port', mongoPort, '--unixSocketPrefix', dataPath], { env: { PURPOSE: 'KITEMATIC' From dc1cb0caecdc415999ad3f22d83701949ffbf5d8 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Mon, 1 Sep 2014 17:19:19 -0700 Subject: [PATCH 39/58] Adding update process --- index.js | 5 ++++ meteor/client/lib/installer.js | 6 +++++ meteor/client/lib/router.js | 13 +++++++---- meteor/client/main.js | 2 +- .../views/dashboard/setup/setup-install.js | 2 +- .../views/dashboard/setup/setup-layout.html | 9 ++++++-- .../views/dashboard/setup/setup-layout.js | 11 +++++++++ meteor/collections/apps.js | 8 +++---- meteor/collections/installs.js | 23 +++++++++++++++++-- resources/kite-dns/dnsmasq.tmpl | 2 +- script/setup.sh | 6 +---- script/versions.sh | 2 +- 12 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 meteor/client/views/dashboard/setup/setup-layout.js diff --git a/index.js b/index.js index 9fe8b62b3e..ba3a23f38f 100644 --- a/index.js +++ b/index.js @@ -51,7 +51,12 @@ var start = function (callback) { }); var started = false; mongoChild.stdout.setEncoding('utf8'); + mongoChild.stderr.setEncoding('utf8'); + mongoChild.stderr.on('data', function (data) { + console.log(data); + }); mongoChild.stdout.on('data', function (data) { + console.log(data); if (data.indexOf('waiting for connections on port ' + mongoPort)) { if (!started) { started = true; diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js index 76a3465e0f..459ed06f7b 100644 --- a/meteor/client/lib/installer.js +++ b/meteor/client/lib/installer.js @@ -2,6 +2,12 @@ var async = require('async'); Installer = {}; +Installer.CURRENT_VERSION = '0.0.2'; + +Installer.isUpToDate = function () { + return !!Installs.findOne({version: Installer.CURRENT_VERSION}); +}; + /** * Install steps. A step is a function that accepts a function (err) callback and returns once that step is complete.keys: * - run: Function that runs the installation step and calls the callback with an error if failed. diff --git a/meteor/client/lib/router.js b/meteor/client/lib/router.js index 1ef66ba0dd..6e8168efd6 100755 --- a/meteor/client/lib/router.js +++ b/meteor/client/lib/router.js @@ -48,10 +48,15 @@ Router.map(function () { controller: 'SetupController', action: function () { if (this.ready()) { - var install = Installs.findOne(); - if (!install) { - console.log('No installs detected, running installer again.'); - this.redirect('/setup/intro'); + if (!Installer.isUpToDate()) { + if (!Installs.findOne()) { + console.log('No installs detected, running installer again.'); + this.redirect('/setup/intro'); + } else { + // There's an install but it's lower than the current version, re-run as an 'update'. + Session.set('isUpdating', true); + this.redirect('/setup/intro'); + } } else { this.redirect('/apps'); } diff --git a/meteor/client/main.js b/meteor/client/main.js index 0033c360b4..429201d295 100755 --- a/meteor/client/main.js +++ b/meteor/client/main.js @@ -146,7 +146,7 @@ Meteor.setInterval(function () { }, 5000); Meteor.setInterval(function () { - if (Installs.findOne()) { + if (Installer.isUpToDate()) { resolveWatchers(function () {}); if (!Session.get('boot2dockerOff')) { fixBoot2DockerVM(function (err) { diff --git a/meteor/client/views/dashboard/setup/setup-install.js b/meteor/client/views/dashboard/setup/setup-install.js index cc222f82e2..d5774fbbd7 100644 --- a/meteor/client/views/dashboard/setup/setup-install.js +++ b/meteor/client/views/dashboard/setup/setup-install.js @@ -7,7 +7,7 @@ Template.setup_install.rendered = function() { console.log('Setup failed.'); console.log(err); } else { - Installs.insert({}); + Installs.insert({version: Installer.CURRENT_VERSION}); Router.go('dashboard_apps'); } }); diff --git a/meteor/client/views/dashboard/setup/setup-layout.html b/meteor/client/views/dashboard/setup/setup-layout.html index 12c0f8682b..ce3765beb4 100644 --- a/meteor/client/views/dashboard/setup/setup-layout.html +++ b/meteor/client/views/dashboard/setup/setup-layout.html @@ -1,8 +1,13 @@ diff --git a/meteor/client/views/dashboard/setup/setup-layout.js b/meteor/client/views/dashboard/setup/setup-layout.js new file mode 100644 index 0000000000..2d99b3bc4b --- /dev/null +++ b/meteor/client/views/dashboard/setup/setup-layout.js @@ -0,0 +1,11 @@ +Template.setup_layout.rendered = function () { + Meteor.setInterval(function () { + $('.header .icons a').tooltip(); + }, 1000); +}; + +Template.setup_layout.helpers({ + isUpdating: function () { + return Session.get('isUpdating'); + } +}); diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index ba40c9b2e9..85cf5c5266 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -60,7 +60,7 @@ Apps.helpers({ return Images.findOne(this.imageId); }, hostUrl: function () { - return this.name + '.dev'; + return this.name + '.kite'; }, ports: function () { var app = this; @@ -78,7 +78,7 @@ Apps.helpers({ var app = this; var image = Images.findOne(app.imageId); if (image && image.meta.app && image.meta.app.webPort) { - return 'http://' + app.name + '.dev:' + image.meta.app.webPort; + return 'http://' + app.name + '.kite:' + image.meta.app.webPort; } else if (image && image.meta.app && image.meta.app.webPort === false) { return null; } else { @@ -93,14 +93,14 @@ Apps.helpers({ } }); if (pickedPort) { - return 'http://' + app.name + '.dev:' + pickedPort; + return 'http://' + app.name + '.kite:' + pickedPort; } else { if (keys.length > 0) { // Picks the first port that's not SSH for (var i = 0; i < keys.length; i++) { var port = parseInt(keys[i].split('/')[0], 10); if (port !== 22) { - return 'http://' + app.name + '.dev:' + port; + return 'http://' + app.name + '.kite:' + port; } } return null; diff --git a/meteor/collections/installs.js b/meteor/collections/installs.js index 6f0a492ce7..314b538050 100644 --- a/meteor/collections/installs.js +++ b/meteor/collections/installs.js @@ -1,7 +1,26 @@ 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({ @@ -16,4 +35,4 @@ Installs.allow({ } }); -Installs.attachSchema(schemaInstalls); +Installs.attachSchema(schemaInstalls); \ No newline at end of file diff --git a/resources/kite-dns/dnsmasq.tmpl b/resources/kite-dns/dnsmasq.tmpl index 546fda7910..0355b257cc 100644 --- a/resources/kite-dns/dnsmasq.tmpl +++ b/resources/kite-dns/dnsmasq.tmpl @@ -1,6 +1,6 @@ user=root {{ range $index, $value := $ }} {{ with $address := index $value.Addresses 0 }} -address=/{{$value.Name}}.dev/{{$address.IP}} +address=/{{$value.Name}}.kite/{{$address.IP}} {{ end }} {{ end }} diff --git a/script/setup.sh b/script/setup.sh index c40c686e78..217e096da2 100755 --- a/script/setup.sh +++ b/script/setup.sh @@ -15,6 +15,7 @@ pushd cache if [ ! -f $BASE_IMAGE_VERSION_FILE ]; then cecho "-----> Downloading Kitematic base images..." $purple curl -L --progress-bar -o $BASE_IMAGE_VERSION_FILE https://s3.amazonaws.com/kite-installer/$BASE_IMAGE_VERSION_FILE + cp $BASE_IMAGE_VERSION_FILE ../resources/$BASE_IMAGE_FILE fi if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then @@ -60,11 +61,6 @@ if [ ! -f $COCOASUDO_FILE ]; then chmod +x $COCOASUDO_FILE fi - -if [ ! -f $BASE_IMAGE_FILE ]; then - cp ../cache/$BASE_IMAGE_VERSION_FILE $BASE_IMAGE_FILE -fi - cp ../cache/$BOOT2DOCKER_CLI_VERSION_FILE $BOOT2DOCKER_CLI_FILE chmod +x $BOOT2DOCKER_CLI_FILE diff --git a/script/versions.sh b/script/versions.sh index f1f7853a2f..e46149f22c 100644 --- a/script/versions.sh +++ b/script/versions.sh @@ -1,4 +1,4 @@ -BASE_IMAGE_VERSION=0.0.1 +BASE_IMAGE_VERSION=0.0.2 BASE_IMAGE_VERSION_FILE=base-images-$BASE_IMAGE_VERSION.tar.gz BASE_IMAGE_FILE=base-images.tar.gz From cc9b694e1fd6e02d615b547021c25af6c4492048 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Mon, 1 Sep 2014 18:11:52 -0700 Subject: [PATCH 40/58] Fixing launchagent to wait for our host-only network interface to be available before adding a route to the static routing table --- resources/install | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/resources/install b/resources/install index f558efe007..6f8f7c122f 100755 --- a/resources/install +++ b/resources/install @@ -21,15 +21,9 @@ echo " com.kitematic.route ProgramArguments - /sbin/route - -n - add - -net - 172.17.0.0 - -netmask - 255.255.0.0 - -gateway - $GATEWAY + bash + -c + /usr/sbin/scutil -w State:/Network/Interface/$IFACE/IPv4;/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY KeepAlive From 8e3db7c30d6a1755279b8c77cae3356d35195100 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Mon, 1 Sep 2014 18:12:37 -0700 Subject: [PATCH 41/58] Linked --- meteor/client/lib/boot2docker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index b1cb48837f..589b85b67d 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -32,7 +32,7 @@ Boot2Docker.stop = function (callback) { }; Boot2Docker.erase = function (callback) { - var VMFileLocation = path.join(getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm'); + var VMFileLocation = path.join(Util.getHomePath(), 'VirtualBox\\ VMs/boot2docker-vm'); exec('rm -rf ' + VMFileLocation, function (err) { callback(err); }); @@ -85,7 +85,7 @@ Boot2Docker.start = function (callback) { self.injectUtilities(function (err) { callback(err); }); - }) + }); } else { callback(err); } From 6d50d91c08608b733d0857420b4717ffbd703aeb Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 1 Sep 2014 22:41:15 -0700 Subject: [PATCH 42/58] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8cf734076..ff65bc23ee 100755 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Kitematic is still in Beta. Any effort in helping us find issues and improving t - Remove VirtualBox - rm /usr/local/bin/boot2docker -- sudo route delete 172.17.0.0/16 192.168.59.103 (disable routing to containers through VM) +- sudo route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway 192.168.60.103 (disable routing to containers through VM) - rm -rf ~/Library/Application\ Support/Kitematic (remove app data) - rm /Library/LaunchAgents/com.kitematic.route.plist (remove launch job that sets up routing to the containers) From f56acd75b84cb752fd2ca6a6239a549b0efa3b8b Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Tue, 2 Sep 2014 00:00:40 -0700 Subject: [PATCH 43/58] Fixing installer bug if the route doesnt exist. --- resources/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/install b/resources/install index 6f8f7c122f..cc67be5d49 100755 --- a/resources/install +++ b/resources/install @@ -35,5 +35,5 @@ echo " " > /Library/LaunchAgents/com.kitematic.route.plist # Add entries to routing table for Kitematic VM -/sbin/route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY +/sbin/route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY > /dev/null 2>&1 || true /sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY \ No newline at end of file From f5d76b600f02f7266750c83d3b6a418bf76c9936 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Tue, 2 Sep 2014 01:36:27 -0700 Subject: [PATCH 44/58] Fixing IFNAME variable in installer --- resources/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/install b/resources/install index cc67be5d49..2d9b03c07c 100755 --- a/resources/install +++ b/resources/install @@ -23,7 +23,7 @@ echo " bash -c - /usr/sbin/scutil -w State:/Network/Interface/$IFACE/IPv4;/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY + /usr/sbin/scutil -w State:/Network/Interface/$IFNAME/IPv4;/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY KeepAlive From a9b68123382e4b81cd701fa9c12a0456f857034f Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Tue, 2 Sep 2014 03:40:29 -0700 Subject: [PATCH 45/58] Fixing DNS issues, should have been .kite and not .dev --- meteor/server/apps.js | 2 +- meteor/server/docker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor/server/apps.js b/meteor/server/apps.js index a8e7c8dce7..d12a1e20a6 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -14,7 +14,7 @@ Apps.restart = function (app, callback) { }).run(); callback(null); // Use dig to refresh the DNS - exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {}); + exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {}); } else { callback(null); } diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 37a27d6d88..6978024245 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -72,7 +72,7 @@ Docker.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() {}); + exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {}); callback(null, container); }); }); From 965d7ac650702f7304b3c53cb1226f683e71db75 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Tue, 2 Sep 2014 13:24:08 -0700 Subject: [PATCH 46/58] Updated Trello board link. --- CONTRIBUTING.md | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45d12a3bca..2739e50077 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,7 @@ Example: Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. A roadmap of the project is kept -on the [Kitematic Roadmap](https://trello.com/b/xea5AHRk/kitematic-roadmap) Trello board. +on the [Kitematic Roadmap](https://trello.com/b/G5Aw0Rqc/kitematic-roadmap) Trello board. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. diff --git a/README.md b/README.md index ff65bc23ee..ec66bc4084 100755 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Note:** If the installer gets stuck at any step for more than 1 minute, there is probably an error. Please help us troubleshoot by running it from the command line, and submit the logs to [contact@kitematic.com](mailto:contact@kitematic.com). 1. `cd ` -2. Run `./Kitematic.app/Contents/MacOS/node-webkit` +2. Run `./Kitematic.app/Contents/MacOS/node-webkit` Kitematic is still in Beta. Any effort in helping us find issues and improving the experience is greatly appreciated! @@ -61,7 +61,7 @@ Kitematic's documentation and other information can be found at [http://kitemati Please read through our [Contributing Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development. -Development [Roadmap](https://trello.com/b/xea5AHRk/kitematic-roadmap) can be found on our Trello board. +Development [Roadmap](https://trello.com/b/G5Aw0Rqc/kitematic-roadmap) can be found on our Trello board. ## Community From 3a738cd24e33d4ba3c734515557985b4937ef78c Mon Sep 17 00:00:00 2001 From: Sean Li Date: Tue, 2 Sep 2014 22:25:16 -0700 Subject: [PATCH 47/58] Changed Trello board link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec66bc4084..beca2bb56c 100755 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Development [Roadmap](https://trello.com/b/G5Aw0Rqc/kitematic-roadmap) can be fo Keep track of development and community news. - Follow [@kitematic on Twitter](https://twitter.com/kitematic). -- Check out Kitematic's [Roadmap](https://trello.com/b/xea5AHRk/kite-roadmap) on our Trello board. +- Check out Kitematic's [Roadmap](https://trello.com/b/G5Aw0Rqc/kitematic-roadmap) on our Trello board. - Read and subscribe to [The Official Kitematic Blog](https://kitematic.com/blog). - Chat with developers using Kitematic in our [HipChat room](http://www.hipchat.com/giAT9Fqb5). From 29fbc0cb5866520ab769fedb4996ff339f98e9a5 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 18:17:46 -0700 Subject: [PATCH 48/58] Moved server code to the client side for performance. --- meteor/.jshintrc | 9 +- meteor/.meteor/packages | 1 - meteor/client/lib/apps.js | 119 +++++++ meteor/client/lib/boot2docker.js | 6 +- meteor/{server => client/lib}/docker.js | 80 +---- meteor/client/lib/images.js | 246 ++++++++++++++ meteor/client/lib/init/constants.js | 33 ++ .../lib/init}/form-schemas.js | 15 - meteor/client/lib/init/requires.js | 10 + meteor/client/lib/installer.js | 8 +- meteor/client/lib/requires.js | 2 - meteor/client/lib/startup.js | 18 ++ meteor/client/lib/sync.js | 8 +- meteor/client/lib/utilities.js | 134 ++++++++ meteor/{server => client}/lib/validations.js | 0 meteor/client/lib/virtualbox.js | 8 +- meteor/client/main.js | 16 +- .../dashboard/apps/dashboard-apps-settings.js | 16 +- .../dashboard/apps/dashboard-single-app.js | 12 +- .../dashboard/components/modal-create-app.js | 59 +++- .../components/modal-create-image.js | 49 ++- .../images/dashboard-images-settings.js | 46 ++- .../images/dashboard-single-image.js | 4 +- .../layouts/dashboard-apps-layout.js | 8 +- .../layouts/dashboard-images-layout.js | 4 +- meteor/collections/apps.js | 97 +----- meteor/collections/images.js | 98 ------ meteor/collections/installs.js | 25 -- meteor/lib/constants.js | 9 - meteor/lib/utilities.js | 146 --------- meteor/server/apps.js | 149 --------- meteor/server/constants.js | 24 -- meteor/server/images.js | 306 ------------------ meteor/server/lib/requires.js | 11 - meteor/smart.json | 1 - meteor/smart.lock | 11 - package.json | 9 +- 37 files changed, 732 insertions(+), 1065 deletions(-) create mode 100644 meteor/client/lib/apps.js rename meteor/{server => client/lib}/docker.js (79%) mode change 100755 => 100644 create mode 100644 meteor/client/lib/images.js create mode 100644 meteor/client/lib/init/constants.js rename meteor/{server => client/lib/init}/form-schemas.js (55%) mode change 100755 => 100644 create mode 100644 meteor/client/lib/init/requires.js delete mode 100644 meteor/client/lib/requires.js rename meteor/{server => client}/lib/validations.js (100%) mode change 100755 => 100644 delete mode 100644 meteor/lib/constants.js delete mode 100644 meteor/lib/utilities.js delete mode 100755 meteor/server/apps.js delete mode 100644 meteor/server/constants.js delete mode 100755 meteor/server/images.js delete mode 100755 meteor/server/lib/requires.js diff --git a/meteor/.jshintrc b/meteor/.jshintrc index ab5b799ac1..dca33cf8e9 100755 --- a/meteor/.jshintrc +++ b/meteor/.jshintrc @@ -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 } } diff --git a/meteor/.meteor/packages b/meteor/.meteor/packages index 0739b78bee..45a51bb89e 100755 --- a/meteor/.meteor/packages +++ b/meteor/.meteor/packages @@ -10,7 +10,6 @@ npm iron-router headers handlebar-helpers -collection2 collection-hooks moment underscore-string-latest diff --git a/meteor/client/lib/apps.js b/meteor/client/lib/apps.js new file mode 100644 index 0000000000..f8745c4306 --- /dev/null +++ b/meteor/client/lib/apps.js @@ -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); } + }); + } + }); + }); +}; diff --git a/meteor/client/lib/boot2docker.js b/meteor/client/lib/boot2docker.js index 589b85b67d..7dfac20eb0 100644 --- a/meteor/client/lib/boot2docker.js +++ b/meteor/client/lib/boot2docker.js @@ -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); }); }; diff --git a/meteor/server/docker.js b/meteor/client/lib/docker.js old mode 100755 new mode 100644 similarity index 79% rename from meteor/server/docker.js rename to meteor/client/lib/docker.js index 6978024245..02d55c6954 --- a/meteor/server/docker.js +++ b/meteor/client/lib/docker.js @@ -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)(); - } -}); diff --git a/meteor/client/lib/images.js b/meteor/client/lib/images.js new file mode 100644 index 0000000000..0c121d86ec --- /dev/null +++ b/meteor/client/lib/images.js @@ -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); + }); + }); + }); + }); +}; diff --git a/meteor/client/lib/init/constants.js b/meteor/client/lib/init/constants.js new file mode 100644 index 0000000000..0aafe5ef0f --- /dev/null +++ b/meteor/client/lib/init/constants.js @@ -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 +]; diff --git a/meteor/server/form-schemas.js b/meteor/client/lib/init/form-schemas.js old mode 100755 new mode 100644 similarity index 55% rename from meteor/server/form-schemas.js rename to meteor/client/lib/init/form-schemas.js index d3092abe04..ea165b9b54 --- a/meteor/server/form-schemas.js +++ b/meteor/client/lib/init/form-schemas.js @@ -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); -}); diff --git a/meteor/client/lib/init/requires.js b/meteor/client/lib/init/requires.js new file mode 100644 index 0000000000..78e07108dc --- /dev/null +++ b/meteor/client/lib/init/requires.js @@ -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(); diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js index 459ed06f7b..a5bc41ec81 100644 --- a/meteor/client/lib/installer.js +++ b/meteor/client/lib/installer.js @@ -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' } ]; diff --git a/meteor/client/lib/requires.js b/meteor/client/lib/requires.js deleted file mode 100644 index 2b261be406..0000000000 --- a/meteor/client/lib/requires.js +++ /dev/null @@ -1,2 +0,0 @@ -path = require('path'); -fs = require('fs'); diff --git a/meteor/client/lib/startup.js b/meteor/client/lib/startup.js index 512c2ffc32..51407e4460 100644 --- a/meteor/client/lib/startup.js +++ b/meteor/client/lib/startup.js @@ -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; } + }); + } }); diff --git a/meteor/client/lib/sync.js b/meteor/client/lib/sync.js index cd2468ef2d..242cabf89e 100644 --- a/meteor/client/lib/sync.js +++ b/meteor/client/lib/sync.js @@ -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 () {}); } diff --git a/meteor/client/lib/utilities.js b/meteor/client/lib/utilities.js index 3315ad4040..5bc891436b 100755 --- a/meteor/client/lib/utilities.js +++ b/meteor/client/lib/utilities.js @@ -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: + *
    + *
  • + * lexicographical: true compares each part of the version strings lexicographically instead of + * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than + * "1.2". + *
  • + *
  • + * zeroExtend: true changes the result if one version string has less parts than the other. In + * this case the shorter string will be padded with "zero" parts instead of being considered smaller. + *
  • + *
+ * @returns {number|NaN} + *
    + *
  • 0 if the versions are equal
  • + *
  • a negative integer iff v1 < v2
  • + *
  • a positive integer iff v1 > v2
  • + *
  • NaN if either version string is in the wrong format
  • + *
+ * + */ +Util.compareVersions = function (v1, v2, options) { + var lexicographical = options && options.lexicographical, + zeroExtend = options && options.zeroExtend, + v1parts = v1.split('.'), + v2parts = v2.split('.'); + + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + + if (zeroExtend) { + while (v1parts.length < v2parts.length) { + v1parts.push('0'); + } + while (v2parts.length < v1parts.length) { + v2parts.push('0'); + } + } + + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + + if (v1parts[i] === v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + return 1; + } + else { + return -1; + } + } + + if (v1parts.length !== v2parts.length) { + return -1; + } + + return 0; +}; diff --git a/meteor/server/lib/validations.js b/meteor/client/lib/validations.js old mode 100755 new mode 100644 similarity index 100% rename from meteor/server/lib/validations.js rename to meteor/client/lib/validations.js diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js index 4368f5c72f..577c610797 100644 --- a/meteor/client/lib/virtualbox.js +++ b/meteor/client/lib/virtualbox.js @@ -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) { diff --git a/meteor/client/main.js b/meteor/client/main.js index 429201d295..8b28f32921 100755 --- a/meteor/client/main.js +++ b/meteor/client/main.js @@ -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); - diff --git a/meteor/client/views/dashboard/apps/dashboard-apps-settings.js b/meteor/client/views/dashboard/apps/dashboard-apps-settings.js index 4b65f986d0..ca119c5d9a 100755 --- a/meteor/client/views/dashboard/apps/dashboard-apps-settings.js +++ b/meteor/client/views/dashboard/apps/dashboard-apps-settings.js @@ -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'); } } diff --git a/meteor/client/views/dashboard/apps/dashboard-single-app.js b/meteor/client/views/dashboard/apps/dashboard-single-app.js index 5d32c4818f..964c082d3c 100755 --- a/meteor/client/views/dashboard/apps/dashboard-single-app.js +++ b/meteor/client/views/dashboard/apps/dashboard-single-app.js @@ -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); } }); diff --git a/meteor/client/views/dashboard/components/modal-create-app.js b/meteor/client/views/dashboard/components/modal-create-app.js index 1458d0566d..19f5d75f69 100755 --- a/meteor/client/views/dashboard/components/modal-create-app.js +++ b/meteor/client/views/dashboard/components/modal-create-app.js @@ -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; diff --git a/meteor/client/views/dashboard/components/modal-create-image.js b/meteor/client/views/dashboard/components/modal-create-image.js index 25e16aac51..8b212222a1 100755 --- a/meteor/client/views/dashboard/components/modal-create-image.js +++ b/meteor/client/views/dashboard/components/modal-create-image.js @@ -20,26 +20,55 @@ Template.modal_create_image.events({ $('#picked-directory-error').html(''); if (pickedDirectory) { $('#picked-directory').html('' + pickedDirectory + ''); - 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); } + }); + }); + }); } }); diff --git a/meteor/client/views/dashboard/images/dashboard-images-settings.js b/meteor/client/views/dashboard/images/dashboard-images-settings.js index cfb1f8aa2d..764be3cf3e 100755 --- a/meteor/client/views/dashboard/images/dashboard-images-settings.js +++ b/meteor/client/views/dashboard/images/dashboard-images-settings.js @@ -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('' + err.reason + ''); - $('#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('This image is currently being used by ' + app.name + '.'); + $('#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 + } + }); + } } } }); diff --git a/meteor/client/views/dashboard/images/dashboard-single-image.js b/meteor/client/views/dashboard/images/dashboard-single-image.js index b6c44f1b27..aa0723790e 100755 --- a/meteor/client/views/dashboard/images/dashboard-single-image.js +++ b/meteor/client/views/dashboard/images/dashboard-single-image.js @@ -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); } }); } }); diff --git a/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js b/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js index ae4b40de04..9a32743715 100644 --- a/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js +++ b/meteor/client/views/dashboard/layouts/dashboard-apps-layout.js @@ -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; diff --git a/meteor/client/views/dashboard/layouts/dashboard-images-layout.js b/meteor/client/views/dashboard/layouts/dashboard-images-layout.js index 78e84305c3..f4051a5332 100755 --- a/meteor/client/views/dashboard/layouts/dashboard-images-layout.js +++ b/meteor/client/views/dashboard/layouts/dashboard-images-layout.js @@ -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); } }); } }); diff --git a/meteor/collections/apps.js b/meteor/collections/apps.js index 85cf5c5266..df5836415e 100755 --- a/meteor/collections/apps.js +++ b/meteor/collections/apps.js @@ -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.'); - }); -}); diff --git a/meteor/collections/images.js b/meteor/collections/images.js index 5a4de5b4f8..1c94dfe578 100755 --- a/meteor/collections/images.js +++ b/meteor/collections/images.js @@ -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); - } -}); diff --git a/meteor/collections/installs.js b/meteor/collections/installs.js index 314b538050..7b1311731e 100644 --- a/meteor/collections/installs.js +++ b/meteor/collections/installs.js @@ -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); \ No newline at end of file diff --git a/meteor/lib/constants.js b/meteor/lib/constants.js deleted file mode 100644 index b53ada417d..0000000000 --- a/meteor/lib/constants.js +++ /dev/null @@ -1,9 +0,0 @@ -COMMON_WEB_PORTS = [ - 80, - 8000, - 8080, - 3000, - 5000, - 2368, - 1337 -] diff --git a/meteor/lib/utilities.js b/meteor/lib/utilities.js deleted file mode 100644 index 363ab245e7..0000000000 --- a/meteor/lib/utilities.js +++ /dev/null @@ -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: - *
    - *
  • - * lexicographical: true compares each part of the version strings lexicographically instead of - * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than - * "1.2". - *
  • - *
  • - * zeroExtend: true changes the result if one version string has less parts than the other. In - * this case the shorter string will be padded with "zero" parts instead of being considered smaller. - *
  • - *
- * @returns {number|NaN} - *
    - *
  • 0 if the versions are equal
  • - *
  • a negative integer iff v1 < v2
  • - *
  • a positive integer iff v1 > v2
  • - *
  • NaN if either version string is in the wrong format
  • - *
- * - */ -Util.compareVersions = function (v1, v2, options) { - var lexicographical = options && options.lexicographical, - zeroExtend = options && options.zeroExtend, - v1parts = v1.split('.'), - v2parts = v2.split('.'); - - function isValidPart(x) { - return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); - } - - if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { - return NaN; - } - - if (zeroExtend) { - while (v1parts.length < v2parts.length) v1parts.push('0'); - while (v2parts.length < v1parts.length) v2parts.push('0'); - } - - if (!lexicographical) { - v1parts = v1parts.map(Number); - v2parts = v2parts.map(Number); - } - - for (var i = 0; i < v1parts.length; ++i) { - if (v2parts.length == i) { - return 1; - } - - if (v1parts[i] == v2parts[i]) { - continue; - } - else if (v1parts[i] > v2parts[i]) { - return 1; - } - else { - return -1; - } - } - - if (v1parts.length != v2parts.length) { - return -1; - } - - return 0; -}; - diff --git a/meteor/server/apps.js b/meteor/server/apps.js deleted file mode 100755 index d12a1e20a6..0000000000 --- a/meteor/server/apps.js +++ /dev/null @@ -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)(); - } -}); diff --git a/meteor/server/constants.js b/meteor/server/constants.js deleted file mode 100644 index cb35d90451..0000000000 --- a/meteor/server/constants.js +++ /dev/null @@ -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; } - }); -} diff --git a/meteor/server/images.js b/meteor/server/images.js deleted file mode 100755 index 6ad43fe7d0..0000000000 --- a/meteor/server/images.js +++ /dev/null @@ -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 ' + app.name + "."); - } - } -}); diff --git a/meteor/server/lib/requires.js b/meteor/server/lib/requires.js deleted file mode 100755 index 6174cd0202..0000000000 --- a/meteor/server/lib/requires.js +++ /dev/null @@ -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(); diff --git a/meteor/smart.json b/meteor/smart.json index 6959af2f2d..6680b5eb87 100755 --- a/meteor/smart.json +++ b/meteor/smart.json @@ -5,7 +5,6 @@ "npm": {}, "headers": {}, "handlebar-helpers": {}, - "collection2": {}, "collection-hooks": {}, "moment": {}, "underscore-string-latest": {}, diff --git a/meteor/smart.lock b/meteor/smart.lock index 20d2835feb..bceed7a0ad 100755 --- a/meteor/smart.lock +++ b/meteor/smart.lock @@ -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", diff --git a/package.json b/package.json index 62c09055d8..cca8152c33 100644 --- a/package.json +++ b/package.json @@ -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" } } From 3c99d4e0d01eecbd2a24ccbbac189a1b2b74df7d Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 18:37:09 -0700 Subject: [PATCH 49/58] Fixed missing callback for last setup step. --- meteor/client/lib/installer.js | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meteor/client/lib/installer.js b/meteor/client/lib/installer.js index a5bc41ec81..a7f5af769f 100644 --- a/meteor/client/lib/installer.js +++ b/meteor/client/lib/installer.js @@ -118,7 +118,7 @@ Installer.steps = [ { run: function (callback) { reloadDefaultContainers(function (err) { - if (err) { console.error(err); } + callback(err); }); }, pastMessage: 'Started the Boot2Docker VM', diff --git a/package.json b/package.json index cca8152c33..7295823766 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "version": "0.1.0", "window": { "show": false, - "toolbar": true, - "frame": true, + "toolbar": false, + "frame": false, "width": 800, "height": 600, "resizable": false From 4ddcc92b9eae3cdfc65f53a02be2c8d33ba376ba Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 19:02:06 -0700 Subject: [PATCH 50/58] Rebuild image restart the apps now. --- meteor/client/lib/images.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor/client/lib/images.js b/meteor/client/lib/images.js index 0c121d86ec..4a7b6281fe 100644 --- a/meteor/client/lib/images.js +++ b/meteor/client/lib/images.js @@ -118,9 +118,9 @@ ImageUtil.rebuildImage = function (imageId) { }); _.each(apps, function (app) { app = Apps.findOne(app._id); - /*Meteor.call('runApp', app, function (err) { + AppUtil.run(app, function (err) { if (err) { console.error(err); } - });*/ + }); }); } else { ImageUtil.rebuild(image, function (err) { From 9ff6af34d5f0aaa7c47d8763942f2a89678dc129 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 19:30:38 -0700 Subject: [PATCH 51/58] Renamed image rebuild functions. --- meteor/client/lib/apps.js | 2 +- meteor/client/lib/docker.js | 2 +- meteor/client/lib/images.js | 8 ++++---- .../views/dashboard/images/dashboard-single-image.js | 2 +- .../views/dashboard/layouts/dashboard-images-layout.js | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/meteor/client/lib/apps.js b/meteor/client/lib/apps.js index f8745c4306..9a9d606183 100644 --- a/meteor/client/lib/apps.js +++ b/meteor/client/lib/apps.js @@ -32,7 +32,7 @@ AppUtil.restartHelper = function (app) { docker: data }}); // Use dig to refresh the DNS - // exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1 ', function() {}); + exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1', function() {}); }); }); } diff --git a/meteor/client/lib/docker.js b/meteor/client/lib/docker.js index 02d55c6954..14622695a8 100644 --- a/meteor/client/lib/docker.js +++ b/meteor/client/lib/docker.js @@ -63,7 +63,7 @@ Docker.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 + '.kite @172.17.42.1 ', function() {}); + exec('/usr/bin/dig dig ' + app.name + '.kite @172.17.42.1', function() {}); callback(null, container); }); }); diff --git a/meteor/client/lib/images.js b/meteor/client/lib/images.js index 4a7b6281fe..d82f613c37 100644 --- a/meteor/client/lib/images.js +++ b/meteor/client/lib/images.js @@ -56,7 +56,7 @@ ImageUtil.saveFolder = function (directory, imageId, callback) { } }; -ImageUtil.rebuild = function (image, callback) { +ImageUtil.rebuildHelper = function (image, callback) { Util.deleteFolder(image.path); var imageMetaData = ImageUtil.getMetaData(image.originPath); if (imageMetaData.logo) { @@ -91,7 +91,7 @@ ImageUtil.rebuild = function (image, callback) { }); }; -ImageUtil.rebuildImage = function (imageId) { +ImageUtil.rebuild = function (imageId) { var image = Images.findOne(imageId); if (!image) { throw new Meteor.Error(403, "No image found with this ID."); @@ -113,7 +113,7 @@ ImageUtil.rebuildImage = function (imageId) { } }); }); - ImageUtil.rebuild(image, function (err) { + ImageUtil.rebuildHelper(image, function (err) { if (err) { console.error(err); } }); _.each(apps, function (app) { @@ -123,7 +123,7 @@ ImageUtil.rebuildImage = function (imageId) { }); }); } else { - ImageUtil.rebuild(image, function (err) { + ImageUtil.rebuildHelper(image, function (err) { if (err) { console.error(err); } }); } diff --git a/meteor/client/views/dashboard/images/dashboard-single-image.js b/meteor/client/views/dashboard/images/dashboard-single-image.js index aa0723790e..4e21649bc2 100755 --- a/meteor/client/views/dashboard/images/dashboard-single-image.js +++ b/meteor/client/views/dashboard/images/dashboard-single-image.js @@ -18,7 +18,7 @@ Template.dashboard_single_image.events({ }, 'click .btn-rebuild': function () { $('.btn-icon').tooltip('hide'); - ImageUtil.rebuildImage(this._id, function (err) { + ImageUtil.rebuild(this._id, function (err) { if (err) { console.error(err); } }); } diff --git a/meteor/client/views/dashboard/layouts/dashboard-images-layout.js b/meteor/client/views/dashboard/layouts/dashboard-images-layout.js index f4051a5332..8018fff5a0 100755 --- a/meteor/client/views/dashboard/layouts/dashboard-images-layout.js +++ b/meteor/client/views/dashboard/layouts/dashboard-images-layout.js @@ -18,7 +18,7 @@ Template.dashboard_images_layout.events({ }, 'click .btn-rebuild': function () { $('.header .icons a').tooltip('hide'); - ImageUtil.rebuildImage(this._id, function (err) { + ImageUtil.rebuild(this._id, function (err) { if (err) { console.error(err); } }); } From 5f2cb41440778bbd52ad2df24adf7a40d39fb198 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 19:45:34 -0700 Subject: [PATCH 52/58] Fixed up dig command. --- meteor/client/lib/apps.js | 6 +++++- meteor/client/lib/docker.js | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/meteor/client/lib/apps.js b/meteor/client/lib/apps.js index 9a9d606183..fe74ab8a45 100644 --- a/meteor/client/lib/apps.js +++ b/meteor/client/lib/apps.js @@ -32,7 +32,11 @@ AppUtil.restartHelper = function (app) { docker: data }}); // 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) { + console.log(err); + console.log(stdout); + console.log(stderr); + }); }); }); } diff --git a/meteor/client/lib/docker.js b/meteor/client/lib/docker.js index 14622695a8..252cf011b5 100644 --- a/meteor/client/lib/docker.js +++ b/meteor/client/lib/docker.js @@ -63,8 +63,12 @@ Docker.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 + '.kite @172.17.42.1', function() {}); - callback(null, container); + exec('/usr/bin/dig ' + app.name + '.kite @172.17.42.1', function(err, stdout, stderr) { + console.log(err); + console.log(stdout); + console.log(stderr); + callback(null, container); + }); }); }); }; From 80bf56c35ed881b3100f7c1fa8628f50f79c87a6 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 20:39:26 -0700 Subject: [PATCH 53/58] Turned off log timestamp. --- meteor/client/lib/apps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor/client/lib/apps.js b/meteor/client/lib/apps.js index fe74ab8a45..b2d23823b1 100644 --- a/meteor/client/lib/apps.js +++ b/meteor/client/lib/apps.js @@ -80,7 +80,7 @@ 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) { + container.logs({follow: false, stdout: true, stderr: true, timestamps: false, tail: 300}, function (err, response) { if (err) { throw err; } Apps.update(app._id, { $set: { From 04888e3e5a81eb06a72ca0665c663edabfde9be0 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Wed, 3 Sep 2014 20:57:26 -0700 Subject: [PATCH 54/58] Only updates status after dig is ran. --- meteor/client/lib/apps.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/meteor/client/lib/apps.js b/meteor/client/lib/apps.js index b2d23823b1..9b510901b8 100644 --- a/meteor/client/lib/apps.js +++ b/meteor/client/lib/apps.js @@ -27,15 +27,15 @@ AppUtil.restartHelper = function (app) { 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 ' + 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 + }}); }); }); }); From f263f76b4ba1f31a865536076c63e2a6be3b49c7 Mon Sep 17 00:00:00 2001 From: Jeff Morgan Date: Wed, 3 Sep 2014 21:02:31 -0700 Subject: [PATCH 55/58] Bumping virtualbox --- meteor/client/lib/virtualbox.js | 8 +- meteor/{server => }/lib/validations.js | 0 meteor/server/apps.js | 117 ++++------ meteor/server/docker.js | 307 +++++++++++++++++++++---- script/versions.sh | 2 +- 5 files changed, 321 insertions(+), 113 deletions(-) rename meteor/{server => }/lib/validations.js (100%) diff --git a/meteor/client/lib/virtualbox.js b/meteor/client/lib/virtualbox.js index 4368f5c72f..4d0cda78c6 100644 --- a/meteor/client/lib/virtualbox.js +++ b/meteor/client/lib/virtualbox.js @@ -4,9 +4,9 @@ var path = require('path'); VirtualBox = {}; -VirtualBox.REQUIRED_VERSION = '4.3.12'; -VirtualBox.INCLUDED_VERSION = '4.3.12'; -VirtualBox.INSTALLER_FILENAME = 'virtualbox-4.3.12.pkg'; +VirtualBox.REQUIRED_VERSION = '4.3.14'; +VirtualBox.INCLUDED_VERSION = '4.3.14'; +VirtualBox.INSTALLER_FILENAME = 'virtualbox-4.3.14.pkg'; // Info for the hostonly interface we add to the VM. VirtualBox.HOSTONLY_HOSTIP = '192.168.60.3'; @@ -25,6 +25,8 @@ 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) { + console.log(stdout); + console.log(stderr); if (error) { callback(error); return; diff --git a/meteor/server/lib/validations.js b/meteor/lib/validations.js similarity index 100% rename from meteor/server/lib/validations.js rename to meteor/lib/validations.js diff --git a/meteor/server/apps.js b/meteor/server/apps.js index d12a1e20a6..af9383afa3 100755 --- a/meteor/server/apps.js +++ b/meteor/server/apps.js @@ -1,55 +1,10 @@ -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); - } +removeBindFolder = function (name, callback) { + exec(path.join(getBinDir(), 'boot2docker') + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function(err, stdout) { + callback(err, stdout); + }); }; -Apps.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) { +recoverApps = function (callback) { var apps = Apps.find({}).fetch(); _.each(apps, function (app) { // Update the app with the latest container info @@ -62,7 +17,7 @@ Apps.recover = function (callback) { console.log('restarting: ' + app.name); console.log(app.docker.Id); Fiber(function () { - Apps.restart(app, function (err) { + restartApp(app, function (err) { if (err) { console.error(err); } }); }).run(); @@ -74,8 +29,7 @@ Apps.recover = function (callback) { Meteor.methods({ recoverApps: function () { - this.unblock(); - return Meteor._wrapAsync(Apps.recover)(); + return Meteor._wrapAsync(recoverApps)(); }, configVar: function (appId, configVars) { this.unblock(); @@ -94,38 +48,64 @@ Meteor.methods({ if (!app) { throw new Meteor.Error(403, 'No app found with this ID'); } - Apps.remove({_id: app._id}); + deleteApp(app, function (err) { + if (err) { console.error(err); } + var appPath = path.join(KITE_PATH, app.name); + deleteFolder(appPath); + removeBindFolder(app.name, function () { + console.log('Deleted Kite ' + app.name + ' directory.'); + Fiber(function () { + Apps.remove({_id: app._id}); + }).run(); + }); + }); }, createApp: function (formData) { - this.unblock(); var validationResult = formValidate(formData, FormSchema.formCreateApp); if (validationResult.errors) { throw new Meteor.Error(400, 'Validation Failed.', validationResult.errors); } else { var cleaned = validationResult.cleaned; - var appName = cleaned.name; - var appPath = path.join(KITE_PATH, appName); + var appObj = { + name: cleaned.name, + imageId: cleaned.imageId, + status: 'STARTING', + config: {} + }; + var appId = Apps.insert(appObj); + var appPath = path.join(KITE_PATH, appObj.name); if (!fs.existsSync(appPath)) { - console.log('Created Kite ' + appName + ' directory.'); + console.log('Created Kite ' + appObj.name + ' 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); + Apps.update(appId, { + $set: { + 'config.APP_ID': appId, + path: appPath + } + }); + var image = Images.findOne(appObj.imageId); + loadKiteVolumes(image.path, appObj.name); + var app = Apps.findOne(appId); + removeBindFolder(app.name, function (err) { + if (err) { + console.error(err); + } + Fiber(function () { + Meteor.call('runApp', app, function (err) { + if (err) { throw err; } + }); + }).run(); + }); } }, getAppLogs: function (appId) { this.unblock(); var app = Apps.findOne(appId); if (app) { - Apps.logs(app, function (err) { + getAppLogs(app, function (err) { if (err) { throw err; } }); } @@ -137,13 +117,12 @@ Meteor.methods({ Apps.update(app._id, {$set: { status: 'STARTING' }}); - Apps.restart(app, function (err) { + restartApp(app, function (err) { if (err) { console.error(err); } }); } }, resolveWatchers: function () { - this.unblock(); return Meteor._wrapAsync(resolveWatchers)(); } }); diff --git a/meteor/server/docker.js b/meteor/server/docker.js index 6978024245..d71b12688b 100755 --- a/meteor/server/docker.js +++ b/meteor/server/docker.js @@ -1,11 +1,16 @@ -Dockerode = Meteor.require('dockerode'); +Docker = Meteor.require('dockerode'); -var DOCKER_HOST='192.168.60.103'; -docker = new Dockerode({host: DOCKER_HOST, port: '2375'}); +var Convert = Meteor.require('ansi-to-html'); +var convert = new Convert(); -Docker = {}; +var DOCKER_HOST='192.168.59.103'; +docker = new Docker({host: '192.168.59.103', port: '2375'}); -Docker.removeContainer = function (containerId, callback) { +hasDockerfile = function (directory) { + return fs.existsSync(path.join(directory, 'Dockerfile')); +}; + +removeContainer = function (containerId, callback) { var container = docker.getContainer(containerId); container.kill(function (err) { if (err) { callback(err); return; } @@ -17,11 +22,28 @@ Docker.removeContainer = function (containerId, callback) { }); }; -Docker.removeContainerSync = function (containerId) { - return Meteor._wrapAsync(Docker.removeContainer)(containerId); +removeContainerSync = function (containerId) { + return Meteor._wrapAsync(removeContainer)(containerId); }; -Docker.getContainerData = function (containerId, callback) { +deleteApp = function (app, callback) { + if (!app.docker) { + callback(null); + return; + } + try { + removeContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } + callback(null); +}; + +deleteAppSync = function (app) { + return Meteor._wrapAsync(deleteApp)(app); +}; + +getContainerData = function (containerId, callback) { var container = docker.getContainer(containerId); container.inspect(function (err, data) { if (err) { @@ -37,11 +59,11 @@ Docker.getContainerData = function (containerId, callback) { }); }; -Docker.getContainerDataSync = function (containerId) { - return Meteor._wrapAsync(Docker.getContainerData)(containerId); +getContainerDataSync = function (containerId) { + return Meteor._wrapAsync(getContainerData)(containerId); }; -Docker.runContainer = function (app, image, callback) { +runContainer = function (app, image, callback) { var envParam = []; _.each(_.keys(app.config), function (key) { var builtStr = key + '=' + app.config[key]; @@ -72,17 +94,17 @@ Docker.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 + '.kite @172.17.42.1 ', function() {}); + exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {}); callback(null, container); }); }); }; -Docker.runContainerSync = function (app, image) { - return Meteor._wrapAsync(Docker.runContainer)(app, image); +runContainerSync = function (app, image) { + return Meteor._wrapAsync(runContainer)(app, image); }; -Docker.restartContainer = function (containerId, callback) { +restartContainer = function (containerId, callback) { var container = docker.getContainer(containerId); container.restart(function (err) { if (err) { @@ -95,8 +117,83 @@ Docker.restartContainer = function (containerId, callback) { }); }; -Docker.restartContainerSync = function (containerId) { - return Meteor._wrapAsync(Docker.restartContainer)(containerId); +restartContainerSync = function (containerId) { + return Meteor._wrapAsync(restartContainer)(containerId); +}; + +var getFromImage = function (dockerfile) { + var patternString = "(FROM)(.*)"; + var regex = new RegExp(patternString, "g"); + var fromInstruction = dockerfile.match(regex); + if (fromInstruction && fromInstruction.length > 0) { + return fromInstruction[0].replace('FROM', '').trim(); + } else { + return null; + } +}; + +restartApp = function (app, callback) { + if (app.docker && app.docker.Id) { + try { + restartContainerSync(app.docker.Id); + } catch (e) { + console.error(e); + } + var containerData = getContainerDataSync(app.docker.Id); + Fiber(function () { + Apps.update(app._id, {$set: { + status: 'READY', + docker: containerData + }}); + }).run(); + callback(null); + // Use dig to refresh the DNS + exec('/usr/bin/dig dig ' + app.name + '.dev @172.17.42.1 ', function() {}); + } else { + callback(null); + } +}; + +getAppLogs = function (app) { + if (app.docker && app.docker.Id) { + var container = docker.getContainer(app.docker.Id); + container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) { + if (err) { throw err; } + Fiber(function () { + Apps.update(app._id, { + $set: { + logs: [] + } + }); + }).run(); + var logs = []; + response.setEncoding('utf8'); + response.on('data', function (line) { + logs.push(convert.toHtml(line.slice(8))); + Fiber(function () { + Apps.update(app._id, { + $set: { + logs: logs + } + }); + }).run(); + }); + response.on('end', function () {}); + }); + } +}; + +createTarFile = function (image, callback) { + var TAR_PATH = path.join(KITE_TAR_PATH, image._id + '.tar'); + exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) { + if (err) { callback(err, null); return; } + console.log('Created tar file: ' + TAR_PATH); + callback(null, TAR_PATH); + }); +}; + +createTarFileSync = function (image) { + return Meteor._wrapAsync(createTarFile)(image); }; var convertVolumeObjToArray = function (obj) { @@ -112,7 +209,7 @@ var convertVolumeObjToArray = function (obj) { return result; }; -Docker.getImageData = function (imageId, callback) { +getImageData = function (imageId, callback) { var image = docker.getImage(imageId.toLowerCase()); image.inspect(function (err, data) { if (err) { @@ -127,11 +224,11 @@ Docker.getImageData = function (imageId, callback) { }); }; -Docker.getImageDataSync = function (imageId) { - return Meteor._wrapAsync(Docker.getImageData)(imageId); +getImageDataSync = function (imageId) { + return Meteor._wrapAsync(getImageData)(imageId); }; -Docker.removeImage = function (imageId, callback) { +removeImage = function (imageId, callback) { var image = docker.getImage(imageId.toLowerCase()); image.remove({force: true}, function (err) { if (err) { callback(err); return; } @@ -140,14 +237,25 @@ Docker.removeImage = function (imageId, callback) { }); }; -Docker.removeImageSync = function (imageId) { - return Meteor._wrapAsync(Docker.removeImage)(imageId); +removeImageSync = function (imageId) { + return Meteor._wrapAsync(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) { - callback(err, stdout); - }); +deleteImage = function (image, callback) { + if (!image.docker) { + callback(null, {}); + return; + } + try { + removeImageSync(image.docker.Id); + } catch (e) { + console.error(e); + } + callback(null); +}; + +deleteImageSync = function (image) { + return Meteor._wrapAsync(deleteImage)(image); }; var defaultContainerOptions = function () { @@ -196,7 +304,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; @@ -266,7 +374,7 @@ reloadDefaultContainers = function (callback) { async.until(function () { return ready; }, function (callback) { - docker.listContainers(function (err) { + docker.listContainers(function (err, containers) { if (!err) { ready = true; } @@ -283,7 +391,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; @@ -392,18 +500,142 @@ killAndRemoveContainers = function (names, callback) { }); }; +pullImageFromDockerfile = function (dockerfile, imageId, callback) { + var fromImage = getFromImage(dockerfile); + console.log('From image: ' + fromImage); + var installedImage = null; + try { + installedImage = getImageDataSync(fromImage); + } catch (e) { + console.error(e); + } + if (fromImage && !installedImage) { + Fiber(function () { + Images.update(imageId, { + $set: { + buildLogs: [] + } + }); + }).run(); + var logs = []; + docker.pull(fromImage, function (err, response) { + if (err) { callback(err); return; } + response.setEncoding('utf8'); + response.on('data', function (data) { + try { + var logData = JSON.parse(data); + var logDisplay = ''; + if (logData.id) { + logDisplay += logData.id + ' | '; + } + logDisplay += logData.status; + if (logData.progressDetail && logData.progressDetail.current && logData.progressDetail.total) { + logDisplay += ' - ' + Math.round(logData.progressDetail.current / logData.progressDetail.total * 100) + '%'; + } + logs.push(logDisplay); + Fiber(function () { + Images.update(imageId, { + $set: { + buildLogs: logs + } + }); + }).run(); + } catch (e) { + console.error(e); + } + }); + response.on('end', function () { + console.log('Finished pulling image: ' + fromImage); + callback(null); + }); + }); + } else { + callback(null); + } +}; + +buildImage = function (image, callback) { + Fiber(function () { + var tarFilePath = createTarFileSync(image); + Images.update(image._id, { + $set: { + buildLogs: [] + } + }); + docker.buildImage(tarFilePath, {t: image._id.toLowerCase()}, function (err, response) { + if (err) { callback(err); } + console.log('Building Docker image...'); + var logs = []; + response.setEncoding('utf8'); + response.on('data', function (data) { + try { + var line = JSON.parse(data).stream; + logs.push(convert.toHtml(line)); + Fiber(function () { + Images.update(image._id, { + $set: { + buildLogs: logs + } + }); + }).run(); + } catch (e) { + console.error(e); + } + }); + response.on('end', function () { + console.log('Finished building Docker image.'); + try { + fs.unlinkSync(tarFilePath); + console.log('Cleaned up tar file.'); + } catch (e) { + console.error(e); + } + Fiber(function () { + var imageData = null; + try { + imageData = getImageDataSync(image._id); + Images.update(image._id, { + $set: { + docker: imageData, + status: 'READY' + } + }); + } catch (e) { + console.log(e); + Images.update(image._id, { + $set: { + status: 'ERROR' + } + }); + } + var oldImageId = null; + if (image.docker && image.docker.Id) { + oldImageId = image.docker.Id; + } + if (oldImageId && imageData && oldImageId !== imageData.Id) { + try { + removeImageSync(oldImageId); + } catch (e) { + console.error(e); + } + } + }).run(); + callback(null); + }); + }); + }).run(); +}; + Meteor.methods({ runApp: function (app) { this.unblock(); var image = Images.findOne({_id: app.imageId}); - // Delete old container if one already exists try { - Docker.removeContainerSync(app.name); + 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 + var container = runContainerSync(app, image); + var containerData = getContainerDataSync(container.id); Meteor.setTimeout(function () { Apps.update(app._id, {$set: { docker: containerData, @@ -418,23 +650,18 @@ Meteor.methods({ return DOCKER_HOST; }, reloadDefaultContainers: function () { - this.unblock(); return Meteor._wrapAsync(reloadDefaultContainers)(); }, checkDefaultImages: function () { - this.unblock(); return Meteor._wrapAsync(checkDefaultImages)(); }, resolveDefaultImages: function () { - this.unblock(); return Meteor._wrapAsync(resolveDefaultImages)(); }, checkDefaultContainers: function () { - this.unblock(); return Meteor._wrapAsync(checkDefaultContainers)(); }, resolveDefaultContainers: function () { - this.unblock(); return Meteor._wrapAsync(resolveDefaultContainers)(); } }); diff --git a/script/versions.sh b/script/versions.sh index e46149f22c..7cfc5c99c9 100644 --- a/script/versions.sh +++ b/script/versions.sh @@ -2,7 +2,7 @@ BASE_IMAGE_VERSION=0.0.2 BASE_IMAGE_VERSION_FILE=base-images-$BASE_IMAGE_VERSION.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_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION From c3009506dc2a5ffc9571b8a95f6f9974bfaf3a4d Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Thu, 4 Sep 2014 00:27:50 -0700 Subject: [PATCH 56/58] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index beca2bb56c..af4c654f34 100755 --- a/README.md +++ b/README.md @@ -44,7 +44,6 @@ Kitematic is still in Beta. Any effort in helping us find issues and improving t (This will improve over time.) - Remove VirtualBox -- rm /usr/local/bin/boot2docker - sudo route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway 192.168.60.103 (disable routing to containers through VM) - rm -rf ~/Library/Application\ Support/Kitematic (remove app data) - rm /Library/LaunchAgents/com.kitematic.route.plist (remove launch job that sets up routing to the containers) From 9e1162eae228fe53e39249d78e5de4e84d9b19ae Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 4 Sep 2014 12:51:01 -0700 Subject: [PATCH 57/58] Can now select logs. --- meteor/client/stylesheets/dashboard.import.less | 1 + meteor/client/stylesheets/mixins.import.less | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/meteor/client/stylesheets/dashboard.import.less b/meteor/client/stylesheets/dashboard.import.less index 916d4abe3b..b97cd726f5 100755 --- a/meteor/client/stylesheets/dashboard.import.less +++ b/meteor/client/stylesheets/dashboard.import.less @@ -199,6 +199,7 @@ padding: 1em; font-family: monospace; min-height: @dashboard-content-height; + .text-select(); } .warning-badge { diff --git a/meteor/client/stylesheets/mixins.import.less b/meteor/client/stylesheets/mixins.import.less index 6676691544..affd06ca77 100644 --- a/meteor/client/stylesheets/mixins.import.less +++ b/meteor/client/stylesheets/mixins.import.less @@ -12,3 +12,11 @@ -webkit-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; +} From 47dfb0775db9002313b16108f9a66d7656dfbd31 Mon Sep 17 00:00:00 2001 From: Peter Nguyen Date: Sat, 6 Sep 2014 22:14:09 +0200 Subject: [PATCH 58/58] Added feature to start/stop apps --- meteor/.meteor/packages | 1 - meteor/client/lib/apputil.js | 41 ++++++++++++++++++- meteor/client/lib/docker.js | 26 ++++++++++++ .../dashboard/apps/dashboard-single-app.html | 14 +++++-- .../dashboard/apps/dashboard-single-app.js | 8 ++++ 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/meteor/.meteor/packages b/meteor/.meteor/packages index 6d8743e310..0a499fa9ec 100755 --- a/meteor/.meteor/packages +++ b/meteor/.meteor/packages @@ -10,6 +10,5 @@ iron-router handlebar-helpers underscore-string-latest collection-helpers -octicons fast-render iron-router-ga diff --git a/meteor/client/lib/apputil.js b/meteor/client/lib/apputil.js index 03ff2b4bf1..4af94d476d 100644 --- a/meteor/client/lib/apputil.js +++ b/meteor/client/lib/apputil.js @@ -47,6 +47,45 @@ AppUtil.restartHelper = function (app) { } }; +AppUtil.start = function (appId) { + var app = Apps.findOne(appId); + if (app && app.docker) { + Apps.update(app._id, {$set: { + status: 'STARTING' + }}); + 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.stop = function (appId) { + var app = Apps.findOne(appId); + if (app && app.docker) { + Apps.update(app._id, {$set: { + status: 'STOPPING' + }}); + Docker.stopContainer(app.docker.Id, function (err) { + if (err) { console.error(err); } + Meteor.setTimeout(function () { + Apps.update(app._id, {$set: { + status: 'STOPPED' + }}); + }, 2500); + }); + } +}; + AppUtil.restart = function (appId) { var app = Apps.findOne(appId); if (app && app.docker) { @@ -115,7 +154,7 @@ AppUtil.recover = function () { } var container = Docker.client().getContainer(app.docker.Id); container.inspect(function (err, data) { - if (app.status !== 'STARTING' && data && data.State && !data.State.Running) { + if (app.status !== 'STARTING' && app.status !== 'STOPPING' && app.status !== 'STOPPED' && data && data.State && !data.State.Running) { console.log('Restarting: ' + app.name); console.log(app.docker.Id); AppUtil.restartHelper(app, function (err) { diff --git a/meteor/client/lib/docker.js b/meteor/client/lib/docker.js index 2f592ba865..f4c4e70855 100644 --- a/meteor/client/lib/docker.js +++ b/meteor/client/lib/docker.js @@ -81,6 +81,32 @@ Docker.runContainer = function (app, image, callback) { }); }; +Docker.startContainer = function (containerId, callback) { + var container = docker.getContainer(containerId); + container.stop(function (err) { + if (err) { + console.log(err); + callback(err); + return; + } + console.log('Started container: ' + containerId); + callback(null); + }); +}; + +Docker.stopContainer = function (containerId, callback) { + var container = docker.getContainer(containerId); + container.stop(function (err) { + if (err) { + console.log(err); + callback(err); + return; + } + console.log('Stopped container: ' + containerId); + callback(null); + }); +}; + Docker.restartContainer = function (containerId, callback) { var container = docker.getContainer(containerId); container.restart(function (err) { diff --git a/meteor/client/views/dashboard/apps/dashboard-single-app.html b/meteor/client/views/dashboard/apps/dashboard-single-app.html index f1e3740ff8..e8fd11b7ac 100755 --- a/meteor/client/views/dashboard/apps/dashboard-single-app.html +++ b/meteor/client/views/dashboard/apps/dashboard-single-app.html @@ -7,10 +7,14 @@ {{#if $eq status 'STARTING'}} {{else}} - {{#if $eq status 'ERROR'}} - + {{#if $eq status 'STOPPING'}} + {{else}} - + {{#if $eq status 'ERROR'}} + + {{else}} + + {{/if}} {{/if}} {{/if}} {{/if}} @@ -18,7 +22,11 @@ {{image.meta.name}}
+ {{#if $eq status 'STOPPED'}} + + {{/if}} {{#if $eq status 'READY'}} + {{#if url}} {{/if}} diff --git a/meteor/client/views/dashboard/apps/dashboard-single-app.js b/meteor/client/views/dashboard/apps/dashboard-single-app.js index 1b6190205c..8bd9da0b01 100755 --- a/meteor/client/views/dashboard/apps/dashboard-single-app.js +++ b/meteor/client/views/dashboard/apps/dashboard-single-app.js @@ -32,6 +32,14 @@ Template.dashboard_single_app.events({ } }); }, + 'click .btn-start': function () { + AppUtil.start(this._id); + $('.btn-icon').tooltip('hide'); + }, + 'click .btn-stop': function () { + AppUtil.stop(this._id); + $('.btn-icon').tooltip('hide'); + }, 'click .btn-restart': function () { AppUtil.restart(this._id); },