Skip to content

Commit

Permalink
Quality Checks for Databases (DAT-9298) (#2715)
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenMassaro committed Jun 3, 2022
1 parent c1a67a7 commit 8c9cde5
Show file tree
Hide file tree
Showing 32 changed files with 558 additions and 537 deletions.
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

0 comments on commit 8c9cde5

Please sign in to comment.