Merge pull request #2487 from mposolda/master
databaseSchema option - proper support for "validate" for both JPA and Mongo
This commit is contained in:
commit
1714422b10
8 changed files with 132 additions and 46 deletions
|
@ -186,7 +186,9 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||||
<term>databaseSchema</term>
|
<term>databaseSchema</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Specify if schema should be updated or validated. Valid values are "update" and "validate" ("update is default).
|
Specify if schema should be updated or validated. Valid values are <literal>update</literal> and <literal>validate</literal>.
|
||||||
|
Value <literal>update</literal> is default and means that DB schema will be updated to latest version.
|
||||||
|
Value <literal>validate</literal> won't touch database schema, but it will fail to start if schema is not updated to latest version.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -303,7 +305,11 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||||
},
|
},
|
||||||
|
|
||||||
"user": {
|
"user": {
|
||||||
"provider": "${keycloak.user.provider:jpa}"
|
"provider": "jpa"
|
||||||
|
},
|
||||||
|
|
||||||
|
"userSessionPersister": {
|
||||||
|
"provider": "jpa"
|
||||||
},
|
},
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
|
|
||||||
|
@ -324,6 +330,10 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||||
"user": {
|
"user": {
|
||||||
"provider": "mongo"
|
"provider": "mongo"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"userSessionPersister": {
|
||||||
|
"provider": "mongo"
|
||||||
|
},
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
|
|
||||||
And at the end of the file add the snippet like this where you can configure details about your Mongo database:
|
And at the end of the file add the snippet like this where you can configure details about your Mongo database:
|
||||||
|
@ -333,7 +343,8 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"port": "27017",
|
"port": "27017",
|
||||||
"db": "keycloak",
|
"db": "keycloak",
|
||||||
"connectionsPerHost": 100
|
"connectionsPerHost": 100,
|
||||||
|
"databaseSchema": "update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
|
@ -343,6 +354,14 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||||
if you want authenticate against your MongoDB. If user and password are not specified, Keycloak will connect
|
if you want authenticate against your MongoDB. If user and password are not specified, Keycloak will connect
|
||||||
unauthenticated to your MongoDB.
|
unauthenticated to your MongoDB.
|
||||||
</para>
|
</para>
|
||||||
|
<para>
|
||||||
|
Option <literal>databaseSchema</literal> specify if mongo database "schema" should be updated or validated. Valid values are <literal>update</literal> and <literal>validate</literal>.
|
||||||
|
Value <literal>update</literal> means that DB schema will be updated to latest version.
|
||||||
|
Value <literal>validate</literal> won't touch database schema, but it will fail to start if schema is not updated to latest version.
|
||||||
|
</para>
|
||||||
|
<note>
|
||||||
|
Mongo doesn't have real database schema, but 'schema' in this case means to which Keycloak version the data in the Mongo database corresponds.
|
||||||
|
</note>
|
||||||
<para>Finally there is set of optional configuration options, which can be used to specify connection-pooling capabilities of Mongo client. Supported int options are:
|
<para>Finally there is set of optional configuration options, which can be used to specify connection-pooling capabilities of Mongo client. Supported int options are:
|
||||||
<literal>connectionsPerHost</literal>, <literal>threadsAllowedToBlockForConnectionMultiplier</literal>, <literal>maxWaitTime</literal>, <literal>connectTimeout</literal>
|
<literal>connectionsPerHost</literal>, <literal>threadsAllowedToBlockForConnectionMultiplier</literal>, <literal>maxWaitTime</literal>, <literal>connectTimeout</literal>
|
||||||
<literal>socketTimeout</literal>. Supported boolean options are: <literal>socketKeepAlive</literal>, <literal>autoConnectRetry</literal>.
|
<literal>socketTimeout</literal>. Supported boolean options are: <literal>socketKeepAlive</literal>, <literal>autoConnectRetry</literal>.
|
||||||
|
@ -359,7 +378,8 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||||
"connectionsMongo": {
|
"connectionsMongo": {
|
||||||
"default": {
|
"default": {
|
||||||
"uri": "mongodb://user:password@127.0.0.1/authentication",
|
"uri": "mongodb://user:password@127.0.0.1/authentication",
|
||||||
"db": "keycloak"
|
"db": "keycloak",
|
||||||
|
"databaseSchema": "update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
|
|
|
@ -95,8 +95,6 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
|
|
||||||
Connection connection = null;
|
Connection connection = null;
|
||||||
|
|
||||||
String databaseSchema = config.get("databaseSchema");
|
|
||||||
|
|
||||||
Map<String, Object> properties = new HashMap<String, Object>();
|
Map<String, Object> properties = new HashMap<String, Object>();
|
||||||
|
|
||||||
String unitName = "keycloak-default";
|
String unitName = "keycloak-default";
|
||||||
|
@ -127,7 +125,12 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
|
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (databaseSchema != null) {
|
|
||||||
|
String databaseSchema = config.get("databaseSchema");
|
||||||
|
if (databaseSchema == null) {
|
||||||
|
throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration");
|
||||||
|
}
|
||||||
|
|
||||||
if (databaseSchema.equals("development-update")) {
|
if (databaseSchema.equals("development-update")) {
|
||||||
properties.put("hibernate.hbm2ddl.auto", "update");
|
properties.put("hibernate.hbm2ddl.auto", "update");
|
||||||
databaseSchema = null;
|
databaseSchema = null;
|
||||||
|
@ -135,7 +138,6 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
properties.put("hibernate.hbm2ddl.auto", "validate");
|
properties.put("hibernate.hbm2ddl.auto", "validate");
|
||||||
databaseSchema = null;
|
databaseSchema = null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
||||||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||||
|
|
|
@ -21,6 +21,7 @@ import liquibase.Contexts;
|
||||||
import liquibase.Liquibase;
|
import liquibase.Liquibase;
|
||||||
import liquibase.changelog.ChangeSet;
|
import liquibase.changelog.ChangeSet;
|
||||||
import liquibase.changelog.RanChangeSet;
|
import liquibase.changelog.RanChangeSet;
|
||||||
|
import liquibase.exception.LiquibaseException;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||||
|
@ -96,16 +97,27 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(Connection connection, String defaultSchema) {
|
public void validate(Connection connection, String defaultSchema) {
|
||||||
|
logger.debug("Validating if database is updated");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Liquibase liquibase = getLiquibase(connection, defaultSchema);
|
Liquibase liquibase = getLiquibase(connection, defaultSchema);
|
||||||
|
|
||||||
liquibase.validate();
|
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||||
} catch (Exception e) {
|
if (!changeSets.isEmpty()) {
|
||||||
|
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||||
|
String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database",
|
||||||
|
ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
|
||||||
|
throw new RuntimeException(errorMessage);
|
||||||
|
} else {
|
||||||
|
logger.debug("Validation passed. Database is up-to-date");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (LiquibaseException e) {
|
||||||
throw new RuntimeException("Failed to validate database", e);
|
throw new RuntimeException("Failed to validate database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception {
|
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
|
||||||
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||||
return liquibaseProvider.getLiquibase(connection, defaultSchema);
|
return liquibaseProvider.getLiquibase(connection, defaultSchema);
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,13 +156,24 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update(KeycloakSession session) {
|
private void update(KeycloakSession session) {
|
||||||
MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
|
String databaseSchema = config.get("databaseSchema");
|
||||||
|
|
||||||
|
if (databaseSchema == null) {
|
||||||
|
throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration of mongo connections");
|
||||||
|
} else {
|
||||||
|
MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
|
||||||
if (mongoUpdater == null) {
|
if (mongoUpdater == null) {
|
||||||
throw new RuntimeException("Can't update database: Mongo updater provider not found");
|
throw new RuntimeException("Can't update database: Mongo updater provider not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchema.equals("update")) {
|
||||||
mongoUpdater.update(session, db);
|
mongoUpdater.update(session, db);
|
||||||
|
} else if (databaseSchema.equals("validate")) {
|
||||||
|
mongoUpdater.validate(session, db);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.keycloak.provider.Provider;
|
||||||
*/
|
*/
|
||||||
public interface MongoUpdaterProvider extends Provider {
|
public interface MongoUpdaterProvider extends Provider {
|
||||||
|
|
||||||
public void update(KeycloakSession session, DB db);
|
void update(KeycloakSession session, DB db);
|
||||||
|
|
||||||
|
void validate(KeycloakSession session, DB db);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,38 +65,19 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
||||||
log.debug("Starting database update");
|
log.debug("Starting database update");
|
||||||
try {
|
try {
|
||||||
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
|
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
|
||||||
boolean realmExists = db.collectionExists("realms");
|
|
||||||
|
|
||||||
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
|
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
|
||||||
|
|
||||||
List<String> executed = new LinkedList<String>();
|
List<String> executed = getExecuted(db, changeLogExists, changeLog);
|
||||||
if (!changeLogExists && realmExists) {
|
List<Update> updatesToRun = getUpdatesToRun(executed);
|
||||||
Update1_0_0_Final u = new Update1_0_0_Final();
|
|
||||||
executed.add(u.getId());
|
|
||||||
createLog(changeLog, u, 1);
|
|
||||||
} else if (changeLogExists) {
|
|
||||||
DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
|
|
||||||
while (cursor.hasNext()) {
|
|
||||||
executed.add((String) cursor.next().get("_id"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Update> updatesToRun = new LinkedList<Update>();
|
|
||||||
for (Class<? extends Update> updateClass : updates) {
|
|
||||||
Update u = updateClass.newInstance();
|
|
||||||
if (!executed.contains(u.getId())) {
|
|
||||||
updatesToRun.add(u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!updatesToRun.isEmpty()) {
|
if (!updatesToRun.isEmpty()) {
|
||||||
if (executed.isEmpty()) {
|
if (executed.isEmpty()) {
|
||||||
log.info("Initializing database schema");
|
log.info("Initializing database schema");
|
||||||
} else {
|
} else {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.infov("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
|
log.debugv("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
|
||||||
} else {
|
} else {
|
||||||
log.debugv("Updating database");
|
log.info("Updating database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +102,66 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(KeycloakSession session, DB db) {
|
||||||
|
log.debug("Validating database");
|
||||||
|
|
||||||
|
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
|
||||||
|
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
|
||||||
|
|
||||||
|
List<String> executed = getExecuted(db, changeLogExists, changeLog);
|
||||||
|
List<Update> updatesToRun = getUpdatesToRun(executed);
|
||||||
|
|
||||||
|
if (!updatesToRun.isEmpty()) {
|
||||||
|
String errorMessage = String.format("Failed to validate Mongo database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update'",
|
||||||
|
executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
|
||||||
|
throw new RuntimeException(errorMessage);
|
||||||
|
} else {
|
||||||
|
log.debug("Validation passed. Database is up to date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<String> getExecuted(DB db, boolean changeLogExists, DBCollection changeLog) {
|
||||||
|
boolean realmExists = db.collectionExists("realms");
|
||||||
|
|
||||||
|
List<String> executed = new LinkedList<>();
|
||||||
|
if (!changeLogExists && realmExists) {
|
||||||
|
Update1_0_0_Final u = new Update1_0_0_Final();
|
||||||
|
executed.add(u.getId());
|
||||||
|
createLog(changeLog, u, 1);
|
||||||
|
} else if (changeLogExists) {
|
||||||
|
DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
executed.add((String) cursor.next().get("_id"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<Update> getUpdatesToRun(List<String> executed) {
|
||||||
|
try {
|
||||||
|
List<Update> updatesToRun = new LinkedList<>();
|
||||||
|
for (Class<? extends Update> updateClass : updates) {
|
||||||
|
Update u = updateClass.newInstance();
|
||||||
|
if (!executed.contains(u.getId())) {
|
||||||
|
updatesToRun.add(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatesToRun;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
|
private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
|
||||||
changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted));
|
changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@
|
||||||
"host": "${keycloak.connectionsMongo.host:127.0.0.1}",
|
"host": "${keycloak.connectionsMongo.host:127.0.0.1}",
|
||||||
"port": "${keycloak.connectionsMongo.port:27017}",
|
"port": "${keycloak.connectionsMongo.port:27017}",
|
||||||
"db": "${keycloak.connectionsMongo.db:keycloak}",
|
"db": "${keycloak.connectionsMongo.db:keycloak}",
|
||||||
|
"databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
|
||||||
"connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
|
"connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
"host": "${keycloak.connectionsMongo.host:127.0.0.1}",
|
"host": "${keycloak.connectionsMongo.host:127.0.0.1}",
|
||||||
"port": "${keycloak.connectionsMongo.port:27017}",
|
"port": "${keycloak.connectionsMongo.port:27017}",
|
||||||
"db": "${keycloak.connectionsMongo.db:keycloak}",
|
"db": "${keycloak.connectionsMongo.db:keycloak}",
|
||||||
|
"databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
|
||||||
"connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
|
"connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue