Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(docker): properly detect docker socket path #695

Merged
merged 3 commits into from
Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ Supported Network Node Versions:
Polar requires that you have Docker installed to create the local networks

- On Mac & Windows, you can just install [Docker Desktop](https://www.docker.com/products/docker-desktop)
- On Linux, you need to install [Docker Server](https://docs.docker.com/install/#server) and [Docker Compose](https://docs.docker.com/compose/install/) separately
- On Linux, you need to install [Docker Server](https://docs.docker.com/engine/install/#server) and [Docker Compose](https://docs.docker.com/compose/install/#scenario-two-install-the-compose-plugin) separately.

You will be prompted to install Docker if Polar cannot detect it automatically

⚠️ **Important Docker Notes**

- On Mac & Windows, you must uncheck "Use Docker Compose V2" in Docker Desktop settings. We're waiting on Compose v2 support in the `docker-compose` NPM package (See [PDMLab/docker-compose#228](https://github.com/PDMLab/docker-compose/pull/228))
- On Linux, Docker Desktop is currently not supported due to a significant change in how it handles file sharing between host and container (See [#636](https://github.com/jamaljsr/polar/issues/636#issuecomment-1450201391))

## Download

Download Polar v1.4.0 for your OS
Expand Down Expand Up @@ -104,7 +109,3 @@ If you would like to learn how to package Polar from source code or want to fix
## Recognition

Huge thanks to maintainers of [Lightning Joule](https://github.com/joule-labs/joule-extension), [Zap Wallet](https://github.com/LN-Zap/zap-desktop), [LND](https://github.com/lightningnetwork/lnd), [Bitcoin Core](https://github.com/bitcoin/bitcoin), along with many others for the amazing apps & libraries that gave this project inspiration, ideas & sometimes even a little code 😊.

## Contact

The best place to reach me is on Twitter @jamaljsr. I also lurk in the LND Slack server, so you can msg me there as well.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"i18next": "22.4.13",
"i18next-browser-languagedetector": "7.0.1",
"i18next-scanner": "4.2.0",
"jest-canvas-mock": "2.5.0",
"jest-environment-jsdom-sixteen": "1.0.3",
"js-yaml": "4.1.0",
"less": "4.1.3",
Expand Down
4 changes: 2 additions & 2 deletions src/components/dockerLogs/DockerLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import { useParams } from 'react-router';
import { debug, error, info } from 'electron-log';
import styled from '@emotion/styled';
import detectPort from 'detect-port';
import Docker from 'dockerode';
import { usePrefixedTranslation } from 'hooks';
import { PassThrough } from 'stream';
import WebSocket from 'ws';
import { getDocker } from 'lib/docker/dockerService';
import { useStoreActions } from 'store';

const docker = new Docker();
let wsServer: WebSocket.Server;

/**
Expand All @@ -27,6 +26,7 @@ const startWebSocketServer = async (name: string): Promise<number> => {
const port = await detectPort(0);
wsServer = new WebSocket.Server({ port });
wsServer.on('connection', async socket => {
const docker = await getDocker();
debug(`getting docker container with name '${name}'`);
const containers = await docker.listContainers();
debug(`all containers: ${JSON.stringify(containers)}`);
Expand Down
9 changes: 4 additions & 5 deletions src/components/terminal/DockerTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@
/* istanbul ignore file */
import React, { useEffect, useRef } from 'react';
import { useParams } from 'react-router';
import { remote, clipboard } from 'electron';
import { clipboard, remote } from 'electron';
import { debug, info } from 'electron-log';
import styled from '@emotion/styled';
import 'xterm/css/xterm.css';
import Docker from 'dockerode';
import { message } from 'antd';
import { usePrefixedTranslation } from 'hooks';
import { ITerminalOptions, Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { getDocker } from 'lib/docker/dockerService';
import { useStoreActions } from 'store';
import { eclairCredentials } from 'utils/constants';
import { nord } from './themes';
import { message } from 'antd';

const docker = new Docker();

// exec command and options configuration
const execCommand = {
Expand Down Expand Up @@ -84,6 +82,7 @@ const connectStreams = async (term: Terminal, name: string, type: string, l: any
const config = nodeConfig[type];
if (!config) throw new Error(l('nodeTypeErr', { type }));

const docker = await getDocker();
debug(`getting docker container with name '${name}'`);
const containers = await docker.listContainers();
debug(`all containers: ${JSON.stringify(containers)}`);
Expand Down
58 changes: 58 additions & 0 deletions src/lib/docker/dockerService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { APP_VERSION, defaultRepoState, DOCKER_REPO } from 'utils/constants';
import * as files from 'utils/files';
import { createNetwork } from 'utils/network';
import { getNetwork, mockProperty, testManagedImages } from 'utils/tests';
import { getDocker } from './dockerService';

jest.mock('dockerode');
jest.mock('os');
Expand Down Expand Up @@ -618,4 +619,61 @@ describe('DockerService', () => {
Object.defineProperty(electronMock.remote, 'process', { get: () => ({ env: {} }) });
});
});

describe('getDocker', () => {
it('should detect DOCKER_HOST', async () => {
Object.defineProperty(electronMock.remote, 'process', {
get: () => ({ env: { DOCKER_HOST: '/var/run/docker.sock' } }),
});
await getDocker(false);
expect(mockDockerode.prototype.constructor).toBeCalledWith();
Object.defineProperty(electronMock.remote, 'process', { get: () => ({ env: {} }) });
});

it('should check paths on Mac', async () => {
mockOS.platform.mockReturnValue('darwin');
Object.defineProperty(electronMock.remote, 'process', {
get: () => ({ env: { HOME: '/home/user' } }),
});
filesMock.exists.mockImplementation((path: string) => {
return Promise.resolve(path === '/var/run/docker.sock');
});
await getDocker(false);
expect(filesMock.exists).toBeCalledWith('/home/user/.docker/run/docker.sock');
expect(filesMock.exists).toBeCalledWith('/var/run/docker.sock');
expect(mockDockerode.prototype.constructor).toBeCalledWith({
socketPath: '/var/run/docker.sock',
});
Object.defineProperty(electronMock.remote, 'process', { get: () => ({ env: {} }) });
});

it('should check paths on Linux', async () => {
mockOS.platform.mockReturnValue('linux');
Object.defineProperty(electronMock.remote, 'process', {
get: () => ({ env: { HOME: '/home/user' } }),
});
filesMock.exists.mockImplementation((path: string) => {
return Promise.resolve(path === '/var/run/docker.sock');
});
await getDocker(false);
expect(filesMock.exists).toBeCalledWith('/home/user/.docker/run/docker.sock');
expect(filesMock.exists).toBeCalledWith('/var/run/docker.sock');
expect(mockDockerode.prototype.constructor).toBeCalledWith({
socketPath: '/var/run/docker.sock',
});
Object.defineProperty(electronMock.remote, 'process', { get: () => ({ env: {} }) });
});

it('should not check paths on windows', async () => {
mockOS.platform.mockReturnValue('win32');
await getDocker(false);
expect(mockDockerode.prototype.constructor).toBeCalledWith();
});

it('should reuse a cached instance for multiple calls', async () => {
const docker1 = await getDocker();
const docker2 = await getDocker();
expect(docker1).toBe(docker2);
});
});
});
45 changes: 42 additions & 3 deletions src/lib/docker/dockerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,48 @@ import { legacyDataPath, networksPath, nodePath } from 'utils/config';
import { APP_VERSION, dockerConfigs } from 'utils/constants';
import { exists, read, write } from 'utils/files';
import { migrateNetworksFile } from 'utils/migrations';
import { isLinux } from 'utils/system';
import { isLinux, isMac } from 'utils/system';
import ComposeFile from './composeFile';

let dockerInst: Dockerode | undefined;
/**
* Creates a new Dockerode instance by detecting the docker socket
*/
export const getDocker = async (useCached = true): Promise<Dockerode> => {
// re-use the stored instance if available
if (useCached && dockerInst) return dockerInst;

if (remote.process.env.DOCKER_HOST) {
debug('DOCKER_HOST detected. Copying DOCKER_* env vars:');
// copy all env vars that start with DOCKER_ to the current process env
Object.keys(remote.process.env)
.filter(key => key.startsWith('DOCKER_'))
.forEach(key => {
debug(`- ${key} = '${remote.process.env[key]}'`);
process.env[key] = remote.process.env[key];
});
// let Dockerode handle DOCKER_HOST parsing
return (dockerInst = new Dockerode());
}
if (isLinux() || isMac()) {
// try to detect the socket path in the default locations on linux/mac
const socketPaths = [
`${remote.process.env.HOME}/.docker/run/docker.sock`,
'/var/run/docker.sock',
];
for (const socketPath of socketPaths) {
if (await exists(socketPath)) {
debug('docker socket detected:', socketPath);
return (dockerInst = new Dockerode({ socketPath }));
}
}
}

debug('no DOCKER_HOST or docker socket detected');
// fallback to letting Dockerode detect the socket path
return (dockerInst = new Dockerode());
};

class DockerService implements DockerLibrary {
/**
* Gets the versions of docker and docker-compose installed
Expand All @@ -33,7 +72,7 @@ class DockerService implements DockerLibrary {

try {
debug('fetching docker version');
const dockerVersion = await new Dockerode().version();
const dockerVersion = await (await getDocker()).version();
debug(`Result: ${JSON.stringify(dockerVersion)}`);
versions.docker = dockerVersion.Version;
} catch (error: any) {
Expand All @@ -60,7 +99,7 @@ class DockerService implements DockerLibrary {
async getImages(): Promise<string[]> {
try {
debug('fetching docker images');
const allImages = await new Dockerode().listImages();
const allImages = await (await getDocker()).listImages();
debug(`All Images: ${JSON.stringify(allImages)}`);
const imageNames = ([] as string[])
.concat(...allImages.map(i => i.RepoTags || []))
Expand Down
26 changes: 26 additions & 0 deletions src/lib/lightning/clightning/clightningService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ describe('CLightningService', () => {
expect(actual).toEqual(expected);
});

it('should get list of channels with a funding txid', async () => {
const infoResponse: Partial<CLN.GetInfoResponse> = {
id: 'abc',
binding: [],
};
const chanResponse: Partial<CLN.GetChannelsResponse>[] = [
{
id: 'xyz',
channelId: '',
fundingTxid: 'bec9d46e09f7787f16e4c190b3469dab2faa41899427402d0cb558c66e2757fa',
state: CLN.ChannelState.CHANNELD_NORMAL,
msatoshiTotal: 0,
msatoshiToUs: 0,
fundingAllocationMsat: {
abc: 100,
xyz: 0,
},
},
];
clightningApiMock.httpGet.mockResolvedValue(chanResponse);
clightningApiMock.httpGet.mockResolvedValueOnce(infoResponse);
const expected = [expect.objectContaining({ pubkey: 'xyz' })];
const actual = await clightningService.getChannels(node);
expect(actual).toEqual(expected);
});

it('should not throw error when connecting to peers', async () => {
const listPeersResponse: Partial<CLN.Peer>[] = [
{ id: 'asdf', connected: true, netaddr: ['1.1.1.1:9735'] },
Expand Down
2 changes: 2 additions & 0 deletions src/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import './i18n';
import '@testing-library/jest-dom/extend-expect';
// this is needed for antd v4 components
import 'regenerator-runtime/runtime';
// this is needed for antd v4 components
import 'jest-canvas-mock';

// Prevent displaying some un-fixable warnings in tests
const originalConsoleWarning = console.warn;
Expand Down
22 changes: 21 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6510,7 +6510,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=

color-name@^1.0.0, color-name@~1.1.4:
color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
Expand Down Expand Up @@ -7443,6 +7443,11 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==

cssfontparser@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3"
integrity sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==

cssnano-preset-default@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76"
Expand Down Expand Up @@ -12262,6 +12267,14 @@ jake@^10.8.5:
filelist "^1.0.1"
minimatch "^3.0.4"

[email protected]:
version "2.5.0"
resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.5.0.tgz#3e60f87f77ddfa273cf8e7e4ea5f86fa827c7117"
integrity sha512-s2bmY2f22WPMzhB2YA93kiyf7CAfWAnV/sFfY9s48IVOrGmwui1eSFluDPesq1M+7tSC1hJAit6mzO0ZNXvVBA==
dependencies:
cssfontparser "^1.2.1"
moo-color "^1.0.2"

jest-changed-files@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
Expand Down Expand Up @@ -14292,6 +14305,13 @@ monocle-ts@^2.3.5:
resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-2.3.11.tgz#562cd960bc24eaa84d9b6118d46a5441f3c0aac0"
integrity sha512-YJQdpDeKU0NNAecDjFgDDnDoivmf2nXsJZTRQdKh5jsPKG2lT/15dFnSuUcQoKB/VZN1z7jQ0B0bffbRFUtLmg==

moo-color@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.3.tgz#d56435f8359c8284d83ac58016df7427febece74"
integrity sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==
dependencies:
color-name "^1.1.4"

move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
Expand Down