Skip to content

Commit

Permalink
Liquibase flow command (DAT-10419) (#2946)
Browse files Browse the repository at this point in the history
* make base license message public (DAT-10584) (#2941)

* make BASE_INVALID_LICENSE_MESSAGE variable private

* Added testing framework modifications for flow DAT 10586 (#2981)

Co-authored-by: Steven Massaro <steven.massaro.web@gmail.com>

* general changes to support flow file validation (DAT-10588) (#2986)

add createIfNull(Map) method to CollectionUtil
expose separate validate() method in CommandScope
add constructor to CommandValidationException(Throwable)

* do not allow commands to close the top level output stream

* create the UnclosableOutputStream in the setOutput method

* Sonar lint cleanup

* Moved UnclosableOutputStream class into CommandScope and made it private
Do not create if output stream is null

DAT-10419

* add liquibase flow and flow.validate goals to maven plugin (DAT-10576) (#3015)

Add a flow and flow.validate goal to the maven plugin. Also, add class check before casting in Main.java.

* allow passing any arguments to flow commands from maven plugin (DAT-10944) (#3103)

Add a flowCommandArguments Map to the mojo definition for flow and flow.validate maven goals. The arguments will be passed to every command that is run in flow.

Co-authored-by: Wesley willard <wwillard@austin.rr.com>
Co-authored-by: Wesley Willard <wwillard@datical.com>
Co-authored-by: suryaaki2 <80348493+suryaaki2@users.noreply.github.com>
  • Loading branch information
4 people committed Jul 28, 2022
1 parent 6ad7472 commit 24102a9
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 9 deletions.
52 changes: 46 additions & 6 deletions liquibase-core/src/main/java/liquibase/command/CommandScope.java
Expand Up @@ -3,8 +3,11 @@
import liquibase.Scope;
import liquibase.configuration.*;
import liquibase.exception.CommandExecutionException;
import liquibase.exception.CommandValidationException;
import liquibase.util.StringUtil;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

Expand Down Expand Up @@ -126,16 +129,21 @@ public <T> T getArgumentValue(CommandArgumentDefinition<T> argument) {
* Think "what would be piped out", not "what the user is told about what is happening".
*/
public CommandScope setOutput(OutputStream outputStream) {
this.outputStream = outputStream;
/*
This is an UnclosableOutputStream because we do not want individual command steps to inadvertently (or
intentionally) close the System.out OutputStream. Closing System.out renders it unusable for other command
steps which expect it to still be open. If the passed OutputStream is null then we do not create it.
*/
if (outputStream != null) {
this.outputStream = new UnclosableOutputStream(outputStream);
} else {
this.outputStream = null;
}

return this;
}

/**
* Executes the command in this scope, and returns the results.
*/
public CommandResults execute() throws CommandExecutionException {
CommandResultsBuilder resultsBuilder = new CommandResultsBuilder(this, outputStream);
public void validate() throws CommandValidationException {
for (ConfigurationValueProvider provider : Scope.getCurrentScope().getSingleton(LiquibaseConfiguration.class).getProviders()) {
provider.validate(this);
}
Expand All @@ -151,6 +159,15 @@ public CommandResults execute() throws CommandExecutionException {
for (CommandStep step : pipeline) {
step.validate(this);
}
}

/**
* Executes the command in this scope, and returns the results.
*/
public CommandResults execute() throws CommandExecutionException {
CommandResultsBuilder resultsBuilder = new CommandResultsBuilder(this, outputStream);
final List<CommandStep> pipeline = commandDefinition.getPipeline();
validate();
try {
for (CommandStep command : pipeline) {
command.run(resultsBuilder);
Expand Down Expand Up @@ -191,6 +208,29 @@ private <T> ConfigurationDefinition<T> createConfigurationDefinition(CommandArgu
.buildTemporary();
}

/**
* This class is a wrapper around OutputStreams, and makes them impossible for callers to close.
*/
private static class UnclosableOutputStream extends FilterOutputStream {
public UnclosableOutputStream(OutputStream out) {
super(out);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}

/**
* This method does not actually close the underlying stream, but rather only flushes it. Callers should not be
* closing the stream they are given.
*/
@Override
public void close() throws IOException {
out.flush();
}
}

private class CommandScopeValueProvider extends AbstractMapConfigurationValueProvider {

@Override
Expand Down
Expand Up @@ -13,6 +13,10 @@ public CommandValidationException(String message) {
super(message);
}

public CommandValidationException(Throwable cause) {
super(cause);
}

public CommandValidationException(String argument, String message, Throwable cause) {
super(buildMessage(argument, message), cause);
}
Expand Down
Expand Up @@ -288,8 +288,10 @@ public Integer run() throws Exception {
java.util.logging.Logger liquibaseLogger = java.util.logging.Logger.getLogger("liquibase");
liquibaseLogger.setParent(rootLogger);

final JavaLogService logService = (JavaLogService) Scope.getCurrentScope().get(Scope.Attr.logService, LogService.class);
logService.setParent(liquibaseLogger);
LogService logService = Scope.getCurrentScope().get(Scope.Attr.logService, LogService.class);
if (logService instanceof JavaLogService) {
((JavaLogService) logService).setParent(liquibaseLogger);
}

if (main.logLevel == null) {
String defaultLogLevel = System.getProperty("liquibase.log.level");
Expand Down
13 changes: 12 additions & 1 deletion liquibase-core/src/main/java/liquibase/util/CollectionUtil.java
Expand Up @@ -78,7 +78,7 @@ public static <T> T[] createIfNull(T[] arguments) {
}

/**
* Returns a new empty set if the passed array is null.
* Returns a new empty set if the passed set is null.
*/
public static <T> Set<T> createIfNull(Set<T> currentValue) {
if (currentValue == null) {
Expand All @@ -88,6 +88,17 @@ public static <T> Set<T> createIfNull(Set<T> currentValue) {
}
}

/**
* Returns a new empty map if the passed map is null.
*/
public static <T, E> Map<T, E> createIfNull(Map<T, E> currentValue) {
if (currentValue == null) {
return new HashMap<>();
} else {
return currentValue;
}
}

/**
* Converts a set of nested maps (like from yaml/json) into a flat map with dot-separated properties
*/
Expand Down
Expand Up @@ -1105,6 +1105,9 @@ Long Description: ${commandDefinition.getLongDescription() ?: "NOT SET"}
this.setups.add(new SetupModifyTextFile(textFile, originalString, newString))
}

void modifyDbCredentials(File textFile) {
this.setups.add(new SetupModifyDbCredentials(textFile))
}
private void validate() throws IllegalArgumentException {

}
Expand Down
@@ -0,0 +1,31 @@
package liquibase.extension.testing.setup

import liquibase.util.FileUtil

/**
*
* This class allows modification of a text file to
* replace tokens with the actual database credential
* as specified in the environment
*
*/
class SetupModifyDbCredentials extends TestSetup {

private static final String URL = "_URL_"
private static final String USERNAME = "_USERNAME_"
private static final String PASSWORD = "_PASSWORD_"
private final File textFile

SetupModifyDbCredentials(File textFile) {
this.textFile = textFile
}

@Override
void setup(TestSetupEnvironment testSetupEnvironment) throws Exception {
String contents = FileUtil.getContents(textFile)
contents = contents.replaceAll(URL, testSetupEnvironment.url)
contents = contents.replaceAll(USERNAME, testSetupEnvironment.username)
contents = contents.replaceAll(PASSWORD, testSetupEnvironment.password)
FileUtil.write(contents, textFile)
}
}
@@ -0,0 +1,67 @@
package org.liquibase.maven.plugins;

import liquibase.Liquibase;
import liquibase.Scope;
import liquibase.command.CommandScope;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.exception.CommandExecutionException;
import liquibase.exception.LiquibaseException;
import org.liquibase.maven.property.PropertyElement;
import org.liquibase.maven.provider.FlowCommandArgumentValueProvider;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Map;

public abstract class AbstractLiquibaseFlowMojo extends AbstractLiquibaseMojo {
/**
* Specifies the <i>flowFile</i> to use. If not specified, the default
* checks will be used and no file will be created.
*
* @parameter property="liquibase.flowFile"
*/
@PropertyElement
protected String flowFile;

/**
* @parameter property="liquibase.outputFile"
*/
@PropertyElement
protected File outputFile;

/**
* Arbitrary map of parameters that the underlying liquibase command will use. These arguments will be passed
* verbatim to the underlying liquibase command that is being run.
*
* @parameter property="flowCommandArguments"
*/
@PropertyElement
protected Map<String, Object> flowCommandArguments;

@Override
public boolean databaseConnectionRequired() {
return false;
}

@Override
protected void performLiquibaseTask(Liquibase liquibase) throws LiquibaseException {
CommandScope liquibaseCommand = new CommandScope(getCommandName());
liquibaseCommand.addArgumentValue("flowFile", flowFile);
liquibaseCommand.addArgumentValue("flowIntegration", "maven");
if (flowCommandArguments != null) {
FlowCommandArgumentValueProvider flowCommandArgumentValueProvider = new FlowCommandArgumentValueProvider(flowCommandArguments);
Scope.getCurrentScope().getSingleton(LiquibaseConfiguration.class).registerProvider(flowCommandArgumentValueProvider);
}
if (outputFile != null) {
try {
liquibaseCommand.setOutput(new FileOutputStream(outputFile));
} catch (FileNotFoundException e) {
throw new CommandExecutionException(e);
}
}
liquibaseCommand.execute();
}

public abstract String[] getCommandName();
}
@@ -0,0 +1,14 @@
package org.liquibase.maven.plugins;

/**
* Run a series of commands contained in one or more stages, as configured in a liquibase flow-file.
*
* @goal flow
*/
public class LiquibaseFlowMojo extends AbstractLiquibaseFlowMojo {

@Override
public String[] getCommandName() {
return new String[]{"flow"};
}
}
@@ -0,0 +1,13 @@
package org.liquibase.maven.plugins;

/**
* Validate a series of commands contained in one or more stages, as configured in a liquibase flow-file.
*
* @goal flow.validate
*/
public class LiquibaseFlowValidateMojo extends AbstractLiquibaseFlowMojo{
@Override
public String[] getCommandName() {
return new String[]{"flow", "validate"};
}
}
@@ -0,0 +1,41 @@
package org.liquibase.maven.provider;

import liquibase.configuration.AbstractMapConfigurationValueProvider;

import java.util.Map;

public class FlowCommandArgumentValueProvider extends AbstractMapConfigurationValueProvider {

private final Map<String, Object> args;

public FlowCommandArgumentValueProvider(Map<String, Object> args) {
this.args = args;
}

@Override
public int getPrecedence() {
return 250;
}

@Override
protected Map<?, ?> getMap() {
return args;
}

@Override
protected String getSourceDescription() {
return "Arguments provided through maven when invoking flow or flow.validate maven goals";
}

@Override
protected boolean keyMatches(String wantedKey, String storedKey) {
if (super.keyMatches(wantedKey, storedKey)) {
return true;
}
if (wantedKey.startsWith("liquibase.command.")) {
return super.keyMatches(wantedKey.replaceFirst("^liquibase\\.command\\.", ""), storedKey);
}

return super.keyMatches(wantedKey.replaceFirst("^liquibase\\.", ""), storedKey);
}
}

0 comments on commit 24102a9

Please sign in to comment.