diff --git a/fixtures/docker-compose/docker-compose-with-healthcheck.yml b/fixtures/docker-compose/docker-compose-with-healthcheck.yml index 0a5faeb67..acb5e16bb 100644 --- a/fixtures/docker-compose/docker-compose-with-healthcheck.yml +++ b/fixtures/docker-compose/docker-compose-with-healthcheck.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.5" services: container: diff --git a/fixtures/docker-compose/docker-compose-with-name.yml b/fixtures/docker-compose/docker-compose-with-name.yml index 699b28d71..d865d2f0d 100644 --- a/fixtures/docker-compose/docker-compose-with-name.yml +++ b/fixtures/docker-compose/docker-compose-with-name.yml @@ -1,4 +1,5 @@ -version: "3" +version: "3.5" + services: db: container_name: custom_container_name diff --git a/fixtures/docker-compose/docker-compose-with-network.yml b/fixtures/docker-compose/docker-compose-with-network.yml index 475ebd0cd..225f2b80d 100644 --- a/fixtures/docker-compose/docker-compose-with-network.yml +++ b/fixtures/docker-compose/docker-compose-with-network.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.5" services: container: diff --git a/fixtures/docker-compose/docker-compose-with-volume.yml b/fixtures/docker-compose/docker-compose-with-volume.yml index 1526ffe98..af7a10633 100644 --- a/fixtures/docker-compose/docker-compose-with-volume.yml +++ b/fixtures/docker-compose/docker-compose-with-volume.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.5" services: container: diff --git a/fixtures/docker-compose/docker-compose.yml b/fixtures/docker-compose/docker-compose.yml index a197575dc..aaf84d4ab 100644 --- a/fixtures/docker-compose/docker-compose.yml +++ b/fixtures/docker-compose/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.5" services: container: diff --git a/package-lock.json b/package-lock.json index 7f08a1f14..673e2124f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6644,8 +6644,7 @@ "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "slice-ansi": { "version": "3.0.0", diff --git a/package.json b/package.json index 7928d1a46..a59d476e6 100755 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "get-port": "^5.1.1", "glob": "^7.1.6", "node-duration": "^1.0.4", + "slash": "^3.0.0", "stream-to-array": "^2.3.0", "tar-fs": "^2.1.0" }, diff --git a/src/container.ts b/src/container.ts index 154bed8a0..67499273f 100644 --- a/src/container.ts +++ b/src/container.ts @@ -18,6 +18,9 @@ export type InspectResult = { type ExecInspectResult = { exitCode: ExitCode; + running: boolean; + entrypoint: string; + arguments: string[]; }; interface Exec { @@ -151,6 +154,12 @@ class DockerodeExec implements Exec { public async inspect(): Promise { const inspectResult = await this.exec.inspect(); - return { exitCode: inspectResult.ExitCode === null ? -1 : inspectResult.ExitCode }; + + return { + exitCode: inspectResult.ExitCode === null ? -1 : inspectResult.ExitCode, + running: inspectResult.Running, + entrypoint: inspectResult.ProcessConfig.entrypoint, + arguments: inspectResult.ProcessConfig.arguments, + }; } } diff --git a/src/docker-client.ts b/src/docker-client.ts index acfd267de..717ded96c 100644 --- a/src/docker-client.ts +++ b/src/docker-client.ts @@ -2,6 +2,7 @@ import Dockerode, { Network, PortMap as DockerodePortBindings } from "dockerode" import { Duration, TemporalUnit } from "node-duration"; import streamToArray from "stream-to-array"; import tar from "tar-fs"; +import slash from "slash"; import { BoundPorts } from "./bound-ports"; import { Container, DockerodeContainer, Id } from "./container"; import { Host } from "./docker-client-factory"; @@ -10,6 +11,7 @@ import { log } from "./logger"; import { PortString } from "./port"; import { RepoTag } from "./repo-tag"; import { PullStreamParser } from "./pull-stream-parser"; +import { Readable } from "stream"; export type Command = string; export type ContainerName = string; @@ -172,7 +174,6 @@ export class DockerodeClient implements DockerClient { } public start(container: Container): Promise { - log.info(`Starting container with ID: ${container.getId()}`); return container.start(); } @@ -183,11 +184,22 @@ export class DockerodeClient implements DockerClient { attachStderr: true, }); - const stream = await exec.start(); - const output = Buffer.concat(await streamToArray(stream)).toString(); - const { exitCode } = await exec.inspect(); + const stream = (await exec.start()).setEncoding("utf-8") as Readable; - return { output, exitCode }; + return await new Promise((resolve) => { + let output = ""; + stream.on("data", (chunk) => (output += chunk)); + + const interval = setInterval(async () => { + const { running, exitCode } = await exec.inspect(); + + if (!running) { + clearInterval(interval); + stream.destroy(); + resolve({ output, exitCode }); + } + }, 100); + }); } public async buildImage( @@ -198,7 +210,7 @@ export class DockerodeClient implements DockerClient { ): Promise { log.info(`Building image '${repoTag.toString()}' with context '${context}'`); const dockerIgnoreFiles = await findDockerIgnoreFiles(context); - const tarStream = tar.pack(context, { ignore: (name) => dockerIgnoreFiles.has(name) }); + const tarStream = tar.pack(context, { ignore: (name) => dockerIgnoreFiles.has(slash(name)) }); const stream = await this.dockerode.buildImage(tarStream, { dockerfile: dockerfileName, buildargs: buildArgs, diff --git a/src/docker-compose-environment.test.ts b/src/docker-compose-environment.test.ts index bc0243b7c..bef97249b 100644 --- a/src/docker-compose-environment.test.ts +++ b/src/docker-compose-environment.test.ts @@ -12,7 +12,7 @@ describe("DockerComposeEnvironment", () => { it("should throw error when compose file is malformed", async () => { await expect(new DockerComposeEnvironment(fixtures, "docker-compose-malformed.yml").up()).rejects.toThrowError( - `Version in "./docker-compose-malformed.yml" is invalid - it should be a string.` + `Version in ".${path.sep}docker-compose-malformed.yml" is invalid - it should be a string.` ); }); diff --git a/src/docker-ignore.ts b/src/docker-ignore.ts index ea07cf480..428e7a2cd 100644 --- a/src/docker-ignore.ts +++ b/src/docker-ignore.ts @@ -1,4 +1,5 @@ import { existsSync, promises as fs } from "fs"; +import os from "os"; import glob from "glob"; import path from "path"; @@ -12,7 +13,7 @@ export const findDockerIgnoreFiles = async (context: string): Promise path.resolve(context, dockerIgnorePattern)); const dockerIgnoreMatches: string[][] = await Promise.all( diff --git a/src/generic-container.ts b/src/generic-container.ts index 0572b7f9c..7948199bf 100644 --- a/src/generic-container.ts +++ b/src/generic-container.ts @@ -129,6 +129,7 @@ export class GenericContainer implements TestContainer { autoRemove: this.daemonMode, }); + log.info(`Starting container ${this.repoTag} with ID: ${container.getId()}`); await dockerClient.start(container); if (!this.daemonMode) { @@ -241,6 +242,13 @@ export class GenericContainer implements TestContainer { log.info("Container is ready"); } catch (err) { log.error("Container failed to be ready"); + + if (this.daemonMode) { + (await container.logs()) + .on("data", (data) => containerLog.trace(`${container.getId()}: ${data}`)) + .on("err", (data) => containerLog.error(`${container.getId()}: ${data}`)); + } + try { await container.stop({ timeout: new Duration(0, TemporalUnit.MILLISECONDS) }); await container.remove({ removeVolumes: true }); diff --git a/src/reaper.ts b/src/reaper.ts index c27fb1b94..2d60de034 100644 --- a/src/reaper.ts +++ b/src/reaper.ts @@ -32,7 +32,7 @@ export class Reaper { .withName(`ryuk-${sessionId}`) .withExposedPorts(8080) .withWaitStrategy(Wait.forLogMessage("Started!")) - .withBindMount(dockerClient.getSocketPath() || "/var/run/docker.sock", "/var/run/docker.sock") + .withBindMount("/var/run/docker.sock", "/var/run/docker.sock") .withDaemonMode() .withPrivilegedMode() .start();