mirror of https://github.com/docker/docs.git
WIP upgrade
This commit is contained in:
parent
a44f20168d
commit
9dc6a03047
|
|
@ -229,7 +229,9 @@ module.exports = function (grunt) {
|
|||
babel: {
|
||||
options: {
|
||||
sourceMap: 'inline',
|
||||
blacklist: 'regenerator'
|
||||
blacklist: 'regenerator',
|
||||
stage: 1,
|
||||
optional: ['asyncToGenerator']
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
38
package.json
38
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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import alt from '../alt';
|
||||
|
||||
class SetupActions {
|
||||
constructor () {
|
||||
this.generateActions(
|
||||
'progress',
|
||||
'error'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(SetupActions);
|
||||
39
src/app.js
39
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(<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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
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;
|
||||
class SetupStore {
|
||||
constructor () {
|
||||
this.bindActions(setupActions);
|
||||
this.title = '';
|
||||
this.message = '';
|
||||
this.percent = 0;
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
})
|
||||
}];
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
error ({error}) {
|
||||
this.setState({error});
|
||||
}
|
||||
|
||||
_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);
|
||||
progress ({progress}) {
|
||||
this.setState({progress})
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
retry (removeVM) {
|
||||
if (removeVM) {
|
||||
machine.rm().finally(() => {
|
||||
console.log('machine removed');
|
||||
_retryPromise.resolve();
|
||||
});
|
||||
} else {
|
||||
fs.unlinkSync(filename);
|
||||
_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'));
|
||||
}
|
||||
|
||||
progress(request({ uri: url, rejectUnauthorized: false }), { throttle: 10 }).on('progress', state => {
|
||||
if (percentCallback) {
|
||||
percentCallback(state.percent);
|
||||
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));
|
||||
}
|
||||
}).on('error', err => {
|
||||
reject(err);
|
||||
}).pipe(fs.createWriteStream(filename)).on('error', err => {
|
||||
reject(err);
|
||||
}).on('close', err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
await machine.start();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue