Skip to content

Commit

Permalink
JENKINS-37699: Allow expressions in configuration fields.
Browse files Browse the repository at this point in the history
General cleanup of properties assemblage
  • Loading branch information
prospero238 committed Aug 25, 2016
1 parent 3643a97 commit e4bfd70
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 56 deletions.
16 changes: 16 additions & 0 deletions pom.xml
Expand Up @@ -174,5 +174,21 @@
<jenkins.version>2.7.2</jenkins.version>
</properties>
</profile>
<profile>
<id>logback</id>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
</profile>

</profiles>
</project>
Expand Up @@ -3,7 +3,7 @@
public enum LiquibaseProperty {
USERNAME,
PASSWORD,
CLASSPATH,
CLASSPATH("classpath"),
LABELS,
DEFAULTS_FILE("defaultsFile"),
CHANGELOG_FILE("changeLogFile"),
Expand Down
@@ -1,5 +1,6 @@
package org.jenkinsci.plugins.liquibase.common;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.AbstractBuild;

Expand All @@ -23,52 +24,74 @@ public class PropertiesAssembler {
private static final String DEFAULT_DB_DRIVER = "org.h2.Driver";

/**
* Creates a properties instance for use with liquibase execution.
* Creates a properties instance for use with liquibase execution. Property values may come from these sources,
* in order of least to most precedence:
*
* <ul>
* <li>Plugin Default values</li>
* <li>Values from properties file described by {@link AbstractLiquibaseBuilder#liquibasePropertiesPath}</li>
* <li>Values on the {@link AbstractLiquibaseBuilder} itself.</li>
* </ul>
*
* Furthermore, any token expressions found are replaced with values found in the passed environment.
*
* @param liquibaseBuilder
* @param build
* @param environment
* @return
*/
public static Properties createLiquibaseProperties(AbstractLiquibaseBuilder liquibaseBuilder, AbstractBuild<?, ?> build) {
public static Properties createLiquibaseProperties(AbstractLiquibaseBuilder liquibaseBuilder,
AbstractBuild<?, ?> build, EnvVars environment)
throws IOException, InterruptedException {
Properties properties = new Properties();
assembleDefaults(properties);
assembleFromProjectConfiguration(liquibaseBuilder, properties, build);
String propertiesPath = resolvePropertiesPath(liquibaseBuilder, environment);
assembleFromPropertiesFile(properties, propertiesPath, build);
assembleFromProjectConfiguration(liquibaseBuilder, properties, environment);
return properties;
}

protected static void assembleFromProjectConfiguration(AbstractLiquibaseBuilder liquibaseBuilder,
Properties properties,
AbstractBuild<?, ?> build) {


String liquibasePropertiesPath = liquibaseBuilder.getLiquibasePropertiesPath();
readFromExternalProperties(properties, liquibasePropertiesPath, build);
addPropertyIfDefined(properties, LiquibaseProperty.CHANGELOG_FILE, liquibaseBuilder.getChangeLogFile());
addPropertyIfDefined(properties, LiquibaseProperty.USERNAME, liquibaseBuilder.getUsername());
addPropertyIfDefined(properties, LiquibaseProperty.PASSWORD, liquibaseBuilder.getPassword());
addPropertyIfDefined(properties, LiquibaseProperty.DEFAULT_SCHEMA_NAME, liquibaseBuilder.getDefaultSchemaName());
addPropertyIfDefined(properties, LiquibaseProperty.URL, liquibaseBuilder.getUrl());
addPropertyIfDefined(properties, LiquibaseProperty.CHANGELOG_FILE, liquibaseBuilder.getChangeLogFile());
addPropertyIfDefined(properties, LiquibaseProperty.LABELS, liquibaseBuilder.getLabels());
addPropertyIfDefined(properties, LiquibaseProperty.CONTEXTS, liquibaseBuilder.getContexts());
resolveDatabaseDriver(liquibaseBuilder, properties);
EnvVars environment)
throws IOException, InterruptedException {



addPropertyIfDefined(properties, LiquibaseProperty.CHANGELOG_FILE, liquibaseBuilder.getChangeLogFile(),
environment);

addPropertyIfDefined(properties, LiquibaseProperty.CLASSPATH, liquibaseBuilder.getClasspath(), environment);
addPropertyIfDefined(properties, LiquibaseProperty.USERNAME, liquibaseBuilder.getUsername(), environment);
addPropertyIfDefined(properties, LiquibaseProperty.PASSWORD, liquibaseBuilder.getPassword(), environment);
addPropertyIfDefined(properties, LiquibaseProperty.DEFAULT_SCHEMA_NAME, liquibaseBuilder.getDefaultSchemaName(),
environment);
addPropertyIfDefined(properties, LiquibaseProperty.URL, liquibaseBuilder.getUrl(), environment);
addPropertyIfDefined(properties, LiquibaseProperty.CHANGELOG_FILE, liquibaseBuilder.getChangeLogFile(),
environment);
addPropertyIfDefined(properties, LiquibaseProperty.LABELS, liquibaseBuilder.getLabels(), environment);
addPropertyIfDefined(properties, LiquibaseProperty.CONTEXTS, liquibaseBuilder.getContexts(), environment);
resolveDatabaseDriver(liquibaseBuilder, properties, environment);
}

private static void resolveDatabaseDriver(AbstractLiquibaseBuilder liquibaseBuilder, Properties properties) {
private static void resolveDatabaseDriver(AbstractLiquibaseBuilder liquibaseBuilder,
Properties properties,
EnvVars environment) {
if (!Strings.isNullOrEmpty(liquibaseBuilder.getDatabaseEngine())) {
PropertiesAssembler.setDriverFromDBEngine(liquibaseBuilder, properties);
} else {
if (!Strings.isNullOrEmpty(liquibaseBuilder.getDriverClassname())) {
setProperty(properties, LiquibaseProperty.DRIVER, liquibaseBuilder.getDriverClassname());
}
addPropertyIfDefined(properties, LiquibaseProperty.DRIVER, liquibaseBuilder.getDriverClassname(),
environment);
}
}

private static void readFromExternalProperties(Properties properties,

private static void assembleFromPropertiesFile(Properties properties,
String liquibasePropertiesPath,
AbstractBuild<?, ?> build) {
if (!Strings.isNullOrEmpty(liquibasePropertiesPath)) {
FilePath workspace = build.getWorkspace();
if (workspace!=null) {
if (workspace != null) {
InputStreamReader streamReader = null;
try {
FilePath liquibaseProperties = workspace.child(liquibasePropertiesPath);
Expand All @@ -85,7 +108,9 @@ private static void readFromExternalProperties(Properties properties,
IOUtils.closeQuietly(streamReader);
}
} else {
throw new LiquibaseRuntimeException("Project workspace was found to be null when attempting to load liquibase properties file at '" + liquibasePropertiesPath + '.');
throw new LiquibaseRuntimeException(
"Project workspace was found to be null when attempting to load liquibase properties file at '" +
liquibasePropertiesPath + '.');
}
}
}
Expand All @@ -99,14 +124,19 @@ private static void setProperty(Properties properties, LiquibaseProperty liquiba
properties.setProperty(liquibaseProperty.propertyName(), value);
}

private static void addPropertyIfDefined(Properties properties,
LiquibaseProperty liquibaseProperty,
String value) {
protected static void addPropertyIfDefined(Properties properties,
LiquibaseProperty liquibaseProperty,
String value, EnvVars environment) {
if (!Strings.isNullOrEmpty(value)) {
setProperty(properties, liquibaseProperty, value);
String resolvedValue = hudson.Util.replaceMacro(value, environment);
properties.setProperty(liquibaseProperty.propertyName(), resolvedValue);
}
}

private static String resolvePropertiesPath(AbstractLiquibaseBuilder liquibaseBuilder, EnvVars environment) {
return hudson.Util.replaceMacro(liquibaseBuilder.getLiquibasePropertiesPath(), environment);
}

public static void setDriverFromDBEngine(AbstractLiquibaseBuilder liquibaseBuilder, Properties properties) {
if (!Strings.isNullOrEmpty(liquibaseBuilder.getDatabaseEngine())) {
for (IncludedDatabaseDriver includedDatabaseDriver : liquibaseBuilder.getDrivers()) {
Expand Down
@@ -1,5 +1,6 @@
package org.jenkinsci.plugins.liquibase.evaluator;

import hudson.EnvVars;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
Expand Down Expand Up @@ -86,7 +87,7 @@ public abstract void doPerform(AbstractBuild<?, ?> build,
BuildListener listener,
Liquibase liquibase,
Contexts contexts,
ExecutedChangesetAction executedChangesetAction)
ExecutedChangesetAction executedChangesetAction, Properties configProperties)
throws InterruptedException, IOException, LiquibaseException;

abstract public Descriptor<Builder> getDescriptor();
Expand All @@ -95,13 +96,14 @@ public abstract void doPerform(AbstractBuild<?, ?> build,
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {

Properties configProperties = PropertiesAssembler.createLiquibaseProperties(this, build);
Properties configProperties = PropertiesAssembler.createLiquibaseProperties(this, build,
build.getEnvironment(listener));
ExecutedChangesetAction executedChangesetAction = new ExecutedChangesetAction(build);
Liquibase liquibase = createLiquibase(build, listener, executedChangesetAction, configProperties, launcher);
String liqContexts = getProperty(configProperties, LiquibaseProperty.CONTEXTS);
Contexts contexts = new Contexts(liqContexts);
try {
doPerform(build, listener, liquibase, contexts, executedChangesetAction);
doPerform(build, listener, liquibase, contexts, executedChangesetAction, configProperties);
} catch (LiquibaseException e) {
e.printStackTrace(listener.getLogger());
build.setResult(Result.UNSTABLE);
Expand All @@ -118,13 +120,14 @@ public Liquibase createLiquibase(AbstractBuild<?, ?> build,
BuildListener listener,
ExecutedChangesetAction action,
Properties configProperties,
Launcher launcher) {
Launcher launcher) throws IOException, InterruptedException {
Liquibase liquibase;
String driverName = getProperty(configProperties, LiquibaseProperty.DRIVER);
String resolvedClasspath = getProperty(configProperties, LiquibaseProperty.CLASSPATH);

try {
if (!Strings.isNullOrEmpty(classpath)) {
Util.addClassloader(launcher.isUnix(), build.getWorkspace(), classpath);
if (!Strings.isNullOrEmpty(resolvedClasspath)) {
Util.addClassloader(launcher.isUnix(), build.getWorkspace(), resolvedClasspath);
}

JdbcConnection jdbcConnection = createJdbcConnection(configProperties, driverName);
Expand All @@ -146,20 +149,33 @@ public Liquibase createLiquibase(AbstractBuild<?, ?> build,
}
BuildChangeExecListener buildChangeExecListener = new BuildChangeExecListener(action, listener);
liquibase.setChangeExecListener(buildChangeExecListener);

if (!Strings.isNullOrEmpty(changeLogParameters)) {
Map<String, String> keyValuePairs = Splitter.on("\n").withKeyValueSeparator("=").split(changeLogParameters);
for (Map.Entry<String, String> entry : keyValuePairs.entrySet()) {
liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
}
EnvVars environment = build.getEnvironment(listener);
populateChangeLogParameters(liquibase, environment, changeLogParameters);
}
return liquibase;
}

protected static void populateChangeLogParameters(Liquibase liquibase,
EnvVars environment,
String changeLogParameters) {
Map<String, String> keyValuePairs = Splitter.on("\n").withKeyValueSeparator("=").split(changeLogParameters);
for (Map.Entry<String, String> entry : keyValuePairs.entrySet()) {
String value = entry.getValue();

String resolvedValue = hudson.Util.replaceMacro(value, environment);
String resolvedKey = hudson.Util.replaceMacro(entry.getKey(), environment);
liquibase.setChangeLogParameter(resolvedKey, resolvedValue);
}
}

private JdbcConnection createJdbcConnection(Properties configProperties, String driverName) {
Connection connection;
String dbUrl = getProperty(configProperties, LiquibaseProperty.URL);
try {
Util.registerDatabaseDriver(driverName, classpath);
Util.registerDatabaseDriver(driverName,
configProperties.getProperty(LiquibaseProperty.CLASSPATH.propertyName()));
String userName = getProperty(configProperties, LiquibaseProperty.USERNAME);
String password = getProperty(configProperties, LiquibaseProperty.PASSWORD);
connection = DriverManager.getConnection(dbUrl, userName, password);
Expand Down
Expand Up @@ -14,8 +14,10 @@
import liquibase.exception.MigrationFailedException;

import java.io.IOException;
import java.util.Properties;

import org.jenkinsci.plugins.liquibase.common.LiquibaseCommand;
import org.jenkinsci.plugins.liquibase.common.LiquibaseProperty;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.slf4j.Logger;
Expand Down Expand Up @@ -68,7 +70,7 @@ public void doPerform(AbstractBuild<?, ?> build,
BuildListener listener,
Liquibase liquibase,
Contexts contexts,
ExecutedChangesetAction executedChangesetAction)
ExecutedChangesetAction executedChangesetAction, Properties configProperties)
throws InterruptedException, IOException {


Expand All @@ -88,7 +90,7 @@ public void doPerform(AbstractBuild<?, ?> build,
}
listener.getLogger().println("Running liquibase command '" + resolvedCommand + "'");

LabelExpression labelExpression = new LabelExpression(labels);
LabelExpression labelExpression = new LabelExpression(getProperty(configProperties, LiquibaseProperty.LABELS));
if (LiquibaseCommand.UPDATE_TESTING_ROLLBACKS.isCommand(resolvedCommand)) {
liquibase.updateTestingRollback(contexts, labelExpression);
}
Expand Down
Expand Up @@ -16,6 +16,7 @@
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;

import org.jenkinsci.plugins.liquibase.exception.LiquibaseRuntimeException;
import org.kohsuke.stapler.DataBoundConstructor;
Expand Down Expand Up @@ -80,7 +81,7 @@ public void doPerform(AbstractBuild<?, ?> build,
BuildListener listener,
Liquibase liquibase,
Contexts contexts,
ExecutedChangesetAction executedChangesetAction)
ExecutedChangesetAction executedChangesetAction, Properties configProperties)
throws InterruptedException, IOException, LiquibaseException {

executedChangesetAction.setRollbackOnly(true);
Expand Down
@@ -0,0 +1,88 @@
package org.jenkinsci.plugins.liquibase.common;

import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.tasks.Builder;
import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.exception.LiquibaseException;

import java.io.IOException;
import java.util.Properties;

import org.jenkinsci.plugins.liquibase.evaluator.AbstractLiquibaseBuilder;
import org.jenkinsci.plugins.liquibase.evaluator.ExecutedChangesetAction;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class PropertiesAssemblerTest {

@Mock
private AbstractBuild build;
@Mock
private BuildListener listener;
protected Properties properties = new Properties();
protected LiquibaseProperty changelogFile = LiquibaseProperty.CHANGELOG_FILE;
protected EnvVars envVars = new EnvVars();


@Test
public void should_resolve_env_variables() throws IOException, InterruptedException {
String environmentProperty = "token";
String environmentValue = "resolvedValue";
envVars.put(environmentProperty, environmentValue);

when(build.getEnvironment(listener)).thenReturn(envVars);

String expression = "${" + environmentProperty + "}";
BuilderStub liquibaseBuilder = new BuilderStub();
liquibaseBuilder.setChangeLogFile(expression);
PropertiesAssembler.assembleFromProjectConfiguration(liquibaseBuilder, properties,
build.getEnvironment(listener));

assertThat(properties.getProperty(changelogFile.propertyName()), is(environmentValue));

}

@Test
public void should_not_resolve_expression() {
Properties properties = new Properties();
String value = "${not_in_env}";
PropertiesAssembler.addPropertyIfDefined(properties, changelogFile, value, envVars);

assertThat(properties.getProperty(changelogFile.propertyName()), is(value));
}



private static class BuilderStub extends AbstractLiquibaseBuilder {
@Override
public void doPerform(AbstractBuild<?, ?> build,
BuildListener listener,
Liquibase liquibase,
Contexts contexts,
ExecutedChangesetAction executedChangesetAction, Properties configProperties)
throws InterruptedException, IOException, LiquibaseException {

}

@Override
public Descriptor<Builder> getDescriptor() {
return null;
}

@Override
public String getChangeLogFile() {
return "${token}";
}
}
}

0 comments on commit e4bfd70

Please sign in to comment.