Skip to content

Commit

Permalink
#5 Add support for ghcr.io/fdcastel/firebird as alternative image
Browse files Browse the repository at this point in the history
  • Loading branch information
mrotteveel committed Dec 16, 2024
1 parent 9f18981 commit 9d9e6e2
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 60 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Version History
- Updated org.testcontainers:jdbc to 1.20.4
- Updated various test-dependencies
- Updated Maven build plugins
- Added support for [ghcr.io/fdcastel/firebird](https://github.com/fdcastel/firebird-docker) images. \
The name is defined in `FirebirdContainer.FDCASTEL_IMAGE` (`String`) and `FirebirdContainer.FDCASTEL_IMAGE_NAME` (`DockerImageName`). \
These images are not accessible as a `jdbc:tc:firebird[sql]:...` URL, only through `FirebirdContainer`.\
All existing configuration options are mapped in a backwards compatible way.

1.4.0
-----
Expand Down
42 changes: 19 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ firebird-testcontainers-java
Firebird-testcontainers-java is a module for [Testcontainers](https://www.testcontainers.org/)
to provide lightweight, throwaway instances of Firebird for JUnit tests.

The docker image used is [jacobalberty/firebird](https://hub.docker.com/r/jacobalberty/firebird/).
The docker image used is [jacobalberty/firebird](https://hub.docker.com/r/jacobalberty/firebird/), and also supports [ghcr.io/fdcastel/firebird](https://github.com/fdcastel/firebird-docker).

If you want to use 2.5, use the 2.5.x-sc (SuperClassic) variant of the image, or 2.5.9-ss
If you want to use Firebird 2.5, use the 2.5.x-sc (SuperClassic) variant of the image, or 2.5.9-ss
as earlier versions of the 2.5.x-ss (SuperServer) variant seem to be broken.

Prerequisites
Expand Down Expand Up @@ -56,30 +56,24 @@ The container defines several `withXXX` methods for configuration.

Important standard options are:

- `withUsername(String)` - Sets the username to create (defaults to `test`); if
other than `sysdba` (case-insensitive), sets docker environment variable
`FIREBIRD_USER`
- `withPassword(String)` - Sets the password of the user (defaults to `test`);
if `withUsername` is other than `sysdba` (case-insensitive), sets the docker
environment variable `FIREBIRD_PASSWORD`, otherwise `ISC_PASSWORD`
- `withDatabaseName(String)` - Sets the database name (defaults to `test`);
sets docker environment variable `FIREBIRD_DATABASE`
- `withUsername(String)` - Sets the username to create (defaults to `test`); sets docker environment variable `FIREBIRD_USER`. \
For `jacobalberty/firebird`, if the value is `sysdba`, `FIREBIRD_USER` is not set.
- `withPassword(String)` - Sets the password of the user (defaults to `test`); sets the docker environment variable `FIREBIRD_PASSWORD`. \
For `jacobalberty/firebird`, if the username is `sysdba`, `ISC_PASSWORD` is set instead of `FIREBIRD_PASSWORD`. \
For `ghcr.io/fdcastel/firebird`, if the username is `sysdba`, it also sets `FIREBIRD_ROOT_PASSWORD`.
- `withDatabaseName(String)` - Sets the database name (defaults to `test`); sets docker environment variable `FIREBIRD_DATABASE`

Firebird specific options are:

- `withEnableLegacyClientAuth()` - (_Firebird 3+_) Enables `LegacyAuth` and uses
it as the default for creating users, also relaxes `WireCrypt` to `Enabled`;
sets docker environment variable `EnableLegacyClientAuth` to `true`;
passes connection property `authPlugins` with value `Srp256,Srp,Legacy_Auth` if
this property is not explicitly set through `withUrlParam`
- `withEnableWireCrypt` - (_Firebird 3+_) Relaxes `WireCrypt` from `Required` to
`Enabled`; sets docker environment variable `EnableWireCrypt` to `true`
- `withTimeZone(String)` - Sets the time zone (defaults to JVM default time
zone); sets docker environment variable `TZ` to the specified value
- `withSysdbaPassword(String)` - Sets the SYSDBA password, but if
`withUsername(String)` is set to `sysdba` (case-insensitive), this property is
ignored and the value of `withPassword` is used instead; sets docker
environment variable `ISC_PASSWORD` to the specified value
- `withEnableLegacyClientAuth()` - (_Firebird 3+_) Enables `LegacyAuth` and uses it as the default for creating users, also relaxes `WireCrypt` to `Enabled`;
sets docker environment variable `EnableLegacyClientAuth` (`jacobalberty/firebird`) or `FIREBIRD_USE_LEGACY_AUTH` (`ghcr.io/fdcastel/firebird`) to `true`;
passes connection property `authPlugins` with value `Srp256,Srp,Legacy_Auth` if this property is not explicitly set through `withUrlParam`.
- `withEnableWireCrypt` - (_Firebird 3+_) Relaxes `WireCrypt` from `Required` to `Enabled`;
sets docker environment variable `EnableWireCrypt` (`jacobalberty/firebird`) to `true`, or `FIREBIRD_CONF_WireCrypt` (`ghcr.io/fdcastel/firebird`) to `Enabled`.
- `withTimeZone(String)` - Sets the time zone (defaults to JVM default time zone);
- sets docker environment variable `TZ` to the specified value
- `withSysdbaPassword(String)` - Sets the SYSDBA password, but if `withUsername(String)` is set to `sysdba` (case-insensitive), this property is ignored and the value of `withPassword` is used instead;
sets docker environment variable `ISC_PASSWORD` (`jacobalberty/firebird`) or `FIREBIRD_ROOT_PASSWORD` (`ghcr.io/fdcastel/firebird`) to the specified value.

Example of use:

Expand Down Expand Up @@ -134,6 +128,8 @@ Where:
- `password` (_optional_) specifies the password for the user (defaults to `test`)
- `<value>` is the value of the property

Currently, these URLs can only use the `jacobalberty/firebird` images.

Example of use:

```java
Expand Down
113 changes: 96 additions & 17 deletions src/main/java/org/firebirdsql/testcontainers/FirebirdContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public class FirebirdContainer<SELF extends FirebirdContainer<SELF>> extends Jdb
public static final String NAME = "firebird";
public static final String ALTERNATE_NAME = "firebirdsql";
public static final String IMAGE = "jacobalberty/firebird";
public static final String FDCASTEL_IMAGE = "ghcr.io/fdcastel/firebird";
static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(IMAGE);
static final DockerImageName FDCASTEL_IMAGE_NAME = DockerImageName.parse(FDCASTEL_IMAGE);
public static final String DEFAULT_TAG = "v4.0.2";

public static final Integer FIREBIRD_PORT = 3050;
Expand Down Expand Up @@ -65,41 +67,33 @@ public FirebirdContainer(String dockerImageName) {
*/
public FirebirdContainer(DockerImageName dockerImageName) {
super(dockerImageName);

dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, FDCASTEL_IMAGE_NAME);

addExposedPort(FIREBIRD_PORT);
}

@Override
protected void configure() {
addEnv("TZ", timeZone);
addEnv("FIREBIRD_DATABASE", databaseName);

if (FIREBIRD_SYSDBA.equalsIgnoreCase(username)) {
addEnv("ISC_PASSWORD", password);
} else {
addEnv("FIREBIRD_USER", username);
addEnv("FIREBIRD_PASSWORD", password);
if (sysdbaPassword != null) {
addEnv("ISC_PASSWORD", sysdbaPassword);
}
}
ImageVariant variant = ImageVariant.of(getDockerImageName());
variant.setTimeZone(this);
variant.setDatabaseName(this);

variant.setUserAndPassword(this);

if (enableLegacyClientAuth) {
addEnv("EnableLegacyClientAuth", "true");
variant.enableLegacyAuth(this);
if (!urlParameters.containsKey(CONNECTION_PROPERTY_AUTH_PLUGINS)) {
// Allow legacy auth with Jaybird 4, while also allowing Srp256 and Srp
withUrlParam(CONNECTION_PROPERTY_AUTH_PLUGINS, "Srp256,Srp,Legacy_Auth");
}
}

if (enableWireCrypt) {
addEnv("EnableWireCrypt", "true");
variant.setWireCryptEnabled(this);
} else if (!isWireEncryptionSupported()) {
log.warn("Java Virtual Machine does not support wire protocol encryption requirements. " +
"Downgrading to EnableWireCrypt = true. To fix this, configure the JVM with unlimited strength Cryptographic Jurisdiction Policy.");
addEnv("EnableWireCrypt", "true");
variant.setWireCryptEnabled(this);
}
}

Expand All @@ -118,12 +112,24 @@ public String getJdbcUrl() {
@Override
public String getDatabaseName() {
if (isRunning()) {
ImageVariant imageVariant = ImageVariant.of(getDockerImageName());
switch (imageVariant) {
case JACOBALBERTY:
if (isFirebird25Image()) {
// The 2.5 images of jacobalberty/firebird require an absolute path to access the database
// Provide this value only when the container is running
String databasePath = getEnvMap().getOrDefault("DBPATH", "/firebird/data");
return databasePath + "/" + databaseName;
}
return databaseName;
case FDCASTEL:
// The fdcastel/firebird images require an absolute path to access the database
// Provide this value only when the container is running
if (databaseName.charAt(0) != '/') {
return "/var/lib/firebird/data/" + databaseName;
}
return databaseName;
}
}
return databaseName;
}
Expand Down Expand Up @@ -233,4 +239,77 @@ public static boolean isWireEncryptionSupported() {
return false;
}
}

private enum ImageVariant {
JACOBALBERTY {
@Override
void setUserAndPassword(FirebirdContainer<?> container) {
if (FIREBIRD_SYSDBA.equalsIgnoreCase(container.username)) {
container.addEnv("ISC_PASSWORD", container.password);
} else {
container.addEnv("FIREBIRD_USER", container.username);
container.addEnv("FIREBIRD_PASSWORD", container.password);
if (container.sysdbaPassword != null) {
container.addEnv("ISC_PASSWORD", container.sysdbaPassword);
}
}
}

@Override
void enableLegacyAuth(FirebirdContainer<?> container) {
container.addEnv("EnableLegacyClientAuth", "true");
}

@Override
void setWireCryptEnabled(FirebirdContainer<?> container) {
container.addEnv("EnableWireCrypt", "true");
}
},
FDCASTEL {
@Override
void setUserAndPassword(FirebirdContainer<?> container) {
container.addEnv("FIREBIRD_USER", container.username);
container.addEnv("FIREBIRD_PASSWORD", container.password);
if (FIREBIRD_SYSDBA.equalsIgnoreCase(container.username)) {
container.addEnv("FIREBIRD_ROOT_PASSWORD", container.password);
} else if (container.sysdbaPassword != null) {
container.addEnv("FIREBIRD_ROOT_PASSWORD", container.sysdbaPassword);
}
}

@Override
void enableLegacyAuth(FirebirdContainer<?> container) {
container.addEnv("FIREBIRD_USE_LEGACY_AUTH", "true");
}

@Override
void setWireCryptEnabled(FirebirdContainer<?> container) {
container.addEnv("FIREBIRD_CONF_WireCrypt", "Enabled");
}
};

void setTimeZone(FirebirdContainer<?> container) {
container.addEnv("TZ", container.timeZone);
}

void setDatabaseName(FirebirdContainer<?> container) {
container.addEnv("FIREBIRD_DATABASE", container.databaseName);
}

abstract void setUserAndPassword(FirebirdContainer<?> container);

abstract void enableLegacyAuth(FirebirdContainer<?> container);

abstract void setWireCryptEnabled(FirebirdContainer<?> container);

static ImageVariant of(String imageNameString) {
DockerImageName imageName = DockerImageName.parse(imageNameString);
if (imageName.isCompatibleWith(FDCASTEL_IMAGE_NAME)) {
return FDCASTEL;
}
// Assume the default
return JACOBALBERTY;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
import org.firebirdsql.jdbc.FirebirdConnection;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.testcontainers.utility.DockerImageName;

import java.sql.*;
import java.util.stream.Stream;

import static org.firebirdsql.testcontainers.FirebirdContainer.FIREBIRD_PORT;
import static org.firebirdsql.testcontainers.FirebirdTestImages.FDCASTEL_TEST_IMAGE;
import static org.firebirdsql.testcontainers.FirebirdTestImages.FIREBIRD_259_SC_IMAGE;
import static org.firebirdsql.testcontainers.FirebirdTestImages.FIREBIRD_259_SS_IMAGE;
import static org.firebirdsql.testcontainers.FirebirdTestImages.FIREBIRD_TEST_IMAGE;
Expand All @@ -18,12 +23,14 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SuppressWarnings("resource")
class FirebirdContainerTest {

@Test
void testWithSysdbaPassword() throws SQLException {
@ParameterizedTest
@MethodSource("defaultTestImages")
void testWithSysdbaPassword(DockerImageName imageName) throws SQLException {
final String sysdbaPassword = "sysdbapassword";
try (FirebirdContainer<?> container = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE)
try (FirebirdContainer<?> container = new FirebirdContainer<>(imageName)
.withSysdbaPassword(sysdbaPassword)) {
container.start();

Expand All @@ -50,11 +57,12 @@ void testImplicitImage() throws SQLException {
/**
* With {@code username} set to sysdba, {@code password} should take precedence over {@code sysdbaPassword}
*/
@Test
void testUserPasswordTakesPrecedenceOverWithSysdbaPassword() throws SQLException {
@ParameterizedTest
@MethodSource("defaultTestImages")
void testUserPasswordTakesPrecedenceOverWithSysdbaPassword(DockerImageName imageName) throws SQLException {
final String userPassword = "password1";
final String withSysdbaPassword = "password2";
try (FirebirdContainer<?> container = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE)
try (FirebirdContainer<?> container = new FirebirdContainer<>(imageName)
.withUsername("sysdba").withPassword(userPassword).withSysdbaPassword(withSysdbaPassword)) {
container.start();

Expand All @@ -64,9 +72,10 @@ void testUserPasswordTakesPrecedenceOverWithSysdbaPassword() throws SQLException
}
}

@Test
void testWithEnableLegacyClientAuth() throws SQLException {
try (FirebirdContainer<?> container = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE)
@ParameterizedTest
@MethodSource("defaultTestImages")
void testWithEnableLegacyClientAuth(DockerImageName imageName) throws SQLException {
try (FirebirdContainer<?> container = new FirebirdContainer<>(imageName)
.withEnableLegacyClientAuth()) {
container.start();

Expand All @@ -79,9 +88,10 @@ void testWithEnableLegacyClientAuth() throws SQLException {
}
}

@Test
void testWithEnableLegacyClientAuth_jdbcUrlIncludeAuthPlugins_default() {
try (FirebirdContainer<?> container = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE)
@ParameterizedTest
@MethodSource("defaultTestImages")
void testWithEnableLegacyClientAuth_jdbcUrlIncludeAuthPlugins_default(DockerImageName imageName) {
try (FirebirdContainer<?> container = new FirebirdContainer<>(imageName)
.withEnableLegacyClientAuth()) {
container.start();

Expand All @@ -92,9 +102,10 @@ void testWithEnableLegacyClientAuth_jdbcUrlIncludeAuthPlugins_default() {
}
}

@Test
void testWithEnableLegacyClientAuth_jdbcUrlIncludeAuthPlugins_explicitlySet() {
try (FirebirdContainer<?> container = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE)
@ParameterizedTest
@MethodSource("defaultTestImages")
void testWithEnableLegacyClientAuth_jdbcUrlIncludeAuthPlugins_explicitlySet(DockerImageName imageName) {
try (FirebirdContainer<?> container = new FirebirdContainer<>(imageName)
.withEnableLegacyClientAuth()
.withUrlParam("authPlugins", "Legacy_Auth")) {
container.start();
Expand All @@ -106,9 +117,10 @@ void testWithEnableLegacyClientAuth_jdbcUrlIncludeAuthPlugins_explicitlySet() {
}
}

@Test
void testWithEnableWireCrypt() throws SQLException {
try (FirebirdContainer<?> container = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE).withEnableWireCrypt()) {
@ParameterizedTest
@MethodSource("defaultTestImages")
void testWithEnableWireCrypt(DockerImageName imageName) throws SQLException {
try (FirebirdContainer<?> container = new FirebirdContainer<>(imageName).withEnableWireCrypt()) {
container.start();

if (FirebirdContainer.isWireEncryptionSupported()) {
Expand Down Expand Up @@ -174,9 +186,32 @@ void test259_ssImage() throws Exception {
}
}

/**
* The fdcastel/firebird images handle FIREBIRD_DATABASE and need an absolute path to access the database
*/
@Test
void testWithAdditionalUrlParamInJdbcUrl() {
try (FirebirdContainer<?> firebird = new FirebirdContainer<>(FIREBIRD_TEST_IMAGE)
void fdCastleImage_databaseName() throws Exception {
try (FirebirdContainer<?> container = new FirebirdContainer<>(FDCASTEL_TEST_IMAGE).withDatabaseName("test")) {
assertEquals("test", container.getDatabaseName(), "Expect original database name before start");

container.start();

assertEquals("/var/lib/firebird/data/test", container.getDatabaseName(),
"Expect modified database name after start");

try (Connection connection = DriverManager
.getConnection("jdbc:firebirdsql://" + container.getHost() + ":" + container.getMappedPort(FIREBIRD_PORT) + "/" + container.getDatabaseName(),
container.getUsername(), container.getPassword())
) {
assertTrue(connection.isValid(1000));
}
}
}

@ParameterizedTest
@MethodSource("defaultTestImages")
void testWithAdditionalUrlParamInJdbcUrl(DockerImageName imageName) {
try (FirebirdContainer<?> firebird = new FirebirdContainer<>(imageName)
.withUrlParam("charSet", "utf-8")
.withUrlParam("blobBufferSize", "2048")) {

Expand All @@ -189,4 +224,8 @@ void testWithAdditionalUrlParamInJdbcUrl() {
containsString("charSet=utf-8")));
}
}

static Stream<DockerImageName> defaultTestImages() {
return Stream.of(FIREBIRD_TEST_IMAGE, FDCASTEL_TEST_IMAGE);
}
}
Loading

0 comments on commit 9d9e6e2

Please sign in to comment.