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

Added support for interpolated variables #35

Merged
merged 7 commits into from
Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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();
}
}
Original file line number Diff line number Diff line change
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example would be nice here. Not all users understands what interpolated means :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I've added an extra example with interpolation.

</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>
Original file line number Diff line number Diff line change
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));
}
}