Skip to content

Commit

Permalink
Merge pull request #35 from bipolar/master
Browse files Browse the repository at this point in the history
Added support for interpolated variables
  • Loading branch information
rsandell committed Feb 13, 2018
2 parents 2620547 + 3653aa5 commit f9309ce
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 7 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -62,6 +62,11 @@
</properties>

<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>structs</artifactId>
Expand Down
Expand Up @@ -46,6 +46,7 @@
*/
public class ReadPropertiesStep extends AbstractFileOrTextStep {
private Map defaults;
private boolean interpolate;

@DataBoundConstructor
public ReadPropertiesStep() {
Expand Down Expand Up @@ -75,6 +76,27 @@ public void setDefaults(Map defaults) {
this.defaults = defaults;
}

/**
* Flag to indicate if the properties should be interpolated or not.
* I.E. :
* baseUrl = http://localhost
* url = ${baseUrl}/resources
* The value of <i>url</i> should be evaluated to http://localhost/resources with the interpolation on.
* @return the value of interpolated
*/
public Boolean isInterpolate() {
return interpolate;
}

/**
* Set the interpolated parameter.
* @param interpolate parameter.
*/
@DataBoundSetter
public void setInterpolate(Boolean interpolate) {
this.interpolate = interpolate;
}

@Extension
public static class DescriptorImpl extends AbstractFileOrTextStepDescriptorImpl {
public DescriptorImpl() {
Expand Down
Expand Up @@ -26,15 +26,14 @@

import hudson.FilePath;
import hudson.model.TaskListener;
import org.apache.commons.configuration2.AbstractConfiguration;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.ConfigurationConverter;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.pipeline.utility.steps.AbstractFileOrTextStep;
import org.jenkinsci.plugins.pipeline.utility.steps.AbstractFileOrTextStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import java.io.InputStream;
import java.io.PrintStream;
import java.io.StringReader;
Expand All @@ -59,9 +58,7 @@ protected ReadPropertiesStepExecution(@Nonnull ReadPropertiesStep step, @Nonnull

@Override
protected Map<String, Object> doRun() throws Exception {
TaskListener listener = getContext().get(TaskListener.class);
assert listener != null;
PrintStream logger = listener.getLogger();
PrintStream logger = getLogger();
Properties properties = new Properties();


Expand All @@ -87,6 +84,12 @@ protected Map<String, Object> doRun() throws Exception {
properties.load(sr);
}

// Check if we should interpolated values in the properties
if ( step.isInterpolate() ) {
logger.println("Interpolation set to true, starting to parse the variable!");
properties = interpolateProperties(properties);
}

Map<String, Object> result = new HashMap<>();
addAll(step.getDefaults(), result);
addAll(properties, result);
Expand All @@ -108,4 +111,41 @@ private void addAll(Map src, Map<String, Object> dst) {
dst.put(e.getKey() != null ? e.getKey().toString(): null, e.getValue());
}
}

/**
* Using commons collection to interpolated the values inside the properties
* @param properties the list of properties to be interpolated
* @return a new Properties object with the interpolated values
*/
private Properties interpolateProperties(Properties properties) throws Exception {
if ( properties == null)
return null;
Configuration interpolatedProp;
PrintStream logger = getLogger();
try {
// Convert the Properties to a Configuration object in order to apply the interpolation
Configuration conf = ConfigurationConverter.getConfiguration(properties);

// Apply interpolation
interpolatedProp = ((AbstractConfiguration)conf).interpolatedConfiguration();
} catch (Exception e) {
logger.println("Got exception while interpolating the variables: " + e.getMessage());
logger.println("Returning the original properties list!");
return properties;
}

// Convert back to properties
return ConfigurationConverter.getProperties(interpolatedProp);
}

/**
* Helper method to get the logger from the context.
* @return the logger from the context.
* @throws Exception
*/
private PrintStream getLogger() throws Exception {
TaskListener listener = getContext().get(TaskListener.class);
assert listener != null;
return listener.getLogger();
}
}
Expand Up @@ -45,6 +45,11 @@
An Optional Map containing default key/values.
<em>These are added to the resulting map first.</em>
</li>
<li>
<code>interpolate</code>:
Flag to indicate if the properties should be interpolated or not.
In case of error or cycling dependencies, the original properties will be returned.
</li>
</ul>
<p>
<strong>Example:</strong><br/>
Expand All @@ -58,4 +63,14 @@
assert props.other == 'Override'
</pre>
</code>
<strong>Example with interpolation:</strong>
<code>
<pre>
def props = readProperties interpolate: true, file: 'test.properties'
assert props.url = 'http://localhost'
assert props.resource = 'README.txt'
// if fullUrl is defined to ${url}/${resource} then it should evaluate to http://localhost/README.txt
assert props.fullUrl = 'http://localhost/README.txt'
</pre>
</code>
</p>
Expand Up @@ -181,4 +181,128 @@ public void readFileAndText() throws Exception {
"}", true));
j.assertBuildStatusSuccess(p.scheduleBuild2(0));
}

@Test
public void readFileAndTextInterpolatedDisabled() throws Exception {
Properties props = new Properties();
props.setProperty("test", "One");
props.setProperty("another", "Two");
File file = temp.newFile();
try (FileWriter f = new FileWriter(file)) {
props.store(f, "Pipeline test");
}

props = new Properties();
props.setProperty("url", "http://localhost");
props.setProperty("file", "book.txt");
props.setProperty("fileUrl", "${url}/${file}");
File textFile = temp.newFile();
try (FileWriter f = new FileWriter(textFile)) {
props.store(f, "Pipeline test");
}

WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node('slaves') {\n" +
" String propsText = readFile file: '" + separatorsToSystemEscaped(textFile.getAbsolutePath()) + "'\n" +
" def props = readProperties text: propsText, file: '" + separatorsToSystemEscaped(file.getAbsolutePath()) + "'\n" +
" assert props['test'] == 'One'\n" +
" assert props.test == 'One'\n" +
" assert props['url'] == 'http://localhost'\n" +
" assert props.url == 'http://localhost'\n" +
" assert props['file'] == 'book.txt'\n" +
" assert props.file == 'book.txt'\n" +
" assert props['fileUrl'] == '${url}/${file}'\n" +
" assert props.fileUrl == '${url}/${file}'\n" +
"}", true));
j.assertBuildStatusSuccess(p.scheduleBuild2(0));

p = j.jenkins.createProject(WorkflowJob.class, "p2");
p.setDefinition(new CpsFlowDefinition(
"node('slaves') {\n" +
" String propsText = readFile file: '" + separatorsToSystemEscaped(textFile.getAbsolutePath()) + "'\n" +
" def props = readProperties interpolate: false, text: propsText, file: '" + separatorsToSystemEscaped(file.getAbsolutePath()) + "'\n" +
" assert props['test'] == 'One'\n" +
" assert props.test == 'One'\n" +
" assert props['url'] == 'http://localhost'\n" +
" assert props.url == 'http://localhost'\n" +
" assert props['file'] == 'book.txt'\n" +
" assert props.file == 'book.txt'\n" +
" assert props['fileUrl'] == '${url}/${file}'\n" +
" assert props.fileUrl == '${url}/${file}'\n" +
"}", true));
j.assertBuildStatusSuccess(p.scheduleBuild2(0));
}

@Test
public void readFileAndTextInterpolated() throws Exception {
Properties props = new Properties();
props.setProperty("test", "One");
props.setProperty("another", "Two");
File file = temp.newFile();
try (FileWriter f = new FileWriter(file)) {
props.store(f, "Pipeline test");
}

props = new Properties();
props.setProperty("url", "http://localhost");
props.setProperty("file", "book.txt");
props.setProperty("fileUrl", "${url}/${file}");
File textFile = temp.newFile();
try (FileWriter f = new FileWriter(textFile)) {
props.store(f, "Pipeline test");
}

WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node('slaves') {\n" +
" String propsText = readFile file: '" + separatorsToSystemEscaped(textFile.getAbsolutePath()) + "'\n" +
" def props = readProperties interpolate: true, text: propsText, file: '" + separatorsToSystemEscaped(file.getAbsolutePath()) + "'\n" +
" assert props['test'] == 'One'\n" +
" assert props.test == 'One'\n" +
" assert props['url'] == 'http://localhost'\n" +
" assert props.url == 'http://localhost'\n" +
" assert props['file'] == 'book.txt'\n" +
" assert props.file == 'book.txt'\n" +
" assert props['fileUrl'] == 'http://localhost/book.txt'\n" +
" assert props.fileUrl == 'http://localhost/book.txt'\n" +
"}", true));
j.assertBuildStatusSuccess(p.scheduleBuild2(0));
}

@Test
public void readFileAndTextInterpolatedWithCyclicValues() throws Exception {
Properties props = new Properties();
props.setProperty("test", "One");
props.setProperty("another", "Two");
File file = temp.newFile();
try (FileWriter f = new FileWriter(file)) {
props.store(f, "Pipeline test");
}

props = new Properties();
props.setProperty("url", "${file}");
props.setProperty("file", "${fileUrl}");
props.setProperty("fileUrl", "${url}");
File textFile = temp.newFile();
try (FileWriter f = new FileWriter(textFile)) {
props.store(f, "Pipeline test");
}

WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node('slaves') {\n" +
" String propsText = readFile file: '" + separatorsToSystemEscaped(textFile.getAbsolutePath()) + "'\n" +
" def props = readProperties text: propsText, interpolate: true, file: '" + separatorsToSystemEscaped(file.getAbsolutePath()) + "'\n" +
" assert props['test'] == 'One'\n" +
" assert props.test == 'One'\n" +
" assert props['url'] == '${file}'\n" +
" assert props.url == '${file}'\n" +
" assert props['file'] == '${fileUrl}'\n" +
" assert props.file == '${fileUrl}'\n" +
" assert props['fileUrl'] == '${url}'\n" +
" assert props.fileUrl == '${url}'\n" +
"}", true));
j.assertBuildStatusSuccess(p.scheduleBuild2(0));
}
}

0 comments on commit f9309ce

Please sign in to comment.