diff --git a/package.json b/package.json index 2d8a42cae1..08a3352965 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "dockerode": "^2.2.7", "install": "^0.1.8", "jquery": "^2.1.3", + "JSONStream": "^1.0.7", "mixpanel": "kitematic/mixpanel-node", "mkdirp": "^0.5.0", "node-uuid": "^1.4.3", diff --git a/src/actions/SetupActions.js b/src/actions/SetupActions.js index 9278430224..c4d5bbc4a9 100644 --- a/src/actions/SetupActions.js +++ b/src/actions/SetupActions.js @@ -6,6 +6,11 @@ class SetupActions { this.dispatch({removeVM}); setupUtil.retry(removeVM); } + + useVbox () { + this.dispatch({}); + setupUtil.useVbox(); + } } export default alt.createActions(SetupActions); diff --git a/src/app.js b/src/app.js index 293cf72481..aa8307f965 100644 --- a/src/app.js +++ b/src/app.js @@ -1,4 +1,5 @@ require.main.paths.splice(0, 0, process.env.NODE_PATH); + import electron from 'electron'; const remote = electron.remote; const Menu = remote.Menu; diff --git a/src/browser.js b/src/browser.js index 4b2e83af83..3c4c58445a 100644 --- a/src/browser.js +++ b/src/browser.js @@ -4,7 +4,6 @@ const BrowserWindow = electron.BrowserWindow; import fs from 'fs'; import os from 'os'; - import path from 'path'; import child_process from 'child_process'; diff --git a/src/components/ContainerHomeFolders.react.js b/src/components/ContainerHomeFolders.react.js index cab35902df..fbbc704bf0 100644 --- a/src/components/ContainerHomeFolders.react.js +++ b/src/components/ContainerHomeFolders.react.js @@ -32,6 +32,7 @@ var ContainerHomeFolder = React.createClass({ mounts.forEach(m => { if (m.Destination === destination) { m.Source = util.windowsToLinuxPath(newSource); + m.Driver = null; } }); diff --git a/src/components/ContainerSettingsVolumes.react.js b/src/components/ContainerSettingsVolumes.react.js index 914fa57494..7462db8c04 100644 --- a/src/components/ContainerSettingsVolumes.react.js +++ b/src/components/ContainerSettingsVolumes.react.js @@ -28,7 +28,7 @@ var ContainerSettingsVolumes = React.createClass({ metrics.track('Choose Directory for Volume'); - var mounts = _.clone(this.props.container.Mounts); + let mounts = _.clone(this.props.container.Mounts); _.each(mounts, m => { if (m.Destination === dockerVol) { m.Source = util.windowsToLinuxPath(directory); @@ -36,7 +36,7 @@ var ContainerSettingsVolumes = React.createClass({ } }); - var binds = mounts.map(m => { + let binds = mounts.map(m => { return m.Source + ':' + m.Destination; }); @@ -50,7 +50,7 @@ var ContainerSettingsVolumes = React.createClass({ from: 'settings' }); - var mounts = _.clone(this.props.container.Mounts); + let mounts = _.clone(this.props.container.Mounts); _.each(mounts, m => { if (m.Destination === dockerVol) { m.Source = null; diff --git a/src/components/Containers.react.js b/src/components/Containers.react.js index d7b611168f..655a7930b3 100644 --- a/src/components/Containers.react.js +++ b/src/components/Containers.react.js @@ -106,44 +106,44 @@ var Containers = React.createClass({ metrics.track('Opened Issue Reporter', { from: 'app' }); - shell.openExternal('https://github.com/kitematic/kitematic/issues/new'); + shell.openExternal('https://github.com/docker/kitematic/issues/new'); }, - handleMouseEnterDockerTerminal: function () { - this.setState({ - currentButtonLabel: 'Open terminal to use Docker command line.' - }); - }, - - handleMouseLeaveDockerTerminal: function () { - this.setState({ - currentButtonLabel: '' - }); - }, - - handleMouseEnterReportIssue: function () { - this.setState({ - currentButtonLabel: 'Report an issue or suggest feedback.' - }); - }, - - handleMouseLeaveReportIssue: function () { - this.setState({ - currentButtonLabel: '' - }); - }, - - handleMouseEnterPreferences: function () { - this.setState({ - currentButtonLabel: 'Change app preferences.' - }); - }, - - handleMouseLeavePreferences: function () { - this.setState({ - currentButtonLabel: '' - }); - }, + // handleMouseEnterDockerTerminal: function () { + // this.setState({ + // currentButtonLabel: 'Open terminal to use Docker command line.' + // }); + // }, + // + // handleMouseLeaveDockerTerminal: function () { + // this.setState({ + // currentButtonLabel: '' + // }); + // }, + // + // handleMouseEnterReportIssue: function () { + // this.setState({ + // currentButtonLabel: 'Report an issue or suggest feedback.' + // }); + // }, + // + // handleMouseLeaveReportIssue: function () { + // this.setState({ + // currentButtonLabel: '' + // }); + // }, + // + // handleMouseEnterPreferences: function () { + // this.setState({ + // currentButtonLabel: 'Change app preferences.' + // }); + // }, + // + // handleMouseLeavePreferences: function () { + // this.setState({ + // currentButtonLabel: '' + // }); + // }, render: function () { var sidebarHeaderClass = 'sidebar-header'; @@ -169,9 +169,9 @@ var Containers = React.createClass({
- DOCKER CLI - - + DOCKER CLI + +
diff --git a/src/components/Setup.react.js b/src/components/Setup.react.js index 741322e174..80827ef3da 100644 --- a/src/components/Setup.react.js +++ b/src/components/Setup.react.js @@ -3,12 +3,13 @@ import Router from 'react-router'; import Radial from './Radial.react.js'; import RetinaImage from 'react-retina-image'; import Header from './Header.react'; -import Util from '../utils/Util'; +import util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; import setupStore from '../stores/SetupStore'; import setupActions from '../actions/SetupActions'; import shell from 'shell'; + var Setup = React.createClass({ mixins: [Router.Navigation], @@ -32,6 +33,10 @@ var Setup = React.createClass({ setupActions.retry(false); }, + handleUseVbox: function () { + setupActions.useVbox(); + }, + handleErrorRemoveRetry: function () { console.log('Deleting VM and trying again.' ); setupActions.retry(true); @@ -63,6 +68,12 @@ var Setup = React.createClass({ }, renderProgress: function () { + let title = 'Starting Docker VM'; + let descr = 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...'; + if (util.isNative()) { + title = 'Checking Docker'; + descr = 'To run Docker containers on your computer, Kitematic is checking the Docker connection.'; + } return (
@@ -72,8 +83,8 @@ var Setup = React.createClass({
-

Starting Docker VM

-

To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...

+

{title}

+

{descr}

@@ -84,14 +95,18 @@ var Setup = React.createClass({ renderError: function () { let deleteVmAndRetry; - if (Util.isLinux()) { + if (util.isLinux()) { if (!this.state.started) { deleteVmAndRetry = ( ); } } else { - if (this.state.started) { + if (util.isNative()) { + deleteVmAndRetry = ( + + ); + } else if (this.state.started) { deleteVmAndRetry = ( ); diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index 49886372c4..e676416c30 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -158,11 +158,12 @@ var DockerMachine = { } }); }); - } else if (util.isLinux()) { + } else if (util.isNative()) { cmd = cmd || process.env.SHELL; - var terminal = util.linuxTerminal(); - if (terminal) - util.execFile(terminal.concat([cmd])).then(() => {}); + var terminal = util.isLinux() ? util.linuxTerminal() : path.join(process.env.RESOURCES_PATH, 'terminal'); + if (terminal) { + util.exec([terminal, cmd]).then(() => {}); + } } else { cmd = cmd || process.env.SHELL; this.url(machineName).then(machineUrl => { diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index a06765d330..1942791936 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -11,6 +11,9 @@ import containerServerActions from '../actions/ContainerServerActions'; import rimraf from 'rimraf'; import stream from 'stream'; import JSONStream from 'JSONStream'; +import Promise from 'bluebird'; + + export default { host: null, @@ -20,20 +23,23 @@ export default { activeContainerName: null, setup (ip, name) { - if (!ip || !name) { + if (!ip && !name) { throw new Error('Falsy ip or name passed to docker client setup'); } + this.host = ip; - if (util.isLinux()) { - this.host = 'localhost'; - this.client = new dockerode({socketPath: '/var/run/docker.sock'}); + if (ip.indexOf('local') !== -1) { + try { + this.client = new dockerode({socketPath: '/var/run/docker.sock'}); + } catch (error) { + throw new Error('Cannot connect to the Docker daemon. Is the daemon running?'); + } } else { let certDir = path.join(util.home(), '.docker/machine/machines/', name); if (!fs.existsSync(certDir)) { throw new Error('Certificate directory does not exist'); } - this.host = ip; this.client = new dockerode({ protocol: 'https', host: ip, @@ -45,6 +51,28 @@ export default { } }, + async version () { + let version = null; + let maxRetries = 10; + let retries = 0; + let error_message = ""; + while (version == null && retries < maxRetries) { + this.client.version((error,data) => { + if (!error) { + version = data.Version; + } else { + error_message = error; + } + retries++; + }); + await Promise.delay(1000); + } + if (version == null) { + throw new Error(error_message); + } + return version; + }, + init () { this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {}; this.fetchAllContainers(); @@ -88,6 +116,7 @@ export default { container.start((error) => { if (error) { containerServerActions.error({name, error}); + console.log('error starting: %o - %o', name, error); return; } containerServerActions.started({name, error}); @@ -117,7 +146,7 @@ export default { if (!containerData.HostConfig || (containerData.HostConfig && !containerData.HostConfig.PortBindings)) { containerData.PublishAllPorts = true; } - + if (image.Config.Cmd) { containerData.Cmd = image.Config.Cmd; } else if (!image.Config.Entrypoint) { @@ -156,6 +185,7 @@ export default { fetchAllContainers () { this.client.listContainers({all: true}, (err, containers) => { if (err) { + console.error(err); return; } async.map(containers, (container, callback) => { @@ -171,6 +201,7 @@ export default { containers = containers.filter(c => c !== null); if (err) { // TODO: add a global error handler for this + console.error(err); return; } containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')}); @@ -367,6 +398,8 @@ export default { timestamps: 1 }, (err, logStream) => { if (err) { + // socket hang up can be captured + console.error(err); return; } @@ -393,6 +426,8 @@ export default { timestamps: 1 }, (err, logStream) => { if (err) { + // Socket hang up also can be found here + console.error(err); return; } diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index d000fc26ca..7b50ec6e6a 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -18,6 +18,7 @@ const precreateCheckExitCode = 3; let _retryPromise = null; let _timers = []; +let useNative = util.isNative() ? util.isNative() : true; export default { simulateProgress (estimateSeconds) { @@ -36,12 +37,21 @@ export default { _timers = []; }, + async useVbox () { + metrics.track('Retried Setup with VBox'); + localStorage.setItem('settings.useNative', false); + router.get().transitionTo('loading'); + setupServerActions.error({ error: { message: null }}); + _retryPromise.resolve(); + }, + retry (removeVM) { metrics.track('Retried Setup', { removeVM }); router.get().transitionTo('loading'); + setupServerActions.error({ error: { message: null }}); if (removeVM) { machine.rm().finally(() => { _retryPromise.resolve(); @@ -56,35 +66,50 @@ export default { return _retryPromise.promise; }, - setup() { - return util.isLinux() ? this.nativeSetup() : this.nonNativeSetup(); - }, - - async nativeSetup () { + async setup () { while (true) { try { - docker.setup('localhost', machine.name()); - docker.isDockerRunning(); - - break; + if (util.isNative()) { + localStorage.setItem('setting.useNative', true); + let stats = fs.statSync('/var/run/docker.sock'); + if (stats.isSocket()) { + await this.nativeSetup(); + } else { + throw new Error('File found is not a socket'); + } + } else { + await this.nonNativeSetup(); + } + return; } catch (error) { - router.get().transitionTo('setup'); metrics.track('Native Setup Failed'); setupServerActions.error({error}); - let message = error.message.split('\n'); - let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.'; - bugsnag.notify('Native Setup Failed', lastLine, { - 'Docker Machine Logs': error.message + bugsnag.notify('Native Setup Failed', error.message, { + 'Docker Error': error.message }, 'info'); - this.clearTimers(); await this.pause(); } } }, + async nativeSetup () { + while (true) { + try { + router.get().transitionTo('setup'); + docker.setup(util.isLinux() ? 'localhost':'docker.local'); + setupServerActions.started({started: true}); + this.simulateProgress(20); + return docker.version(); + } catch (error) { + throw new Error(error); + } + } + }, + async nonNativeSetup () { + console.log('Non-native setup'); let virtualBoxVersion = null; let machineVersion = null; while (true) { @@ -127,13 +152,16 @@ export default { } else { let state = await machine.status(); if (state !== 'Running') { + router.get().transitionTo('setup'); + setupServerActions.started({started: true}); if (state === 'Saved') { - router.get().transitionTo('setup'); this.simulateProgress(10); } else if (state === 'Stopped') { - router.get().transitionTo('setup'); this.simulateProgress(25); + } else { + this.simulateProgress(40); } + await machine.start(); } } @@ -151,6 +179,7 @@ export default { if (ip) { docker.setup(ip, machine.name()); + await docker.version(); } else { throw new Error('Could not determine IP from docker-machine.'); } diff --git a/src/utils/Util.js b/src/utils/Util.js index 872827c892..873241ff26 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -37,6 +37,18 @@ module.exports = { isLinux: function () { return process.platform === 'linux'; }, + isNative: function () { + let native = JSON.parse(localStorage.getItem('settings.useNative')); + if (native === null) { + try { + let stats = fs.statSync('/var/run/docker.sock'); + native = true; + } catch(e){ + native = false; + } + } + return native; + }, binsPath: function () { return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin'); }, @@ -163,9 +175,9 @@ module.exports = { dialog.showMessageBox({ type: 'warning', buttons: ['OK'], - message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/kitematic/kitematic/wiki/Common-Issues-and-Fixes#early-linux-support-from-zedtux.' + message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/docker/kitematic/wiki/Early-Linux-Support.' }); - return; + return false; } }, webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']