Skip to content

Cloudfare module #8366

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ body:
- Cassandra
- ChromaDB
- Clickhouse
- Cloudflare
- CockroachDB
- Consul
- Couchbase
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/enhancement.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ body:
- Cassandra
- ChromaDB
- Clickhouse
- Cloudflare
- CockroachDB
- Consul
- Couchbase
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ body:
- Cassandra
- ChromaDB
- Clickhouse
- Cloudflare
- CockroachDB
- CrateDB
- Consul
Expand Down
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
46 changes: 46 additions & 0 deletions docs/modules/cloudflare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Cloudflare Module

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.

## Usage example

Start a Cloudflared container as follows:

<!--codeinclude-->
[Starting a Cloudflared Container](../../modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:starting
<!--/codeinclude-->

### 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.

<!--codeinclude-->
[Get the public Url](../../modules/cloudflare/src/test/java/org/testcontainers/cloudflare/CloudflaredContainerTest.java) inside_block:get_public_url
<!--/codeinclude-->

## 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
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>cloudflare</artifactId>
<version>{{latest_version}}</version>
<scope>test</scope>
</dependency>
```
7 changes: 7 additions & 0 deletions modules/cloudflare/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
description = "Testcontainers :: Cloudflare"

dependencies {
api project(":testcontainers")

testImplementation 'org.assertj:assertj-core:3.25.2'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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;

public class CloudflaredContainer extends GenericContainer<CloudflaredContainer> {

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", String.format("http://host.testcontainers.internal:%d", 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?");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

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 (
// 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");
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();
}
}
16 changes: 16 additions & 0 deletions modules/cloudflare/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>

<logger name="org.testcontainers" level="INFO"/>
</configuration>