Skip to content

#26 Support init scripts for test containers #35

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 1 commit 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ To configure a target database, you need to specify at least database `type` pro
| username | | Provided from database container if not specified | Database username for container |
| password | | Provided from database container if not specified | Database password for container |
| databaseName | | Provided from database container if not specified | Database name for container |
| initScript | | | Path to SQL script to run after container creation |

#### `database` block configuration

Expand All @@ -35,6 +36,7 @@ To configure a target database, you need to specify at least database `type` pro
<username>test</username>
<password>test</password>
<databaseName>test</databaseName>
<initScript>filesystem:src/test/resources/db/init.sql</initScript>
</database>
```

Expand Down
55 changes: 36 additions & 19 deletions src/main/java/org/testcontainers/jooq/codegen/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import org.apache.maven.plugin.AbstractMojo;
Expand Down Expand Up @@ -62,15 +63,12 @@ public void execute() throws MojoExecutionException {
throw new MojoExecutionException("Property 'type' should be specified inside 'database' block");
}

final var oldCL = Thread.currentThread().getContextClassLoader();
final var mavenClassloader = getMavenClassloader();

try (var targetDatasource = TargetDatasource.createOrJoinExisting(jooq, database)) {
doExecute(mavenClassloader, targetDatasource);
} catch (Exception ex) {
throw new MojoExecutionException("Error running jOOQ code generation tool", ex);
} finally {
closeClassloader(oldCL, mavenClassloader);
try (var closableContextClassLoader = new ClosableContextClassLoader(getMavenClassloader())) {
Copy link
Contributor Author

@zzzLobster zzzLobster Oct 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked it to set context ClassLoader before running the container, to be able loading initScript from classpath.
Unfortunately tests as they are cannot cover this case properly (they successfully work without this change). I can try implementing multi-module test to cover it (to make it failing without the change)

try (var targetDatasource = TargetDatasource.createOrJoinExisting(jooq, database)) {
doExecute(closableContextClassLoader.getClassLoader(), targetDatasource);
} catch (Exception ex) {
throw new MojoExecutionException("Error running jOOQ code generation tool", ex);
}
}
}

Expand All @@ -81,7 +79,6 @@ private void doExecute(URLClassLoader mavenClassloader, TargetDatasource targetD
.log(getLog())
.mavenClassloader(mavenClassloader)
.build();
Thread.currentThread().setContextClassLoader(mavenClassloader);

final var oFlyway = Optional.<MigrationRunner>ofNullable(flyway);
final var oLiquibase = Optional.<MigrationRunner>ofNullable(liquibase);
Expand All @@ -104,15 +101,6 @@ private void doExecute(URLClassLoader mavenClassloader, TargetDatasource targetD
jooqGenerator.generateSources(properties, jooq);
}

private void closeClassloader(ClassLoader oldCL, URLClassLoader mavenClassloader) {
Thread.currentThread().setContextClassLoader(oldCL);
try {
mavenClassloader.close();
} catch (Throwable e) {
getLog().error("Couldn't close the classloader.", e);
}
}

private URLClassLoader getMavenClassloader() throws MojoExecutionException {
try {
List<String> classpathElements = project.getRuntimeClasspathElements();
Expand All @@ -130,4 +118,33 @@ private URLClassLoader getMavenClassloader() throws MojoExecutionException {
throw new MojoExecutionException("Couldn't create a classloader.", e);
}
}

private class ClosableContextClassLoader implements AutoCloseable {
private URLClassLoader newClassLoader;
private ClassLoader oldClassLoader;

public ClosableContextClassLoader(URLClassLoader cl) {
newClassLoader = Objects.requireNonNull(cl);
oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(newClassLoader);
}

public URLClassLoader getClassLoader() {
return newClassLoader;
}

@Override
public void close() {
if (newClassLoader == null) {
return;
}
Thread.currentThread().setContextClassLoader(oldClassLoader);
try {
newClassLoader.close();
newClassLoader = null;
} catch (Throwable e) {
getLog().error("Couldn't close the classloader.", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ public class DatabaseProps {
*/
@Parameter
private String databaseName;
/**
* Optional
*/
@Parameter
private String initScript;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if scripts support is added then supporting a list would be more flexible.

}
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
package org.testcontainers.jooq.codegen.datasource;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.sql.Driver;
import java.util.Objects;
import javax.script.ScriptException;
import lombok.experimental.Delegate;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
import org.testcontainers.ext.ScriptUtils;
import org.testcontainers.jdbc.JdbcDatabaseDelegate;
import org.testcontainers.shaded.org.apache.commons.io.FileUtils;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;

/**
* Containerized target datasource
*/
public final class ContainerTargetDatasource implements TargetDatasource {
private static final String FILESYSTEM_PREFIX = "filesystem:";
private static final String CLASSPATH_PREFIX = "classpath:";

/**
* Getting datasource properties from container, auto stopping container <br/>
* {@link AutoCloseable} is implemented by container and {@code close()} delegated to {@code container.stop()}
*/
@Delegate
private final JdbcDatabaseContainer<?> container;

public ContainerTargetDatasource(JdbcDatabaseContainer<?> container) {
public ContainerTargetDatasource(JdbcDatabaseContainer<?> container, String initScript) {
this.container = Objects.requireNonNull(container);
this.container.setWaitStrategy(new HostPortWaitStrategy());
this.container.start();
runInitScript(initScript);
}

@Override
Expand All @@ -32,4 +44,30 @@ public String getUrl() {
public Driver getDriverInstance() {
return container.getJdbcDriverInstance();
}

private void runInitScript(String initScript) {
if (StringUtils.isEmpty(initScript)) {
return;
}
try (var jdbcDelegate = new JdbcDatabaseDelegate(container, "")) {
if (initScript.startsWith(FILESYSTEM_PREFIX)) {
var file = Path.of(initScript.substring(FILESYSTEM_PREFIX.length()))
.toAbsolutePath()
.toFile();
try {
var scriptBody = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
ScriptUtils.executeDatabaseScript(jdbcDelegate, initScript, scriptBody);
return;
} catch (IOException e) {
throw new RuntimeException("Failed to load " + file.getAbsolutePath(), e);
} catch (ScriptException e) {
throw new RuntimeException("Failed to execute " + file.getAbsolutePath(), e);
}
}
var scriptClassPath = initScript.startsWith(CLASSPATH_PREFIX)
? initScript.substring(CLASSPATH_PREFIX.length())
: initScript;
ScriptUtils.runInitScript(jdbcDelegate, scriptClassPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface TargetDatasource extends AutoCloseable {
static TargetDatasource createOrJoinExisting(JooqProps jooq, DatabaseProps database) {
if (needSpinContainer(jooq)) {
var databaseContainer = DatabaseProvider.getDatabaseContainer(database);
return new ContainerTargetDatasource(databaseContainer);
return new ContainerTargetDatasource(databaseContainer, database.getInitScript());
}

return new ExistingTargetDatasource(jooq.getJdbc());
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/db/postgres/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE USER read_user WITH PASSWORD 'test123';
1 change: 1 addition & 0 deletions src/test/resources/pom/postgis-flyway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<username>test</username>
<password>test</password>
<databaseName>test</databaseName>
<initScript>classpath:db/postgres/init.sql</initScript>
</database>
<flyway>
<locations>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ create table users
primary key (id),
CONSTRAINT user_email_unique UNIQUE (email)
);

grant select on users to read_user;
1 change: 1 addition & 0 deletions src/test/resources/pom/postgres-flyway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<username>test</username>
<password>test</password>
<databaseName>test</databaseName>
<initScript>filesystem:${project.basedir}/src/test/resources/db/init.sql</initScript>
</database>
<flyway>
<locations>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ create table users
primary key (id),
CONSTRAINT user_email_unique UNIQUE (email)
);

grant select on users to read_user;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE USER read_user WITH PASSWORD 'test123';