Skip to content

Commit e8cd368

Browse files
authored
Merge branch 'testcontainers:main' into feat/update-compose-container
2 parents 841887a + 7d83019 commit e8cd368

File tree

45 files changed

+566
-80
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+566
-80
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ subprojects {
135135
}
136136

137137
checkstyle {
138-
toolVersion = "10.12.4"
138+
toolVersion = "10.23.0"
139139
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
140140
}
141141
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.testcontainers.containers;
2+
3+
import com.github.dockerjava.api.command.InspectContainerResponse;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.testcontainers.containers.wait.strategy.Wait;
6+
import org.testcontainers.utility.DockerImageName;
7+
8+
import java.io.BufferedReader;
9+
import java.io.IOException;
10+
import java.io.InputStreamReader;
11+
import java.io.OutputStream;
12+
import java.net.HttpURLConnection;
13+
import java.net.URL;
14+
import java.nio.charset.StandardCharsets;
15+
16+
/**
17+
* Testcontainers proxy container for the Docker Model Runner service
18+
* provided by Docker Desktop.
19+
* <p>
20+
* Supported images: {@code alpine/socat}
21+
* <p>
22+
* Exposed ports: 80
23+
*/
24+
@Slf4j
25+
public class DockerModelRunnerContainer extends SocatContainer {
26+
27+
private static final String MODEL_RUNNER_ENDPOINT = "model-runner.docker.internal";
28+
29+
private static final int PORT = 80;
30+
31+
private String model;
32+
33+
public DockerModelRunnerContainer(String image) {
34+
this(DockerImageName.parse(image));
35+
}
36+
37+
public DockerModelRunnerContainer(DockerImageName image) {
38+
super(image);
39+
withTarget(PORT, MODEL_RUNNER_ENDPOINT);
40+
waitingFor(Wait.forHttp("/").forResponsePredicate(res -> res.contains("The service is running")));
41+
}
42+
43+
@Override
44+
protected void containerIsStarted(InspectContainerResponse containerInfo) {
45+
if (this.model != null) {
46+
logger().info("Pulling model: {}. Please be patient.", this.model);
47+
48+
String url = getBaseEndpoint() + "/models/create";
49+
String payload = String.format("{\"from\": \"%s\"}", this.model);
50+
51+
try {
52+
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
53+
connection.setRequestMethod("POST");
54+
connection.setRequestProperty("Content-Type", "application/json");
55+
connection.setDoOutput(true);
56+
57+
try (OutputStream os = connection.getOutputStream()) {
58+
os.write(payload.getBytes());
59+
os.flush();
60+
}
61+
62+
try (
63+
BufferedReader br = new BufferedReader(
64+
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)
65+
)
66+
) {
67+
while (br.readLine() != null) {}
68+
}
69+
connection.disconnect();
70+
} catch (IOException e) {
71+
logger().error("Failed to pull model {}: {}", this.model, e);
72+
}
73+
logger().info("Finished pulling model: {}", this.model);
74+
}
75+
}
76+
77+
public DockerModelRunnerContainer withModel(String model) {
78+
this.model = model;
79+
return this;
80+
}
81+
82+
public String getBaseEndpoint() {
83+
return "http://" + getHost() + ":" + getMappedPort(PORT);
84+
}
85+
86+
public String getOpenAIEndpoint() {
87+
return getBaseEndpoint() + "/engines";
88+
}
89+
}

core/src/main/java/org/testcontainers/images/PullPolicy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static synchronized ImagePullPolicy defaultPolicy() {
4040
.currentThread()
4141
.getContextClassLoader()
4242
.loadClass(imagePullPolicyClassName)
43-
.getConstructor()
43+
.getDeclaredConstructor()
4444
.newInstance();
4545
} catch (Exception e) {
4646
throw new IllegalArgumentException(

core/src/main/java/org/testcontainers/utility/RyukContainer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
class RyukContainer extends GenericContainer<RyukContainer> {
1010

1111
RyukContainer() {
12-
super("testcontainers/ryuk:0.11.0");
12+
super("testcontainers/ryuk:0.12.0");
1313
withExposedPorts(8080);
1414
withCreateContainerCmdModifier(cmd -> {
1515
cmd.withName("testcontainers-ryuk-" + DockerClientFactory.SESSION_ID);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.testcontainers.containers;
2+
3+
import io.restassured.RestAssured;
4+
import io.restassured.response.Response;
5+
import org.junit.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assumptions.assumeThat;
9+
10+
public class DockerModelRunnerContainerTest {
11+
12+
@Test
13+
public void checkStatus() {
14+
assumeThat(System.getenv("CI")).isNull();
15+
16+
try (
17+
// container {
18+
DockerModelRunnerContainer dmr = new DockerModelRunnerContainer("alpine/socat:1.7.4.3-r0")
19+
// }
20+
) {
21+
dmr.start();
22+
23+
Response modelResponse = RestAssured.get(dmr.getBaseEndpoint() + "/status").thenReturn();
24+
assertThat(modelResponse.body().asString()).contains("The service is running");
25+
}
26+
}
27+
28+
@Test
29+
public void pullsModelAndExposesInference() {
30+
assumeThat(System.getenv("CI")).isNull();
31+
32+
String modelName = "ai/smollm2:360M-Q4_K_M";
33+
34+
try (
35+
// pullModel {
36+
DockerModelRunnerContainer dmr = new DockerModelRunnerContainer("alpine/socat:1.7.4.3-r0")
37+
.withModel(modelName)
38+
// }
39+
) {
40+
dmr.start();
41+
42+
Response modelResponse = RestAssured.get(dmr.getBaseEndpoint() + "/models").thenReturn();
43+
assertThat(modelResponse.body().jsonPath().getList("tags.flatten()")).contains(modelName);
44+
45+
Response openAiResponse = RestAssured.get(dmr.getOpenAIEndpoint() + "/v1/models").prettyPeek().thenReturn();
46+
assertThat(openAiResponse.body().jsonPath().getList("data.id")).contains(modelName);
47+
}
48+
}
49+
}

core/src/test/java/org/testcontainers/images/OverrideImagePullPolicyTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,20 @@ public void simpleConfigurationTest() {
5151
container.stop();
5252
}
5353
}
54+
55+
@Test
56+
public void alwaysPullConfigurationTest() {
57+
Mockito
58+
.doReturn(AlwaysPullPolicy.class.getCanonicalName())
59+
.when(TestcontainersConfiguration.getInstance())
60+
.getImagePullPolicy();
61+
62+
try (DockerRegistryContainer registry = new DockerRegistryContainer()) {
63+
registry.start();
64+
GenericContainer<?> container = new GenericContainer<>(registry.createImage()).withExposedPorts(8080);
65+
container.start();
66+
assertThat(container.getImage().imagePullPolicy).isInstanceOf(AlwaysPullPolicy.class);
67+
container.stop();
68+
}
69+
}
5470
}

docs/examples/junit4/generic/src/test/java/generic/support/TestSpecificImageNameSubstitutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class TestSpecificImageNameSubstitutor extends ImageNameSubstitutor {
1313
@Override
1414
public DockerImageName apply(final DockerImageName original) {
1515
if (original.equals(DockerImageName.parse("registry.mycompany.com/mirror/mysql:8.0.36"))) {
16-
return DockerImageName.parse("mysql");
16+
return DockerImageName.parse("mysql:8.0.36");
1717
} else {
1818
return original;
1919
}

docs/features/advanced_options.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ You can also configure Testcontainers to use your custom implementation by using
3838
pull.policy=com.mycompany.testcontainers.ExampleImagePullPolicy
3939
```
4040

41+
You can also use the provided implementation to always pull images
42+
43+
=== "`src/test/resources/testcontainers.properties`"
44+
```text
45+
pull.policy=org.testcontainers.images.AlwaysPullPolicy
46+
```
47+
48+
4149
Please see [the documentation on configuration mechanisms](./configuration.md) for more information.
4250

4351
## Customizing the container

docs/modules/databases/r2dbc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ So that the URL becomes:
3535

3636
#### Using ClickHouse
3737

38-
`r2dbc:tc:clickhouse:///databasename?TC_IMAGE_TAG=21.9.2-alpine`
38+
`r2dbc:tc:clickhouse:///databasename?TC_IMAGE_TAG=21.11.11-alpine`
3939

4040
#### Using MySQL
4141

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Docker Model Runner
2+
3+
This module helps connect to [Docker Model Runner](https://docs.docker.com/desktop/features/model-runner/)
4+
provided by Docker Desktop 4.40.0.
5+
6+
## DockerModelRunner's usage examples
7+
8+
You can start a Docker Model Runner proxy container instance from any Java application by using:
9+
10+
<!--codeinclude-->
11+
[Create a DockerModelRunnerContainer](../../core/src/test/java/org/testcontainers/containers/DockerModelRunnerContainerTest.java) inside_block:container
12+
<!--/codeinclude-->
13+
14+
### Pulling the model
15+
16+
Pulling the model is as simple as:
17+
18+
<!--codeinclude-->
19+
[Pull model](../../core/src/test/java/org/testcontainers/containers/DockerModelRunnerContainerTest.java) inside_block:pullModel
20+
<!--/codeinclude-->
21+
22+
## Adding this module to your project dependencies
23+
24+
*Docker Model Runner support is part of the core Testcontainers library.*
25+
26+
Add the following dependency to your `pom.xml`/`build.gradle` file:
27+
28+
=== "Gradle"
29+
```groovy
30+
testImplementation "org.testcontainers:testcontainers:{{latest_version}}"
31+
```
32+
=== "Maven"
33+
```xml
34+
<dependency>
35+
<groupId>org.testcontainers</groupId>
36+
<artifactId>testcontainers</artifactId>
37+
<version>{{latest_version}}</version>
38+
<scope>test</scope>
39+
</dependency>
40+
```
41+

0 commit comments

Comments
 (0)