Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

warn if XSD version does not match build version in validate command (DAT-9874) #3016

Merged
merged 5 commits into from Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -6,12 +6,15 @@
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.InputStreamList;
import liquibase.resource.ResourceAccessor;
import liquibase.util.LiquibaseUtil;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.EntityResolver2;

import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Finds the Liquibase schema from the classpath rather than fetching it over the Internet.
Expand All @@ -37,6 +40,8 @@ public InputSource resolveEntity(String name, String publicId, String baseURI, S
.replace("http://www.liquibase.org/xml/ns/migrator/", "http://www.liquibase.org/xml/ns/dbchangelog/")
.replaceFirst("https?://", "");

warnForMismatchedXsdVersion(systemId);

ResourceAccessor resourceAccessor = Scope.getCurrentScope().getResourceAccessor();
InputStreamList streams = resourceAccessor.openStreams(null, path);
if (streams.isEmpty()) {
Expand Down Expand Up @@ -70,6 +75,31 @@ public InputSource resolveEntity(String name, String publicId, String baseURI, S

}

/**
* Print a warning message to the logs and UI if the build version does not match the XSD version. This is a best
* effort check, this method will never throw an exception.
*/
private void warnForMismatchedXsdVersion(String systemId) {
try {
Pattern versionPattern = Pattern.compile("(?:-pro-|-)(?<version>[\\d.]*)\\.xsd");
Matcher versionMatcher = versionPattern.matcher(systemId);
boolean found = versionMatcher.find();
if (found) {
String buildVersion = LiquibaseUtil.getBuildVersion();
if (!buildVersion.equals("DEV")) {
String xsdVersion = versionMatcher.group("version");
if (!buildVersion.startsWith(xsdVersion)) {
String msg = "WARNING: An older version of the XSD is specified in the changelog's <databaseChangeLog> header. This can lead to unexpected outcomes. Please update it to '" + buildVersion + "'. Learn more at https://docs.liquibase.com";
Scope.getCurrentScope().getLog(getClass()).warning(msg);
Scope.getCurrentScope().getUI().sendMessage(msg);
}
}
}
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).fine("Failed to compare XSD version with build version.", e);
}
}

/**
* ResourceAccessor to use if the standard one does not have the XSD files in it.
* Returns a ClassLoaderResourceAccessor that checks the system classloader which should include the liquibase.jar.
Expand Down
@@ -1,8 +1,11 @@
package liquibase.parser.core.xml

import liquibase.GlobalConfiguration
import liquibase.LiquibaseTest
import liquibase.Scope
import liquibase.resource.FileSystemResourceAccessor
import liquibase.util.LiquibaseUtil
import org.xml.sax.InputSource
import spock.lang.Specification
import spock.lang.Unroll

Expand All @@ -25,6 +28,45 @@ class LiquibaseEntityResolverTest extends Specification {
]
}

@Unroll
def "warning message for mismatched xsd and build versions #systemId /// #buildVersion"() {
given:
def uiService = new LiquibaseTest.TestConsoleUIService()
// Save these props for later
def originalProperties = LiquibaseUtil.liquibaseBuildProperties
LiquibaseUtil.liquibaseBuildProperties = new Properties()
LiquibaseUtil.liquibaseBuildProperties.put("build.version", buildVersion)

expect:
Scope.child([
(Scope.Attr.ui.name()) : uiService
], {
new LiquibaseEntityResolver().resolveEntity(null, null, null, systemId)
} as Scope.ScopedRunnerWithReturn<InputSource>) != null

// This is an ugly assertion line, it is essentially saying, either we expect the message, so make sure it's there
// or we expect no message, so make sure there are no messages.
((expectedWarningMessage && uiService.getMessages().contains("WARNING: An older version of the XSD is specified in the changelog's <databaseChangeLog> header. This can lead to unexpected outcomes. Please update it to '" + buildVersion + "'. Learn more at https://docs.liquibase.com"))
|| (!expectedWarningMessage && uiService.getMessages().isEmpty()))

cleanup:
// Set the build properties back to what they were before the test.
LiquibaseUtil.liquibaseBuildProperties = originalProperties

where:
buildVersion | systemId | expectedWarningMessage
"3.1.0" | "http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd" | false
"3.1.1" | "http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd" | false
"4.12.0" | "http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd" | true
"4.12.0" | "https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd" | true
"4.12.0" | "http://www.liquibase.org/xml/ns/migrator/dbchangelog-3.1.xsd" | true
"4.12.0" | "http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-next.xsd" | false
"4.12.0" | "http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd" | false
"4.12.0" | "http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd" | false
"4.12.0" | "/liquibase/banner.txt" | false
"4.12.0" | "http://liquibase/banner.txt" | false
}

@Unroll
def "resolveEntity finds packaged files correctly even if the configured resourceAccessor doesn't have it"() {
expect:
Expand Down