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

Liquibase flow command (DAT-10419) #2946

Merged
merged 22 commits into from Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d9b7e4b
make base license message public (DAT-10584) (#2941)
StevenMassaro Jun 15, 2022
6a76491
make BASE_INVALID_LICENSE_MESSAGE variable private
StevenMassaro Jun 16, 2022
0418087
Added testing framework modifications for flow DAT 10586 (#2981)
wwillard7800 Jun 22, 2022
5aacdb4
general changes to support flow file validation (DAT-10588) (#2986)
StevenMassaro Jun 22, 2022
37c5506
do not allow commands to close the top level output stream
StevenMassaro Jun 23, 2022
137a92a
create the UnclosableOutputStream in the setOutput method
StevenMassaro Jun 23, 2022
a0d0519
Merge pull request #2999 from liquibase/DAT-10419-issue-6
wwillard7800 Jun 23, 2022
e62f0d9
Sonar lint cleanup
wwillard7800 Jun 23, 2022
ab3862f
Moved UnclosableOutputStream class into CommandScope and made it private
wwillard7800 Jun 24, 2022
40318c2
Merge remote-tracking branch 'origin/master' into DAT-10419
wwillard7800 Jun 27, 2022
c36c11a
Merge remote-tracking branch 'origin/master' into DAT-10419
wwillard7800 Jun 28, 2022
44a3c50
Merge remote-tracking branch 'origin/master' into DAT-10419
wwillard7800 Jun 29, 2022
16c0d58
Merge branch 'master' into DAT-10419
StevenMassaro Jul 11, 2022
4873f13
Merge remote-tracking branch 'origin/master' into DAT-10419
wwillard7800 Jul 13, 2022
2d6c3b0
add liquibase flow and flow.validate goals to maven plugin (DAT-10576…
StevenMassaro Jul 13, 2022
c7db5af
Merge remote-tracking branch 'origin/master' into DAT-10419
wwillard7800 Jul 21, 2022
a631442
Merge branch 'master' into DAT-10419
StevenMassaro Jul 22, 2022
5962525
Merge branch 'master' into DAT-10419
StevenMassaro Jul 25, 2022
011e79b
Merge branch 'master' into DAT-10419
StevenMassaro Jul 26, 2022
39a6cf7
allow passing any arguments to flow commands from maven plugin (DAT-1…
StevenMassaro Jul 27, 2022
c798a83
Merge branch 'master' into DAT-10419
suryaaki2 Jul 28, 2022
2e2cb89
Merge branch 'master' into DAT-10419
suryaaki2 Jul 28, 2022
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 @@ -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
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);
}
}