Skip to content

Commit

Permalink
feature(@embark/core): IPC adapts when run in Docker Linux on Win
Browse files Browse the repository at this point in the history
When embark is running on Linux and macOS, unix socket files are used for
geth's and embark's IPC files. If the context is a Linux container running on a
Windows Docker host, and if the DApp is mounted from the host's file system,
there is a problem: unix socket files are incompatible with the Windows file
system.

Detect the problematic scenario and use a path outside of the mounted directory
for embark's and geth's IPC files.

To avoid a circular dependency problem, remove the `utils/utils.js` dependency
from `core/env.js`, don't have `utils/host.ts` depdend on `core/fs.js`, and
implement the `ipcPath()` helper as a static method on the class exported from
`core/ipc.js`.
  • Loading branch information
michaelsbradleyjr committed Dec 18, 2018
1 parent 944b392 commit 6f219e2
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 34 deletions.
11 changes: 5 additions & 6 deletions src/lib/core/env.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* global __dirname module process require */

const {execSync} = require('child_process');
const {delimiter} = require('path');
const {joinPath} = require('../utils/utils.js');
const {delimiter, join} = require('path');

function anchoredValue(anchor, value) {
if (!arguments.length) {
Expand Down Expand Up @@ -39,11 +38,11 @@ const DEFAULT_CMD_HISTORY_SIZE = 20;
anchoredValue(CMD_HISTORY_SIZE, DEFAULT_CMD_HISTORY_SIZE);

const DIAGRAM_PATH = 'DIAGRAM_PATH';
const DEFAULT_DIAGRAM_PATH = joinPath(anchoredValue(DAPP_PATH), 'diagram.svg');
const DEFAULT_DIAGRAM_PATH = join(anchoredValue(DAPP_PATH), 'diagram.svg');
anchoredValue(DIAGRAM_PATH, DEFAULT_DIAGRAM_PATH);

const EMBARK_PATH = 'EMBARK_PATH';
const DEFAULT_EMBARK_PATH = joinPath(__dirname, '../../..');
const DEFAULT_EMBARK_PATH = join(__dirname, '../../..');
anchoredValue(EMBARK_PATH, DEFAULT_EMBARK_PATH);

const PKG_PATH = 'PKG_PATH';
Expand All @@ -52,7 +51,7 @@ anchoredValue(PKG_PATH, DEFAULT_PKG_PATH);

let YARN_GLOBAL_PATH;
try {
YARN_GLOBAL_PATH = joinPath(
YARN_GLOBAL_PATH = join(
execSync('yarn global dir', {stdio: ['pipe', 'pipe', 'ignore']})
.toString()
.trim(),
Expand All @@ -65,7 +64,7 @@ try {
const NODE_PATH = 'NODE_PATH';
// NOTE: setting NODE_PATH at runtime won't effect lookup behavior in the
// current process, but will take effect in child processes
process.env[NODE_PATH] = joinPath(anchoredValue(EMBARK_PATH), 'node_modules') +
process.env[NODE_PATH] = join(anchoredValue(EMBARK_PATH), 'node_modules') +
(YARN_GLOBAL_PATH ? delimiter : '') +
(YARN_GLOBAL_PATH || '') +
(process.env[NODE_PATH] ? delimiter : '') +
Expand Down
25 changes: 22 additions & 3 deletions src/lib/core/ipc.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
const fs = require('./fs.js');
const ipc = require('node-ipc');
const {isDappMountedFromWindowsDockerHost} = require('../utils/host');
const os = require('os');
const {parse, stringify} = require('flatted/cjs');
const utils = require('../utils/utils.js');

class IPC {

constructor(options) {
this.logger = options.logger;
this.socketPath = options.socketPath || fs.dappPath(".embark/embark.ipc");
this.socketPath = options.socketPath || IPC.ipcPath('embark.ipc');
this.ipcRole = options.ipcRole;
ipc.config.silent = true;
this.connected = false;
}

static ipcPath(basename, usePipePathOnWindows = false) {
if (!(basename && typeof basename === 'string')) {
throw new TypeError('first argument must be a non-empty string');
}
let ipcDir;
if (process.platform === 'win32' && usePipePathOnWindows) {
return `\\\\.\\pipe\\${basename}`;
} else if (isDappMountedFromWindowsDockerHost) {
ipcDir = utils.joinPath(
os.tmpdir(), `embark-${utils.sha512(fs.dappPath()).slice(0, 8)}`
);
} else {
ipcDir = fs.dappPath('.embark');
}
return utils.joinPath(ipcDir, basename);
}

connect(done) {
const self = this;
function connecting(_socket) {
Expand Down Expand Up @@ -39,7 +58,7 @@ class IPC {
}

serve() {
fs.mkdirpSync(fs.dappPath(".embark"));
fs.mkdirpSync(utils.dirname(this.socketPath));
ipc.serve(this.socketPath, () => {});
ipc.server.start();

Expand Down
1 change: 0 additions & 1 deletion src/lib/core/processes/processManager.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

const ProcessState = {
Unstarted: 'unstarted',
Starting: 'starting',
Expand Down
5 changes: 4 additions & 1 deletion src/lib/modules/blockchain_process/gethClient.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const async = require('async');
const GethMiner = require('./miner');
const IPC = require('../../core/ipc');
const semver = require('semver');
const constants = require('../../constants');

Expand Down Expand Up @@ -50,7 +51,7 @@ class GethClient {
needKeepAlive() {
// TODO: check version also (geth version < 1.8.15)
if (this.isDev) {
// Trigger regular txs due to a bug in geth (< 1.8.15) and stuck transactions in --dev mode.
// Trigger regular txs due to a bug in geth (< 1.8.15) and stuck transactions in --dev mode.
return true;
}
return false;
Expand Down Expand Up @@ -78,6 +79,8 @@ class GethClient {
cmd.push("--verbosity=" + config.verbosity);
}

cmd.push(`--ipcpath=${IPC.ipcPath('geth.ipc', true)}`);

return cmd;
}

Expand Down
10 changes: 2 additions & 8 deletions src/lib/modules/blockchain_process/miner.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const async = require('async');
const IPC = require('../../core/ipc');
const NetcatClient = require('netcat/client');

//Constants
Expand Down Expand Up @@ -43,14 +44,7 @@ class GethMiner {
}
}

const isWin = process.platform === "win32";

let ipcPath;
if (isWin) {
ipcPath = '\\\\.\\pipe\\geth.ipc';
} else {
ipcPath = this.datadir + '/geth.ipc';
}
const ipcPath = IPC.ipcPath('geth.ipc', true);

this.client = new NetcatClient();
this.client.unixSocket(ipcPath)
Expand Down
60 changes: 45 additions & 15 deletions src/lib/utils/host.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,59 @@
const isDocker = (() => {
let isDockerProcess;
const {execSync} = require("child_process");
const {anchoredValue, DAPP_PATH} = require("../core/env");
const {hostname} = require("os");

const dappPath = anchoredValue(DAPP_PATH);

const hostname = require("os").hostname();
const pattern = new RegExp(
"[0-9]+\:[a-z_-]+\:\/docker\/" + hostname + "[0-9a-z]+", "i",
);
function subdir(pdir_, dir_) {
let pdir = path.resolve(path.normalize(pdir_)) + (path.sep || "/");
const dir = path.resolve(pdir, path.normalize(dir_));
if (pdir === "//") { pdir = "/"; }
if (pdir === dir) { return false; }
return dir.slice(0, pdir.length) === pdir;
}

const isDocker = (() => {
// assumption: an Embark container is always a Linux Docker container, though
// the Docker host may be Linux, macOS, or Windows
if (process.platform !== "linux") { return false; }
try {
return (
new RegExp(`[0-9]+\:[a-z_-]+\:\/docker\/${hostname()}[0-9a-z]+`, "i")
).test(
execSync(
"cat /proc/self/cgroup",
{stdio: ["ignore", "pipe", "ignore"]},
).toString(),
);
} catch (e) {
return false;
}
})();

const isDappMountedFromWindowsDockerHost = (() => {
if (!isDocker) { return false; }
try {
isDockerProcess = require("child_process")
.execSync(
"cat /proc/self/cgroup",
return execSync(
"mount",
{stdio: ["ignore", "pipe", "ignore"]},
)
.toString().match(pattern) !== null;
.toString()
.split("\n")
.filter((line) => /nounix/.test(line))
.some((line) => {
const mount = line.match(/on (\/.*) type/)[1];
return mount === dappPath || subdir(mount, dappPath);
});
} catch (e) {
isDockerProcess = false;
return false;
}

return isDockerProcess;
})();

const defaultHost = isDocker ? "0.0.0.0" : "localhost";

// when we"re runing in Docker, we can expect (generally, in a development
// when we're runing in Docker, we can expect (generally, in a development
// scenario) that the user would like to connect to the service in the
// container via the **host"s** loopback address, so this helper can be used to
// container via the **host's** loopback address, so this helper can be used to
// swap 0.0.0.0 for localhost in code/messages that pertain to client-side
function canonicalHost(host: string): string {
return isDocker && host === "0.0.0.0" ? "localhost" : host;
Expand All @@ -41,5 +70,6 @@ export {
defaultCorsHost,
defaultHost,
dockerHostSwap,
isDappMountedFromWindowsDockerHost,
isDocker,
};
7 changes: 7 additions & 0 deletions src/lib/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,12 @@ function sha3(arg) {
return Web3.utils.sha3(arg);
}

function sha512(arg) {
const crypto = require('crypto');
const hash = crypto.createHash('sha512');
return hash.update(arg, 'utf8').digest('hex');
}

function soliditySha3(arg) {
const Web3 = require('web3');
return Web3.utils.soliditySha3(arg);
Expand Down Expand Up @@ -623,6 +629,7 @@ module.exports = {
getExternalContractUrl,
toChecksumAddress,
sha3,
sha512,
soliditySha3,
normalizeInput,
buildUrl,
Expand Down

0 comments on commit 6f219e2

Please sign in to comment.