diff --git a/engine/src/main/java/de/gesellix/docker/engine/OkDockerClient.java b/engine/src/main/java/de/gesellix/docker/engine/OkDockerClient.java index e536c722..ec6afb88 100644 --- a/engine/src/main/java/de/gesellix/docker/engine/OkDockerClient.java +++ b/engine/src/main/java/de/gesellix/docker/engine/OkDockerClient.java @@ -347,6 +347,7 @@ public EngineResponse handleResponse(Response httpResponse, EngineRequest config mimeType = ""; } switch (mimeType) { + case "application/vnd.docker.multiplexed-stream": case "application/vnd.docker.raw-stream": InputStream rawStream = new RawInputStream(body.byteStream()); if (config.getStdout() != null) { diff --git a/engine/src/test/groovy/de/gesellix/docker/engine/OkDockerClientSpec.groovy b/engine/src/test/groovy/de/gesellix/docker/engine/OkDockerClientSpec.groovy index 19961fd9..fe8a8c3d 100644 --- a/engine/src/test/groovy/de/gesellix/docker/engine/OkDockerClientSpec.groovy +++ b/engine/src/test/groovy/de/gesellix/docker/engine/OkDockerClientSpec.groovy @@ -67,8 +67,7 @@ class OkDockerClientSpec extends Specification { cleanup: if (oldDockerHost) { System.setProperty("docker.host", oldDockerHost) - } - else { + } else { System.clearProperty("docker.host") } } @@ -108,8 +107,7 @@ class OkDockerClientSpec extends Specification { cleanup: if (oldDockerCertPath) { System.setProperty("docker.cert.path", oldDockerCertPath) - } - else { + } else { System.clearProperty("docker.cert.path") } } @@ -128,8 +126,7 @@ class OkDockerClientSpec extends Specification { cleanup: if (oldDockerCertPath) { System.setProperty("docker.cert.path", oldDockerCertPath) - } - else { + } else { System.clearProperty("docker.cert.path") } } @@ -141,13 +138,14 @@ class OkDockerClientSpec extends Specification { expect: client.getMimeType(contentType) == expectedMimeType where: - contentType | expectedMimeType - null | null - "application/json" | "application/json" - "text/plain" | "text/plain" - "text/plain; charset=utf-8" | "text/plain" - "text/html" | "text/html" - "application/vnd.docker.raw-stream" | "application/vnd.docker.raw-stream" + contentType | expectedMimeType + null | null + "application/json" | "application/json" + "text/plain" | "text/plain" + "text/plain; charset=utf-8" | "text/plain" + "text/html" | "text/html" + "application/vnd.docker.raw-stream" | "application/vnd.docker.raw-stream" + "application/vnd.docker.multiplexed-stream" | "application/vnd.docker.multiplexed-stream" } def "getCharset"() { @@ -157,12 +155,13 @@ class OkDockerClientSpec extends Specification { expect: client.getCharset(contentType) == expectedCharset where: - contentType | expectedCharset - "application/json" | "utf-8" - "text/plain" | "utf-8" - "text/plain; charset=ISO-8859-1" | "ISO-8859-1" - "text/html" | "utf-8" - "application/vnd.docker.raw-stream" | "utf-8" + contentType | expectedCharset + "application/json" | "utf-8" + "text/plain" | "utf-8" + "text/plain; charset=ISO-8859-1" | "ISO-8859-1" + "text/html" | "utf-8" + "application/vnd.docker.raw-stream" | "utf-8" + "application/vnd.docker.multiplexed-stream" | "utf-8" } @Unroll @@ -322,8 +321,7 @@ class OkDockerClientSpec extends Specification { cleanup: if (oldDockerCertPath) { System.setProperty("docker.cert.path", oldDockerCertPath) - } - else { + } else { System.clearProperty("docker.cert.path") } } @@ -479,7 +477,7 @@ class OkDockerClientSpec extends Specification { when: def response = client.request(new EngineRequest(OPTIONS, "/a-resource") - .tap { apiVersion = "v1.23" }) + .tap { apiVersion = "v1.23" }) then: response.status.success and: @@ -522,7 +520,7 @@ class OkDockerClientSpec extends Specification { when: def response = client.request(new EngineRequest(OPTIONS, "/a-resource") - .tap { query = [baz: ["la/la"], answer: ["42"]] }) + .tap { query = [baz: ["la/la"], answer: ["42"]] }) then: response.status.success and: @@ -536,9 +534,9 @@ class OkDockerClientSpec extends Specification { given: def mockWebServer = new MockWebServer() mockWebServer.enqueue(new MockResponse() - .setResponseCode(101) - .addHeader("Connection", "Upgrade") - .addHeader("Upgrade", "tcp")) + .setResponseCode(101) + .addHeader("Connection", "Upgrade") + .addHeader("Upgrade", "tcp")) mockWebServer.start() def errors = [] @@ -575,14 +573,14 @@ class OkDockerClientSpec extends Specification { } client.request(new EngineRequest(OPTIONS, "/a-resource") - .tap { - attach = new AttachConfig(onResponse: onResponse) - headers = [ - "header-a" : "header-a-value", - "Upgrade" : "tcp", - "Connection": "Upgrade" - ] - }) + .tap { + attach = new AttachConfig(onResponse: onResponse) + headers = [ + "header-a" : "header-a-value", + "Upgrade" : "tcp", + "Connection": "Upgrade" + ] + }) then: latch.await(10, TimeUnit.SECONDS) and: @@ -675,8 +673,7 @@ class OkDockerClientSpec extends Specification { mockWebServer.shutdown() if (oldDockerCertPath) { System.setProperty("docker.cert.path", oldDockerCertPath) - } - else { + } else { System.clearProperty("docker.cert.path") } } @@ -785,7 +782,7 @@ class OkDockerClientSpec extends Specification { when: def response = client.request(new EngineRequest(OPTIONS, "/a-resource") - .tap { it.stdout = stdout }) + .tap { it.stdout = stdout }) then: stdout.toByteArray() == "holy ship".bytes @@ -859,7 +856,7 @@ class OkDockerClientSpec extends Specification { when: def response = client.request(new EngineRequest(OPTIONS, "/a-resource") - .tap { it.stdout = stdout }) + .tap { it.stdout = stdout }) then: stdout.toByteArray() == "holy ship".bytes @@ -946,7 +943,74 @@ class OkDockerClientSpec extends Specification { when: def response = client.request(new EngineRequest(OPTIONS, "/a-resource") - .tap { it.stdout = stdout }) + .tap { it.stdout = stdout }) + + then: + stdout.toByteArray() == actualText.bytes + and: + responseBody.available() == 0 + and: + response.stream == null + and: + response.content == null + } + + def "request with docker multiplexed-stream response"() { + given: + def actualText = "holy ship" + def headerAndPayload = new RawHeaderAndPayload(STDOUT, actualText.bytes) + def responseBody = new ByteArrayInputStream(headerAndPayload.bytes as byte[]) + def mediaType = MediaType.parse("application/vnd.docker.multiplexed-stream") + def client = new OkDockerClient() { + + @Override + OkHttpClient newClient(OkHttpClient.Builder clientBuilder) { + clientBuilder + .addInterceptor(new ConstantResponseInterceptor(ResponseBody.create(responseBody.bytes, mediaType))) + .build() + } + } + client.dockerClientConfig.apply(new DockerEnv(dockerHost: "http://127.0.0.1:2375")) + + when: + def response = client.request(new EngineRequest(OPTIONS, "/a-resource")) + + then: + response.stream instanceof RawInputStream + and: + IOUtils.toString(response.stream as RawInputStream) == actualText + and: + response.content == null + } + + def "request with docker multiplexed-stream response on stdout"() { + given: + def actualText = "holy ship" + def headerAndPayload = new RawHeaderAndPayload(STDOUT, actualText.bytes) + def responseBody = new ByteArrayInputStream(headerAndPayload.bytes as byte[]) + def mediaType = MediaType.parse("application/vnd.docker.multiplexed-stream") + def client = new OkDockerClient() { + + @Override + OkHttpClient newClient(OkHttpClient.Builder clientBuilder) { + clientBuilder + .addInterceptor(new ConstantResponseInterceptor(ResponseBody.create(responseBody.bytes, mediaType))) + .build() + } + } + client.dockerClientConfig.apply(new DockerEnv(dockerHost: "https://127.0.0.1:2376")) + client.socketFactories["https"] = new SslSocketConfigFactory() { + + @Override + DockerSslSocket createDockerSslSocket(String certPath) { + return null + } + } + def stdout = new ByteArrayOutputStream() + + when: + def response = client.request(new EngineRequest(OPTIONS, "/a-resource") + .tap { it.stdout = stdout }) then: stdout.toByteArray() == actualText.bytes @@ -1045,8 +1109,8 @@ class OkDockerClientSpec extends Specification { def hosts = isWindows ? new File("${System.getenv("SystemRoot")}\\System32\\drivers\\etc\\hosts") : new File("/etc/hosts") if (hosts.isFile()) { whitelist.addAll(hosts.readLines() - .grep({ it.startsWith("127.0.0.1 ") }) - .collect({ it.substring("127.0.0.1 ".length()).toLowerCase() })) + .grep({ it.startsWith("127.0.0.1 ") }) + .collect({ it.substring("127.0.0.1 ".length()).toLowerCase() })) } def isAllowed = host.toLowerCase() in whitelist || host.endsWith(".internal") if (!isAllowed) { diff --git a/integrationtest/src/test/groovy/de/gesellix/docker/engine/TestConstants.groovy b/integrationtest/src/test/groovy/de/gesellix/docker/engine/TestConstants.groovy index 15d8b0f5..1b498931 100644 --- a/integrationtest/src/test/groovy/de/gesellix/docker/engine/TestConstants.groovy +++ b/integrationtest/src/test/groovy/de/gesellix/docker/engine/TestConstants.groovy @@ -12,18 +12,18 @@ class TestConstants { TestConstants() { imageRepo = "gesellix/echo-server" - imageTag = "2023-03-12T23-40-00" + imageTag = "2023-04-05T20-08-00" imageName = "$imageRepo:$imageTag" if (System.getenv("GITHUB_ACTOR")) { // TODO consider checking the Docker api version instead of "GITHUB_ACTOR" if (LocalDocker.isNativeWindows()) { versionDetails = [ - ApiVersion : { it == "1.41" }, + ApiVersion : { it == "1.42" }, Arch : { it in ["amd64", "arm64"] }, - BuildTime : { it =~ "2022-\\d{2}-\\d{2}T\\w+" }, + BuildTime : { it =~ "2023-\\d{2}-\\d{2}T\\w+" }, GitCommit : { it =~ "\\w{6,}" }, - GoVersion : { it == "go1.18.7" }, + GoVersion : { it == "go1.19.8" }, KernelVersion: { it =~ "\\d.\\d{1,2}.\\d{1,2}\\w*" }, MinAPIVersion: { it == "1.24" }, Os : { it == "windows" },