Skip to content

Commit

Permalink
Fix docker compose environment parsing for bitnami images
Browse files Browse the repository at this point in the history
Signed-off-by: He Zean <[email protected]>
  • Loading branch information
hezean committed Jan 10, 2025
1 parent 7900023 commit d715ba1
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
* @author He Zean
*/
class PostgresJdbcDockerComposeConnectionDetailsFactoryIntegrationTests {

@DockerComposeTest(composeFile = "postgres-compose.yaml", image = TestImage.POSTGRESQL)
void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) {
void runCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException {
assertConnectionDetails(connectionDetails);
}

Expand All @@ -53,10 +54,21 @@ void runCreatesConnectionDetailsThatCanAccessDatabaseWhenHostAuthMethodIsTrust(
}

@DockerComposeTest(composeFile = "postgres-bitnami-compose.yaml", image = TestImage.BITNAMI_POSTGRESQL)
void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails) {
void runWithBitnamiImageCreatesConnectionDetails(JdbcConnectionDetails connectionDetails)
throws ClassNotFoundException {
assertConnectionDetails(connectionDetails);
}

@DockerComposeTest(composeFile = "postgres-bitnami-compose-empty-password.yaml",
image = TestImage.BITNAMI_POSTGRESQL)
void runWithBitnamiImageCreatesConnectionDetailsWithAllowEmptyPassword(JdbcConnectionDetails connectionDetails)
throws ClassNotFoundException {
assertThat(connectionDetails.getUsername()).isEqualTo("myuser");
assertThat(connectionDetails.getPassword()).isEmpty();
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase");
checkDatabaseAccess(connectionDetails);
}

@DockerComposeTest(composeFile = "postgres-application-name-compose.yaml", image = TestImage.POSTGRESQL)
void runCreatesConnectionDetailsApplicationName(JdbcConnectionDetails connectionDetails)
throws ClassNotFoundException {
Expand All @@ -68,10 +80,11 @@ void runCreatesConnectionDetailsApplicationName(JdbcConnectionDetails connection
.isEqualTo("spring boot");
}

private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) {
private void assertConnectionDetails(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException {
assertThat(connectionDetails.getUsername()).isEqualTo("myuser");
assertThat(connectionDetails.getPassword()).isEqualTo("secret");
assertThat(connectionDetails.getJdbcUrl()).startsWith("jdbc:postgresql://").endsWith("/mydatabase");
checkDatabaseAccess(connectionDetails);
}

private void checkDatabaseAccess(JdbcConnectionDetails connectionDetails) throws ClassNotFoundException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
* @author He Zean
*/
class PostgresR2dbcDockerComposeConnectionDetailsFactoryIntegrationTests {

Expand All @@ -61,6 +62,17 @@ void runWithBitnamiImageCreatesConnectionDetails(R2dbcConnectionDetails connecti
assertConnectionDetails(connectionDetails);
}

@DockerComposeTest(composeFile = "postgres-bitnami-compose-empty-password.yaml",
image = TestImage.BITNAMI_POSTGRESQL)
void runWithBitnamiImageCreatesConnectionDetailsWithAllowEmptyPassword(R2dbcConnectionDetails connectionDetails) {
ConnectionFactoryOptions connectionFactoryOptions = connectionDetails.getConnectionFactoryOptions();
assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("myuser");
assertThat(connectionFactoryOptions.getValue(ConnectionFactoryOptions.PASSWORD)).isNull();
assertThat(connectionFactoryOptions.getRequiredValue(ConnectionFactoryOptions.DATABASE))
.isEqualTo("mydatabase");
checkDatabaseAccess(connectionDetails);
}

@DockerComposeTest(composeFile = "postgres-application-name-compose.yaml", image = TestImage.POSTGRESQL)
void runCreatesConnectionDetailsApplicationName(R2dbcConnectionDetails connectionDetails) {
assertConnectionDetails(connectionDetails);
Expand All @@ -78,6 +90,7 @@ private void assertConnectionDetails(R2dbcConnectionDetails connectionDetails) {
assertThat(options.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("myuser");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("secret");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("postgresql");
checkDatabaseAccess(connectionDetails);
}

private void checkDatabaseAccess(R2dbcConnectionDetails connectionDetails) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
database:
image: '{imageName}'
ports:
- '5432'
environment:
- 'POSTGRESQL_USERNAME=myuser'
- 'POSTGRESQL_DATABASE=mydatabase'
- 'ALLOW_EMPTY_PASSWORD=yes'
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ services:
ports:
- '5432'
environment:
- 'POSTGRESQL_USER=myuser'
- 'POSTGRESQL_DB=mydatabase'
- 'POSTGRESQL_USERNAME=myuser'
- 'POSTGRESQL_DATABASE=mydatabase'
- 'POSTGRESQL_PASSWORD=secret'
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Map;

import org.springframework.boot.docker.compose.service.util.BitnamiUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

Expand All @@ -41,7 +42,7 @@ class ClickHouseEnvironment {
}

private String extractPassword(Map<String, String> env) {
boolean allowEmpty = Boolean.parseBoolean(env.getOrDefault("ALLOW_EMPTY_PASSWORD", Boolean.FALSE.toString()));
boolean allowEmpty = BitnamiUtils.isBooleanYes(env.get("ALLOW_EMPTY_PASSWORD"));
String password = env.get("CLICKHOUSE_PASSWORD");
Assert.state(StringUtils.hasLength(password) || allowEmpty, "No ClickHouse password found");
return (password != null) ? password : "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Map;

import org.springframework.boot.docker.compose.service.util.BitnamiUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

Expand Down Expand Up @@ -53,7 +54,8 @@ private String extractPassword(Map<String, String> env) {
Assert.state(!env.containsKey("MYSQL_RANDOM_ROOT_PASSWORD"), "MYSQL_RANDOM_ROOT_PASSWORD is not supported");
Assert.state(!env.containsKey("MARIADB_ROOT_PASSWORD_HASH"), "MARIADB_ROOT_PASSWORD_HASH is not supported");
boolean allowEmpty = env.containsKey("MARIADB_ALLOW_EMPTY_PASSWORD")
|| env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD") || env.containsKey("ALLOW_EMPTY_PASSWORD");
|| env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD")
|| BitnamiUtils.isBooleanYes(env.get("ALLOW_EMPTY_PASSWORD"));
String password = env.get("MARIADB_PASSWORD");
password = (password != null) ? password : env.get("MYSQL_PASSWORD");
password = (password != null) ? password : env.get("MARIADB_ROOT_PASSWORD");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Map;

import org.springframework.boot.docker.compose.service.util.BitnamiUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

Expand Down Expand Up @@ -45,7 +46,8 @@ class MySqlEnvironment {

private String extractPassword(Map<String, String> env) {
Assert.state(!env.containsKey("MYSQL_RANDOM_ROOT_PASSWORD"), "MYSQL_RANDOM_ROOT_PASSWORD is not supported");
boolean allowEmpty = env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD") || env.containsKey("ALLOW_EMPTY_PASSWORD");
boolean allowEmpty = env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD")
|| BitnamiUtils.isBooleanYes(env.get("ALLOW_EMPTY_PASSWORD"));
String password = env.get("MYSQL_PASSWORD");
password = (password != null) ? password : env.get("MYSQL_ROOT_PASSWORD");
Assert.state(StringUtils.hasLength(password) || allowEmpty, "No MySQL password found");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package org.springframework.boot.docker.compose.service.connection.postgres;

import java.util.Map;
import java.util.Objects;

import org.springframework.boot.docker.compose.service.util.BitnamiUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

Expand All @@ -29,6 +31,7 @@
* @author Phillip Webb
* @author Scott Frederick
* @author Sidmar Theodoro
* @author He Zean
*/
class PostgresEnvironment {

Expand All @@ -39,18 +42,24 @@ class PostgresEnvironment {
private final String database;

PostgresEnvironment(Map<String, String> env) {
this.username = env.getOrDefault("POSTGRES_USER", env.getOrDefault("POSTGRESQL_USER", "postgres"));
this.username = env.getOrDefault("POSTGRES_USER", env.getOrDefault("POSTGRESQL_USERNAME", "postgres"));
this.password = extractPassword(env);
this.database = env.getOrDefault("POSTGRES_DB", env.getOrDefault("POSTGRESQL_DB", this.username));
this.database = env.getOrDefault("POSTGRES_DB", env.getOrDefault("POSTGRESQL_DATABASE", this.username));
}

private String extractPassword(Map<String, String> env) {
if (isUsingTrustHostAuthMethod(env)) {
return null;
}
String password = env.getOrDefault("POSTGRES_PASSWORD", env.get("POSTGRESQL_PASSWORD"));
Assert.state(StringUtils.hasLength(password), "PostgreSQL password must be provided");
return password;
Assert.state(isAllowingEmptyPassword(env) || StringUtils.hasLength(password),
"PostgreSQL password must be provided");
return Objects.requireNonNullElse(password, "");
}

private boolean isAllowingEmptyPassword(Map<String, String> env) {
String allowEmptyPassword = env.get("ALLOW_EMPTY_PASSWORD");
return BitnamiUtils.isBooleanYes(allowEmptyPassword);
}

private boolean isUsingTrustHostAuthMethod(Map<String, String> env) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.docker.compose.service.util;

import java.util.Locale;
import java.util.Objects;

/**
* Vendor specific utilities for Bitnami.
*
* @author He Zean
* @since 3.4.1
*/
public abstract class BitnamiUtils {

/**
* Checks if the provided argument is a boolean or is the string 'yes/true'.
* Equivalent to the {@code is_boolean_yes} function in Bitnami's
* {@code libvalidations.sh}.
* @param value value to check
* @return parsed boolean value
*/
public static boolean isBooleanYes(String value) {
if (Objects.isNull(value)) {
return false;
}

value = value.toLowerCase(Locale.ROOT);
return "1".equals(value) || "yes".equals(value) || "true".equals(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Service connection utilities for Docker Compose.
*/
package org.springframework.boot.docker.compose.service.util;
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ void getPasswordWhenHasPassword() {

@Test
void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() {
ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "true"));
ClickHouseEnvironment environment = new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "yes"));
assertThat(environment.getPassword()).isEmpty();
}

@Test
void getPasswordWhenHasNoPasswordAndAllowEmptyPasswordIsFalse() {
void getPasswordWhenHasNoPasswordAndAllowEmptyPasswordIsNo() {
assertThatIllegalStateException()
.isThrownBy(() -> new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "false")))
.isThrownBy(() -> new ClickHouseEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "no")))
.withMessage("No ClickHouse password found");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ void getPasswordWhenHasMariadbPasswordAndMysqlRootPassword() {
@Test
void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() {
MariaDbEnvironment environment = new MariaDbEnvironment(
Map.of("ALLOW_EMPTY_PASSWORD", "true", "MARIADB_DATABASE", "db"));
Map.of("ALLOW_EMPTY_PASSWORD", "yes", "MARIADB_DATABASE", "db"));
assertThat(environment.getPassword()).isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ void getPasswordWhenHasNoPasswordAndMysqlAllowEmptyPassword() {
@Test
void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() {
MySqlEnvironment environment = new MySqlEnvironment(
Map.of("ALLOW_EMPTY_PASSWORD", "true", "MYSQL_DATABASE", "db"));
Map.of("ALLOW_EMPTY_PASSWORD", "yes", "MYSQL_DATABASE", "db"));
assertThat(environment.getPassword()).isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* @author Phillip Webb
* @author Scott Frederick
* @author Sidmar Theodoro
* @author He Zean
*/
class PostgresEnvironmentTests {

Expand Down Expand Up @@ -63,7 +64,7 @@ void getUsernameWhenHasPostgresUser() {
@Test
void getUsernameWhenHasPostgresqlUser() {
PostgresEnvironment environment = new PostgresEnvironment(
Map.of("POSTGRESQL_USER", "me", "POSTGRESQL_PASSWORD", "secret"));
Map.of("POSTGRES_USER", "me", "POSTGRESQL_PASSWORD", "secret"));
assertThat(environment.getUsername()).isEqualTo("me");
}

Expand All @@ -85,6 +86,12 @@ void getPasswordWhenHasTrustHostAuthMethod() {
assertThat(environment.getPassword()).isNull();
}

@Test
void getPasswordWhenHasNoPasswordAndAllowEmptyPassword() {
PostgresEnvironment environment = new PostgresEnvironment(Map.of("ALLOW_EMPTY_PASSWORD", "yes"));
assertThat(environment.getPassword()).isEmpty();
}

@Test
void getDatabaseWhenNoPostgresDbOrPostgresUser() {
PostgresEnvironment environment = new PostgresEnvironment(Map.of("POSTGRES_PASSWORD", "secret"));
Expand All @@ -107,7 +114,7 @@ void getDatabaseWhenNoPostgresDbAndPostgresUser() {
@Test
void getDatabaseWhenNoPostgresqlDbAndPostgresUser() {
PostgresEnvironment environment = new PostgresEnvironment(
Map.of("POSTGRESQL_USER", "me", "POSTGRESQL_PASSWORD", "secret"));
Map.of("POSTGRES_USER", "me", "POSTGRESQL_PASSWORD", "secret"));
assertThat(environment.getDatabase()).isEqualTo("me");
}

Expand All @@ -119,9 +126,9 @@ void getDatabaseWhenHasPostgresDb() {
}

@Test
void getDatabaseWhenHasPostgresqlDb() {
void getDatabaseWhenHasPostgresqlDatabase() {
PostgresEnvironment environment = new PostgresEnvironment(
Map.of("POSTGRESQL_DB", "db", "POSTGRESQL_PASSWORD", "secret"));
Map.of("POSTGRESQL_DATABASE", "db", "POSTGRESQL_PASSWORD", "secret"));
assertThat(environment.getDatabase()).isEqualTo("db");
}

Expand Down
Loading

0 comments on commit d715ba1

Please sign in to comment.