WIP upgrade

This commit is contained in:
Jeffrey Morgan 2015-10-05 17:06:08 -04:00
parent a44f20168d
commit 9dc6a03047
14 changed files with 171 additions and 632 deletions

View File

@ -229,7 +229,9 @@ module.exports = function (grunt) {
babel: {
options: {
sourceMap: 'inline',
blacklist: 'regenerator'
blacklist: 'regenerator',
stage: 1,
optional: ['asyncToGenerator']
},
dist: {
files: [{

View File

@ -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);
});
});

View File

@ -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"
}
}

View File

@ -0,0 +1,12 @@
import alt from '../alt';
class SetupActions {
constructor () {
this.generateActions(
'progress',
'error'
);
}
}
export default alt.createActions(SetupActions);

View File

@ -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(<Handler/>, document.body));
routerContainer.set(router);
let history = createHashHistory()
React.render(<Router history={history}>{routes}</Router>, 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
};

View File

@ -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());
}

View File

@ -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 (
<div className="containers">
<Header />

View File

@ -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 (
<div className="contents">
<RetinaImage src={img} checkIfRetinaImgExists={false}/>
<RetinaImage src="boot2docker.png" checkIfRetinaImgExists={false}/>
<div className="detail">
<Radial progress={this.state.progress} thick={true} gray={true}/>
</div>
</div>
);
},
renderStep: function () {
renderProgress: function () {
return (
<div className="setup">
<Header hideLogin={true}/>
@ -83,9 +47,8 @@ var Setup = React.createClass({
</div>
<div className="desc">
<div className="content">
<h4>Step {SetupStore.number()} out of {SetupStore.stepCount()}</h4>
<h1>{SetupStore.step().title}</h1>
<p>{SetupStore.step().message}</p>
<h1>{this.state.title}</h1>
<p>{this.state.message}</p>
</div>
</div>
</div>
@ -141,15 +104,14 @@ var Setup = React.createClass({
</div>
);
},
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();
}
}
});

View File

@ -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');
}
},
{

View File

@ -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 (
<RouteHandler/>
<div>
{this.props.children}
</div>
);
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="account" path="account" handler={Account}>
<Route name="signup" path="signup" handler={AccountSignup}/>
<Route name="login" path="login" handler={AccountLogin}/>
<Route path="/" component={App}>
<IndexRoute component={Setup}/>
<Route path="account" component={Account}>
<Route path="signup" component={AccountSignup}/>
<Route path="login" component={AccountLogin}/>
</Route>
<Route name="containers" path="containers" handler={Containers}>
<Route name="container" path="details/:name" handler={ContainerDetails}>
<DefaultRoute name="containerHome" handler={ContainerHome} />
<Route name="containerLogs" path="logs" handler={ContainerLogs}/>
<Route name="containerSettings" path="settings" handler={ContainerSettings}>
<Route name="containerSettingsGeneral" path="general" handler={ContainerSettingsGeneral}/>
<Route name="containerSettingsPorts" path="ports" handler={ContainerSettingsPorts}/>
<Route name="containerSettingsVolumes" path="volumes" handler={ContainerSettingsVolumes}/>
<Route name="containerSettingsAdvanced" path="advanced" handler={ContainerSettingsAdvanced}/>
<Route path="containers" component={Containers}>
<Route path="details/:name" component={ContainerDetails}>
<IndexRoute component={ContainerHome} />
<Route path="logs" component={ContainerLogs}/>
<Route path="settings" component={ContainerSettings}>
<Route path="general" component={ContainerSettingsGeneral}/>
<Route path="ports" component={ContainerSettingsPorts}/>
<Route path="volumes" component={ContainerSettingsVolumes}/>
<Route path="advanced" component={ContainerSettingsAdvanced}/>
</Route>
</Route>
<Route name="new" path="new">
<DefaultRoute name="search" handler={NewContainerSearch}/>
<Route name="pull" path="pull" handler={NewContainerPull}></Route>
<IndexRoute component={NewContainerSearch}/>
</Route>
<Route name="preferences" path="preferences" handler={Preferences}/>
<Route name="about" path="about" handler={About}/>
<Route path="preferences" component={Preferences}/>
<Route path="about" component={About}/>
</Route>
<DefaultRoute name="setup" handler={Setup}/>
</Route>
);

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
};

View File

@ -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;
});
}
};