Skip to content

Commit ee387fa

Browse files
committed
Merge branch 'main' into oceanbase-ce
2 parents d35fb61 + ba9e3cc commit ee387fa

File tree

14 files changed

+286
-6
lines changed

14 files changed

+286
-6
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ body:
3131
- Kafka
3232
- LocalStack
3333
- MariaDB
34+
- MinIO
3435
- MockServer
3536
- MongoDB
3637
- MSSQLServer

.github/ISSUE_TEMPLATE/enhancement.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ body:
3131
- Kafka
3232
- LocalStack
3333
- MariaDB
34+
- MinIO
3435
- MockServer
3536
- MongoDB
3637
- MSSQLServer

.github/ISSUE_TEMPLATE/feature.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ body:
3131
- Kafka
3232
- LocalStack
3333
- MariaDB
34+
- MinIO
3435
- MockServer
3536
- MongoDB
3637
- MSSQLServer

.github/dependabot.yml

+5
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ updates:
154154
ignore:
155155
- dependency-name: "org.mariadb:r2dbc-mariadb"
156156
update-types: [ "version-update:semver-minor" ]
157+
- package-ecosystem: "gradle"
158+
directory: "/modules/minio"
159+
schedule:
160+
interval: "monthly"
161+
open-pull-requests-limit: 10
157162
- package-ecosystem: "gradle"
158163
directory: "/modules/mockserver"
159164
schedule:

.github/labeler.yml

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
- modules/localstack/**/*
4848
"modules/mariadb":
4949
- modules/mariadb/**/*
50+
"modules/minio":
51+
- modules/minio/**/*
5052
"modules/mockserver":
5153
- modules/mockserver/**/*
5254
"modules/mongodb":

core/src/main/java/org/testcontainers/containers/GenericContainer.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,6 @@ protected void doStart() {
338338
try {
339339
configure();
340340

341-
Instant startedAt = Instant.now();
342-
343341
logger().debug("Starting container: {}", getDockerImageName());
344342

345343
AtomicInteger attempt = new AtomicInteger(0);
@@ -353,7 +351,7 @@ protected void doStart() {
353351
attempt.incrementAndGet(),
354352
startupAttempts
355353
);
356-
tryStart(startedAt);
354+
tryStart();
357355
return true;
358356
}
359357
);
@@ -380,11 +378,12 @@ protected boolean canBeReused() {
380378
return true;
381379
}
382380

383-
private void tryStart(Instant startedAt) {
381+
private void tryStart() {
384382
try {
385383
String dockerImageName = getDockerImageName();
386384
logger().debug("Starting container: {}", dockerImageName);
387385

386+
Instant startedAt = Instant.now();
388387
logger().info("Creating container for image: {}", dockerImageName);
389388
CreateContainerCmd createCommand = dockerClient.createContainerCmd(dockerImageName);
390389
applyConfiguration(createCommand);
@@ -1497,7 +1496,7 @@ public SELF withStartupAttempts(int attempts) {
14971496
}
14981497

14991498
/**
1500-
* Allow low level modifications of {@link CreateContainerCmd} after it was pre-configured in {@link #tryStart(Instant)}.
1499+
* Allow low level modifications of {@link CreateContainerCmd} after it was pre-configured in {@link #tryStart()}.
15011500
* Invocation happens eagerly on a moment when container is created.
15021501
* Warning: this does expose the underlying docker-java API so might change outside of our control.
15031502
*

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ protected final String resolve() {
8080
Exception lastFailure = null;
8181
final Instant lastRetryAllowed = Instant.now().plus(PULL_RETRY_TIME_LIMIT);
8282

83+
Instant startedAt = Instant.now();
8384
while (Instant.now().isBefore(lastRetryAllowed)) {
8485
try {
8586
PullImageCmd pullImageCmd = dockerClient
@@ -95,10 +96,12 @@ protected final String resolve() {
9596
.exec(new TimeLimitedLoggedPullImageResultCallback(logger))
9697
.awaitCompletion();
9798
}
99+
String dockerImageName = imageName.asCanonicalNameString();
100+
logger.info("Image {} pull took {}", dockerImageName, Duration.between(startedAt, Instant.now()));
98101

99102
LocalImagesCache.INSTANCE.refreshCache(imageName);
100103

101-
return imageName.asCanonicalNameString();
104+
return dockerImageName;
102105
} catch (InterruptedException | InternalServerErrorException e) {
103106
// these classes of exception often relate to timeout/connection errors so should be retried
104107
lastFailure = e;
@@ -109,6 +112,7 @@ protected final String resolve() {
109112
);
110113
}
111114
}
115+
112116
logger.error(
113117
"Failed to pull image: {}. Please check output of `docker pull {}`",
114118
imageName,

docs/modules/minio.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# MinIO Containers
2+
3+
Testcontainers can be used to automatically instantiate and manage [MinIO](https://min.io) containers.
4+
5+
## Usage example
6+
7+
Create a `MinIOContainer` to use it in your tests:
8+
<!--codeinclude-->
9+
[Starting a MinIO container](../../modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java) inside_block:minioContainer
10+
<!--/codeinclude-->
11+
12+
The [MinIO Java client](https://min.io/docs/minio/linux/developers/java/API.html) can be configured with the container as such:
13+
<!--codeinclude-->
14+
[Configuring a MinIO client](../../modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java) inside_block:configuringClient
15+
<!--/codeinclude-->
16+
17+
If needed the username and password can be overridden as such:
18+
<!--codeinclude-->
19+
[Overriding a MinIO container](../../modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java) inside_block:minioOverrides
20+
<!--/codeinclude-->
21+
22+
## Adding this module to your project dependencies
23+
24+
Add the following dependency to your `pom.xml`/`build.gradle` file:
25+
26+
=== "Gradle"
27+
```groovy
28+
testImplementation "org.testcontainers:minio:{{latest_version}}"
29+
```
30+
31+
=== "Maven"
32+
```xml
33+
<dependency>
34+
<groupId>org.testcontainers</groupId>
35+
<artifactId>minio</artifactId>
36+
<version>{{latest_version}}</version>
37+
<scope>test</scope>
38+
</dependency>
39+
```

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ nav:
8282
- modules/k3s.md
8383
- modules/kafka.md
8484
- modules/localstack.md
85+
- modules/minio.md
8586
- modules/mockserver.md
8687
- modules/nginx.md
8788
- modules/pulsar.md

modules/minio/build.gradle

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
description = "Testcontainers :: MinIO"
2+
3+
dependencies {
4+
api project(':testcontainers')
5+
6+
testImplementation("io.minio:minio:8.5.5")
7+
testImplementation 'org.assertj:assertj-core:3.24.2'
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package org.testcontainers.containers;
2+
3+
import org.testcontainers.containers.wait.strategy.Wait;
4+
import org.testcontainers.utility.DockerImageName;
5+
6+
import java.time.Duration;
7+
import java.time.temporal.ChronoUnit;
8+
9+
/**
10+
* Testcontainers implementation for MinIO.
11+
* <p>
12+
* Supported image: {@code minio/minio}
13+
* <p>
14+
* Exposed ports:
15+
* <ul>
16+
* <li>S3: 9000</li>
17+
* <li>Console: 9001</li>
18+
* </ul>
19+
*/
20+
public class MinIOContainer extends GenericContainer<MinIOContainer> {
21+
22+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("minio/minio");
23+
24+
private static final int MINIO_S3_PORT = 9000;
25+
26+
private static final int MINIO_UI_PORT = 9001;
27+
28+
private static final String DEFAULT_USER = "minioadmin";
29+
30+
private static final String DEFAULT_PASSWORD = "minioadmin";
31+
32+
private String userName;
33+
34+
private String password;
35+
36+
/**
37+
* Constructs a MinIO container from the dockerImageName
38+
* @param dockerImageName the full image name to use
39+
*/
40+
public MinIOContainer(final String dockerImageName) {
41+
this(DockerImageName.parse(dockerImageName));
42+
}
43+
44+
/**
45+
* Constructs a MinIO container from the dockerImageName
46+
* @param dockerImageName the full image name to use
47+
*/
48+
public MinIOContainer(final DockerImageName dockerImageName) {
49+
super(dockerImageName);
50+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
51+
}
52+
53+
/**
54+
* Overrides the DEFAULT_USER
55+
* @param userName the Root user to override
56+
* @return this
57+
*/
58+
public MinIOContainer withUserName(String userName) {
59+
this.userName = userName;
60+
return this;
61+
}
62+
63+
/**
64+
* Overrides the DEFAULT_PASSWORD
65+
* @param password the Root user's password to override
66+
* @return this
67+
*/
68+
public MinIOContainer withPassword(String password) {
69+
this.password = password;
70+
return this;
71+
}
72+
73+
/**
74+
* Configures the MinIO container
75+
*/
76+
@Override
77+
public void configure() {
78+
withExposedPorts(MinIOContainer.MINIO_S3_PORT, MinIOContainer.MINIO_UI_PORT);
79+
80+
if (this.userName != null) {
81+
addEnv("MINIO_ROOT_USER", this.userName);
82+
} else {
83+
this.userName = DEFAULT_USER;
84+
}
85+
if (this.password != null) {
86+
addEnv("MINIO_ROOT_PASSWORD", this.password);
87+
} else {
88+
this.password = DEFAULT_PASSWORD;
89+
}
90+
91+
withCommand("server", "--console-address", ":" + MINIO_UI_PORT, "/data");
92+
93+
waitingFor(
94+
Wait
95+
.forLogMessage(".*Status: 1 Online, 0 Offline..*", 1)
96+
.withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))
97+
);
98+
}
99+
100+
/**
101+
* @return the URL to upload/download objects from
102+
*/
103+
public String getS3URL() {
104+
return String.format("http://%s:%s", this.getHost(), getMappedPort(MINIO_S3_PORT));
105+
}
106+
107+
/**
108+
* @return the Username for the Root user
109+
*/
110+
public String getUserName() {
111+
return this.userName;
112+
}
113+
114+
/**
115+
* @return the password for the Root user
116+
*/
117+
public String getPassword() {
118+
return this.password;
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.testcontainers.containers;
2+
3+
import io.minio.BucketExistsArgs;
4+
import io.minio.MakeBucketArgs;
5+
import io.minio.MinioClient;
6+
import io.minio.StatObjectArgs;
7+
import io.minio.StatObjectResponse;
8+
import io.minio.UploadObjectArgs;
9+
import org.junit.Test;
10+
11+
import java.net.URL;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
public class MinIOContainerTest {
16+
17+
@Test
18+
public void testBasicUsage() throws Exception {
19+
try (
20+
// minioContainer {
21+
MinIOContainer container = new MinIOContainer("minio/minio:RELEASE.2023-09-04T19-57-37Z");
22+
// }
23+
) {
24+
container.start();
25+
26+
// configuringClient {
27+
MinioClient minioClient = MinioClient
28+
.builder()
29+
.endpoint(container.getS3URL())
30+
.credentials(container.getUserName(), container.getPassword())
31+
.build();
32+
33+
// }
34+
minioClient.makeBucket(MakeBucketArgs.builder().bucket("test-bucket").region("us-west-2").build());
35+
36+
BucketExistsArgs existsArgs = BucketExistsArgs.builder().bucket("test-bucket").build();
37+
38+
assertThat(minioClient.bucketExists(existsArgs)).isTrue();
39+
40+
URL file = this.getClass().getResource("/object_to_upload.txt");
41+
assertThat(file).isNotNull();
42+
minioClient.uploadObject(
43+
UploadObjectArgs
44+
.builder()
45+
.bucket("test-bucket")
46+
.object("my-objectname")
47+
.filename(file.getPath())
48+
.build()
49+
);
50+
51+
StatObjectResponse objectStat = minioClient.statObject(
52+
StatObjectArgs.builder().bucket("test-bucket").object("my-objectname").build()
53+
);
54+
55+
assertThat(objectStat.object()).isEqualTo("my-objectname");
56+
}
57+
}
58+
59+
@Test
60+
public void testDefaultUserPassword() {
61+
try (MinIOContainer container = new MinIOContainer("minio/minio:RELEASE.2023-09-04T19-57-37Z")) {
62+
container.start();
63+
assertThat(container.getUserName()).isEqualTo("minioadmin");
64+
assertThat(container.getPassword()).isEqualTo("minioadmin");
65+
}
66+
}
67+
68+
@Test
69+
public void testOverwriteUserPassword() {
70+
try (
71+
// minioOverrides {
72+
MinIOContainer container = new MinIOContainer("minio/minio:RELEASE.2023-09-04T19-57-37Z")
73+
.withUserName("testuser")
74+
.withPassword("testpassword");
75+
// }
76+
) {
77+
container.start();
78+
assertThat(container.getUserName()).isEqualTo("testuser");
79+
assertThat(container.getPassword()).isEqualTo("testpassword");
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<configuration>
2+
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<!-- encoders are assigned the type
5+
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
6+
<encoder>
7+
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
8+
</encoder>
9+
</appender>
10+
11+
<root level="INFO">
12+
<appender-ref ref="STDOUT"/>
13+
</root>
14+
15+
<logger name="org.testcontainers" level="INFO"/>
16+
</configuration>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a file

0 commit comments

Comments
 (0)