From 6a7408aaa9af44101db3e0a71f954a541af97a78 Mon Sep 17 00:00:00 2001 From: Arne Diekmann Date: Tue, 5 Mar 2024 12:26:36 +0100 Subject: [PATCH] fix(runtime-env): remove dependency on dockerize for cross-plattform support --- images/runtime-env/Dockerfile | 22 +-- images/runtime-env/build/bin/run.sh | 7 +- images/runtime-env/build/context.xml.tmpl | 15 -- .../java/de/neoskop/DockerEntrypoint.java | 2 + .../java/de/neoskop/model/Datasource.java | 64 ++++++++ .../neoskop/service/ContextSetupService.java | 54 +++++++ .../service/DatasourceParserService.java | 69 +++++++++ .../neoskop/service/WaitOnMySQLService.java | 141 ++++-------------- images/runtime-env/docker-compose.tls.yml | 4 +- images/runtime-env/docker-compose.yml | 3 +- 10 files changed, 224 insertions(+), 157 deletions(-) delete mode 100644 images/runtime-env/build/context.xml.tmpl create mode 100644 images/runtime-env/build/entrypoint/src/main/java/de/neoskop/model/Datasource.java create mode 100644 images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/ContextSetupService.java create mode 100644 images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/DatasourceParserService.java diff --git a/images/runtime-env/Dockerfile b/images/runtime-env/Dockerfile index 90a2f5f..b9779ec 100644 --- a/images/runtime-env/Dockerfile +++ b/images/runtime-env/Dockerfile @@ -1,29 +1,16 @@ -FROM scratch -FROM alpine AS dockerize -ENV DOCKERIZE_VERSION=v0.6.1 -RUN apk add --no-cache wget ca-certificates && \ - cd /tmp && \ - wget -q https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - tar -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - rm -f dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz - -FROM maven:3.6.3-jdk-13 as java-entrypoint +FROM maven:3.9.6-eclipse-temurin-17 as java-entrypoint WORKDIR /root COPY entrypoint ./ RUN mvn -T 1C package -FROM tomcat:9.0.33-jdk13-openjdk-oracle +FROM tomcat:10.1.19-jdk17-temurin-jammy LABEL maintainer="Neoskop DevOps " -# Setup dockerize -COPY --from=dockerize /tmp/dockerize /usr/local/bin - # Setup custom entrypoint COPY --from=java-entrypoint /root/target/docker-entrypoint.jar /usr/local/bin # Setup Tomcat config template RUN mkdir -p $CATALINA_HOME/conf/Catalina/localhost -COPY context.xml.tmpl $CATALINA_HOME/conf/Catalina/localhost/ROOT.xml.tmpl # Overwrite server.xml COPY server.xml $CATALINA_HOME/conf/ @@ -52,9 +39,8 @@ RUN mkdir -p /home/tomcat/light-modules # Set workdir WORKDIR /home/tomcat -# Expose port 8080 for http access and 5005 for debugging +# Expose port 8080 for http access and 5005 for debugging EXPOSE 8080 5005 CMD ["/usr/local/bin/run.sh"] -VOLUME [ "/home/tomcat/magnolia_tmp/repositories" ] -ENTRYPOINT ["dockerize", "-template", "/usr/local/tomcat/conf/Catalina/localhost/ROOT.xml.tmpl:/usr/local/tomcat/conf/Catalina/localhost/ROOT.xml"] +VOLUME [ "/home/tomcat/magnolia_tmp/repositories" ] \ No newline at end of file diff --git a/images/runtime-env/build/bin/run.sh b/images/runtime-env/build/bin/run.sh index e98eb9c..76af9a4 100755 --- a/images/runtime-env/build/bin/run.sh +++ b/images/runtime-env/build/bin/run.sh @@ -3,12 +3,9 @@ set -e if ! [ -d /home/tomcat/light-modules/mtk ] && [ -w /home/tomcat/light-modules ]; then mkdir -p /home/tomcat/light-modules/mtk/templates/pages/ - echo "visible: false" > /home/tomcat/light-modules/mtk/templates/pages/basic.yaml -fi - -if [ -z "$DISABLE_CONNECTION_CHECK" ]; then - java -jar /usr/local/bin/docker-entrypoint.jar + echo "visible: false" >/home/tomcat/light-modules/mtk/templates/pages/basic.yaml fi +java -jar /usr/local/bin/docker-entrypoint.jar export JAVA_OPTS="-Dlog4j2.formatMsgNoLookups=true -XX:InitialRAMPercentage=10 -XX:MaxRAMPercentage=80 -XX:MinRAMPercentage=50 -Dmagnolia.repositories.home=/home/tomcat/magnolia_tmp/repositories -Djava.awt.headless=true -Dfile.encoding=UTF-8 $JAVA_OPTS" ${CATALINA_HOME}/bin/catalina.sh run diff --git a/images/runtime-env/build/context.xml.tmpl b/images/runtime-env/build/context.xml.tmpl deleted file mode 100644 index 89177f7..0000000 --- a/images/runtime-env/build/context.xml.tmpl +++ /dev/null @@ -1,15 +0,0 @@ - - - - WEB-INF/web.xml - - - - {{ if .Env.DATASOURCES }} {{ range $index, $datasource := jsonQuery .Env.DATASOURCES "datasources" }} - - {{ end }} - {{ end }} - {{ if .Env.MAGNOLIA_CONFIG }} - - {{ end }} - \ No newline at end of file diff --git a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/DockerEntrypoint.java b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/DockerEntrypoint.java index f437ed0..1d5989e 100644 --- a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/DockerEntrypoint.java +++ b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/DockerEntrypoint.java @@ -1,9 +1,11 @@ package de.neoskop; +import de.neoskop.service.ContextSetupService; import de.neoskop.service.WaitOnMySQLService; public class DockerEntrypoint { public static void main(String[] args) { + ContextSetupService.setupContext(); WaitOnMySQLService.waitForAllConnections(); } } diff --git a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/model/Datasource.java b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/model/Datasource.java new file mode 100644 index 0000000..f879c6e --- /dev/null +++ b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/model/Datasource.java @@ -0,0 +1,64 @@ +package de.neoskop.model; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +public class Datasource { + private static final Logger logger = LogManager.getLogger(Datasource.class); + + public final String name; + public final String host; + public final String username; + public final String password; + public final String database; + public final String port; + public final boolean useSsl; + public final String trustStore; + public final String trustStorePassword; + public final String enabledTLSProtocols; + + public Datasource(String name, String host, String username, String password, String database, String port, + boolean useSsl, + String trustStore, String trustStorePassword, String enabledTLSProtocols) { + this.name = name; + this.host = host; + this.username = username; + this.password = password; + this.database = database; + this.port = port; + this.useSsl = useSsl; + this.trustStore = trustStore; + this.trustStorePassword = trustStorePassword; + this.enabledTLSProtocols = enabledTLSProtocols; + } + + public String getConnectionUrl() { + final StringBuilder sb = new StringBuilder("jdbc:mysql://"); + sb.append(host); + sb.append(":"); + sb.append(port); + sb.append("/"); + sb.append(database); + sb.append("?user="); + sb.append(username); + sb.append("&password="); + sb.append(password); + sb.append("&useSSL="); + sb.append(useSsl); + + if (trustStore != null) { + sb.append("&trustCertificateKeyStoreUrl=file://"); + sb.append(trustStore); + sb.append("&trustCertificateKeyStorePassword="); + sb.append(trustStorePassword); + } + + if (enabledTLSProtocols != null) { + sb.append("&enabledTLSProtocols="); + sb.append(enabledTLSProtocols); + } + + logger.debug("Connection URL: " + sb.toString()); + return sb.toString(); + } +} \ No newline at end of file diff --git a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/ContextSetupService.java b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/ContextSetupService.java new file mode 100644 index 0000000..e458515 --- /dev/null +++ b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/ContextSetupService.java @@ -0,0 +1,54 @@ +package de.neoskop.service; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import de.neoskop.model.Datasource; + +public class ContextSetupService { + private static final Logger LOGGER = LogManager.getLogger(ContextSetupService.class); + private static final String CATALINA_HOME = System.getenv("CATALINA_HOME"); + private static final String MAGNOLIA_CONFIG = System.getenv("MAGNOLIA_CONFIG"); + + public static void setupContext() { + File contextFile = new File(CATALINA_HOME + "/conf/Catalina/localhost/ROOT.xml"); + try (PrintWriter writer = new PrintWriter(new FileWriter(contextFile))) { + writer.println(""); + writer.println(""); + + writer.println(" "); + writer.println(" WEB-INF/web.xml"); + writer.println(" "); + writer.println(" "); + writer.println(" "); + + if (MAGNOLIA_CONFIG != null) { + writer.printf( + " \n", + MAGNOLIA_CONFIG); + } + + final List datasources = DatasourceParserService.getDatasources(); + + if (datasources != null) { + datasources.stream().forEach(datasource -> { + writer.printf( + " "); + }); + } + + writer.println(""); + } catch (IOException e) { + LOGGER.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/DatasourceParserService.java b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/DatasourceParserService.java new file mode 100644 index 0000000..89b3d49 --- /dev/null +++ b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/DatasourceParserService.java @@ -0,0 +1,69 @@ +package de.neoskop.service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import de.neoskop.model.Datasource; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.StreamSupport; + +class DatasourceParserService { + public static List getDatasources() { + final String json = System.getenv("DATASOURCES"); + + if (json == null || json.equals("")) { + return null; + } + + final JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + final JsonArray datasources = jsonObject.get("datasources").getAsJsonArray(); + + return StreamSupport.stream(datasources.spliterator(), false).map(JsonElement::getAsJsonObject) + .map(datasource -> { + final String name = getStringWithDefault(datasource, "name", ""); + final String host = getStringWithDefault(datasource, "host", ""); + final String username = getStringWithDefault(datasource, "username", "root"); + final String password = getStringWithDefault(datasource, "password", ""); + final String database = getStringWithDefault(datasource, "database", "mysql"); + final String port = getStringWithDefault(datasource, "port", "3306"); + final boolean useSsl = getBooleanWithDefault(datasource, "useSsl", false); + final String trustStore = getStringWithDefault(datasource, "trustStore", null, false); + final String trustStorePassword = getStringWithDefault(datasource, "trustStorePassword", + "changeit"); + final String enabledTLSProtocols = getStringWithDefault(datasource, "enabledTLSProtocols", null); + return new Datasource(name, host, username, password, database, port, useSsl, trustStore, + trustStorePassword, enabledTLSProtocols); + }).toList(); + } + + private static String getStringWithDefault(JsonObject object, String property, String defaultValue) { + return getStringWithDefault(object, property, defaultValue, true); + } + + private static String getStringWithDefault(JsonObject object, String property, String defaultValue, + boolean urlEncode) { + if (object.has(property)) { + final String value = object.get(property).getAsString(); + + if (urlEncode) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } else { + return value; + } + } + + return defaultValue; + } + + private static boolean getBooleanWithDefault(JsonObject object, String property, boolean defaultValue) { + if (object.has(property)) { + return object.get(property).getAsBoolean(); + } + + return defaultValue; + } +} \ No newline at end of file diff --git a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/WaitOnMySQLService.java b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/WaitOnMySQLService.java index 18c4d9b..628cec7 100644 --- a/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/WaitOnMySQLService.java +++ b/images/runtime-env/build/entrypoint/src/main/java/de/neoskop/service/WaitOnMySQLService.java @@ -1,18 +1,13 @@ package de.neoskop.service; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import de.neoskop.exception.WrongCredentialsException; +import de.neoskop.model.Datasource; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.concurrent.*; -import java.util.stream.StreamSupport; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -22,27 +17,10 @@ public class WaitOnMySQLService { private static final int ACCESS_DENIED_ERROR_CODE = 1045; private static final ExecutorService executor = Executors.newFixedThreadPool(5); private static final int DELAY = 1; - private final String hostname; - private final String username; - private final String password; - private final String database; - private final String port; - private final boolean useSsl; - private final String trustStore; - private final String trustStorePassword; - private final String enabledTLSProtocols; - - private WaitOnMySQLService(String hostname, String username, String password, String database, String port, - boolean useSsl, String trustStore, String trustStorePassword, String enabledTLSProtocols) { - this.hostname = hostname; - this.username = username; - this.password = password; - this.database = database; - this.port = port; - this.useSsl = useSsl; - this.trustStore = trustStore; - this.trustStorePassword = trustStorePassword; - this.enabledTLSProtocols = enabledTLSProtocols; + private final Datasource datasource; + + private WaitOnMySQLService(Datasource datasource) { + this.datasource = datasource; } private Future waitForConnection() { @@ -51,7 +29,7 @@ private Future waitForConnection() { for (;;) { try { try { - Connection connection = DriverManager.getConnection(getConnectionUrl()); + Connection connection = DriverManager.getConnection(datasource.getConnectionUrl()); connection.close(); return true; } catch (SQLException e) { @@ -62,7 +40,7 @@ private Future waitForConnection() { } } - logger.info("Waiting for connection to " + hostname); + logger.info("Waiting for connection to " + datasource.host); TimeUnit.SECONDS.sleep(DELAY); } catch (InterruptedException e) { return false; @@ -76,102 +54,33 @@ private Future waitForConnection() { return executor.submit(task); } - private String getConnectionUrl() { - final StringBuilder sb = new StringBuilder("jdbc:mysql://"); - sb.append(hostname); - sb.append(":"); - sb.append(port); - sb.append("/"); - sb.append(database); - sb.append("?user="); - sb.append(username); - sb.append("&password="); - sb.append(password); - sb.append("&useSSL="); - sb.append(useSsl); - - if (trustStore != null) { - sb.append("&trustCertificateKeyStoreUrl=file://"); - sb.append(trustStore); - sb.append("&trustCertificateKeyStorePassword="); - sb.append(trustStorePassword); - } - - if (enabledTLSProtocols != null) { - sb.append("&enabledTLSProtocols="); - sb.append(enabledTLSProtocols); - } - - logger.debug("Connection URL: " + sb.toString()); - return sb.toString(); - } - public static void waitForAllConnections() { DriverManager.setLoginTimeout(10); - final String json = System.getenv("DATASOURCES"); - if (json == null || json.equals("")) { + final List datasources = DatasourceParserService.getDatasources(); + + if (datasources == null) { return; } - final JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); - final JsonArray datasources = jsonObject.get("datasources").getAsJsonArray(); - StreamSupport.stream(datasources.spliterator(), false).map(JsonElement::getAsJsonObject).map(datasource -> { - final String host = getStringWithDefault(datasource, "host", ""); - final String username = getStringWithDefault(datasource, "username", "root"); - final String password = getStringWithDefault(datasource, "password", ""); - final String database = getStringWithDefault(datasource, "database", "mysql"); - final String port = getStringWithDefault(datasource, "port", "3306"); - final boolean useSsl = getBooleanWithDefault(datasource, "useSsl", false); - final String trustStore = getStringWithDefault(datasource, "trustStore", null, false); - final String trustStorePassword = getStringWithDefault(datasource, "trustStorePassword", "changeit"); - final String enabledTLSProtocols = getStringWithDefault(datasource, "enabledTLSProtocols", null); - return new WaitOnMySQLService(host, username, password, database, port, useSsl, trustStore, - trustStorePassword, enabledTLSProtocols); - }).map(WaitOnMySQLService::waitForConnection).forEach(future -> { - boolean credentialsCorrect; + datasources.stream().map(datasource -> new WaitOnMySQLService(datasource)) + .map(WaitOnMySQLService::waitForConnection).forEach(future -> { + boolean credentialsCorrect; - try { - credentialsCorrect = future.get(); - } catch (InterruptedException | ExecutionException e) { - logger.error("Connection test failed: " + e.getMessage()); - return; - } + try { + credentialsCorrect = future.get(); + } catch (InterruptedException | ExecutionException e) { + logger.error("Connection test failed: " + e.getMessage()); + return; + } - if (!credentialsCorrect) { - logger.error("Credentials are incorrect. Exiting."); - System.exit(1); - } - }); + if (!credentialsCorrect) { + logger.error("Credentials are incorrect. Exiting."); + System.exit(1); + } + }); executor.shutdown(); } - private static String getStringWithDefault(JsonObject object, String property, String defaultValue) { - return getStringWithDefault(object, property, defaultValue, true); - } - - private static String getStringWithDefault(JsonObject object, String property, String defaultValue, - boolean urlEncode) { - if (object.has(property)) { - final String value = object.get(property).getAsString(); - - if (urlEncode) { - return URLEncoder.encode(value, StandardCharsets.UTF_8); - } else { - return value; - } - } - - return defaultValue; - } - - private static boolean getBooleanWithDefault(JsonObject object, String property, boolean defaultValue) { - if (object.has(property)) { - return object.get(property).getAsBoolean(); - } - - return defaultValue; - } - } diff --git a/images/runtime-env/docker-compose.tls.yml b/images/runtime-env/docker-compose.tls.yml index ad7699c..74a406b 100644 --- a/images/runtime-env/docker-compose.tls.yml +++ b/images/runtime-env/docker-compose.tls.yml @@ -1,4 +1,3 @@ - version: "3.4" services: sut: @@ -7,6 +6,7 @@ services: context: build environment: LOG_LEVEL: DEBUG + MAGNOLIA_CONFIG: foo DATASOURCES: | { "datasources": [ @@ -37,4 +37,4 @@ services: - --require_secure_transport=ON - --ssl - --default_authentication_plugin=mysql_native_password - - --tls_version=TLSv1.2 \ No newline at end of file + - --tls_version=TLSv1.2 diff --git a/images/runtime-env/docker-compose.yml b/images/runtime-env/docker-compose.yml index 5cf2294..7c5229a 100644 --- a/images/runtime-env/docker-compose.yml +++ b/images/runtime-env/docker-compose.yml @@ -6,6 +6,7 @@ services: context: build environment: LOG_LEVEL: DEBUG + MAGNOLIA_CONFIG: foo DATASOURCES: | { "datasources": [ @@ -24,4 +25,4 @@ services: MYSQL_DATABASE: author MYSQL_USER: author MYSQL_PASSWORD: <&%!^> - MYSQL_RANDOM_ROOT_PASSWORD: "true" \ No newline at end of file + MYSQL_RANDOM_ROOT_PASSWORD: "true"