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

feat: support docker-in-docker #166

Merged
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
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
"dev:link": "lerna run dev:dockest:link --stream && lerna run dev:examples:link --stream",
"dev:link:list": "ls -la ~/.config/yarn/link/@examples",
"dev:unlink": "lerna run dev:examples:unlink --stream && lerna run dev:dockest:unlink --stream",
"yarn:locks": "yarn --force && lerna exec \"yarn --force\" --ignore @examples/aws-codebuild--src",
"yarn:upgrade": "yarn upgrade --latest && lerna exec \"yarn upgrade --latest\" --ignore @examples/aws-codebuild--src",
"yarn:locks": "yarn --force && lerna exec \"yarn --force\" --ignore @examples/aws-codebuild--src --ignore @examples/docker-in-docker--src --ignore @examples/docker-in-docker--app",
"yarn:upgrade": "yarn upgrade --latest && lerna exec \"yarn upgrade --latest\" --ignore @examples/aws-codebuild--src --ignore @examples/docker-in-docker--src --ignore @examples/docker-in-docker--app",
"prep:root:install:deps": "yarn",
"prep:root": "yarn prep:root:install:deps",
"prep:dockest:install:deps": "lerna exec \"yarn\" --stream --scope dockest",
"prep:dockest:build": "yarn lerna run build --stream --scope dockest",
"prep:dockest": "yarn prep:dockest:install:deps && yarn prep:dockest:build",
"prep:examples:install:deps": "lerna exec \"yarn\" --stream --scope @examples/* --ignore @examples/aws-codebuild--src",
"prep:examples:install:deps": "lerna exec \"yarn\" --stream --scope @examples/* --ignore @examples/aws-codebuild--src --ignore @examples/docker-in-docker--src --ignore @examples/docker-in-docker--app",
"prep:examples:build": "yarn lerna run build --stream --scope @examples/*",
"prep:examples": "yarn prep:examples:install:deps && yarn prep:examples:build",
"prep": "yarn prep:root && yarn prep:dockest && yarn prep:examples",
Expand Down
4 changes: 3 additions & 1 deletion packages/dockest/src/@types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ export interface DockestOpts {
jestOpts: JestOpts
}

export type TestRunModeType = 'docker-local-socket' | 'docker-injected-host-socket' | 'host'

interface InternalConfig {
hostname: string
isInsideDockerContainer: boolean
runMode: TestRunModeType
perfStart: number
}

Expand Down
8 changes: 4 additions & 4 deletions packages/dockest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Dockest {
dumpErrors,
exitHandler,
hostname,
isInsideDockerContainer,
runMode,
jestLib,
jestOpts,
mutables,
Expand All @@ -55,15 +55,15 @@ export class Dockest {
dockestServices,
dumpErrors,
exitHandler,
isInsideDockerContainer,
runMode,
mutables,
perfStart,
})

await waitForServices({ composeOpts, mutables, hostname, isInsideDockerContainer, runInBand, skipCheckConnection })
await waitForServices({ composeOpts, mutables, hostname, runMode, runInBand, skipCheckConnection })
await debugMode({ debug, mutables })
const { success } = await runJest({ jestLib, jestOpts, mutables })
await teardown({ hostname, isInsideDockerContainer, mutables, perfStart })
await teardown({ hostname, runMode, mutables, perfStart })

success ? process.exit(0) : process.exit(1)
}
Expand Down
6 changes: 3 additions & 3 deletions packages/dockest/src/run/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ export const bootstrap = async ({
dockestServices,
dumpErrors,
exitHandler,
isInsideDockerContainer,
runMode,
mutables,
perfStart,
}: {
composeFile: DockestConfig['composeFile']
dockestServices: DockestService[]
dumpErrors: DockestConfig['dumpErrors']
exitHandler: DockestConfig['exitHandler']
isInsideDockerContainer: DockestConfig['isInsideDockerContainer']
runMode: DockestConfig['runMode']
mutables: DockestConfig['mutables']
perfStart: DockestConfig['perfStart']
}) => {
Expand All @@ -34,7 +34,7 @@ export const bootstrap = async ({
mutables.runners = transformDockestServicesToRunners({
dockerComposeFile,
dockestServices,
isInsideDockerContainer,
runMode,
dockerEventEmitter,
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { EventEmitter } from 'events'
import { transformDockestServicesToRunners } from './transformDockestServicesToRunners'
import { DockestService, DockerComposeFile } from '../../@types'
import { getOpts } from '../../utils/getOpts'

const { isInsideDockerContainer } = getOpts()

const serviceName = 'service1'
const dockerComposeFile: DockerComposeFile = {
Expand All @@ -20,7 +17,7 @@ describe('transformDockestServicesToRunners', () => {
const runners = transformDockestServicesToRunners({
dockerComposeFile,
dockestServices,
isInsideDockerContainer,
runMode: 'host',
dockerEventEmitter: new EventEmitter() as any,
})

Expand Down Expand Up @@ -72,7 +69,7 @@ describe('transformDockestServicesToRunners', () => {
transformDockestServicesToRunners({
dockerComposeFile,
dockestServices,
isInsideDockerContainer,
runMode: 'host',
dockerEventEmitter: new EventEmitter() as any,
}),
).toThrow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { Logger } from '../../Logger'
export const transformDockestServicesToRunners = ({
dockerComposeFile,
dockestServices,
isInsideDockerContainer,
runMode,
dockerEventEmitter,
}: {
dockerComposeFile: DockerComposeFile
dockestServices: DockestService[]
isInsideDockerContainer: DockestConfig['isInsideDockerContainer']
runMode: DockestConfig['runMode']
dockerEventEmitter: DockerEventEmitter
}) => {
const createRunner = (dockestService: DockestService) => {
Expand All @@ -36,9 +36,9 @@ export const transformDockestServicesToRunners = ({
serviceName,
}

if (isInsideDockerContainer) {
if (runMode === 'docker-injected-host-socket') {
runner.host = serviceName
runner.isBridgeNetworkMode = isInsideDockerContainer
runner.isBridgeNetworkMode = true
}

return runner
Expand Down
6 changes: 3 additions & 3 deletions packages/dockest/src/run/teardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import { teardownSingle } from '../utils/teardownSingle'

export const teardown = async ({
hostname,
isInsideDockerContainer,
runMode,
mutables: { runners, dockerEventEmitter },
perfStart,
}: {
hostname: DockestConfig['hostname']
isInsideDockerContainer: DockestConfig['isInsideDockerContainer']
runMode: DockestConfig['runMode']
mutables: DockestConfig['mutables']
perfStart: DockestConfig['perfStart']
}) => {
for (const runner of Object.values(runners)) {
await teardownSingle({ runner })
}

if (isInsideDockerContainer) {
if (runMode === 'docker-injected-host-socket') {
await leaveBridgeNetwork({ containerId: hostname })
await removeBridgeNetwork()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export const fixRunnerHostAccessOnLinux = async ({ containerId, logger }: Runner
| awk '{print \\$3\\" ${DEFAULT_HOST_NAME}\\"}' \
>> /etc/hosts"`

await execaWrapper(command).catch(() => {
try {
execaWrapper(command)
} catch (err) {
logger.debug(
'Fixing the host container access failed. This could be related to the container having already been stopped',
)
})
}
}
6 changes: 3 additions & 3 deletions packages/dockest/src/run/waitForServices/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jest.mock('../../utils/sleepWithLog')
jest.mock('../../utils/network/bridgeNetworkExists')
jest.mock('../../utils/network/createBridgeNetwork')

const { composeOpts, hostname, isInsideDockerContainer, runInBand } = getOpts()
const { composeOpts, hostname, runInBand } = getOpts()

describe('waitForServices', () => {
beforeEach(jest.resetAllMocks)
Expand All @@ -40,7 +40,7 @@ describe('waitForServices', () => {
await waitForServices({
composeOpts,
hostname,
isInsideDockerContainer,
runMode: 'host',
mutables: { runners, jestRanWithResult: false, dockerEventEmitter: new EventEmitter() as any },
runInBand,
skipCheckConnection: false,
Expand Down Expand Up @@ -89,7 +89,7 @@ describe('waitForServices', () => {
await waitForServices({
composeOpts,
hostname,
isInsideDockerContainer,
runMode: 'host',
mutables: { runners, jestRanWithResult: false, dockerEventEmitter: new EventEmitter() as any },
runInBand,
skipCheckConnection: false,
Expand Down
6 changes: 3 additions & 3 deletions packages/dockest/src/run/waitForServices/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ const LOG_PREFIX = '[Setup]'
export const waitForServices = async ({
composeOpts,
hostname,
isInsideDockerContainer,
runMode,
mutables: { runners },
runInBand,
skipCheckConnection,
}: {
composeOpts: DockestConfig['composeOpts']
hostname: DockestConfig['hostname']
isInsideDockerContainer: DockestConfig['isInsideDockerContainer']
runMode: DockestConfig['runMode']
mutables: DockestConfig['mutables']
runInBand: DockestConfig['runInBand']
skipCheckConnection: DockestConfig['skipCheckConnection']
Expand Down Expand Up @@ -63,7 +63,7 @@ export const waitForServices = async ({
}
}

if (isInsideDockerContainer) {
if (runMode === 'docker-injected-host-socket') {
if (!(await bridgeNetworkExists())) {
await createBridgeNetwork()
}
Expand Down
19 changes: 13 additions & 6 deletions packages/dockest/src/test-helper/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import isDocker from 'is-docker' // eslint-disable-line import/default
import { DockerComposeFile } from '../@types'
import { DockerComposeFile, TestRunModeType } from '../@types'
import { DOCKEST_ATTACH_TO_PROCESS, DOCKEST_HOST_ADDRESS, DEFAULT_HOST_NAME } from '../constants'
import { DockestError } from '../Errors'
import { selectPortMapping } from '../utils/selectPortMapping'
import { getRunMode as _getRunMode } from '../utils/getRunMode'

let runMode: TestRunModeType | null = null

const getRunMode = (): TestRunModeType => {
if (!runMode) {
runMode = _getRunMode()
}
return runMode
}

const isInsideDockerContainer = isDocker()
const dockestConfig = process.env[DOCKEST_ATTACH_TO_PROCESS]

if (!dockestConfig) {
Expand All @@ -14,10 +22,9 @@ if (!dockestConfig) {
const config: DockerComposeFile = JSON.parse(dockestConfig)

export const getHostAddress = () => {
if (!isInsideDockerContainer) {
if (getRunMode() !== 'docker-injected-host-socket') {
return DEFAULT_HOST_NAME
}

return DOCKEST_HOST_ADDRESS
}

Expand All @@ -32,7 +39,7 @@ export const resolveServiceAddress = (serviceName: string, targetPort: number |
throw new DockestError(`Service "${serviceName}" has no target port ${portBinding}`)
}

if (isInsideDockerContainer) {
if (getRunMode() === 'docker-injected-host-socket') {
return { host: serviceName, port: portBinding.target }
}

Expand Down
2 changes: 1 addition & 1 deletion packages/dockest/src/utils/execaWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Opts {
execaOpts?: SyncOptions<string>
}

export const execaWrapper = async (
export const execaWrapper = (
n1ru4l marked this conversation as resolved.
Show resolved Hide resolved
command: string,
{ runner, logPrefix = '[Shell]', logStdout = false, execaOpts = {} }: Opts = {},
) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/dockest/src/utils/getOpts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ describe('getOpts', () => {
"dumpErrors": false,
"exitHandler": [Function],
"hostname": "host.docker.internal",
"isInsideDockerContainer": false,
"jestLib": Object {
"SearchSource": [Function],
"TestScheduler": [Function],
Expand All @@ -43,6 +42,7 @@ describe('getOpts', () => {
"mutables": Any<Object>,
"perfStart": Any<Number>,
"runInBand": true,
"runMode": "host",
"skipCheckConnection": false,
}
`,
Expand Down
4 changes: 2 additions & 2 deletions packages/dockest/src/utils/getOpts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EventEmitter } from 'events'
import isDocker from 'is-docker' // eslint-disable-line import/default
import { getRunMode } from './getRunMode'
import { DockestOpts, DockestConfig } from '../@types'
import { LOG_LEVEL, DEFAULT_HOST_NAME } from '../constants'

Expand Down Expand Up @@ -47,7 +47,7 @@ export const getOpts = (opts: Partial<DockestOpts> = {}): DockestConfig => {
dockerEventEmitter: new EventEmitter() as any,
},
hostname: process.env.HOSTNAME || DEFAULT_HOST_NAME,
isInsideDockerContainer: isDocker(),
runMode: getRunMode(),
jestLib,
jestOpts: {
projects,
Expand Down
39 changes: 39 additions & 0 deletions packages/dockest/src/utils/getRunMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import isDocker from 'is-docker' // eslint-disable-line import/default
import { execaWrapper } from './execaWrapper'
import { Logger } from '../Logger'
import { TestRunModeType } from '../@types'
import { DockestError } from '../Errors'

export const getRunMode = (): TestRunModeType => {
let mode: TestRunModeType | null = null

if (isDocker()) {
const { stdout: result } = execaWrapper(`
sh -c '
v=$(mount | grep "/run/docker.sock"); \\
if [ -n "$v" ]; \\
then \\
echo "injected-socket"; \\
elif [ -S /var/run/docker.sock ]; \\
then \\
echo "local-socket"; \\
else \\
echo "no-socket"; \\
fi \\
'
`)
if (result === 'local-socket') {
mode = 'docker-local-socket'
} else if (result === 'injected-socket') {
mode = 'docker-injected-host-socket'
} else {
throw new DockestError(`Resolved invalid mode: '${result}'.`)
}
} else {
mode = 'host'
}

Logger.debug(`Run dockest in '${mode}' mode.`)

return mode
}
4 changes: 1 addition & 3 deletions packages/dockest/src/utils/network/bridgeNetworkExists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ export const bridgeNetworkExists = async () => {
--filter name=${BRIDGE_NETWORK_NAME} \
--quiet`

const networkExists = await execaWrapper(command)
.then(({ stdout }) => stdout.trim())
.then(trimmedStdout => !!trimmedStdout)
const networkExists = !!execaWrapper(command).stdout.trim()

if (networkExists) {
Logger.info(`Using existing network "${BRIDGE_NETWORK_NAME}"`)
Expand Down
3 changes: 3 additions & 0 deletions packages/examples/docker-in-docker/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/app/node_modules
src/node_modules
node_modules
1 change: 1 addition & 0 deletions packages/examples/docker-in-docker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/dockest.tgz
Loading