mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Avoid breaking DB changes during patch releases
Closes #38888 Signed-off-by: Ryan Emerson <remerson@ibm.com> Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com> Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Co-authored-by: Pedro Ruivo <pruivo@users.noreply.github.com> Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
parent
047230a052
commit
2c6f56acdc
25 changed files with 1235 additions and 20 deletions
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
find . -path '**/src/test/java' -type d \
|
||||
| grep -v -E '\./(docs|distribution|misc|operator|((.+/)?tests)|testsuite|test-framework|quarkus)/' \
|
||||
| grep -v -E '\./(docs|distribution|operator|((.+/)?tests)|testsuite|test-framework|quarkus)/' \
|
||||
| sed 's|/src/test/java||' \
|
||||
| sed 's|./||' \
|
||||
| sort \
|
||||
|
|
|
|||
87
misc/db-compatibility-verifier/README.md
Normal file
87
misc/db-compatibility-verifier/README.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Database Compatibility Verifier Maven Plugin
|
||||
|
||||
## Overview
|
||||
|
||||
This Maven plugin is used to verify the database compatibility of Keycloak. It ensures that all database schema changes
|
||||
(ChangeSets) are explicitly marked as either supported or unsupported by the rolling upgrades feature.
|
||||
|
||||
## Goals
|
||||
|
||||
The plugin provides the following goals:
|
||||
|
||||
* `db-compatibility-verifier:snapshot`
|
||||
* `db-compatibility-verifier:verify`
|
||||
* `db-compatibility-verifier:supported`
|
||||
* `db-compatibility-verifier:unsupported`
|
||||
|
||||
## Usage
|
||||
|
||||
### `snapshot` - Creates a snapshot of the current database ChangeSets.
|
||||
|
||||
This goal is used to create an initial snapshot of the database ChangeSets. It creates a supported and unsupported JSON
|
||||
file, specified via the `db.verify.supportedFile` and `db.verify.unsupportedFile` property, respectively.
|
||||
|
||||
```bash
|
||||
mvn org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:snapshot \
|
||||
-Ddb.verify.supportedFile=<relative-path-to-create-json-file> \
|
||||
-Ddb.verify.unsupportedFile=<relative-path-to-create-json-file>
|
||||
```
|
||||
|
||||
The `supportedFile` will be created with a record of all known ChangeSets and the `unsupportedFile` will be initialized
|
||||
as an empty JSON array.
|
||||
|
||||
### `verify` - Verifies that all detected ChangeSets recorded in either the supported or unsupported JSON files.
|
||||
|
||||
```bash
|
||||
mvn org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:verify \
|
||||
-Ddb.verify.supportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.unsupportedFile=<relative-path-to-json-file>
|
||||
```
|
||||
|
||||
### `supported` - Adds one or all missing ChangeSets to the supported JSON file
|
||||
|
||||
This goal is used to mark a ChangeSet as supported for rolling upgrades.
|
||||
|
||||
To mark a single ChangeSet as supported:
|
||||
|
||||
```bash
|
||||
mvn org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:supported \
|
||||
-Ddb.verify.supportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.unsupportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.changset.id=<id> \
|
||||
-Ddb.verify.changset.author=<author> \
|
||||
-Ddb.verify.changset.filename=<filename>
|
||||
```
|
||||
|
||||
To mark all missing ChangeSets as supported:
|
||||
|
||||
```bash
|
||||
mvn org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:supported \
|
||||
-Ddb.verify.supportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.unsupportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.changset.addAll=true
|
||||
```
|
||||
|
||||
### `unsupported` - Adds one or all missing ChangeSets to the unsupported JSON file
|
||||
|
||||
This goal is used to mark a ChangeSet as unsupported for rolling upgrades.
|
||||
|
||||
To mark a single ChangeSet as unsupported:
|
||||
|
||||
```bash
|
||||
mvn org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:unsupported \
|
||||
-Ddb.verify.supportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.unsupportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.changset.id=<id> \
|
||||
-Ddb.verify.changset.author=<author> \
|
||||
-Ddb.verify.changset.filename=<filename>
|
||||
```
|
||||
|
||||
To mark all missing ChangeSets as unsupported:
|
||||
|
||||
```bash
|
||||
mvn org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:unsupported \
|
||||
-Ddb.verify.supportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.unsupportedFile=<relative-path-to-json-file> \
|
||||
-Ddb.verify.changset.addAll=true
|
||||
```
|
||||
76
misc/db-compatibility-verifier/pom.xml
Normal file
76
misc/db-compatibility-verifier/pom.xml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>db-compatibility-verifier-maven-plugin</artifactId>
|
||||
|
||||
<name>Database Compatibilility Verifier</name>
|
||||
<description>Database Compatibility Verifier</description>
|
||||
|
||||
<packaging>maven-plugin</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>${maven.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||
<artifactId>maven-plugin-annotations</artifactId>
|
||||
<version>${maven.plugin-tools.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-core</artifactId>
|
||||
<version>${maven.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
|
||||
abstract class AbstractChangeSetMojo extends AbstractMojo {
|
||||
@Parameter(property = "db.verify.changeset.all", defaultValue = "false")
|
||||
boolean addAll;
|
||||
|
||||
@Parameter(property = "db.verify.changeset.id")
|
||||
String id;
|
||||
|
||||
@Parameter(property = "db.verify.changeset.author")
|
||||
String author;
|
||||
|
||||
@Parameter(property = "db.verify.changeset.filename")
|
||||
String filename;
|
||||
|
||||
|
||||
void checkFileExist(String ref, File file) throws MojoExecutionException {
|
||||
if (!file.exists()) {
|
||||
throw new MojoExecutionException("%s file does not exist".formatted(ref));
|
||||
}
|
||||
}
|
||||
|
||||
void checkUnknownChangeSet(Set<ChangeSet> knownChangeSets, ChangeSet changeSet) throws MojoExecutionException {
|
||||
if (!knownChangeSets.contains(changeSet)) {
|
||||
throw new MojoExecutionException("Unknown ChangeSet: " + changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkValidChangeSetId(String id, String author, String filename) throws MojoExecutionException {
|
||||
if (id == null || id.isBlank()) {
|
||||
throw new MojoExecutionException("ChangeSet id not set");
|
||||
}
|
||||
if (author == null || author.isBlank()) {
|
||||
throw new MojoExecutionException("ChangeSet author not set");
|
||||
}
|
||||
if (filename == null || filename.isBlank()) {
|
||||
throw new MojoExecutionException("ChangeSet filename not set");
|
||||
}
|
||||
}
|
||||
|
||||
void addAll(ClassLoader classLoader, File dest, File exclusions) throws IOException {
|
||||
// Discover all known ChangeSets
|
||||
ChangeLogXMLParser xmlParser = new ChangeLogXMLParser(classLoader);
|
||||
Set<ChangeSet> knownChangeSets = xmlParser.discoverAllChangeSets();
|
||||
|
||||
// Load changes to exclude and remove them from the known changesets
|
||||
Set<ChangeSet> excludedChanges = objectMapper.readValue(exclusions, new TypeReference<>() {});
|
||||
knownChangeSets.removeAll(excludedChanges);
|
||||
|
||||
// Overwrite all content in the destination file
|
||||
objectMapper.writeValue(dest, knownChangeSets);
|
||||
}
|
||||
|
||||
void addIndividual(ClassLoader classLoader, ChangeSet changeSet, File dest, File alternate) throws IOException, MojoExecutionException {
|
||||
// Discover all known ChangeSets
|
||||
ChangeLogXMLParser xmlParser = new ChangeLogXMLParser(classLoader);
|
||||
Set<ChangeSet> knownChangeSets = xmlParser.discoverAllChangeSets();
|
||||
|
||||
// It should not be possible to add an unknown changeset
|
||||
checkUnknownChangeSet(knownChangeSets, changeSet);
|
||||
|
||||
Set<ChangeSet> alternateChangeSets = objectMapper.readValue(alternate, new TypeReference<>() {});
|
||||
if (alternateChangeSets.contains(changeSet)) {
|
||||
throw new MojoExecutionException("ChangeSet already defined in the %s file".formatted(alternate.getName()));
|
||||
}
|
||||
|
||||
List<ChangeSet> destChanges = objectMapper.readValue(dest, new TypeReference<>() {});
|
||||
if (!destChanges.contains(changeSet)) {
|
||||
// If the ChangeSet is not already known, append to the end of the JSON array and overwrite the existing file
|
||||
destChanges.add(changeSet);
|
||||
objectMapper.writeValue(dest, destChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.apache.maven.artifact.DependencyResolutionRequiredException;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
abstract class AbstractMojo extends org.apache.maven.plugin.AbstractMojo {
|
||||
|
||||
final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
|
||||
|
||||
@Parameter(defaultValue = "${project}", readonly = true)
|
||||
protected MavenProject project;
|
||||
|
||||
@Parameter(property = "db.verify.supportedFile", required = true)
|
||||
protected String supportedFile;
|
||||
|
||||
@Parameter(property = "db.verify.unsupportedFile", required = true)
|
||||
protected String unsupportedFile;
|
||||
|
||||
@Parameter(property = "db.verify.skip", defaultValue = "false")
|
||||
protected boolean skip;
|
||||
|
||||
ClassLoader classLoader() throws DependencyResolutionRequiredException, MalformedURLException {
|
||||
List<String> elements = project.getRuntimeClasspathElements();
|
||||
URL[] urls = new URL[elements.size()];
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
urls[i] = new File(elements.get(i)).toURI().toURL();
|
||||
}
|
||||
return new URLClassLoader(urls, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamConstants;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
|
||||
record ChangeLogXMLParser(ClassLoader classLoader) {
|
||||
|
||||
static final String RESOURCE_DIR = "META-INF";
|
||||
|
||||
Set<ChangeSet> discoverAllChangeSets() throws IOException {
|
||||
var changeSets = changeSetXmlFiles()
|
||||
.map(this::extractChangeSets)
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
|
||||
Set<ChangeSet> uniqueSets = new HashSet<>(changeSets.size());
|
||||
ChangeSet duplicate = changeSets.stream()
|
||||
.filter(item -> !uniqueSets.add(item))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (duplicate != null) {
|
||||
throw new IllegalStateException("Duplicate ChangeSet detected: " + duplicate);
|
||||
}
|
||||
return uniqueSets;
|
||||
}
|
||||
|
||||
List<ChangeSet> extractChangeSets(String filename) {
|
||||
XMLInputFactory factory = XMLInputFactory.newInstance();
|
||||
// Security: Disable DTDs to prevent XML External Entity (XXE) attacks
|
||||
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
|
||||
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
|
||||
List<ChangeSet> ids = new ArrayList<>();
|
||||
|
||||
try (InputStream is = classLoader.getResourceAsStream(filename)) {
|
||||
XMLStreamReader reader = factory.createXMLStreamReader(is);
|
||||
while (reader.hasNext()) {
|
||||
int event = reader.next();
|
||||
|
||||
if (event == XMLStreamConstants.START_ELEMENT) {
|
||||
String tagName = reader.getLocalName();
|
||||
|
||||
// 1. Handle Root Element
|
||||
if (tagName.equals("databaseChangeLog")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Process changeSet
|
||||
if (tagName.equals("changeSet")) {
|
||||
String id = reader.getAttributeValue(null, "id");
|
||||
String author = reader.getAttributeValue(null, "author");
|
||||
ids.add(new ChangeSet(id, author, filename));
|
||||
|
||||
// Skip all child elements until we find the closing </changeSet>
|
||||
skipUnknownElement(reader);
|
||||
continue;
|
||||
}
|
||||
// 3. Skip all other elements
|
||||
skipUnknownElement(reader);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
} catch (IOException | XMLStreamException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect all jpa-changelog*.xml files on the classpath
|
||||
private Stream<String> changeSetXmlFiles() throws IOException {
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
Enumeration<URL> en = classLoader.getResources(RESOURCE_DIR);
|
||||
|
||||
while (en.hasMoreElements()) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = en.nextElement().toURI();
|
||||
} catch (URISyntaxException e) {
|
||||
// Should never happen
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
if (uri.getScheme().equals("jar")) {
|
||||
// Handle JAR resources
|
||||
try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
|
||||
Path path = fs.getPath(RESOURCE_DIR);
|
||||
fileNames.addAll(listFromPath(path));
|
||||
}
|
||||
} else {
|
||||
// Handle local file system (IDE)
|
||||
fileNames.addAll(listFromPath(Paths.get(uri)));
|
||||
}
|
||||
}
|
||||
return fileNames.stream()
|
||||
.filter(s -> s.startsWith("jpa-changelog") && s.endsWith(".xml"))
|
||||
.map(s -> "%s/%s".formatted(RESOURCE_DIR, s));
|
||||
}
|
||||
|
||||
private List<String> listFromPath(Path path) throws IOException {
|
||||
try (Stream<Path> walk = Files.walk(path, 1)) {
|
||||
return walk.filter(Files::isRegularFile)
|
||||
.map(p -> p.getFileName().toString())
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
private static void skipUnknownElement(XMLStreamReader reader) throws XMLStreamException {
|
||||
int level = 1;
|
||||
while (level > 0 && reader.hasNext()) {
|
||||
int event = reader.next();
|
||||
if (event == XMLStreamConstants.START_ELEMENT) {
|
||||
level++;
|
||||
} else if (event == XMLStreamConstants.END_ELEMENT) {
|
||||
level--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
record ChangeSet(String id, String author, String filename) {
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
|
||||
@Mojo(name = "supported")
|
||||
public class ChangeSetSupportedMojo extends AbstractChangeSetMojo {
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException {
|
||||
if (skip) {
|
||||
getLog().info("Skipping execution");
|
||||
return;
|
||||
}
|
||||
|
||||
File root = project.getBasedir();
|
||||
File sFile = new File(root, supportedFile);
|
||||
File uFile = new File(root, unsupportedFile);
|
||||
checkFileExist("supported", sFile);
|
||||
checkFileExist("unsupported", uFile);
|
||||
|
||||
try {
|
||||
if (addAll) {
|
||||
addAll(classLoader(), sFile, uFile);
|
||||
} else {
|
||||
checkValidChangeSetId(id, author, filename);
|
||||
ChangeSet changeSet = new ChangeSet(id, author, filename);
|
||||
addIndividual(classLoader(), changeSet, sFile, uFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Error adding ChangeSet to supported file", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
|
||||
@Mojo(name = "unsupported")
|
||||
public class ChangeSetUnsupportedMojo extends AbstractChangeSetMojo {
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException {
|
||||
if (skip) {
|
||||
getLog().info("Skipping execution");
|
||||
return;
|
||||
}
|
||||
|
||||
File root = project.getBasedir();
|
||||
File sFile = new File(root, supportedFile);
|
||||
File uFile = new File(root, unsupportedFile);
|
||||
checkFileExist("supported", sFile);
|
||||
checkFileExist("unsupported", uFile);
|
||||
|
||||
try {
|
||||
if (addAll) {
|
||||
addAll(classLoader(), uFile, sFile);
|
||||
} else {
|
||||
checkValidChangeSetId(id, author, filename);
|
||||
ChangeSet changeSet = new ChangeSet(id, author, filename);
|
||||
addIndividual(classLoader(), changeSet, uFile, sFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Error adding ChangeSet to unsupported file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
|
||||
@Mojo(name = "snapshot")
|
||||
public class CreateSnapshotMojo extends AbstractMojo {
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException {
|
||||
if (skip) {
|
||||
getLog().info("Skipping execution");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File root = project.getBasedir();
|
||||
File sFile = new File(root, supportedFile);
|
||||
File uFile = new File(root, unsupportedFile);
|
||||
|
||||
ClassLoader classLoader = classLoader();
|
||||
createSnapshot(classLoader, sFile, uFile);
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Error creating ChangeSet snapshot", e);
|
||||
}
|
||||
}
|
||||
|
||||
void createSnapshot(ClassLoader classLoader, File sFile, File uFile) throws IOException {
|
||||
// Write all known ChangeSet defined in the jpa-changelog*.xml files to the supported file
|
||||
ChangeLogXMLParser xmlParser = new ChangeLogXMLParser(classLoader);
|
||||
Set<ChangeSet> changeSets = xmlParser.discoverAllChangeSets();
|
||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
objectMapper.writeValue(sFile, changeSets);
|
||||
|
||||
// Create an empty JSON array in the unsupported file
|
||||
objectMapper.writeValue(uFile, Set.of());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
|
||||
@Mojo(name = "verify")
|
||||
public class VerifyCompatibilityMojo extends AbstractMojo {
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException {
|
||||
if (skip) {
|
||||
getLog().info("Skipping execution");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File root = project.getBasedir();
|
||||
File sFile = new File(root, supportedFile);
|
||||
File uFile = new File(root, unsupportedFile);
|
||||
verifyCompatibility(classLoader(), sFile, uFile);
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Error loading project resources", e);
|
||||
}
|
||||
}
|
||||
|
||||
void verifyCompatibility(ClassLoader classLoader, File sFile, File uFile) throws IOException, MojoExecutionException {
|
||||
if (!sFile.exists() && !uFile.exists()) {
|
||||
getLog().info("No JSON ChangeSet files exist to verify");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse JSON files to determine all committed ChangeSets
|
||||
List<ChangeSet> sChanges = objectMapper.readValue(sFile, new TypeReference<>() {});
|
||||
List<ChangeSet> uChanges = objectMapper.readValue(uFile, new TypeReference<>() {});
|
||||
Set<ChangeSet> recordedChanges = Stream.of(sChanges, uChanges)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (recordedChanges.isEmpty()) {
|
||||
getLog().info("No supported or unsupported ChangeSet exist in specified files");
|
||||
return;
|
||||
}
|
||||
|
||||
checkIntersection(sChanges, uChanges);
|
||||
|
||||
// Parse all ChangeSets currently defined in the jpa-changegetLog() files
|
||||
ChangeLogXMLParser xmlParser = new ChangeLogXMLParser(classLoader);
|
||||
Set<ChangeSet> currentChanges = xmlParser.discoverAllChangeSets();
|
||||
checkMissingChangeSet(currentChanges, recordedChanges, sFile, uFile);
|
||||
}
|
||||
|
||||
void checkIntersection(List<ChangeSet> sChanges, List<ChangeSet> uChanges) throws MojoExecutionException {
|
||||
Set<ChangeSet> intersection = new HashSet<>(sChanges);
|
||||
intersection.retainAll(uChanges);
|
||||
if (!intersection.isEmpty()) {
|
||||
getLog().error("The following ChangeSets should be defined in either the supported or unsupported file, they cannot appear in both:");
|
||||
intersection.forEach(change -> getLog().error("\t\t" + change.toString()));
|
||||
getLog().error("The offending ChangeSets should be removed from one of the files");
|
||||
throw new MojoExecutionException("One or more ChangeSet definitions exist in both the supported and unsupported file");
|
||||
}
|
||||
}
|
||||
|
||||
void checkMissingChangeSet(Set<ChangeSet> currentChanges, Set<ChangeSet> recordedChanges, File sFile, File uFile) throws MojoExecutionException {
|
||||
if (recordedChanges.equals(currentChanges)) {
|
||||
getLog().info("All ChangeSets in the module recorded as expected in the supported and unsupported files");
|
||||
} else {
|
||||
getLog().error("The recorded ChangeSet JSON files differ from the current repository state");
|
||||
getLog().error("The following ChangeSets should be defined in either the supported '%s' or unsupported '%s' file:".formatted(sFile.toString(), uFile.toString()));
|
||||
currentChanges.removeAll(recordedChanges);
|
||||
currentChanges.forEach(change -> getLog().error("\t\t" + change.toString()));
|
||||
getLog().error("You must determine whether the ChangeSet(s) is compatible with rolling upgrades or not");
|
||||
getLog().error("A ChangeSet that requires locking preventing other cluster members accessing the database or makes schema changes that breaks functionality in earlier Keycloak versions is NOT compatible with rolling upgrades");
|
||||
getLog().error("Rolling upgrade compatibility must be verified against all supported database vendors before the supported file is updated");
|
||||
getLog().error("If the change IS compatible, then it should be committed to the repository in the supported file: '%s'".formatted(sFile.toString()));
|
||||
getLog().error("If the change IS NOT compatible, then it should be committed to the repository in the unsupported file: '%s'".formatted(sFile.toString()));
|
||||
getLog().error("Adding a ChangeSet to the unsupported file ensures that a rolling upgrade is not attempted when upgrading to the first patch version containing the change");
|
||||
getLog().error("ChangeSets can be added to the supported or unsupported files using the org.keycloak:db-compatibility-verifier-maven-plugin. See the module README for usage instructions");
|
||||
throw new MojoExecutionException("One or more ChangeSet definitions are missing from the supported or unsupported files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
abstract class AbstractMojoTest {
|
||||
protected Path testDir;
|
||||
protected File supportedFile;
|
||||
protected File unsupportedFile;
|
||||
|
||||
@BeforeEach
|
||||
void init() throws IOException {
|
||||
testDir = Files.createTempDirectory(ChangeSetSupportedMojoTest.class.getSimpleName());
|
||||
supportedFile = testDir.resolve("supported.json").toFile();
|
||||
unsupportedFile = testDir.resolve("unsupported.json").toFile();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws IOException {
|
||||
if (Files.exists(testDir)) {
|
||||
Files.walkFileTree(testDir, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ChangeSetSupportedMojoTest extends AbstractMojoTest {
|
||||
|
||||
@Test
|
||||
void testAddAll() throws Exception {
|
||||
var classLoader = ChangeSetSupportedMojoTest.class.getClassLoader();
|
||||
var mojo = new ChangeSetSupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
// Create unsupported file with a single ChangeSet
|
||||
List<ChangeSet> unsupportedChanges = new ChangeLogXMLParser(classLoader).extractChangeSets("META-INF/jpa-changelog-2.xml");
|
||||
assertEquals(1, unsupportedChanges.size());
|
||||
mapper.writeValue(unsupportedFile, unsupportedChanges);
|
||||
|
||||
// Execute add all and expect all ChangeSets from jpa-changelog-1.xml to be present
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
mojo.addAll(classLoader, supportedFile, unsupportedFile);
|
||||
|
||||
List<ChangeSet> supportedChanges = mapper.readValue(supportedFile, new TypeReference<>() {});
|
||||
assertEquals(1, supportedChanges.size());
|
||||
|
||||
ChangeSet sChange = supportedChanges.get(0);
|
||||
assertEquals("test", sChange.id());
|
||||
assertEquals("keycloak", sChange.author());
|
||||
assertEquals("META-INF/jpa-changelog-1.xml", sChange.filename());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddIndividual() throws Exception {
|
||||
var classLoader = ChangeSetSupportedMojoTest.class.getClassLoader();
|
||||
var changeLogParser = new ChangeLogXMLParser(classLoader);
|
||||
var mojo = new ChangeSetSupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
assertTrue(unsupportedFile.createNewFile());
|
||||
mapper.writeValue(supportedFile, List.of());
|
||||
mapper.writeValue(unsupportedFile, List.of());
|
||||
|
||||
// Test ChangeSet is added to supported file as expected
|
||||
ChangeSet changeSet = changeLogParser.extractChangeSets("META-INF/jpa-changelog-1.xml").get(0);
|
||||
mojo.addIndividual(classLoader, changeSet, supportedFile, unsupportedFile);
|
||||
|
||||
List<ChangeSet> supportedChanges = mapper.readValue(supportedFile, new TypeReference<>() {});
|
||||
assertEquals(1, supportedChanges.size());
|
||||
ChangeSet sChange = supportedChanges.get(0);
|
||||
assertEquals(changeSet.id(), sChange.id());
|
||||
assertEquals(changeSet.author(), sChange.author());
|
||||
assertEquals(changeSet.filename(), sChange.filename());
|
||||
|
||||
// Test subsequent ChangeSets are added to already populated supported file
|
||||
changeSet = changeLogParser.extractChangeSets("META-INF/jpa-changelog-2.xml").get(0);
|
||||
mojo.addIndividual(classLoader, changeSet, supportedFile, unsupportedFile);
|
||||
|
||||
supportedChanges = mapper.readValue(supportedFile, new TypeReference<>() {});
|
||||
assertEquals(2, supportedChanges.size());
|
||||
|
||||
sChange = supportedChanges.get(1);
|
||||
assertEquals(changeSet.id(), sChange.id());
|
||||
assertEquals(changeSet.author(), sChange.author());
|
||||
assertEquals(changeSet.filename(), sChange.filename());
|
||||
|
||||
// Test ChangeSet already exists handled gracefully
|
||||
mojo.addIndividual(classLoader, changeSet, supportedFile, unsupportedFile);
|
||||
|
||||
supportedChanges = mapper.readValue(supportedFile, new TypeReference<>() {});
|
||||
assertEquals(2, supportedChanges.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangeAlreadyUnsupported() throws Exception {
|
||||
var classLoader = ChangeSetSupportedMojoTest.class.getClassLoader();
|
||||
var mojo = new ChangeSetSupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
assertTrue(unsupportedFile.createNewFile());
|
||||
mapper.writeValue(supportedFile, List.of());
|
||||
|
||||
// Create unsupported file with a single ChangeSet
|
||||
List<ChangeSet> unsupportedChanges = new ChangeLogXMLParser(classLoader).extractChangeSets("META-INF/jpa-changelog-1.xml");
|
||||
assertEquals(1, unsupportedChanges.size());
|
||||
|
||||
ChangeSet changeSet = unsupportedChanges.get(0);
|
||||
mapper.writeValue(unsupportedFile, unsupportedChanges);
|
||||
|
||||
Exception e = assertThrows(
|
||||
MojoExecutionException.class,
|
||||
() -> mojo.addIndividual(classLoader, changeSet, supportedFile, unsupportedFile)
|
||||
);
|
||||
|
||||
assertEquals("ChangeSet already defined in the %s file".formatted(unsupportedFile.getName()), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddUnknownChangeSet() throws Exception {
|
||||
var classLoader = ChangeSetSupportedMojoTest.class.getClassLoader();
|
||||
var mojo = new ChangeSetSupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
assertTrue(unsupportedFile.createNewFile());
|
||||
mapper.writeValue(supportedFile, List.of());
|
||||
ChangeSet unknown = new ChangeSet("asf", "asfgasg", "afasgfas");
|
||||
|
||||
Exception e = assertThrows(
|
||||
MojoExecutionException.class,
|
||||
() -> mojo.addIndividual(classLoader, unknown, supportedFile, unsupportedFile)
|
||||
);
|
||||
|
||||
assertEquals("Unknown ChangeSet: " + unknown, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ChangeSetUnsupportedMojoTest extends AbstractMojoTest {
|
||||
|
||||
@Test
|
||||
void testAddAll() throws Exception {
|
||||
var classLoader = ChangeSetUnsupportedMojoTest.class.getClassLoader();
|
||||
var mojo = new ChangeSetUnsupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
// Create supported file with a single ChangeSet
|
||||
List<ChangeSet> supportedChanges = new ChangeLogXMLParser(classLoader).extractChangeSets("META-INF/jpa-changelog-2.xml");
|
||||
assertEquals(1, supportedChanges.size());
|
||||
mapper.writeValue(unsupportedFile, supportedChanges);
|
||||
|
||||
// Execute add all and expect all ChangeSets from jpa-changelog-1.xml to be present
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
mojo.addAll(classLoader, supportedFile, unsupportedFile);
|
||||
|
||||
List<ChangeSet> unsupportedChanges = mapper.readValue(supportedFile, new TypeReference<>() {});
|
||||
assertEquals(1, unsupportedChanges.size());
|
||||
|
||||
ChangeSet sChange = unsupportedChanges.get(0);
|
||||
assertEquals("test", sChange.id());
|
||||
assertEquals("keycloak", sChange.author());
|
||||
assertEquals("META-INF/jpa-changelog-1.xml", sChange.filename());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddIndividual() throws Exception {
|
||||
var classLoader = ChangeSetUnsupportedMojoTest.class.getClassLoader();
|
||||
var changeLogParser = new ChangeLogXMLParser(classLoader);
|
||||
var mojo = new ChangeSetUnsupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
assertTrue(unsupportedFile.createNewFile());
|
||||
mapper.writeValue(supportedFile, List.of());
|
||||
mapper.writeValue(unsupportedFile, List.of());
|
||||
|
||||
// Test ChangeSet is added to unsupported file as expected
|
||||
ChangeSet changeSet = changeLogParser.extractChangeSets("META-INF/jpa-changelog-1.xml").get(0);
|
||||
mojo.addIndividual(classLoader, changeSet, unsupportedFile, supportedFile);
|
||||
|
||||
List<ChangeSet> unsupportedChanges = mapper.readValue(unsupportedFile, new TypeReference<>() {});
|
||||
assertEquals(1, unsupportedChanges.size());
|
||||
ChangeSet sChange = unsupportedChanges.get(0);
|
||||
assertEquals(changeSet.id(), sChange.id());
|
||||
assertEquals(changeSet.author(), sChange.author());
|
||||
assertEquals(changeSet.filename(), sChange.filename());
|
||||
|
||||
// Test subsequent ChangeSets are added to already populated supported file
|
||||
changeSet = changeLogParser.extractChangeSets("META-INF/jpa-changelog-2.xml").get(0);
|
||||
mojo.addIndividual(classLoader, changeSet, unsupportedFile, supportedFile);
|
||||
|
||||
unsupportedChanges = mapper.readValue(unsupportedFile, new TypeReference<>() {});
|
||||
assertEquals(2, unsupportedChanges.size());
|
||||
|
||||
sChange = unsupportedChanges.get(1);
|
||||
assertEquals(changeSet.id(), sChange.id());
|
||||
assertEquals(changeSet.author(), sChange.author());
|
||||
assertEquals(changeSet.filename(), sChange.filename());
|
||||
|
||||
// Test ChangeSet already exists handled gracefully
|
||||
mojo.addIndividual(classLoader, changeSet, unsupportedFile, supportedFile);
|
||||
|
||||
unsupportedChanges = mapper.readValue(unsupportedFile, new TypeReference<>() {});
|
||||
assertEquals(2, unsupportedChanges.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangeAlreadySupported() throws Exception {
|
||||
var classLoader = ChangeSetUnsupportedMojoTest.class.getClassLoader();
|
||||
var mojo = new ChangeSetUnsupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
assertTrue(unsupportedFile.createNewFile());
|
||||
mapper.writeValue(unsupportedFile, List.of());
|
||||
|
||||
// Create supported file with a single ChangeSet
|
||||
List<ChangeSet> unsupportedChanges = new ChangeLogXMLParser(classLoader).extractChangeSets("META-INF/jpa-changelog-1.xml");
|
||||
assertEquals(1, unsupportedChanges.size());
|
||||
|
||||
ChangeSet changeSet = unsupportedChanges.get(0);
|
||||
mapper.writeValue(supportedFile, unsupportedChanges);
|
||||
|
||||
Exception e = assertThrows(
|
||||
MojoExecutionException.class,
|
||||
() -> mojo.addIndividual(classLoader, changeSet, unsupportedFile, supportedFile)
|
||||
);
|
||||
|
||||
assertEquals("ChangeSet already defined in the %s file".formatted(supportedFile.getName()), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddUnknownChangeSet() throws Exception {
|
||||
var classLoader = ChangeSetSupportedMojoTest.class.getClassLoader();
|
||||
var mojo = new ChangeSetSupportedMojo();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
assertTrue(supportedFile.createNewFile());
|
||||
assertTrue(unsupportedFile.createNewFile());
|
||||
mapper.writeValue(unsupportedFile, List.of());
|
||||
ChangeSet unknown = new ChangeSet("asf", "asfgasg", "afasgfas");
|
||||
|
||||
Exception e = assertThrows(
|
||||
MojoExecutionException.class,
|
||||
() -> mojo.addIndividual(classLoader, unknown, unsupportedFile, supportedFile)
|
||||
);
|
||||
|
||||
assertEquals("Unknown ChangeSet: " + unknown, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class CreateSnapshotMojoTest extends AbstractMojoTest {
|
||||
|
||||
@Test
|
||||
void testSnapshotFilesCreated() throws Exception {
|
||||
var classLoader = CreateSnapshotMojoTest.class.getClassLoader();
|
||||
var mojo = new CreateSnapshotMojo();
|
||||
mojo.createSnapshot(classLoader, supportedFile, unsupportedFile);
|
||||
|
||||
assertTrue(supportedFile.exists());
|
||||
assertTrue(unsupportedFile.exists());
|
||||
|
||||
var mapper = new ObjectMapper();
|
||||
List<ChangeSet> supportedChanges = mapper.readValue(supportedFile, new TypeReference<>() {});
|
||||
assertEquals(2, supportedChanges.size());
|
||||
|
||||
List<ChangeSet> unsupportedChanges = mapper.readValue(unsupportedFile, new TypeReference<>() {});
|
||||
assertEquals(0, unsupportedChanges.size());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package org.keycloak.db.compatibility.verifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class VerifyCompatibilityMojoTest {
|
||||
|
||||
final ClassLoader classLoader = VerifyCompatibilityMojoTest.class.getClassLoader();
|
||||
|
||||
@Test
|
||||
void testChangeSetFilesDoNotExist() {
|
||||
var mojo = new VerifyCompatibilityMojo();
|
||||
File noneExistingFile = new File("noneExistingFile");
|
||||
assertFalse(noneExistingFile.exists());
|
||||
|
||||
assertDoesNotThrow(() -> mojo.verifyCompatibility(classLoader, noneExistingFile, noneExistingFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyChangeSetFiles() {
|
||||
var mojo = new VerifyCompatibilityMojo();
|
||||
File emptyJson = new File(classLoader.getResource("META-INF/empty-array.json").getFile());
|
||||
|
||||
assertDoesNotThrow(() -> mojo.verifyCompatibility(classLoader, emptyJson, emptyJson));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangeSetIncludedInSupportedAndUnsupportedFiles() {
|
||||
var mojo = new VerifyCompatibilityMojo();
|
||||
var changeSet = new ChangeSet("1", "keycloak", "example.xml");
|
||||
|
||||
Exception e = assertThrows(
|
||||
MojoExecutionException.class,
|
||||
() -> mojo.checkIntersection(List.of(changeSet), List.of(changeSet))
|
||||
);
|
||||
assertEquals("One or more ChangeSet definitions exist in both the supported and unsupported file", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllChangeSetsRecorded() {
|
||||
var mojo = new VerifyCompatibilityMojo();
|
||||
var changeSets = Set.of(
|
||||
new ChangeSet("1", "keycloak", "example.xml"),
|
||||
new ChangeSet("2", "keycloak", "example.xml")
|
||||
);
|
||||
|
||||
assertDoesNotThrow(() -> mojo.checkMissingChangeSet(changeSets, new HashSet<>(changeSets), new File(""), new File("")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingChangeSet() {
|
||||
var mojo = new VerifyCompatibilityMojo();
|
||||
var currentChanges = new HashSet<ChangeSet>();
|
||||
currentChanges.add(new ChangeSet("1", "keycloak", "example.xml"));
|
||||
currentChanges.add(new ChangeSet("2", "keycloak", "example.xml"));
|
||||
|
||||
var recordedChanges = Set.of(currentChanges.iterator().next());
|
||||
|
||||
Exception e = assertThrows(
|
||||
MojoExecutionException.class,
|
||||
() -> mojo.checkMissingChangeSet(currentChanges, recordedChanges, new File(""), new File(""))
|
||||
);
|
||||
assertEquals("One or more ChangeSet definitions are missing from the supported or unsupported files", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<changeSet author="keycloak" id="test">
|
||||
<createIndex tableName="BROKER_LINK" indexName="IDX_BROKER_LINK_USER_ID">
|
||||
<column name="USER_ID" type="VARCHAR(255)" />
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<changeSet author="keycloak" id="test">
|
||||
<createIndex tableName="BROKER_LINK" indexName="IDX_BROKER_LINK_USER_ID">
|
||||
<column name="USER_ID" type="VARCHAR(255)" />
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -27,9 +27,7 @@
|
|||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>theme-verifier-maven-plugin</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
|
||||
<name>Keycloak Theme verifier</name>
|
||||
<description>Keycloak Theme verifier</description>
|
||||
|
|
@ -40,27 +38,25 @@
|
|||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven-plugin-tools.version>3.15.1</maven-plugin-tools.version>
|
||||
<maven-plugin-api.version>3.9.9</maven-plugin-api.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>${maven-plugin-api.version}</version>
|
||||
<version>${maven.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||
<artifactId>maven-plugin-annotations</artifactId>
|
||||
<version>${maven-plugin-tools.version}</version>
|
||||
<version>${maven.plugin-tools.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-core</artifactId>
|
||||
<version>${maven-plugin-api.version}</version>
|
||||
<version>${maven.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
|||
71
model/jpa/README.md
Normal file
71
model/jpa/README.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Rolling updates database compatibility
|
||||
|
||||
In order to track database schema changes that are compatible/incompatible with the `rolling-updates` feature, this module
|
||||
makes use of the `db-compatibility-verifier-maven-plugin`. See `misc/db-compaotibility-verifier/README.md` for detailed
|
||||
usage instructions.
|
||||
|
||||
The rolling-update:v2 feature only supports rolling updates of Keycloak patch releases, therefore database changes
|
||||
are only tracked in release branches and not `main`.
|
||||
|
||||
|
||||
## Tracking supported database changes
|
||||
|
||||
All Liquibase ChangeSets at branch time are considered supported by the `rolling-updates:v2` feature, as this is the
|
||||
initial database state from the perspective of the current release stream. When creating a new release branch, a "snapshot"
|
||||
of all known Liquibase ChangeSets in this module are recorded using the `db-compatibility-verifier:snapshot`
|
||||
maven plugin. This generates two JSON files: a "supported" file with all known ChangeSets and an "unsupported"
|
||||
file initialized with an empty array. Both of these files must be committed to the repository.
|
||||
|
||||
A snapshot can be created by executing:
|
||||
|
||||
```
|
||||
./mvnw clean install -am -pl model/jpa -Pdb-changeset-snapshot -DskipTests
|
||||
```
|
||||
|
||||
|
||||
## Verifying all database changes are tracked
|
||||
|
||||
The `db-compatibility-verifier:verify` plugin is used as part of the `model/jpa` test phase to ensure that
|
||||
any Liquibase changeset added during the release branches lifecycle are tracked in either the supported or unsupported files.
|
||||
If one of more unrecorded ChangeSet is detected, contributors need to determine if the ChangeSet is compatible with a
|
||||
rolling update. If the change is not compatible, then it must be recorded in the unsupported file. Conversely, if it is
|
||||
compatible it must be recorded in the supported file.
|
||||
|
||||
Execution of the `db-compatibility-verifier:verify` plugin can be skipped during the test phase by specifying: `-Ddb.verify.skip=true`.
|
||||
|
||||
## Adding a supported database change
|
||||
|
||||
To add an individual ChangeSet to the supported file users can execute:
|
||||
|
||||
```
|
||||
./mvnw -pl model/jpa org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:supported \
|
||||
-Ddb.verify.changeset.id=<id> \
|
||||
-Ddb.verify.changeset.author=<author> \
|
||||
-Ddb.verify.changeset.filename=<filename>
|
||||
```
|
||||
|
||||
If multiple ChangeSets exist, and they are all compatible with rolling updates, the following can be used to add all changes:
|
||||
|
||||
```
|
||||
./mvnw -pl model/jpa org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:supported \
|
||||
-Ddb.verify.changeset.addAll=true
|
||||
```
|
||||
|
||||
|
||||
## Adding an unsupported database change
|
||||
|
||||
To add an individual ChangeSet to the supported file users can execute:
|
||||
|
||||
```
|
||||
./mvnw -pl model/jpa org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:unsupported \
|
||||
-Ddb.verify.changeset.id=<id> \
|
||||
-Ddb.verify.changeset.author=<author> \
|
||||
-Ddb.verify.changeset.filename=<filename>
|
||||
```
|
||||
|
||||
If multiple ChangeSets exist, and they are all compatible with rolling updates, the following can be used to add all changes:
|
||||
|
||||
```
|
||||
./mvnw -pl model/jpa org.keycloak:db-compatibility-verifier-maven-plugin:999.0.0-SNAPSHOT:unsupported \
|
||||
-Ddb.verify.changeset.addAll=true
|
||||
```
|
||||
|
|
@ -38,6 +38,8 @@
|
|||
<jdbc.mvn.groupId>com.h2database</jdbc.mvn.groupId>
|
||||
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
||||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
||||
<db.verify.supportedFile>src/main/resources/META-INF/rolling-upgrades-supported-changes.json</db.verify.supportedFile>
|
||||
<db.verify.unsupportedFile>src/main/resources/META-INF/rolling-upgrades-unsupported-changes.json</db.verify.unsupportedFile>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
|
@ -171,6 +173,44 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>db-compatibility-verifier-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>verify</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>db-changeset-snapshot</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>db-compatibility-verifier-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>snapshot</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>snapshot</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
|||
2
pom.xml
2
pom.xml
|
|
@ -40,6 +40,7 @@
|
|||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<maven.plugin-tools.version>3.15.1</maven.plugin-tools.version>
|
||||
|
||||
<project.version.npm>999.0.0-SNAPSHOT</project.version.npm>
|
||||
|
||||
|
|
@ -304,6 +305,7 @@
|
|||
<module>federation</module>
|
||||
<module>services</module>
|
||||
<module>themes</module>
|
||||
<module>misc/db-compatibility-verifier</module>
|
||||
<module>misc/theme-verifier</module>
|
||||
<module>model</module>
|
||||
<module>util</module>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,34 @@
|
|||
package org.keycloak.quarkus.runtime.configuration.compatibility;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.compatibility.CompatibilityMetadataProvider;
|
||||
import org.keycloak.config.DatabaseOptions;
|
||||
import org.keycloak.config.Option;
|
||||
import org.keycloak.jose.jws.crypto.HashUtils;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalKcValue;
|
||||
|
||||
public class DatabaseCompatibilityMetadataProvider implements CompatibilityMetadataProvider {
|
||||
|
||||
private static final Logger log = Logger.getLogger(DatabaseCompatibilityMetadataProvider.class);
|
||||
|
||||
public static final String ID = "database";
|
||||
public static final String UNSUPPORTED_CHANGE_SET_HASH_KEY = "unsupported-changeset-hash";
|
||||
|
||||
@Override
|
||||
public Map<String, String> metadata() {
|
||||
|
|
@ -30,6 +43,25 @@ public class DatabaseCompatibilityMetadataProvider implements CompatibilityMetad
|
|||
addOptional(DatabaseOptions.DB_URL_PORT, metadata);
|
||||
addOptional(DatabaseOptions.DB_URL_DATABASE, metadata);
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try (InputStream inputStream = DatabaseCompatibilityMetadataProvider.class.getResourceAsStream("/META-INF/rolling-upgrades-unsupported-changes.json")) {
|
||||
if (inputStream != null) {
|
||||
// Load the ChangeSet JSON into memory and write to a JSON String in order to avoid whitespace changes impacting the hash
|
||||
Set<ChangeSet> changeSets = objectMapper.readValue(inputStream, new TypeReference<>() {});
|
||||
List<ChangeSet> sortedChanges = changeSets.stream().sorted(
|
||||
Comparator.comparing(ChangeSet::id)
|
||||
.thenComparing(ChangeSet::author)
|
||||
.thenComparing(ChangeSet::filename)
|
||||
).toList();
|
||||
|
||||
String changeSetJson = objectMapper.writeValueAsString(sortedChanges);
|
||||
String hash = HashUtils.sha256UrlEncodedHash(changeSetJson, StandardCharsets.UTF_8);
|
||||
metadata.put(UNSUPPORTED_CHANGE_SET_HASH_KEY, hash);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to close InputStream when creating database unsupported change hash", e);
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
@ -42,4 +74,7 @@ public class DatabaseCompatibilityMetadataProvider implements CompatibilityMetad
|
|||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public record ChangeSet(String id, String author, String filename) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,13 @@
|
|||
package org.keycloak.it.cli.dist;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Version;
|
||||
|
|
@ -35,6 +38,7 @@ import org.keycloak.it.junit5.extension.RawDistOnly;
|
|||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
import org.keycloak.it.utils.RawKeycloakDistribution;
|
||||
import org.keycloak.jgroups.certificates.DefaultJGroupsCertificateProviderFactory;
|
||||
import org.keycloak.jose.jws.crypto.HashUtils;
|
||||
import org.keycloak.quarkus.runtime.cli.command.UpdateCompatibility;
|
||||
import org.keycloak.quarkus.runtime.cli.command.UpdateCompatibilityCheck;
|
||||
import org.keycloak.quarkus.runtime.cli.command.UpdateCompatibilityMetadata;
|
||||
|
|
@ -46,15 +50,19 @@ import org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProvi
|
|||
import org.keycloak.spi.infinispan.impl.remote.DefaultCacheRemoteConfigProviderFactory;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.keycloak.it.cli.dist.Util.createTempFile;
|
||||
import static org.keycloak.quarkus.runtime.configuration.compatibility.DatabaseCompatibilityMetadataProvider.UNSUPPORTED_CHANGE_SET_HASH_KEY;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
@DistributionTest
|
||||
@RawDistOnly(reason = "Requires creating JSON file to be available between containers")
|
||||
public class UpdateCommandDistTest {
|
||||
|
|
@ -267,9 +275,7 @@ public class UpdateCommandDistTest {
|
|||
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
var expectedMeta = defaultMeta(distribution);
|
||||
expectedMeta.put(DatabaseCompatibilityMetadataProvider.ID, Map.of(
|
||||
DatabaseOptions.DB.getKey(), "postgres"
|
||||
));
|
||||
expectedMeta.get(DatabaseCompatibilityMetadataProvider.ID).put(DatabaseOptions.DB.getKey(), "postgres");
|
||||
info.remove(FeatureCompatibilityMetadataProvider.ID);
|
||||
assertEquals(expectedMeta, info);
|
||||
|
||||
|
|
@ -287,8 +293,8 @@ public class UpdateCommandDistTest {
|
|||
// Assert that expected db-url-* options are written to the metadata when --db-url is not present
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
var expectedMeta = defaultMeta(distribution);
|
||||
expectedMeta.put(DatabaseCompatibilityMetadataProvider.ID, Map.of(
|
||||
DatabaseOptions.DB.getKey(), DatabaseOptions.DB.getDefaultValue().get(),
|
||||
var dbMeta = expectedMeta.get(DatabaseCompatibilityMetadataProvider.ID);
|
||||
dbMeta.putAll(Map.of(
|
||||
DatabaseOptions.DB_URL_DATABASE.getKey(), "keycloak",
|
||||
DatabaseOptions.DB_URL_HOST.getKey(), "localhost",
|
||||
DatabaseOptions.DB_URL_PORT.getKey(), "9999"
|
||||
|
|
@ -306,9 +312,14 @@ public class UpdateCommandDistTest {
|
|||
assertEquals(0, result.exitCode());
|
||||
|
||||
info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
expectedMeta.put(DatabaseCompatibilityMetadataProvider.ID, Map.of(
|
||||
DatabaseOptions.DB.getKey(), DatabaseOptions.DB.getDefaultValue().get()
|
||||
));
|
||||
Map<String, String> expectedDbMeta = new HashMap<>();
|
||||
expectedDbMeta.put(DatabaseOptions.DB.getKey(), DatabaseOptions.DB.getDefaultValue().get());
|
||||
String expectedHash = dbMeta.get(UNSUPPORTED_CHANGE_SET_HASH_KEY);
|
||||
if (expectedHash != null) {
|
||||
expectedDbMeta.put(UNSUPPORTED_CHANGE_SET_HASH_KEY, expectedHash);
|
||||
}
|
||||
expectedMeta.put(DatabaseCompatibilityMetadataProvider.ID, expectedDbMeta);
|
||||
|
||||
info.remove(FeatureCompatibilityMetadataProvider.ID);
|
||||
assertEquals(expectedMeta, info);
|
||||
|
||||
|
|
@ -317,15 +328,25 @@ public class UpdateCommandDistTest {
|
|||
result.assertExitCode(CompatibilityResult.ExitCode.ROLLING.value());
|
||||
}
|
||||
|
||||
private Map<String, Map<String, String>> defaultMeta(KeycloakDistribution distribution) {
|
||||
private Map<String, Map<String, String>> defaultMeta(KeycloakDistribution distribution) throws IOException {
|
||||
Map<String, String> keycloak = new HashMap<>(1);
|
||||
keycloak.put("version", Version.VERSION);
|
||||
|
||||
Map<String, String> dbMeta = new HashMap<>();
|
||||
dbMeta.put(DatabaseOptions.DB.getKey(), DatabaseOptions.DB.getDefaultValue().get());
|
||||
try (InputStream inputStream = UpdateCommandDistTest.class.getResourceAsStream("/META-INF/rolling-upgrades-unsupported-changes.json")) {
|
||||
if (inputStream != null) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Set<DatabaseCompatibilityMetadataProvider.ChangeSet> changeSets = objectMapper.readValue(inputStream, new TypeReference<>() {});
|
||||
String changeSetJson = objectMapper.writeValueAsString(changeSets);
|
||||
String hash = HashUtils.sha256UrlEncodedHash(changeSetJson, StandardCharsets.UTF_8);
|
||||
dbMeta.put(UNSUPPORTED_CHANGE_SET_HASH_KEY, hash);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> m = new HashMap<>();
|
||||
m.put(KeycloakCompatibilityMetadataProvider.ID, keycloak);
|
||||
m.put(DatabaseCompatibilityMetadataProvider.ID, Map.of(
|
||||
DatabaseOptions.DB.getKey(), DatabaseOptions.DB.getDefaultValue().get()
|
||||
));
|
||||
m.put(DatabaseCompatibilityMetadataProvider.ID, dbMeta);
|
||||
m.put(CacheEmbeddedConfigProviderSpi.SPI_NAME, embeddedCachingMeta(distribution));
|
||||
m.put(JGroupsCertificateProviderSpi.SPI_NAME, Map.of(
|
||||
"enabled", "true"
|
||||
|
|
|
|||
Loading…
Reference in a new issue