Skip to content

Commit 95cda94

Browse files
committed
[#2269] Keep containers image and version in docker files
We are trying to achieve two things: * Make it possible for dependabot to upgrade the containers automatically * Collect the image and version of the containers we use for testing in one place Note that the test suite will still create and start the containers programmatically, but it will read the first FROM line in each Dockerfile to extract the image and version to use. It will ignore everything else. My initial plan was to configure each container using the Dockerfile directly, but I prefer to reuse the exsisting Testcontainers classes for each database (for example, PostgreSQLContainer) because they contain out-of-the-box configuration that I would need to copy somewhere else. In any case, this is a good starting point and we can improve it later.
1 parent 126bca2 commit 95cda94

File tree

23 files changed

+528
-29
lines changed

23 files changed

+528
-29
lines changed

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ subprojects {
5252
options.encoding = 'UTF-8'
5353
}
5454

55+
// Configure test tasks for all subprojects
56+
tasks.withType( Test ).configureEach {
57+
// Set the project root for finding Docker files - available to all modules
58+
systemProperty 'hibernate.reactive.project.root', rootProject.projectDir.absolutePath
59+
}
60+
5561
if ( !gradle.ext.javaToolchainEnabled ) {
5662
sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion )
5763
targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion )

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import org.testcontainers.containers.CockroachContainer;
1313
import org.testcontainers.containers.Container;
1414

15-
import static org.hibernate.reactive.containers.DockerImage.imageName;
15+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
1616

1717
class CockroachDBDatabase extends PostgreSQLDatabase {
1818

@@ -25,7 +25,7 @@ class CockroachDBDatabase extends PostgreSQLDatabase {
2525
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
2626
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
2727
*/
28-
public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.3.13" ) )
28+
public static final CockroachContainer cockroachDb = new CockroachContainer( fromDockerfile( "cockroachdb" ) )
2929
// Username, password and database are not supported by test container at the moment
3030
// Testcontainers will use a database named 'postgres' and the 'root' user
3131
.withReuse( true );

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import org.testcontainers.containers.Db2Container;
3030

31-
import static org.hibernate.reactive.containers.DockerImage.imageName;
31+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
3232

3333
class DB2Database implements TestableDatabase {
3434

@@ -87,7 +87,7 @@ class DB2Database implements TestableDatabase {
8787
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
8888
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
8989
*/
90-
static final Db2Container db2 = new Db2Container( imageName( "icr.io", "db2_community/db2", "12.1.0.0" ) )
90+
static final Db2Container db2 = new Db2Container( fromDockerfile( "db2" ) )
9191
.withUsername( DatabaseConfiguration.USERNAME )
9292
.withPassword( DatabaseConfiguration.PASSWORD )
9393
.withDatabaseName( DatabaseConfiguration.DB_NAME )

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
*/
66
package org.hibernate.reactive.containers;
77

8+
import java.io.FileNotFoundException;
9+
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.util.List;
13+
814
import org.testcontainers.utility.DockerImageName;
915

16+
1017
/**
11-
* A utility class with methods to generate {@link DockerImageName} for testcontainers.
18+
* A utility class with methods to generate a {@link DockerImageName} for Testcontainers.
1219
* <p>
1320
* Testcontainers might not work if the image required is available in multiple different registries (for example when
1421
* using podman instead of docker).
@@ -17,15 +24,103 @@
1724
*/
1825
public final class DockerImage {
1926

20-
public static final String DEFAULT_REGISTRY = "docker.io";
27+
/**
28+
* The absolute path of the project root that we have set in Gradle.
29+
*/
30+
private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" );
31+
32+
/**
33+
* The path to the directory containing all the Dockerfile files
34+
*/
35+
private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" );
2136

22-
public static DockerImageName imageName(String image, String version) {
23-
return imageName( DEFAULT_REGISTRY, image, version );
37+
/**
38+
* Extract the image name and version from the first FROM instruction in the Dockerfile.
39+
* Note that everything else is ignored.
40+
*/
41+
public static DockerImageName fromDockerfile(String databaseName) {
42+
try {
43+
final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() );
44+
return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() );
45+
}
46+
catch (IOException e) {
47+
throw new RuntimeException( e );
48+
}
2449
}
2550

2651
public static DockerImageName imageName(String registry, String image, String version) {
2752
return DockerImageName
2853
.parse( registry + "/" + image + ":" + version )
2954
.asCompatibleSubstituteFor( image );
3055
}
56+
57+
private static class ImageInformation {
58+
private final String registry;
59+
private final String image;
60+
private final String version;
61+
62+
public ImageInformation(String fullImageInfo) {
63+
// FullImageInfo pattern: <registry>/<image>:<version>
64+
// For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13"
65+
final int registryEndPos = fullImageInfo.indexOf( '/' );
66+
final int imageEndPos = fullImageInfo.lastIndexOf( ':' );
67+
this.registry = fullImageInfo.substring( 0, registryEndPos );
68+
this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos );
69+
this.version = fullImageInfo.substring( imageEndPos + 1 );
70+
}
71+
72+
public String getRegistry() {
73+
return registry;
74+
}
75+
76+
public String getImage() {
77+
return image;
78+
}
79+
80+
public String getVersion() {
81+
return version;
82+
}
83+
84+
@Override
85+
public String toString() {
86+
return registry + "/" + image + ":" + version;
87+
}
88+
}
89+
90+
private static Path dockerFilePath(String database) {
91+
// Get project root from system property set by Gradle, with fallback
92+
return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" );
93+
}
94+
95+
private static ImageInformation readFromInstruction(String database) throws IOException {
96+
return readFromInstruction( dockerFilePath( database ) );
97+
}
98+
99+
/**
100+
* Read a Dockerfile and extract the first FROM instruction.
101+
*
102+
* @param dockerfilePath path to the Dockerfile
103+
* @return the first FROM instruction found, or empty if none found
104+
* @throws IOException if the file cannot be read
105+
*/
106+
private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException {
107+
if ( !Files.exists( dockerfilePath ) ) {
108+
throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath );
109+
}
110+
111+
List<String> lines = Files.readAllLines( dockerfilePath );
112+
for ( String line : lines ) {
113+
// Skip comments and empty lines
114+
String trimmedLine = line.trim();
115+
if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) {
116+
continue;
117+
}
118+
119+
if ( trimmedLine.startsWith( "FROM " ) ) {
120+
return new ImageInformation( trimmedLine.substring( "FROM ".length() ) );
121+
}
122+
}
123+
124+
throw new IOException( " Missing FROM instruction in " + dockerfilePath );
125+
}
31126
}

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import org.testcontainers.containers.MSSQLServerContainer;
3030

31-
import static org.hibernate.reactive.containers.DockerImage.imageName;
31+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
3232

3333
/**
3434
* The JDBC driver syntax is:
@@ -96,7 +96,7 @@ class MSSQLServerDatabase implements TestableDatabase {
9696
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
9797
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
9898
*/
99-
public static final MSSQLServerContainer<?> mssqlserver = new MSSQLServerContainer<>( imageName( "mcr.microsoft.com", "mssql/server", "2022-latest" ) )
99+
public static final MSSQLServerContainer<?> mssqlserver = new MSSQLServerContainer<>( fromDockerfile( "sqlserver" ) )
100100
.acceptLicense()
101101
.withPassword( PASSWORD )
102102
.withReuse( true );

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import org.testcontainers.containers.MariaDBContainer;
1515

16-
import static org.hibernate.reactive.containers.DockerImage.imageName;
16+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
1717

1818
class MariaDatabase extends MySQLDatabase {
1919

@@ -36,7 +36,7 @@ class MariaDatabase extends MySQLDatabase {
3636
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
3737
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
3838
*/
39-
public static final MariaDBContainer<?> maria = new MariaDBContainer<>( imageName( "mariadb", "11.7.2" ) )
39+
public static final MariaDBContainer<?> maria = new MariaDBContainer<>( fromDockerfile( "maria" ) )
4040
.withUsername( DatabaseConfiguration.USERNAME )
4141
.withPassword( DatabaseConfiguration.PASSWORD )
4242
.withDatabaseName( DatabaseConfiguration.DB_NAME )

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66
package org.hibernate.reactive.containers;
77

8-
import static org.hibernate.reactive.containers.DockerImage.imageName;
8+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
99

1010
import java.io.Serializable;
1111
import java.math.BigDecimal;
@@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase {
8787
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
8888
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
8989
*/
90-
public static final MySQLContainer<?> mysql = new MySQLContainer<>( imageName( "mysql", "9.2.0") )
90+
public static final MySQLContainer<?> mysql = new MySQLContainer<>( fromDockerfile( "mysql" ) )
9191
.withUsername( DatabaseConfiguration.USERNAME )
9292
.withPassword( DatabaseConfiguration.PASSWORD )
9393
.withDatabaseName( DatabaseConfiguration.DB_NAME )

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
import org.testcontainers.containers.OracleContainer;
3131

32-
import static org.hibernate.reactive.containers.DockerImage.imageName;
32+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
3333

3434
/**
3535
* Connection string for Oracle thin should be something like:
@@ -88,9 +88,7 @@ class OracleDatabase implements TestableDatabase {
8888
}
8989
}
9090

91-
public static final OracleContainer oracle = new OracleContainer(
92-
imageName( "gvenzl/oracle-free", "23-slim-faststart" )
93-
.asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) )
91+
public static final OracleContainer oracle = new OracleContainer( fromDockerfile( "oracle" ).asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) )
9492
.withUsername( DatabaseConfiguration.USERNAME )
9593
.withPassword( DatabaseConfiguration.PASSWORD )
9694
.withDatabaseName( DatabaseConfiguration.DB_NAME )

hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
*/
66
package org.hibernate.reactive.containers;
77

8-
import static org.hibernate.reactive.containers.DockerImage.imageName;
9-
108
import java.io.Serializable;
119
import java.math.BigDecimal;
1210
import java.math.BigInteger;
@@ -30,9 +28,11 @@
3028

3129
import org.testcontainers.containers.PostgreSQLContainer;
3230

31+
import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
32+
3333
class PostgreSQLDatabase implements TestableDatabase {
3434

35-
public static PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase();
35+
public static final PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase();
3636

3737
private static Map<Class<?>, String> expectedDBTypeForClass = new HashMap<>();
3838

@@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase {
8787
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
8888
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
8989
*/
90-
public static final PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.5" ) )
90+
public static final PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>( fromDockerfile( "postgresql" ) )
9191
.withUsername( DatabaseConfiguration.USERNAME )
9292
.withPassword( DatabaseConfiguration.PASSWORD )
9393
.withDatabaseName( DatabaseConfiguration.DB_NAME )

integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ public abstract class BaseReactiveIT {
5151
// These properties are in DatabaseConfiguration in core
5252
public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" );
5353

54-
public static final DockerImageName IMAGE_NAME = DockerImageName
55-
.parse( "docker.io/postgres:17.5" )
56-
.asCompatibleSubstituteFor( "postgres" );
54+
public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" );
5755

5856
public static final String USERNAME = "hreact";
5957
public static final String PASSWORD = "hreact";

0 commit comments

Comments
 (0)