diff --git a/Gruntfile.js b/Gruntfile.js index 9b02a0811b..7624bb252b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -229,7 +229,9 @@ module.exports = function (grunt) { babel: { options: { sourceMap: 'inline', - blacklist: 'regenerator' + blacklist: 'regenerator', + stage: 1, + optional: ['asyncToGenerator'] }, dist: { files: [{ diff --git a/__tests__/URLUtil-test.js b/__tests__/URLUtil-test.js deleted file mode 100644 index 2c1212523a..0000000000 --- a/__tests__/URLUtil-test.js +++ /dev/null @@ -1,65 +0,0 @@ -jest.dontMock('../src/utils/URLUtil'); -jest.dontMock('parseUri'); -var urlUtil = require('../src/utils/URLUtil'); -var util = require('../src/utils/Util'); - -describe('URLUtil', function () { - beforeEach(() => { - util.compareVersions.mockClear(); - util.isOfficialRepo.mockClear(); - }); - - it('does nothing if the url is undefined', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl()).toBe(false); - }); - - it('does nothing if the flags object is undefined', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('docker://repository/run/redis')).toBe(false); - }); - - it('does nothing if the url enabled flag is falsy', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('docker://repository/run/redis', {dockerURLEnabledVersion: undefined})).toBe(false); - }); - - it('does nothing if the url enabled flag version is higher than the app version', () => { - util.compareVersions.mockReturnValue(-1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('docker://repository/run/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.18')).toBe(false); - }); - - it('does nothing if the type is not in the whitelist', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('docker://badtype/run/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.18')).toBe(false); - }); - - it('does nothing if the method is not in the whitelist', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('docker://repository/badmethod/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.18')).toBe(false); - }); - - it('does nothing if protocol is not docker:', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('facetime://')).toBe(false); - }); - - it('does nothing if repo is not official', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(false); - expect(urlUtil.openUrl('docker://repository/run/not/official', {dockerURLEnabledVersion: '0.5.19'}, '0.5.20')).toBe(false); - }); - - it('returns true if type and method are correct', () => { - util.compareVersions.mockReturnValue(1); - util.isOfficialRepo.mockReturnValue(true); - expect(urlUtil.openUrl('docker://repository/run/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.20')).toBe(true); - }); -}); diff --git a/package.json b/package.json index 71a485fbc4..35a26545af 100644 --- a/package.json +++ b/package.json @@ -24,47 +24,43 @@ ], "docker-version": "1.8.2", "docker-machine-version": "0.4.1", - "electron-version": "0.27.2", - "virtualbox-version": "5.0.4", - "virtualbox-filename": "VirtualBox-5.0.4.pkg", - "virtualbox-filename-win": "VirtualBox-5.0.4.exe", - "virtualbox-checksum": "4597b2ebdf7a334a3a4028e2285f62cf4cf157477e33fa5f433d944da54737d6", - "virtualbox-checksum-win": "17fe9943eae33d1d23d37160fd862b7c5db0eef8cb48225cf143244d0e934f94", + "electron-version": "0.33.6", "dependencies": { - "alt": "^0.16.2", + "alt": "^0.17.4", "ansi-to-html": "0.3.0", "any-promise": "^0.1.0", - "async": "^0.9.0", + "async": "^1.4.2", "bluebird": "^2.9.24", "bugsnag-js": "^2.4.7", - "classnames": "^1.2.0", + "classnames": "^2.1.5", "coveralls": "^2.11.2", "deep-extend": "^0.4.0", "dockerode": "^2.1.4", - "exec": "0.2.0", + "exec": "0.2.1", + "history": "^1.12.0", "install": "^0.1.8", "jquery": "^2.1.3", "mixpanel": "kitematic/mixpanel-node", "mkdirp": "^0.5.0", "node-uuid": "^1.4.3", - "npm": "^2.9.1", - "object-assign": "^2.0.0", + "npm": "^3.3.5", + "object-assign": "^4.0.1", "osx-release": "^1.1.0", "parseUri": "^1.2.3-2", "react": "^0.13.1", - "react-bootstrap": "^0.20.3", + "react-bootstrap": "^0.26.1", "react-retina-image": "^1.1.2", - "react-router": "^0.13.3", + "react-router": "^1.0.0-rc1", "request": "^2.55.0", "request-progress": "^0.3.1", "rimraf": "^2.3.2", "underscore": "^1.8.3", - "validator": "^3.39.0" + "validator": "^4.1.0" }, "devDependencies": { - "babel": "^5.1.10", + "babel": "^5.8.23", "babel-jest": "^5.2.0", - "electron-prebuilt": "^0.27.3", + "electron-prebuilt": "^0.33.6", "eslint": "^1.3.1", "eslint-plugin-react": "^3.3.0", "grunt": "^0.4.5", @@ -79,7 +75,7 @@ "grunt-curl": "^2.2.0", "grunt-download-electron": "^2.1.1", "grunt-electron": "^2.0.0", - "grunt-electron-installer": "^0.37.0", + "grunt-electron-installer": "^1.0.4", "grunt-if-missing": "^1.0.0", "grunt-newer": "^1.1.1", "grunt-plistbuddy": "^0.1.1", @@ -88,13 +84,13 @@ "grunt-rename": "^0.1.4", "grunt-shell": "^1.1.2", "grunt-shell-spawn": "^0.3.8", - "jest-cli": "^0.4.5", - "jsxhint": "^0.14.0", + "jest-cli": "^0.5.8", + "jsxhint": "^0.15.1", "load-grunt-tasks": "^3.2.0", "minimist": "^1.1.1", "react-tools": "^0.13.1", "run-sequence": "^1.0.2", "shell-escape": "^0.2.0", - "source-map-support": "^0.2.10" + "source-map-support": "^0.3.2" } } diff --git a/src/actions/SetupActions.js b/src/actions/SetupActions.js new file mode 100644 index 0000000000..a9102b8d66 --- /dev/null +++ b/src/actions/SetupActions.js @@ -0,0 +1,12 @@ +import alt from '../alt'; + +class SetupActions { + constructor () { + this.generateActions( + 'progress', + 'error' + ); + } +} + +export default alt.createActions(SetupActions); diff --git a/src/app.js b/src/app.js index a902b06b2f..7c2c2bfe38 100644 --- a/src/app.js +++ b/src/app.js @@ -9,15 +9,16 @@ import metrics from './utils/MetricsUtil'; import template from './menutemplate'; import webUtil from './utils/WebUtil'; import hubUtil from './utils/HubUtil'; -var urlUtil = require('./utils/URLUtil'); -var app = remote.require('app'); +import setupUtil from './utils/SetupUtil'; import request from 'request'; import docker from './utils/DockerUtil'; import hub from './utils/HubUtil'; import Router from 'react-router'; +import createHashHistory from 'history/lib/createHashHistory' import routes from './routes'; import routerContainer from './router'; import repositoryActions from './actions/RepositoryActions'; +var app = remote.require('app'); hubUtil.init(); @@ -40,19 +41,16 @@ setInterval(function () { metrics.track('app heartbeat'); }, 14400000); -var router = Router.create({ - routes: routes -}); -router.run(Handler => React.render(, document.body)); -routerContainer.set(router); +let history = createHashHistory() +React.render({routes}, document.body) -SetupStore.setup().then(() => { +setupUtil.setup().then(() => { Menu.setApplicationMenu(Menu.buildFromTemplate(template())); docker.init(); if (!hub.prompted() && !hub.loggedin()) { - router.transitionTo('login'); + history.replaceState(null, '/account/login'); } else { - router.transitionTo('search'); + history.replaceState(null, '/containers/new'); } }).catch(err => { metrics.track('Setup Failed', { @@ -67,24 +65,3 @@ ipc.on('application:quitting', () => { machine.stop(); } }); - -// Event fires when the app receives a docker:// URL such as -// docker://repository/run/redis -ipc.on('application:open-url', opts => { - request.get('https://kitematic.com/flags.json', (err, response, body) => { - if (err || response.statusCode !== 200) { - return; - } - - var flags = JSON.parse(body); - if (!flags) { - return; - } - - urlUtil.openUrl(opts.url, flags, app.getVersion()); - }); -}); - -module.exports = { - router: router -}; diff --git a/src/browser.js b/src/browser.js index 9ff97d3011..61d5f74273 100644 --- a/src/browser.js +++ b/src/browser.js @@ -42,12 +42,6 @@ if (process.platform === 'win32') { } } -var openURL = null; -app.on('open-url', function (event, url) { - event.preventDefault(); - openURL = url; -}); - app.on('ready', function () { var mainWindow = new BrowserWindow({ width: size.width || 800, @@ -106,18 +100,6 @@ app.on('ready', function () { mainWindow.show(); mainWindow.focus(); - if (openURL) { - mainWindow.webContents.send('application:open-url', { - url: openURL - }); - } - app.on('open-url', function (event, url) { - event.preventDefault(); - mainWindow.webContents.send('application:open-url', { - url: url - }); - }); - if (process.env.NODE_ENV !== 'development') { autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion() + '&beta=' + !!settingsjson.beta + '&platform=' + os.platform()); } diff --git a/src/components/Containers.react.js b/src/components/Containers.react.js index b7058b28df..492d76cba7 100644 --- a/src/components/Containers.react.js +++ b/src/components/Containers.react.js @@ -10,10 +10,6 @@ import shell from 'shell'; import machine from '../utils/DockerMachineUtil'; var Containers = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - getInitialState: function () { return { sidebarOffset: 0, @@ -52,7 +48,9 @@ var Containers = React.createClass({ let containers = containerStore.getState().containers; let sorted = this.sorted(containerStore.getState().containers); - let name = this.context.router.getCurrentParams().name; + console.log(this.props); + + let name = this.props.params.name; if (containerStore.getState().pending) { this.context.router.transitionTo('pull'); } else if (name && !containers[name]) { @@ -151,7 +149,7 @@ var Containers = React.createClass({ sidebarHeaderClass += ' sep'; } - var container = this.context.router.getCurrentParams().name ? this.state.containers[this.context.router.getCurrentParams().name] : {}; + var container = this.props.params ? this.state.containers[ this.props.params] : {}; return (
diff --git a/src/components/Setup.react.js b/src/components/Setup.react.js index 0d42839562..f54702cb8b 100644 --- a/src/components/Setup.react.js +++ b/src/components/Setup.react.js @@ -1,79 +1,43 @@ import React from 'react/addons'; import Router from 'react-router'; import Radial from './Radial.react.js'; -import SetupStore from '../stores/SetupStore'; import RetinaImage from 'react-retina-image'; import Header from './Header.react'; import Util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; +import setupStore from '../stores/SetupStore'; var Setup = React.createClass({ - mixins: [ Router.Navigation ], + mixins: [Router.Navigation], + getInitialState: function () { - return { - progress: 0, - name: '', - }; - }, - componentWillMount: function () { - SetupStore.on(SetupStore.PROGRESS_EVENT, this.update); - SetupStore.on(SetupStore.STEP_EVENT, this.update); - SetupStore.on(SetupStore.ERROR_EVENT, this.update); + return setupStore.getState(); }, + componentDidMount: function () { - this.update(); + setupStore.listen(this.update); }, + componentDidUnmount: function () { - SetupStore.removeListener(SetupStore.PROGRESS_EVENT, this.update); - SetupStore.removeListener(SetupStore.STEP_EVENT, this.update); - SetupStore.removeListener(SetupStore.ERROR_EVENT, this.update); - }, - handleCancelRetry: function () { - metrics.track('Setup Retried', { - from: 'cancel' - }); - SetupStore.retry(); - }, - handleErrorRetry: function () { - metrics.track('Setup Retried', { - from: 'error', - removeVM: false - }); - SetupStore.retry(false); - }, - handleErrorRemoveRetry: function () { - metrics.track('Setup Retried', { - from: 'error', - removeVM: true - }); - SetupStore.retry(true); - }, - handleOpenWebsite: function () { - Util.exec(['open', 'https://www.virtualbox.org/wiki/Downloads']); + setupStore.unlisten(this.update); }, + update: function () { - this.setState({ - progress: SetupStore.percent(), - step: SetupStore.step(), - error: SetupStore.error(), - cancelled: SetupStore.cancelled() - }); + this.setState(setupStore.getState()); }, + renderContents: function () { - var img = 'virtualbox.png'; - if (SetupStore.step().name === 'init' || SetupStore.step().name === 'start') { - img = 'boot2docker.png'; - } return (
- +
); }, - renderStep: function () { + + renderProgress: function () { return (
@@ -83,9 +47,8 @@ var Setup = React.createClass({
-

Step {SetupStore.number()} out of {SetupStore.stepCount()}

-

{SetupStore.step().title}

-

{SetupStore.step().message}

+

{this.state.title}

+

{this.state.message}

@@ -141,15 +104,14 @@ var Setup = React.createClass({ ); }, + render: function () { if (this.state.cancelled) { return this.renderCancelled(); } else if (this.state.error) { return this.renderError(); - } else if (SetupStore.step()) { - return this.renderStep(); } else { - return false; + return this.renderProgress(); } } }); diff --git a/src/menutemplate.js b/src/menutemplate.js index 0f9eda4092..adbf090123 100644 --- a/src/menutemplate.js +++ b/src/menutemplate.js @@ -8,6 +8,7 @@ var metrics = require('./utils/MetricsUtil'); var machine = require('./utils/DockerMachineUtil'); var dialog = remote.require('dialog'); import docker from './utils/DockerUtil'; +import Router from 'react-router'; // main.js var MenuTemplate = function () { @@ -21,7 +22,7 @@ var MenuTemplate = function () { metrics.track('Opened About', { from: 'menu' }); - router.get().transitionTo('about'); + Router.transitionTo('about'); } }, { @@ -35,7 +36,7 @@ var MenuTemplate = function () { metrics.track('Opened Preferences', { from: 'menu' }); - router.get().transitionTo('preferences'); + Router.transitionTo('preferences'); } }, { diff --git a/src/routes.js b/src/routes.js index b8f628fbad..3cb21e9cbd 100644 --- a/src/routes.js +++ b/src/routes.js @@ -16,45 +16,42 @@ import Preferences from './components/Preferences.react'; import About from './components/About.react'; import NewContainerSearch from './components/NewContainerSearch.react'; import NewContainerPull from './components/NewContainerPull.react'; -import Router from 'react-router'; - -var Route = Router.Route; -var DefaultRoute = Router.DefaultRoute; -var RouteHandler = Router.RouteHandler; +import {Router, IndexRoute, Route, Link} from 'react-router' var App = React.createClass({ render: function () { return ( - +
+ {this.props.children} +
); } }); var routes = ( - - - - + + + + + - - - - - - - - - + + + + + + + + + - - + - - + + - ); diff --git a/src/stores/SetupStore.js b/src/stores/SetupStore.js index 9d8418bbe9..67fbd18164 100644 --- a/src/stores/SetupStore.js +++ b/src/stores/SetupStore.js @@ -1,240 +1,22 @@ -import {EventEmitter} from 'events'; -import _ from 'underscore'; -import path from 'path'; -import fs from 'fs'; -import Promise from 'bluebird'; -import machine from '../utils/DockerMachineUtil'; -import virtualBox from '../utils/VirtualBoxUtil'; -import setupUtil from '../utils/SetupUtil'; -import util from '../utils/Util'; -import assign from 'object-assign'; -import metrics from '../utils/MetricsUtil'; -import bugsnag from 'bugsnag-js'; -import docker from '../utils/DockerUtil'; +import alt from '../alt'; +import setupActions from '../actions/SetupActions'; -var _currentStep = null; -var _error = null; -var _cancelled = false; -var _retryPromise = null; -var _requiredSteps = []; - -var _steps = [{ - name: 'download', - title: 'Downloading VirtualBox', - message: 'VirtualBox is being downloaded. Kitematic requires VirtualBox to run containers.', - totalPercent: 35, - percent: 0, - run: function (progressCallback) { - return setupUtil.download(virtualBox.url(), path.join(util.supportDir(), virtualBox.filename()), virtualBox.checksum(), percent => { - progressCallback(percent); - }); +class SetupStore { + constructor () { + this.bindActions(setupActions); + this.title = ''; + this.message = ''; + this.percent = 0; + this.error = null; } -}, { - name: 'install', - title: 'Installing VirtualBox & Docker', - message: 'VirtualBox & Docker are being installed or upgraded in the background. We may need you to type in your password to continue.', - totalPercent: 5, - percent: 0, - seconds: 5, - run: Promise.coroutine(function* (progressCallback) { - if (!virtualBox.installed()) { - yield virtualBox.killall(); - progressCallback(50); // TODO: detect when the installation has started so we can simulate progress - try { - if (util.isWindows()) { - yield util.exec([path.join(util.supportDir(), virtualBox.filename()), '-msiparams', 'REBOOT=ReallySuppress', 'LIMITUI=INSTALLUILEVEL_PROGRESSONLY']); - } else { - yield util.exec(setupUtil.macSudoCmd(setupUtil.installVirtualBoxCmd())); - } - } catch (err) { - throw null; - } - } else if (!util.isWindows() && !virtualBox.active()) { - yield util.exec(setupUtil.macSudoCmd(util.escapePath('/Library/Application Support/VirtualBox/LaunchDaemons/VirtualBoxStartup.sh') + ' restart')); - } - }) -}, { - name: 'init', - title: 'Starting Docker VM', - message: 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...', - totalPercent: 60, - percent: 0, - seconds: 110, - run: Promise.coroutine(function* (progressCallback) { - setupUtil.simulateProgress(this.seconds, progressCallback); - var exists = yield machine.exists(); - if (!exists || (yield machine.state()) === 'Error') { - if (exists && (yield machine.state()) === 'Error') { - yield machine.rm(); - } - yield machine.create(); - return; - } - var isoversion = machine.isoversion(); - var packagejson = util.packagejson(); - var packagejsonVersion = packagejson['docker-version'].split('-')[0]; - if (!isoversion || util.compareVersions(isoversion, packagejsonVersion) < 0) { - yield machine.start(); - yield machine.upgrade(); - } - if ((yield machine.state()) !== 'Running') { - yield machine.start(); - } - }) -}]; + error ({error}) { + this.setState({error}); + } -var SetupStore = assign(Object.create(EventEmitter.prototype), { - PROGRESS_EVENT: 'setup_progress', - STEP_EVENT: 'setup_step', - ERROR_EVENT: 'setup_error', - step: function () { - return _currentStep; - }, - steps: function () { - return _.indexBy(_steps, 'name'); - }, - stepCount: function () { - return _requiredSteps.length; - }, - number: function () { - return _.indexOf(_requiredSteps, _currentStep) + 1; - }, - percent: function () { - var sofar = 0; - var totalPercent = _requiredSteps.reduce((prev, step) => prev + step.totalPercent, 0); - _.each(_requiredSteps, step => { - sofar += step.totalPercent * step.percent / 100; - }); - return Math.min(Math.round(100 * sofar / totalPercent), 99); - }, - error: function () { - return _error; - }, - cancelled: function () { - return _cancelled; - }, - retry: function (remove) { - _error = null; - _cancelled = false; - if (!_retryPromise) { - return; - } - this.emit(this.ERROR_EVENT); - if (remove) { - machine.rm().finally(() => { - _retryPromise.resolve(); - }); - } else { - machine.stop().finally(() => { - _retryPromise.resolve(); - }); - } - }, - setError: function (error) { - _error = error; - this.emit(this.ERROR_EVENT); - }, - pause: function () { - _retryPromise = Promise.defer(); - return _retryPromise.promise; - }, - requiredSteps: Promise.coroutine(function* () { - if (_requiredSteps.length) { - return Promise.resolve(_requiredSteps); - } - var packagejson = util.packagejson(); - var isoversion = machine.isoversion(); - var required = {}; - var vboxfile = path.join(util.supportDir(), virtualBox.filename()); - var vboxNeedsInstall = !virtualBox.installed(); + progress ({progress}) { + this.setState({progress}) + } +} - required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== virtualBox.checksum()); - required.install = vboxNeedsInstall || (!util.isWindows() && !virtualBox.active()); - required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || util.compareVersions(isoversion, packagejson['docker-version']) < 0; - - var exists = yield machine.exists(); - if (isoversion && util.compareVersions(isoversion, packagejson['docker-version']) < 0) { - this.steps().init.seconds = 33; - } else if (exists && (yield machine.state()) === 'Saved') { - this.steps().init.seconds = 8; - } else if (exists && (yield machine.state()) !== 'Error') { - this.steps().init.seconds = 23; - } - - _requiredSteps = _steps.filter(function (step) { - return required[step.name]; - }); - return Promise.resolve(_requiredSteps); - }), - run: Promise.coroutine(function* () { - metrics.track('Started Setup', { - virtualbox: virtualBox.installed() ? yield virtualBox.version() : 'Not Installed' - }); - var steps = yield this.requiredSteps(); - for (let step of steps) { - _currentStep = step; - step.percent = 0; - while (true) { - try { - this.emit(this.STEP_EVENT); - yield step.run(percent => { - if (_currentStep) { - step.percent = percent; - this.emit(this.PROGRESS_EVENT); - } - }); - metrics.track('Setup Completed Step', { - name: step.name - }); - step.percent = 100; - break; - } catch (err) { - if (err) { - throw err; - } else { - metrics.track('Setup Cancelled'); - _cancelled = true; - this.emit(this.STEP_EVENT); - } - yield this.pause(); - } - } - } - _currentStep = null; - return yield machine.ip(); - }), - setup: Promise.coroutine(function * () { - while (true) { - try { - var ip = yield this.run(); - if (!ip || !ip.length) { - throw { - message: 'Machine IP could not be fetched. Please retry the setup. If this fails please file a ticket on our GitHub repo.', - machine: yield machine.info(), - ip: ip - }; - } - docker.setup(ip, machine.name()); - yield docker.waitForConnection(); - metrics.track('Setup Finished'); - break; - } catch (err) { - err.message = util.removeSensitiveData(err.message); - metrics.track('Setup Failed', { - step: _currentStep, - }); - console.log(err); - bugsnag.notify('SetupError', err.message, { - error: err, - output: err.message - }, 'info'); - _error = err; - this.emit(this.ERROR_EVENT); - yield this.pause(); - } - } - }) -}); - -module.exports = SetupStore; +export default alt.createStore(SetupStore); diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index 80b0edc6f2..fccb76ffc8 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -1,113 +1,81 @@ import _ from 'underscore'; -import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; -import request from 'request'; -import progress from 'request-progress'; import Promise from 'bluebird'; import util from './Util'; -import resources from './ResourcesUtil'; -var virtualBox = require ('./VirtualBoxUtil'); +import bugsnag from 'bugsnag-js'; +import virtualBox from './VirtualBoxUtil'; +import setupActions from '../actions/SetupActions'; +import metrics from './MetricsUtil'; +import machine from './DockerMachineUtil'; +import docker from './DockerUtil'; -var SetupUtil = { - needsBinaryFix() { - return this.pathDoesNotExistOrDenied(util.binsPath()) || - (fs.existsSync(util.dockerBinPath()) && this.pathDenied(util.dockerBinPath())) || - (fs.existsSync(util.dockerMachineBinPath()) && this.pathDenied(util.dockerMachineBinPath())); - }, - pathDoesNotExistOrDenied: function (path) { - if(util.isWindows()) { - return (!fs.existsSync(path)); - } else { - return (!fs.existsSync(path) || this.pathDenied(path)); - } - }, - pathDenied: function (path) { - return fs.statSync(path).gid !== 80 || fs.statSync(path).uid !== process.getuid(); - }, - shouldUpdateBinaries: function () { - return !fs.existsSync(util.dockerBinPath()) || - !fs.existsSync(util.dockerMachineBinPath()) || - !fs.existsSync(util.dockerComposeBinPath()) || - this.checksum(util.dockerBinPath()) !== this.checksum(resources.docker()) || - this.checksum(util.dockerMachineBinPath()) !== this.checksum(resources.dockerMachine()) || - this.checksum(util.dockerComposeBinPath()) !== this.checksum(resources.dockerCompose()); - }, - copycmd: function (src, dest) { - return ['rm', '-f', dest, '&&', 'cp', src, dest]; - }, - copyBinariesCmd: function () { - var cmd = ['mkdir', '-p', '/usr/local/bin']; - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(util.escapePath(resources.dockerMachine()), '/usr/local/bin/docker-machine')); - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(util.escapePath(resources.docker()), '/usr/local/bin/docker')); - cmd.push('&&'); - cmd.push.apply(cmd, this.copycmd(util.escapePath(resources.dockerCompose()), '/usr/local/bin/docker-compose')); - return cmd.join(' '); - }, - fixBinariesCmd: function () { - var cmd = []; - cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin')]); - cmd.push('&&'); - cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin', 'docker-machine')]); - cmd.push('&&'); - cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin', 'docker')]); - cmd.push('&&'); - cmd.push.apply(cmd, ['chown', `${process.getuid()}:${80}`, path.join('/usr/local/bin', 'docker-compose')]); - return cmd.join(' '); - }, - installVirtualBoxCmd: function () { - if(util.isWindows()) { - return `powershell.exe -ExecutionPolicy unrestricted -Command "Start-Process \\\"${path.join(util.supportDir(), virtualBox.filename())}\\\" -ArgumentList \\\"--silent --msiparams REBOOT=ReallySuppress\\\" -Verb runAs -Wait"`; - } else { - return `installer -pkg ${util.escapePath(path.join(util.supportDir(), virtualBox.filename()))} -target /`; - } - }, - macSudoCmd: function (cmd) { - return `${util.escapePath(resources.macsudo())} -p "Kitematic requires administrative privileges to install and/or start VirtualBox." sh -c \"${cmd}\"`; - }, - simulateProgress(estimateSeconds, progress) { +let _retryPromise = null; + +export default { + simulateProgress (estimateSeconds, progress) { var times = _.range(0, estimateSeconds * 1000, 200); var timers = []; _.each(times, time => { var timer = setTimeout(() => { - progress(100 * time / (estimateSeconds * 1000)); + setupActions.progress(100 * time / (estimateSeconds * 1000)); }, time); timers.push(timer); }); }, - checksum(filename) { - return crypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); - }, - download(url, filename, checksum, percentCallback) { - return new Promise((resolve, reject) => { - if (fs.existsSync(filename)) { - var existingChecksum = this.checksum(filename); - if (existingChecksum === checksum) { - resolve(); - return; - } else { - fs.unlinkSync(filename); - } - } - progress(request({ uri: url, rejectUnauthorized: false }), { throttle: 10 }).on('progress', state => { - if (percentCallback) { - percentCallback(state.percent); - } - }).on('error', err => { - reject(err); - }).pipe(fs.createWriteStream(filename)).on('error', err => { - reject(err); - }).on('close', err => { - if (err) { - reject(err); - } - resolve(); + retry (removeVM) { + if (removeVM) { + machine.rm().finally(() => { + console.log('machine removed'); + _retryPromise.resolve(); }); - }); + } else { + _retryPromise.resolve(); + } + }, + + pause () { + _retryPromise = Promise.defer(); + return _retryPromise.promise; + }, + + async setup () { + metrics.track('Started Setup'); + while (true) { + try { + if (!util.isWindows() && !virtualBox.active()) { + await util.exec(setupUtil.macSudoCmd(util.escapePath('/Library/Application Support/VirtualBox/LaunchDaemons/VirtualBoxStartup.sh') + ' restart')); + } + + let exists = await virtualBox.vmExists('default'); + if (!exists) { + this.simulateProgress(60, progress => setupActions.progress(progress)); + await machine.rm(); + await machine.create(); + } else { + let state = await machine.state(); + if (state === 'Saved') { + this.simulateProgress(10, progress => setupActions.progress(progress)); + } else { + this.simulateProgress(25, progress => setupActions.progress(progress)); + } + await machine.start(); + } + + let ip = await machine.ip(); + docker.setup(ip, machine.name()); + await docker.waitForConnection(); + break; + } catch (err) { + setupActions.error(err); + bugsnag.notify('SetupError', err.message, { + error: err, + output: err.message + }, 'info'); + await this.pause(); + } + } + metrics.track('Setup Finished'); } }; - -module.exports = SetupUtil; diff --git a/src/utils/URLUtil.js b/src/utils/URLUtil.js deleted file mode 100644 index 68b102fd22..0000000000 --- a/src/utils/URLUtil.js +++ /dev/null @@ -1,60 +0,0 @@ -import util from './Util'; -import parseUri from 'parseUri'; -import containerServerActions from '../actions/ContainerServerActions'; - -module.exports = { - TYPE_WHITELIST: ['repository'], - METHOD_WHITELIST: ['run'], - openUrl: function (url, flags, appVersion) { - if (!url || !flags || !flags.dockerURLEnabledVersion || !appVersion) { - return false; - } - - // Make sure this feature is enabled via the feature flag - if (util.compareVersions(appVersion, flags.dockerURLEnabledVersion) < 0) { - return false; - } - - var parser = parseUri(url); - - if (parser.protocol !== 'docker') { - return false; - } - - // Get the type of object we're operating on, e.g. 'repository' - var type = parser.host; - - if (this.TYPE_WHITELIST.indexOf(type) === -1) { - return false; - } - - // Separate the path into [run', 'redis'] - var tokens = parser.path.replace('/', '').split('/'); - - // Get the method trying to be executed, e.g. 'run' - var method = tokens[0]; - - if (this.METHOD_WHITELIST.indexOf(method) === -1) { - return false; - } - - // Get the repository namespace and repo name, e.g. 'redis' or 'myusername/myrepo' - var repo = tokens.slice(1).join('/'); - - // Only accept official repos for now (one component) - if (tokens > 1) { - return false; - } - - // Only accept official repos for now - if (!util.isOfficialRepo(repo)) { - return false; - } - - if (type === 'repository' && method === 'run') { - containerServerActions.pending({repo, tag: 'latest'}); - return true; - } - return false; - } -}; diff --git a/src/utils/VirtualBoxUtil.js b/src/utils/VirtualBoxUtil.js index 338717676a..20eff5ec08 100644 --- a/src/utils/VirtualBoxUtil.js +++ b/src/utils/VirtualBoxUtil.js @@ -10,15 +10,6 @@ var VirtualBox = { return '/Applications/VirtualBox.app/Contents/MacOS/VBoxManage'; } }, - filename: function () { - return util.isWindows() ? util.packagejson()['virtualbox-filename-win'] : util.packagejson()['virtualbox-filename']; - }, - checksum: function () { - return util.isWindows() ? util.packagejson()['virtualbox-checksum-win'] : util.packagejson()['virtualbox-checksum']; - }, - url: function () { - return `https://github.com/kitematic/virtualbox/releases/download/${util.packagejson()['virtualbox-version']}/${this.filename()}`; - }, installed: function () { if(util.isWindows()) { return fs.existsSync('C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe') && fs.existsSync('C:\\Program Files\\Oracle\\VirtualBox\\VirtualBox.exe'); @@ -68,15 +59,11 @@ var VirtualBox = { }); } }, - vmstate: function (name) { - return new Promise((resolve, reject) => { - util.exec([this.command(), 'showvminfo', name, '--machinereadable']).then(stdout => { - var match = stdout.match(/VMState="(\w+)"/); - if (!match) { - reject('Could not parse VMState'); - } - resolve(match[1]); - }).catch(reject); + vmExists: function (name) { + return util.exec([this.command(), 'showvminfo', name]).then(() => { + return true; + }).catch((err) => { + return false; }); } };