denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
93.5k stars 5.19k forks source link

npm:docker-modem doesn't work (and @testcontainers/* as a result) #20255

Open gflarity opened 11 months ago

gflarity commented 11 months ago

The blog post said we should report, so here we go:

I just trying to use testcontainers (@testcontainers/postgresql) with Deno when I got this strange error:

error: Error: (HTTP code 400) unexpected - starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24
    at file:///Users/geoff/Library/Caches/deno/npm/registry.npmjs.org/docker-modem/3.0.8/lib/modem.js:343:17
    at getCause (file:///Users/geoff/Library/Caches/deno/npm/registry.npmjs.org/docker-modem/3.0.8/lib/modem.js:373:7)
    at Modem.buildPayload (file:///Users/geoff/Library/Caches/deno/npm/registry.npmjs.org/docker-modem/3.0.8/lib/modem.js:342:5)
    at IncomingMessageForClient.<anonymous> (file:///Users/geoff/Library/Caches/deno/npm/registry.npmjs.org/docker-modem/3.0.8/lib/modem.js:310:16)

So it seems there might be some http client compatibility issues, as the docker api is just HTTP last time I checked...

Here's the repro code which was inspired by this Testcontainer Node quickstart repo (which runs fine using node):

import { PostgreSqlContainer } from "npm:@testcontainers/postgresql";
import postgres from "https://deno.land/x/postgresjs/mod.js";

Deno.test("hello postgres", async () => {
  const initScript = `
            create table guides
            (
                id    bigserial     not null,
                title varchar(1023) not null,
                url   varchar(1023) not null,
                primary key (id)
            );

            insert into guides(title, url)
            values ('Getting started with Testcontainers',
                    'https://testcontainers.com/getting-started/'),
                   ('Getting started with Testcontainers for Java',
                    'https://testcontainers.com/guides/getting-started-with-testcontainers-for-java/'),
                   ('Getting started with Testcontainers for .NET',
                    'https://testcontainers.com/guides/getting-started-with-testcontainers-for-dotnet/'),
                   ('Getting started with Testcontainers for Node.js',
                    'https://testcontainers.com/guides/getting-started-with-testcontainers-for-nodejs/'),
                   ('Getting started with Testcontainers for Go',
                    'https://testcontainers.com/guides/getting-started-with-testcontainers-for-go/'),
                   ('Testcontainers container lifecycle management using JUnit 5',
                    'https://testcontainers.com/guides/testcontainers-container-lifecycle/')
            ;
        `;

  const container = await new PostgreSqlContainer("postgres:14-alpine",)
    .withCopyContentToContainer([
      { content: initScript, target: "/docker-entrypoint-initdb.d/init.sql" },
    ])
    .start();

  const sql = postgres(container.getConnectionUri());
  const res = await sql`select title, url from guides`;
  console.log(res);
});
brad-jones commented 4 months ago

I just ran into something similar. When I debugged I saw requests being made to http://localhost instead of /var/run/docker.sock. Looks very similar to https://github.com/apocas/dockerode/issues/747

dionjwa commented 2 months ago

I am also hitting this. Is there any workaround?

dionjwa commented 1 month ago

I adapted the workaround proposed by https://github.com/apocas/dockerode/issues/747#issuecomment-1962746418 to use pure deno and avoid the socat dependency, it works in my own tools:

/******************************************************
 * Begin workarounds for this showstopper issue:
 * https://github.com/apocas/dockerode/issues/747
*/
async function startProxy() {
  // Listen on TCP port 3000
  const tcpListener = Deno.listen({ port: 3000 });
  console.log("Listening on TCP port 3000");

  for await (const tcpConn of tcpListener) {
    handleConnection(tcpConn);
  }
}
async function handleConnection(tcpConn: Deno.Conn) {
  try {
    // Connect to the Unix socket at /var/run/docker.sock
    const unixConn = await Deno.connect({ transport: "unix", path: "/var/run/docker.sock" });

    // Bidirectional forwarding
    const tcpToUnix = copy(tcpConn, unixConn);
    const unixToTcp = copy(unixConn, tcpConn);

    // Wait for both copy operations to complete
    await Promise.all([tcpToUnix, unixToTcp]);

  } catch (error) {
    console.error("Error handling connection:", error);
  } finally {
    tcpConn.close();
  }
}
// Utility function to copy data from one stream to another
async function copy(src: Deno.Reader, dst: Deno.Writer) {
  const buffer = new Uint8Array(1024);
  while (true) {
    const bytesRead = await src.read(buffer);
    if (bytesRead === null) break;
    let offset = 0;
    while (offset < bytesRead) {
      const bytesWritten = await dst.write(buffer.subarray(offset, bytesRead));
      offset += bytesWritten;
    }
  }
}
// Start the proxy
startProxy();
// and this now needs to be changed because of the above:
// const docker = new Docker({socketPath: "/var/run/docker.sock"});
const docker = new Docker({protocol: 'http', host: 'localhost', port: 3000});
/******************************************************
 * End workarounds for this showstopper issue:
 * https://github.com/apocas/dockerode/issues/747
*/