8000 Error message when JDBC driver is missing is not helpful by mabartos · Pull Request #22833 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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
*
* http://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.keycloak.quarkus.deployment;

import io.quarkus.builder.item.EmptyBuildItem;

/**
* A barrier build item that can be consumed by other build steps when JDBC Driver is checked
*/
public class CheckJdbcBuildStep extends EmptyBuildItem {
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
package org.keycloak.quarkus.deployment;

import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
import io.quarkus.bootstrap.logging.InitialConfigurator;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -40,6 +42,7 @@
import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem;
import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem;
import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.runtime.configuration.ProfileManager;
import io.quarkus.vertx.http.deployment.FilterBuildItem;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
Expand Down Expand Up @@ -135,6 +138,7 @@
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Handler;

import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
import static org.keycloak.quarkus.runtime.Environment.getCurrentOrCreateFeatureProfile;
Expand Down Expand Up @@ -230,6 +234,28 @@ void configureProfile(KeycloakRecorder recorder) {
recorder.configureProfile(profile.getName(), profile.getFeatures());
}

/**
* Check whether JDBC driver is present for the specified DB
*
* @param ignore used for changing build items execution order with regards to AgroalProcessor
*/
@BuildStep(>
@Produce(CheckJdbcBuildStep.class)
void checkJdbcDriver(BuildProducer<JdbcDriverBuildItem> ignore) {
final Optional<String> dbDriver = Configuration.getOptionalValue("quarkus.datasource.jdbc.driver");

if (dbDriver.isPresent()) {
try {
// We do not want to initialize the JDBC driver class
Class.forName(dbDriver.get(), false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
// Ignore queued TRACE and DEBUG messages for not initialized log handlers
InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(new Handler[]{});
@mabartos mabartos Sep 1, 2023 •
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

When an exception is thrown during the execution of the build chain, the log handlers might not be initialized yet, and gathered log records are queued in the QuarkusDelayedHandler. When the queue limit is exceeded, or the minimum log level is set to >=INFO, these TRACE and DEBUG log records are dropped.

When the app crashes after the exception is thrown, the DelayedHandler is closed as well, but it doesn't know how to handle the accumulated log records, because no logger is initialized yet. So, the handler just prints the whole queue of log records to the stderr. As the InitialConfigurator is related to bootstrap, we're not able to programmatically set the queue limit or the discard level in the build chain.

We can just workaround this behavior by setting empty build time handlers, and the queue of log records is cleared.

It might be related to all exception handling for build steps, but I don't want to hide some debug info for the other steps. In this case, we don't obtain any additional valuable info about the JDBC. I don't see any problem with this approach.

throw new ConfigurationException(String.format("Unable to find the JDBC driver (%s). You need to install it.", dbDriver.get()));
}
}
}

/**
* <p>Configures the persistence unit for Quarkus.
*
Expand Down Expand Up @@ -279,6 +305,7 @@ void configurePersistenceUnits(HibernateOrmConfig config,
}

@BuildStep(>
@Consume(CheckJdbcBuildStep.class)
void produceDefaultPersistenceUnit(BuildProducer<PersistenceXmlDescriptorBuildItem> producer) {
String storage = Configuration.getRawValue(
NS_KEYCLOAK_PREFIX.concat(StorageOptions.STORAGE.getKey()));
Expand Down Expand Up @@ -556,11 +583,13 @@ void index(BuildProducer<IndexDependencyBuildItem> indexDependencyBuildItemBuild
}

@BuildStep( >
@Consume(CheckJdbcBuildStep.class)
void indexNewJpaStore(BuildProducer<IndexDependencyBuildItem> indexDependencyBuildItemBuildProducer) {
indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem("org.keycloak", "keycloak-model-map-jpa"));
}

@BuildStep(>
@Consume(CheckJdbcBuildStep.class)
void indexLegacyJpaStore(BuildProducer<IndexDependencyBuildItem> indexDependencyBuildItemBuildProducer) {
indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem("org.keycloak", "keycloak-model-jpa"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;

import org.junit.jupiter.api.Test;
import org.keycloak.config.database.Database;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;

Expand Down Expand Up @@ -76,4 +77,16 @@ void testDoNotRecordRuntimeOptionsDuringBuild(KeycloakDistribution distribution)
CLIResult result = distribution.run("start", "--hostname=mykeycloak", OPTIMIZED_BUILD_OPTION_LONG);
result.assertMessage("Key material not provided to setup HTTPS");
}

@Test
@RawDistOnly(reason = "Containers are immutable")
@Launch({"build", "--db=oracle"})
void missingOracleJdbcDriver(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;

String dbDriver = Database.getDriver("oracle", true).orElse("");

cliResult.assertError(String.format("ERROR: Unable to find the JDBC driver (%s). You need to install it.", dbDriver));
cliResult.assertNoBuild();
}
}
0