From 4af797aef5bb43042c8276ff3dc07bfa5137bd48 Mon Sep 17 00:00:00 2001 From: shelajev Date: Tue, 20 Feb 2024 17:10:20 +0200 Subject: [PATCH 1/7] Add CloudflaredContainer module for public tunnels --- modules/cloudflare/build.gradle | 8 +++ .../cloudflare/CloudflaredContainer.java | 42 ++++++++++++ .../cloudflare/CloudflaredContainerTest.java | 64 +++++++++++++++++++ .../src/test/resources/logback-test.xml | 16 +++++ 4 files changed, 130 insertions(+) create mode 100644 modules/cloudflare/build.gradle create mode 100644 modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java create mode 100644 modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java create mode 100644 modules/cloudflare/src/test/resources/logback-test.xml diff --git a/modules/cloudflare/build.gradle b/modules/cloudflare/build.gradle new file mode 100644 index 00000000000..62cf10e708c --- /dev/null +++ b/modules/cloudflare/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: Cloudflared" + +dependencies { + api project(":testcontainers") + + testImplementation 'org.assertj:assertj-core:3.25.2' + testImplementation project(':nginx') +} diff --git a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java new file mode 100644 index 00000000000..16f43793ca3 --- /dev/null +++ b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java @@ -0,0 +1,42 @@ +package org.testcontainers.cloudflare; + +import org.testcontainers.Testcontainers; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +import java.util.Scanner; + +public class CloudflaredContainer extends GenericContainer { + private String publicUrl; + + public CloudflaredContainer(DockerImageName dockerImageName, int port) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DockerImageName.parse("cloudflare/cloudflared")); + withAccessToHost(true); + Testcontainers.exposeHostPorts(port); + withCommand("tunnel", "--url", "http://host.testcontainers.internal:%d".formatted(port)); + waitingFor(Wait.forLogMessage(".*Registered tunnel connection.*", 1)); + } + + public String getPublicUrl() { + if (null != publicUrl) { + return publicUrl; + } + String logs = getLogs(); + String[] split = logs.split(String.format("%n")); + boolean found = false; + for (int i = 0; i < split.length; i++) { + String currentLine = split[i]; + if (currentLine.contains("Your quick Tunnel has been created")) { + found = true; + continue; + } + if (found) { + return publicUrl = currentLine.substring(currentLine.indexOf("http"), currentLine.indexOf(".com") + 4); + } + } + throw new IllegalStateException("Didn't find public url in logs. Has container started?"); + } + +} diff --git a/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java b/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java new file mode 100644 index 00000000000..58ac91a8a7f --- /dev/null +++ b/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java @@ -0,0 +1,64 @@ +package org.testcontainers.cloudflare; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; + + + +import static org.assertj.core.api.Assertions.assertThat; + +public class CloudflaredContainerTest { + + @Test + public void shouldStartAndTunnelToHelloWorld() throws IOException { + try (GenericContainer helloworld = new GenericContainer<>( + DockerImageName.parse("testcontainers/helloworld:1.1.0") + ) + .withNetworkAliases("helloworld") + .withExposedPorts(8080, 8081) + .waitingFor(new HttpWaitStrategy()) ) { + + helloworld.start(); + + try (CloudflaredContainer cloudflare = new CloudflaredContainer(DockerImageName.parse("cloudflare/cloudflared:latest"), helloworld.getFirstMappedPort());) { + cloudflare.start(); + String url = cloudflare.getPublicUrl(); + + assertThat(url) + .as("Public url contains 'cloudflare'") + .contains("cloudflare"); + String body = readUrl(url); + + assertThat(body.trim()) + .as("the index page contains the title 'Hello world'") + .contains("Hello world"); + } + + } + + } + + private String readUrl(String url) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(new URL(url).openStream())); + + StringBuilder sb = new StringBuilder(); + String inputLine = null; + while ((inputLine = in.readLine()) != null) { + sb.append(inputLine); + } + in.close(); + + return sb.toString(); + } +} diff --git a/modules/cloudflare/src/test/resources/logback-test.xml b/modules/cloudflare/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/cloudflare/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + From 143988f06e80015082647cc9c7336063907c1698 Mon Sep 17 00:00:00 2001 From: shelajev Date: Thu, 22 Feb 2024 12:52:53 +0200 Subject: [PATCH 2/7] Add the necessary docs for including the cloudflare module --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/enhancement.yaml | 1 + .github/ISSUE_TEMPLATE/feature.yaml | 1 + .github/dependabot.yml | 5 ++ .github/labeler.yml | 4 ++ docs/modules/cloudflare.md | 46 +++++++++++++++++++ modules/cloudflare/build.gradle | 2 +- .../cloudflare/CloudflaredContainerTest.java | 25 ++++++---- 8 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 docs/modules/cloudflare.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 8d363cb978f..e41fe249d34 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -19,6 +19,7 @@ body: - Cassandra - ChromaDB - Clickhouse + - Cloudflare - CockroachDB - Consul - Couchbase diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 6ee160c982b..1a7253b0123 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -19,6 +19,7 @@ body: - Cassandra - ChromaDB - Clickhouse + - Cloudflare - CockroachDB - Consul - Couchbase diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 3f8920b4059..5eda11949e1 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -19,6 +19,7 @@ body: - Cassandra - ChromaDB - Clickhouse + - Cloudflare - CockroachDB - CrateDB - Consul diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bf9b145f2b3..3e77546ac15 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -61,6 +61,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/cloudflare" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/cockroachdb" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index 0fd1ec90d29..adb422ce83c 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -35,6 +35,10 @@ - changed-files: - any-glob-to-any-file: - modules/clickhouse/**/* +"modules/cloudflare": + - changed-files: + - any-glob-to-any-file: + - modules/cloudflare/**/* "modules/cockroachdb": - changed-files: - any-glob-to-any-file: diff --git a/docs/modules/cloudflare.md b/docs/modules/cloudflare.md new file mode 100644 index 00000000000..ae90069d014 --- /dev/null +++ b/docs/modules/cloudflare.md @@ -0,0 +1,46 @@ +# Cloudflare Module + +Testcontainers module for Cloudflare Tunnels [](https://rancher.com/products/k3s/) for exposing your app to the internet. + +This module is intended to be used for testing components that need to be exposed to public internet - for example to receive hooks from public cloud. +Or to show your local state of the application to friends. + +## Usage example + +Start a Cloudflared container as follows: + + +[Starting a Cloudflared Container](../../modules/k3s/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:starting + + +### Getting the public Url + +`Cloudflared` contaienr exposes a port on your host, via a [Quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/). +To get the public url on which this port is available to the internet, call the `getPublicUrl` method. + + +[Get the public Url](../../modules/k3s/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:get_public_url + + +## Known limitations + +!!! warning + * From the Cloudflare docs: "Quick Tunnels are subject to a hard limit on the number of concurrent requests that can be proxied at any point in time. Currently, this limit is 200 in-flight requests. If a Quick Tunnel hits this limit, the HTTP response will return a 429 status code." + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" + ```groovy + testImplementation "org.testcontainers:cloudflare:{{latest_version}}" + ``` +=== "Maven" + ```xml + + org.testcontainers + cloudflare + {{latest_version}} + test + + ``` diff --git a/modules/cloudflare/build.gradle b/modules/cloudflare/build.gradle index 62cf10e708c..50de51ed4d1 100644 --- a/modules/cloudflare/build.gradle +++ b/modules/cloudflare/build.gradle @@ -1,4 +1,4 @@ -description = "Testcontainers :: Cloudflared" +description = "Testcontainers :: Cloudflare" dependencies { api project(":testcontainers") diff --git a/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java b/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java index 58ac91a8a7f..261b0ee8b43 100644 --- a/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java +++ b/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java @@ -15,7 +15,6 @@ import java.time.Duration; - import static org.assertj.core.api.Assertions.assertThat; public class CloudflaredContainerTest { @@ -23,26 +22,32 @@ public class CloudflaredContainerTest { @Test public void shouldStartAndTunnelToHelloWorld() throws IOException { try (GenericContainer helloworld = new GenericContainer<>( - DockerImageName.parse("testcontainers/helloworld:1.1.0") + DockerImageName.parse("testcontainers/helloworld:1.1.0") ) - .withNetworkAliases("helloworld") - .withExposedPorts(8080, 8081) - .waitingFor(new HttpWaitStrategy()) ) { + .withNetworkAliases("helloworld") + .withExposedPorts(8080, 8081) + .waitingFor(new HttpWaitStrategy())) { helloworld.start(); - try (CloudflaredContainer cloudflare = new CloudflaredContainer(DockerImageName.parse("cloudflare/cloudflared:latest"), helloworld.getFirstMappedPort());) { + try ( + // starting { + CloudflaredContainer cloudflare = new CloudflaredContainer(DockerImageName.parse("cloudflare/cloudflared:latest"), helloworld.getFirstMappedPort()); + // + ) { cloudflare.start(); + // get_public_url { String url = cloudflare.getPublicUrl(); + // } assertThat(url) - .as("Public url contains 'cloudflare'") - .contains("cloudflare"); + .as("Public url contains 'cloudflare'") + .contains("cloudflare"); String body = readUrl(url); assertThat(body.trim()) - .as("the index page contains the title 'Hello world'") - .contains("Hello world"); + .as("the index page contains the title 'Hello world'") + .contains("Hello world"); } } From 208b1d101bfd602b4e44cc0835b21d1caa729a41 Mon Sep 17 00:00:00 2001 From: shelajev Date: Thu, 22 Feb 2024 12:57:19 +0200 Subject: [PATCH 3/7] fix the links in the cloudflare module docs --- docs/modules/cloudflare.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/cloudflare.md b/docs/modules/cloudflare.md index ae90069d014..6d0077ed5c6 100644 --- a/docs/modules/cloudflare.md +++ b/docs/modules/cloudflare.md @@ -1,6 +1,6 @@ # Cloudflare Module -Testcontainers module for Cloudflare Tunnels [](https://rancher.com/products/k3s/) for exposing your app to the internet. +Testcontainers module for Cloudflare Quick Tunnels(https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) for exposing your app to the internet. This module is intended to be used for testing components that need to be exposed to public internet - for example to receive hooks from public cloud. Or to show your local state of the application to friends. @@ -10,7 +10,7 @@ Or to show your local state of the application to friends. Start a Cloudflared container as follows: -[Starting a Cloudflared Container](../../modules/k3s/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:starting +[Starting a Cloudflared Container](../../modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:starting ### Getting the public Url @@ -19,7 +19,7 @@ Start a Cloudflared container as follows: To get the public url on which this port is available to the internet, call the `getPublicUrl` method. -[Get the public Url](../../modules/k3s/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:get_public_url +[Get the public Url](../../modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:get_public_url ## Known limitations From 48a38259750a2cde794f44af5ba6ad0cc48bb2b3 Mon Sep 17 00:00:00 2001 From: shelajev Date: Thu, 22 Feb 2024 13:03:45 +0200 Subject: [PATCH 4/7] spotless fixes --- .../cloudflare/CloudflaredContainer.java | 4 +-- .../cloudflare/CloudflaredContainerTest.java | 35 ++++++++----------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java index 16f43793ca3..d11ef698bb1 100644 --- a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java +++ b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java @@ -5,9 +5,8 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; -import java.util.Scanner; - public class CloudflaredContainer extends GenericContainer { + private String publicUrl; public CloudflaredContainer(DockerImageName dockerImageName, int port) { @@ -38,5 +37,4 @@ public String getPublicUrl() { } throw new IllegalStateException("Didn't find public url in logs. Has container started?"); } - } diff --git a/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java b/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java index 261b0ee8b43..b0fbeecfa06 100644 --- a/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java +++ b/modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java @@ -1,19 +1,14 @@ package org.testcontainers.cloudflare; +import org.junit.Test; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; import org.testcontainers.utility.DockerImageName; -import org.junit.Test; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; -import java.time.Duration; - import static org.assertj.core.api.Assertions.assertThat; @@ -21,37 +16,35 @@ public class CloudflaredContainerTest { @Test public void shouldStartAndTunnelToHelloWorld() throws IOException { - try (GenericContainer helloworld = new GenericContainer<>( + try ( + GenericContainer helloworld = new GenericContainer<>( DockerImageName.parse("testcontainers/helloworld:1.1.0") - ) + ) .withNetworkAliases("helloworld") .withExposedPorts(8080, 8081) - .waitingFor(new HttpWaitStrategy())) { - + .waitingFor(new HttpWaitStrategy()) + ) { helloworld.start(); try ( - // starting { - CloudflaredContainer cloudflare = new CloudflaredContainer(DockerImageName.parse("cloudflare/cloudflared:latest"), helloworld.getFirstMappedPort()); - // + // starting { + CloudflaredContainer cloudflare = new CloudflaredContainer( + DockerImageName.parse("cloudflare/cloudflared:latest"), + helloworld.getFirstMappedPort() + ); + // ) { cloudflare.start(); // get_public_url { String url = cloudflare.getPublicUrl(); // } - assertThat(url) - .as("Public url contains 'cloudflare'") - .contains("cloudflare"); + assertThat(url).as("Public url contains 'cloudflare'").contains("cloudflare"); String body = readUrl(url); - assertThat(body.trim()) - .as("the index page contains the title 'Hello world'") - .contains("Hello world"); + assertThat(body.trim()).as("the index page contains the title 'Hello world'").contains("Hello world"); } - } - } private String readUrl(String url) throws IOException { From 99d949427c2cbe4a6aa1c0884a31229dbcb21b62 Mon Sep 17 00:00:00 2001 From: shelajev Date: Thu, 22 Feb 2024 13:06:50 +0200 Subject: [PATCH 5/7] don't depend on nginx, we use hello world for tests --- modules/cloudflare/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/cloudflare/build.gradle b/modules/cloudflare/build.gradle index 50de51ed4d1..4a1d08c7c8b 100644 --- a/modules/cloudflare/build.gradle +++ b/modules/cloudflare/build.gradle @@ -4,5 +4,4 @@ dependencies { api project(":testcontainers") testImplementation 'org.assertj:assertj-core:3.25.2' - testImplementation project(':nginx') } From 7e389cb3bf8c5eb7e8f5208a69d5f635312556d6 Mon Sep 17 00:00:00 2001 From: shelajev Date: Thu, 22 Feb 2024 14:47:39 +0200 Subject: [PATCH 6/7] hello java8 my old friend, I've come to talk with you again --- .../org/testcontainers/cloudflare/CloudflaredContainer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java index d11ef698bb1..f4bf199be20 100644 --- a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java +++ b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java @@ -5,6 +5,8 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; +import static java.lang.String.format; + public class CloudflaredContainer extends GenericContainer { private String publicUrl; @@ -14,7 +16,7 @@ public CloudflaredContainer(DockerImageName dockerImageName, int port) { dockerImageName.assertCompatibleWith(DockerImageName.parse("cloudflare/cloudflared")); withAccessToHost(true); Testcontainers.exposeHostPorts(port); - withCommand("tunnel", "--url", "http://host.testcontainers.internal:%d".formatted(port)); + withCommand("tunnel", "--url", format("http://host.testcontainers.internal:%d", port)); waitingFor(Wait.forLogMessage(".*Registered tunnel connection.*", 1)); } From 8c5cf121553a388a75c6fb5b586d84bbd7b50eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Thu, 22 Feb 2024 10:55:58 -0500 Subject: [PATCH 7/7] Apply suggestions from code review --- .../org/testcontainers/cloudflare/CloudflaredContainer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java index f4bf199be20..edc30b58295 100644 --- a/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java +++ b/modules/cloudflare/src/main/java/org/testcontainers/cloudflare/CloudflaredContainer.java @@ -5,8 +5,6 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; -import static java.lang.String.format; - public class CloudflaredContainer extends GenericContainer { private String publicUrl; @@ -16,7 +14,7 @@ public CloudflaredContainer(DockerImageName dockerImageName, int port) { dockerImageName.assertCompatibleWith(DockerImageName.parse("cloudflare/cloudflared")); withAccessToHost(true); Testcontainers.exposeHostPorts(port); - withCommand("tunnel", "--url", format("http://host.testcontainers.internal:%d", port)); + withCommand("tunnel", "--url", String.format("http://host.testcontainers.internal:%d", port)); waitingFor(Wait.forLogMessage(".*Registered tunnel connection.*", 1)); }