Merge pull request #1442 from docker/sockets

Better socket support 🌳
This commit is contained in:
French Ben
2016-03-03 11:37:43 -08:00
14 changed files with 158 additions and 93 deletions

View File

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

View File

@@ -6,6 +6,11 @@ class SetupActions {
this.dispatch({removeVM});
setupUtil.retry(removeVM);
}
useVbox () {
this.dispatch({});
setupUtil.useVbox();
}
}
export default alt.createActions(SetupActions);

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ var ContainerHomeFolder = React.createClass({
mounts.forEach(m => {
if (m.Destination === destination) {
m.Source = util.windowsToLinuxPath(newSource);
m.Driver = null;
}
});

View File

@@ -97,7 +97,7 @@ var ContainerListItem = React.createClass({
return (
<Router.Link to="container" params={{name: container.Name}}>
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick}>
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick} id={this.props.key}>
{state}
<div className="info">
<div className="name">

View File

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

View File

@@ -26,7 +26,7 @@ var Containers = React.createClass({
containerStore.listen(this.update);
},
componentDidUnmount: function () {
componentWillUnmount: function () {
containerStore.unlisten(this.update);
},
@@ -106,43 +106,7 @@ var Containers = React.createClass({
metrics.track('Opened Issue Reporter', {
from: 'app'
});
shell.openExternal('https://github.com/kitematic/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: ''
});
shell.openExternal('https://github.com/docker/kitematic/issues/new');
},
render: function () {
@@ -169,9 +133,9 @@ var Containers = React.createClass({
<ContainerList containers={this.state.sorted} newContainer={this.state.newContainer} />
</section>
<section className="sidebar-buttons">
<span className="btn-sidebar btn-terminal" onClick={this.handleClickDockerTerminal} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-docker-cli"></span><span className="text">DOCKER CLI</span></span>
<span className="btn-sidebar btn-feedback" onClick={this.handleClickReportIssue} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-feedback"></span></span>
<span className="btn-sidebar btn-preferences" onClick={this.handleClickPreferences} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-preferences"></span></span>
<span className="btn-sidebar btn-terminal" onClick={this.handleClickDockerTerminal} ><span className="icon icon-docker-cli"></span><span className="text">DOCKER CLI</span></span>
<span className="btn-sidebar btn-feedback" onClick={this.handleClickReportIssue} ><span className="icon icon-feedback"></span></span>
<span className="btn-sidebar btn-preferences" onClick={this.handleClickPreferences} ><span className="icon icon-preferences"></span></span>
</section>
</div>
<Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container}/>

View File

@@ -73,7 +73,6 @@ module.exports = React.createClass({
nextPage = (page + 1 > this.state.totalPage) ? this.state.totalPage : page + 1;
totalPage = this.state.totalPage;
}
this.setState({
query: query,
loading: true,

View File

@@ -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],
@@ -20,7 +21,7 @@ var Setup = React.createClass({
setupStore.listen(this.update);
},
componentDidUnmount: function () {
componentWillUnmount: function () {
setupStore.unlisten(this.update);
},
@@ -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 (
<div className="setup">
<Header hideLogin={true}/>
@@ -72,8 +83,8 @@ var Setup = React.createClass({
</div>
<div className="desc">
<div className="content">
<h1>Starting Docker VM</h1>
<p>To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...</p>
<h1>{title}</h1>
<p>{descr}</p>
</div>
</div>
</div>
@@ -84,22 +95,24 @@ var Setup = React.createClass({
renderError: function () {
let deleteVmAndRetry;
if (Util.isLinux()) {
if (util.isLinux()) {
if (!this.state.started) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button>
);
}
} else if (util.isNative()) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleUseVbox}>Use VirtualBox</button>
);
} else if (this.state.started) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM &amp; Retry Setup</button>
);
} else {
if (this.state.started) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM &amp; Retry Setup</button>
);
} else {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
);
}
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
);
}
return (
<div className="setup">

View File

@@ -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.execFile([terminal, cmd]).then(() => {});
}
} else {
cmd = cmd || process.env.SHELL;
this.url(machineName).then(machineUrl => {

View File

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

View File

@@ -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,30 +66,44 @@ export default {
return _retryPromise.promise;
},
setup() {
return util.isLinux() ? this.nativeSetup() : this.nonNativeSetup();
async setup () {
while (true) {
try {
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) {
metrics.track('Native Setup Failed');
setupServerActions.error({error});
bugsnag.notify('Native Setup Failed', error.message, {
'Docker Error': error.message
}, 'info');
this.clearTimers();
await this.pause();
}
}
},
async nativeSetup () {
while (true) {
try {
docker.setup('localhost', machine.name());
docker.isDockerRunning();
break;
} 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
}, 'info');
this.clearTimers();
await this.pause();
docker.setup(util.isLinux() ? 'localhost':'docker.local');
setupServerActions.started({started: true});
this.simulateProgress(20);
return docker.version();
} catch (error) {
throw new Error(error);
}
}
},
@@ -127,13 +151,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 +178,7 @@ export default {
if (ip) {
docker.setup(ip, machine.name());
await docker.version();
} else {
throw new Error('Could not determine IP from docker-machine.');
}

View File

@@ -37,6 +37,24 @@ module.exports = {
isLinux: function () {
return process.platform === 'linux';
},
isNative: function () {
// let native = JSON.parse(localStorage.getItem('settings.useNative'));
let native = null;
if (native === null) {
try {
// Check if file exists
fs.statSync('/var/run/docker.sock');
native = true;
} catch (e) {
if (this.isLinux()) {
native = true;
} else {
native = false;
}
}
}
return native;
},
binsPath: function () {
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
},
@@ -163,9 +181,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']