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

Quality Checks for Databases (DAT-9298) #2715

Merged
merged 70 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
43cb962
Initial work on returning a snapshot model object
wwillard7800 Mar 23, 2022
6954956
Added snapshot ResultsBuilder to allow DatabaseSnapshot to be availab…
wwillard7800 Mar 23, 2022
dcaaab1
Work on merging SnapshotCommandStep and InternalSnapshotCommandStep
wwillard7800 Mar 24, 2022
59cc04e
Added InternalSnapshotCommandStep code to SnapshotCommandStep
wwillard7800 Mar 24, 2022
803d038
Merge remote-tracking branch 'origin/master' into DAT-9624
wwillard7800 Mar 25, 2022
8f288b1
Fix issue around output stream handling when taking QC Database snapshot
wwillard7800 Mar 25, 2022
9c4d32d
Added abstract class for checks commands which need a database
wwillard7800 Mar 28, 2022
6cea077
Merge remote-tracking branch 'origin/master' into DAT-9624
wwillard7800 Mar 28, 2022
2b3b968
Fix snapshot tests
wwillard7800 Mar 28, 2022
71d819e
Added ability for test to specify a closure as an expected result
wwillard7800 Mar 29, 2022
c8aeb08
initial changes made to support databases for TableColumnCountWarning
StevenMassaro Mar 30, 2022
66cd50f
Merge branch 'DAT-9624' into DAT-9828
StevenMassaro Mar 31, 2022
273660a
do executeSql in try-with-resources
StevenMassaro Mar 31, 2022
198c853
print out results of database checks
StevenMassaro Mar 31, 2022
aef7615
add pro suffix to object type if it's a pro-only object, by registeri…
StevenMassaro Apr 1, 2022
5896187
remove unused method
StevenMassaro Apr 1, 2022
8efaf39
Make SnapshotCommandStep use the code from InternalSnapshotCommand di…
wwillard7800 Apr 4, 2022
b8b2fe6
simplify license checks and move code into Pro repo (DAT-9602) (#2679)
StevenMassaro Apr 4, 2022
b690cf5
Merge branch 'master' into DAT-9298
StevenMassaro Apr 4, 2022
a33382a
Merge remote-tracking branch 'origin/DAT-9298' into DAT-9298
StevenMassaro Apr 4, 2022
5613746
standardize messaging for missing/invalid pro license keys (DAT-9403)…
StevenMassaro Apr 4, 2022
bcba524
Merge remote-tracking branch 'origin/DAT-9298' into DAT-9298
wwillard7800 Apr 4, 2022
be0af43
Merge remote-tracking branch 'origin/DAT-9828' into DAT-9610
wwillard7800 Apr 5, 2022
4f98e0f
Merge remote-tracking branch 'origin/DAT-9611' into DAT-9610
wwillard7800 Apr 5, 2022
27c2922
Test framework changes for DAT-9621 (#2718)
wwillard7800 Apr 6, 2022
8f6f36b
Merge branch 'DAT-9298' into DAT-9828
StevenMassaro Apr 6, 2022
6ca234e
Merge remote-tracking branch 'origin/DAT-9298' into DAT-9298
wwillard7800 Apr 6, 2022
8195521
Merge remote-tracking branch 'origin/DAT-9828' into DAT-9993
wwillard7800 Apr 6, 2022
c0ea361
Merge remote-tracking branch 'origin/DAT-9610' into DAT-9993
wwillard7800 Apr 6, 2022
993bffe
New SnapshotControl argument for filtering in SnapshotCommandStep
wwillard7800 Apr 7, 2022
3be90d0
add helper method to allow for execution of arbitrary SQL in integrat…
StevenMassaro Apr 7, 2022
823ccb8
add support for printing fully qualified name of database objects (DA…
StevenMassaro Apr 7, 2022
5fc711f
Improve error handling of buildFullyQualifiedName (#2728)
StevenMassaro Apr 7, 2022
689b7f7
Remove arguments that were not supposed to be in snapshot command
wwillard7800 Apr 7, 2022
325ed2e
Merge remote-tracking branch 'origin/DAT-9298' into DAT-9993-work
wwillard7800 Apr 8, 2022
910bf18
Merge branch 'master' into DAT-9298
StevenMassaro Apr 8, 2022
70400f2
Merge branch 'DAT-9298' into DAT-9993
StevenMassaro Apr 8, 2022
6c16a87
Merge pull request #2738 from liquibase/DAT-9993
wwillard7800 Apr 13, 2022
9e83a47
Merge branch 'master' into DAT-9298
StevenMassaro Apr 13, 2022
40942bb
Merge branch 'master' into DAT-9298
StevenMassaro Apr 15, 2022
f168606
Merge branch 'master' into DAT-9298
StevenMassaro Apr 18, 2022
ad05d2e
Merge branch 'master' into DAT-9298
StevenMassaro Apr 19, 2022
25e1a34
make trial URL public (DAT-9626) (#2766)
StevenMassaro Apr 20, 2022
3e7a2a0
Merge branch 'master' into DAT-9298
StevenMassaro Apr 22, 2022
0226d4d
Merge branch 'master' into DAT-9298
nvoxland Apr 22, 2022
80a1cf3
Merge branch 'master' into DAT-9298
nvoxland Apr 23, 2022
9bfdbce
Merge branch 'master' into DAT-9298
nvoxland Apr 23, 2022
3a6c856
Merge branch 'master' into DAT-9298
StevenMassaro Apr 28, 2022
f1ce54b
Merge branch 'master' into DAT-9298
StevenMassaro May 3, 2022
eaa0a11
fail build if there are multiple command test definitions with the sa…
StevenMassaro May 5, 2022
75f4f03
add QCs for databases support to maven plugin (DAT-9623) (#2814)
StevenMassaro May 5, 2022
fbe8514
add getExpirationDate method to LicenseService (DAT-10141) (#2822)
StevenMassaro May 5, 2022
3b0cf61
Simplify pro license checks to support running through maven (DAT-100…
StevenMassaro May 5, 2022
e42617e
Merge pull request #2828 from liquibase/master
StevenMassaro May 5, 2022
8f1884e
Test System Framework: add support for "liquibase.sdk.testSystem.skip…
StevenMassaro May 11, 2022
240e177
register DefaultsFileValueProvider inside maven commands (DAT-10003) …
StevenMassaro May 12, 2022
e63de2b
stop database containers when spock execution ends (#2843) (#2844)
StevenMassaro May 12, 2022
3af6467
minor refactorings to CommandTests and TableOutput (DAT-9989) (#2849)
StevenMassaro May 18, 2022
fb86d98
Merge pull request #2855 from liquibase/master
StevenMassaro May 18, 2022
d0494e1
Merge pull request #2872 from liquibase/master
StevenMassaro May 24, 2022
98edad3
prefer license specified in LIQUIBASE_LICENSE_KEY edge case (DAT-1040…
StevenMassaro May 24, 2022
77e0272
change the name "Empire" to "Labs" (DAT-10470) (#2875)
StevenMassaro May 26, 2022
83e4335
Merge branch 'master' into DAT-9298
StevenMassaro May 31, 2022
41c731d
Merge branch 'master' into DAT-9298
StevenMassaro Jun 1, 2022
4afea6c
update 9298 with master
StevenMassaro Jun 2, 2022
8477ac1
resolve DAT-9298 code review comments (#2887)
wwillard7800 Jun 2, 2022
1cae16b
Refactored code to ignore global OUTPUT_DEFAULT* arguments (#2906)
wwillard7800 Jun 2, 2022
1bc7144
resolve code review comment (#2907)
StevenMassaro Jun 2, 2022
a9da661
resolve code review comment (#2908)
StevenMassaro Jun 2, 2022
f9896e3
resolve code review comment (#2912)
StevenMassaro Jun 3, 2022
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
Expand Up @@ -492,6 +492,24 @@ private List<ConfigurationValueProvider> registerValueProviders(String[] args) t
returnList.add(argumentProvider);

final ConfiguredValue<String> defaultsFileConfig = LiquibaseCommandLineConfiguration.DEFAULTS_FILE.getCurrentConfiguredValue();
/*
* The installed licenses are cleared from the license service. Clearing the licenses
* forces the license service to reinstall the licenses.
*
* The call to LiquibaseCommandLineConfiguration.DEFAULTS_FILE.getCurrentConfiguredValue() above will check
* the environment variables for a value, and that requires a valid license. Thus, if the user has a license
* key specified in both an environment variable and in their defaults file (using different property names),
* the value in the defaults file will not be seen, despite it possibly being more preferred than the value
* in the environment variable, because the DefaultsFileValueProvider hasn't been registered yet.
*/
LicenseServiceFactory licenseServiceFactory = Scope.getCurrentScope().getSingleton(LicenseServiceFactory.class);
if (licenseServiceFactory != null) {
LicenseService licenseService = licenseServiceFactory.getLicenseService();
if (licenseService != null) {
licenseService.reset();
}
}

final File defaultsFile = new File(defaultsFileConfig.getValue());
if (defaultsFile.exists()) {
final DefaultsFileValueProvider fileProvider = new DefaultsFileValueProvider(defaultsFile);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package liquibase.command;

import liquibase.Scope;
import liquibase.exception.CommandExecutionException;
import liquibase.integration.commandline.Main;

Expand All @@ -26,7 +27,7 @@ public final void run(CommandResultsBuilder resultsBuilder) throws Exception {
Main.setOutputStream(printStream);
}

CommandScope commandScope = resultsBuilder.getCommandScope();
final CommandScope commandScope = resultsBuilder.getCommandScope();

String[] args = collectArguments(commandScope);
int statusCode = Main.run(args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ public CommandFailedException commandFailed(String message, int exitCode) {
*/
CommandResults build() {
try {
outputStream.flush();
if (this.outputStream != null) {
outputStream.flush();
}
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).warning("Error flushing " + StringUtil.join(commandScope.getCommand().getName(), " ") + " output: " + e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ public CommandScope setOutput(OutputStream outputStream) {
*/
public CommandResults execute() throws CommandExecutionException {
CommandResultsBuilder resultsBuilder = new CommandResultsBuilder(this, outputStream);

for (ConfigurationValueProvider provider : Scope.getCurrentScope().getSingleton(LiquibaseConfiguration.class).getProviders()) {
provider.validate(this);
}
Expand Down Expand Up @@ -164,7 +163,9 @@ public CommandResults execute() throws CommandExecutionException {
}
} finally {
try {
this.outputStream.flush();
if (this.outputStream != null) {
this.outputStream.flush();
}
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).warning("Error flushing command output stream: " + e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
package liquibase.command.core;

import liquibase.CatalogAndSchema;
import liquibase.GlobalConfiguration;
import liquibase.Scope;
import liquibase.command.*;
import liquibase.configuration.ConfigurationValueObfuscator;
import liquibase.exception.CommandExecutionException;
import liquibase.database.Database;
import liquibase.database.ObjectQuotingStrategy;
import liquibase.database.core.*;
import liquibase.exception.DatabaseException;
import liquibase.integration.commandline.CommandLineUtils;
import liquibase.integration.commandline.LiquibaseCommandLineConfiguration;
import liquibase.license.LicenseServiceUtils;
import liquibase.resource.ResourceAccessor;
import liquibase.serializer.SnapshotSerializerFactory;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.SnapshotControl;
import liquibase.snapshot.SnapshotGeneratorFactory;
import liquibase.util.StringUtil;

import java.util.Arrays;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.*;

public class SnapshotCommandStep extends AbstractCliWrapperCommandStep {
import static java.util.ResourceBundle.getBundle;

public class SnapshotCommandStep extends AbstractCommandStep {

public static final String[] COMMAND_NAME = {"snapshot"};
private static final ResourceBundle coreBundle = getBundle("liquibase/i18n/liquibase-core");

public static final CommandArgumentDefinition<String> USERNAME_ARG;
public static final CommandArgumentDefinition<String> PASSWORD_ARG;
Expand All @@ -19,6 +41,8 @@ public class SnapshotCommandStep extends AbstractCliWrapperCommandStep {
public static final CommandArgumentDefinition<String> SNAPSHOT_FORMAT_ARG;
public static final CommandArgumentDefinition<String> DRIVER_ARG;
public static final CommandArgumentDefinition<String> DRIVER_PROPERTIES_FILE_ARG;
public static final CommandArgumentDefinition<Database> DATABASE_ARG;
public static final CommandArgumentDefinition<SnapshotControl> SNAPSHOT_CONTROL_ARG;

static {
CommandBuilder builder = new CommandBuilder(COMMAND_NAME);
Expand All @@ -41,21 +65,208 @@ public class SnapshotCommandStep extends AbstractCliWrapperCommandStep {
.setValueObfuscator(ConfigurationValueObfuscator.STANDARD)
.build();
SNAPSHOT_FORMAT_ARG = builder.argument("snapshotFormat", String.class)
.description("Output format to use (JSON or YAML").build();
.description("Output format to use (JSON, YAML, or TXT)").build();
DATABASE_ARG = builder.argument("database", Database.class).hidden().build();
SNAPSHOT_CONTROL_ARG = builder.argument("snapshotControl", SnapshotControl.class).hidden().build();
}

private Database database;

private Map<String, Object> snapshotMetadata;

@Override
public String[][] defineCommandNames() {
return new String[][] { COMMAND_NAME };
}

@Override
protected String[] collectArguments(CommandScope commandScope) throws CommandExecutionException {
return collectArguments(commandScope, Arrays.asList(SNAPSHOT_FORMAT_ARG.getName()), null);
public void adjustCommandDefinition(CommandDefinition commandDefinition) {
commandDefinition.setShortDescription("Capture the current state of the database");
}

private CatalogAndSchema[] parseSchemas(Database database, String... schemas) {
if ((schemas == null) || (schemas.length == 0) || (schemas[0] == null)) {
return null;
}

schemas = StringUtil.join(schemas, ",").split("\\s*,\\s*");
List<CatalogAndSchema> finalList = new ArrayList<>();
for (String schema : schemas) {
finalList.add(new CatalogAndSchema(null, schema).customize(database));
}

return finalList.toArray(new CatalogAndSchema[finalList.size()]);
}

public Map<String, Object> getSnapshotMetadata() {
return snapshotMetadata;
}

public void setSnapshotMetadata(Map<String, Object> snapshotMetadata) {
this.snapshotMetadata = snapshotMetadata;
}

@Override
public void adjustCommandDefinition(CommandDefinition commandDefinition) {
commandDefinition.setShortDescription("Capture the current state of the database");
public void run(CommandResultsBuilder resultsBuilder) throws Exception {
CommandScope commandScope = resultsBuilder.getCommandScope();

if (commandScope.getArgumentValue(DATABASE_ARG) == null) {
String url = commandScope.getArgumentValue(SnapshotCommandStep.URL_ARG);
String username = commandScope.getArgumentValue(SnapshotCommandStep.USERNAME_ARG);
String password = commandScope.getArgumentValue(SnapshotCommandStep.PASSWORD_ARG);
String defaultSchemaName = commandScope.getArgumentValue(SnapshotCommandStep.DEFAULT_SCHEMA_NAME_ARG);
String defaultCatalogName = commandScope.getArgumentValue(SnapshotCommandStep.DEFAULT_CATALOG_NAME_ARG);
String driver = commandScope.getArgumentValue(SnapshotCommandStep.DRIVER_ARG);
String driverPropertiesFile = commandScope.getArgumentValue(SnapshotCommandStep.DRIVER_PROPERTIES_FILE_ARG);
createDatabaseObject(url, username, password, defaultSchemaName, defaultCatalogName, driver, driverPropertiesFile);
} else {
database = commandScope.getArgumentValue(DATABASE_ARG);
}

CatalogAndSchema[] schemas = parseSchemas(database, commandScope.getArgumentValue(SCHEMAS_ARG));

logUnsupportedDatabase(database, this.getClass());
SnapshotControl snapshotControl;
if (commandScope.getArgumentValue(SNAPSHOT_CONTROL_ARG) == null) {
snapshotControl = new SnapshotControl(database);
} else {
snapshotControl = commandScope.getArgumentValue(SnapshotCommandStep.SNAPSHOT_CONTROL_ARG);
}

if (schemas == null) {
schemas = new CatalogAndSchema[]{database.getDefaultSchema()};
}

ObjectQuotingStrategy originalQuotingStrategy = database.getObjectQuotingStrategy();

database.setObjectQuotingStrategy(ObjectQuotingStrategy.QUOTE_ALL_OBJECTS);

try {
DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(schemas, database, snapshotControl);

snapshot.setMetadata(this.getSnapshotMetadata());

resultsBuilder.addResult("snapshot", snapshot);
resultsBuilder.addResult("statusCode", 0);

OutputStream outputStream = resultsBuilder.getOutputStream();
if (outputStream != null) {
String result = printSnapshot(commandScope, snapshot);
Writer outputWriter = getOutputWriter(outputStream);
outputWriter.write(result);
outputWriter.flush();
}
} finally {
//
// Reset the quoting strategy
//
database.setObjectQuotingStrategy(originalQuotingStrategy);

//
// Need to clean up here since we created the Database
//
if (commandScope.getArgumentValue(DATABASE_ARG) == null) {
closeDatabase();
}
}
}

private Writer getOutputWriter(final OutputStream outputStream) throws IOException {
String charsetName = GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentValue();

return new OutputStreamWriter(outputStream, charsetName);
}

/**
*
* Method to create a Database object given these parameters
*
* @param url URL to connect to
* @param username Username credential
* @param password Password credential
* @param defaultSchemaName Default schema for connection
* @param defaultCatalogName Default catalog for connection
* @param driver Driver class
* @param driverPropertiesFile Additional driver properties
* @throws DatabaseException Thrown when there is a connection error
*
*/
private void createDatabaseObject(String url,
String username,
String password,
String defaultSchemaName,
String defaultCatalogName,
String driver,
String driverPropertiesFile)
throws DatabaseException {
ResourceAccessor resourceAccessor = Scope.getCurrentScope().getResourceAccessor();
String databaseClassName = null;
Class databaseClass = LiquibaseCommandLineConfiguration.DATABASE_CLASS.getCurrentValue();
if (databaseClass != null) {
databaseClassName = databaseClass.getCanonicalName();
}
String propertyProviderClass = null;
Class clazz = LiquibaseCommandLineConfiguration.PROPERTY_PROVIDER_CLASS.getCurrentValue();
if (clazz != null) {
propertyProviderClass = clazz.getName();
}
String liquibaseCatalogName = GlobalConfiguration.LIQUIBASE_CATALOG_NAME.getCurrentValue();
String liquibaseSchemaName = GlobalConfiguration.LIQUIBASE_SCHEMA_NAME.getCurrentValue();
String databaseChangeLogTablespaceName = GlobalConfiguration.LIQUIBASE_TABLESPACE_NAME.getCurrentValue();
String databaseChangeLogLockTableName = GlobalConfiguration.DATABASECHANGELOGLOCK_TABLE_NAME.getCurrentValue();
String databaseChangeLogTableName = GlobalConfiguration.DATABASECHANGELOG_TABLE_NAME.getCurrentValue();
database =
CommandLineUtils.createDatabaseObject(resourceAccessor,
url,
username,
password,
driver,
defaultCatalogName,
defaultSchemaName,
true,
true,
databaseClassName,
driverPropertiesFile,
propertyProviderClass,
liquibaseCatalogName, liquibaseSchemaName,
databaseChangeLogTableName,
databaseChangeLogLockTableName);
database.setLiquibaseTablespaceName(databaseChangeLogTablespaceName);
}

private void closeDatabase() {
try {
if (database != null) {
database.rollback();
database.close();
}
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).warning(
coreBundle.getString("problem.closing.connection"), e);
}
}

private String printSnapshot(CommandScope commandScope, DatabaseSnapshot snapshot) {
String format = commandScope.getArgumentValue(SNAPSHOT_FORMAT_ARG);
if (format == null) {
format = "txt";
}

return SnapshotSerializerFactory.getInstance()
.getSerializer(format.toLowerCase(Locale.US))
.serialize(snapshot, true);
}

private void logUnsupportedDatabase(Database database, Class callingClass) {
if (LicenseServiceUtils.isProLicenseValid()) {
if (!(database instanceof MSSQLDatabase
|| database instanceof OracleDatabase
|| database instanceof MySQLDatabase
|| database instanceof DB2Database
|| database instanceof PostgresDatabase)) {
Scope.getCurrentScope().getUI().sendMessage("INFO This command might not yet capture Liquibase Pro additional object types on " + database.getShortName());
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,23 @@ public SortedSet<ConfigurationDefinition<?>> getRegisteredDefinitions(boolean in
}

/**
* @return the registered {@link ConfigurationDefinition} asssociated with this key. Null if none match.
* @return the registered {@link ConfigurationDefinition} associated with this key. Null if none match.
*/
public ConfigurationDefinition<?> getRegisteredDefinition(String key) {
for (ConfigurationDefinition<?> def : getRegisteredDefinitions(true)) {
if (def.getKey().equalsIgnoreCase(key)) {
return def;
}
final Set<String> aliasKeys = def.getAliasKeys();
if (aliasKeys != null && aliasKeys.contains(key)) {
return def;
}
if(def.getKey().replace(".","").equalsIgnoreCase(key)) {
return def;
List<String> keys = new ArrayList<>();
keys.add(def.getKey());
keys.addAll(def.getAliasKeys());

for (String keyName : keys) {
if (keyName.equalsIgnoreCase(key)) {
return def;
}
if (keyName.replace(".", "").equalsIgnoreCase(key)) {
return def;
}
}

}
return null;
}
Expand Down
Loading