From 47f4781bc4ae49a8af78a2d7b1318493e2336f8b Mon Sep 17 00:00:00 2001 From: Arcadiy Ivanov Date: Mon, 20 Oct 2014 16:17:34 -0400 Subject: [PATCH] [FIXED JENKINS-25223]: Allow executing scripts in JobProperty.prebuild phase Creates the "prebuild" phase JobProperty section, allowing scripts to be executed after SCM is completed but before AbstractBuildExecution.doRun kicks in. Also fixes NPE in EnvInjectJobProperty where disabling a section and saving before applying causes NPE on save --- .../plugins/envinject/AliasInitializer.java | 1 + .../EnvInjectPrebuildJobProperty.java | 390 ++++++++++++++++++ .../service/EnvInjectVariableGetter.java | 7 +- .../EnvInjectPrebuildJobProperty/config.jelly | 40 ++ src/main/webapp/help_prebuild.html | 10 + .../EnvInjectPrebuildJobPropertyTest.java | 130 ++++++ 6 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty.java create mode 100644 src/main/resources/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty/config.jelly create mode 100644 src/main/webapp/help_prebuild.html create mode 100644 src/test/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobPropertyTest.java diff --git a/src/main/java/org/jenkinsci/plugins/envinject/AliasInitializer.java b/src/main/java/org/jenkinsci/plugins/envinject/AliasInitializer.java index b41b38d4..cbbd881e 100644 --- a/src/main/java/org/jenkinsci/plugins/envinject/AliasInitializer.java +++ b/src/main/java/org/jenkinsci/plugins/envinject/AliasInitializer.java @@ -12,6 +12,7 @@ public class AliasInitializer { @Initializer(before = InitMilestone.PLUGINS_STARTED) @SuppressWarnings("unused") public static void addAliases() { + Items.XSTREAM.alias("EnvInjectPrebuildJobProperty", EnvInjectPrebuildJobProperty.class); Items.XSTREAM.alias("EnvInjectJobProperty", EnvInjectJobProperty.class); Items.XSTREAM.alias("EnvInjectBuildWrapper", EnvInjectBuildWrapper.class); Items.XSTREAM.alias("EnvInjectPasswordWrapper", EnvInjectPasswordWrapper.class); diff --git a/src/main/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty.java b/src/main/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty.java new file mode 100644 index 00000000..fe5b8627 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty.java @@ -0,0 +1,390 @@ +package org.jenkinsci.plugins.envinject; + +import hudson.DescriptorExtensionList; +import hudson.Extension; +import hudson.FilePath; +import hudson.model.BuildListener; +import hudson.model.Environment; +import hudson.model.JobProperty; +import hudson.model.JobPropertyDescriptor; +import hudson.model.AbstractBuild; +import hudson.model.Computer; +import hudson.model.Descriptor; +import hudson.model.Job; +import hudson.model.Node; +import hudson.model.Result; +import hudson.model.Run; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.CheckForNull; + +import jenkins.model.Jenkins; +import net.sf.json.JSON; +import net.sf.json.JSONException; +import net.sf.json.JSONObject; + +import org.jenkinsci.lib.envinject.EnvInjectException; +import org.jenkinsci.lib.envinject.EnvInjectLogger; +import org.jenkinsci.plugins.envinject.model.EnvInjectJobPropertyContributor; +import org.jenkinsci.plugins.envinject.model.EnvInjectJobPropertyContributorDescriptor; +import org.jenkinsci.plugins.envinject.service.EnvInjectActionSetter; +import org.jenkinsci.plugins.envinject.service.EnvInjectContributorManagement; +import org.jenkinsci.plugins.envinject.service.EnvInjectEnvVars; +import org.jenkinsci.plugins.envinject.service.EnvInjectVariableGetter; +import org.kohsuke.stapler.StaplerRequest; + +/** + * @author Arcadiy Ivanov (arcivanov) + * @author Gregory Boissinot + */ +public class EnvInjectPrebuildJobProperty> extends JobProperty { + + private EnvInjectJobPropertyInfo info = new EnvInjectJobPropertyInfo(); + private boolean on; + private boolean keepJenkinsSystemVariables; + private boolean keepBuildVariables; + private boolean overrideBuildParameters; + private EnvInjectJobPropertyContributor[] contributors; + + private transient EnvInjectJobPropertyContributor[] contributorsComputed; + + @SuppressWarnings("unused") + public EnvInjectJobPropertyInfo getInfo() { + return info; + } + + @SuppressWarnings("unused") + public boolean isOn() { + return on; + } + + @SuppressWarnings("unused") + public boolean isKeepJenkinsSystemVariables() { + return keepJenkinsSystemVariables; + } + + @SuppressWarnings("unused") + public boolean isKeepBuildVariables() { + return keepBuildVariables; + } + + @SuppressWarnings("unused") + public boolean isOverrideBuildParameters() { + return overrideBuildParameters; + } + + @SuppressWarnings("unused") + public EnvInjectJobPropertyContributor[] getContributors() { + if (contributorsComputed == null) { + try { + contributorsComputed = computeEnvInjectContributors(); + } catch (org.jenkinsci.lib.envinject.EnvInjectException e) { + e.printStackTrace(); + } + contributors = contributorsComputed; + } + + return Arrays.copyOf(contributors, contributors.length); + } + + private EnvInjectJobPropertyContributor[] computeEnvInjectContributors() throws org.jenkinsci.lib.envinject.EnvInjectException { + + DescriptorExtensionList + descriptors = EnvInjectJobPropertyContributor.all(); + + // If the config are loaded with success (this step) and the descriptors size doesn't have change + // we considerate, they are the same, therefore we retrieve instances + if (contributors != null && contributors.length == descriptors.size()) { + return contributors; + } + + EnvInjectContributorManagement envInjectContributorManagement = new EnvInjectContributorManagement(); + EnvInjectJobPropertyContributor[] contributorsInstance = envInjectContributorManagement.getNewContributorsInstance(); + + //No jobProperty Contributors ==> new configuration + if (contributors == null || contributors.length == 0) { + return contributorsInstance; + } + + List result = new ArrayList(); + for (EnvInjectJobPropertyContributor contributor1 : contributorsInstance) { + for (EnvInjectJobPropertyContributor contributor2 : contributors) { + if (contributor1.getDescriptor().getClass() == contributor2.getDescriptor().getClass()) { + result.add(contributor2); + } else { + result.add(contributor1); + } + } + } + return result.toArray(new EnvInjectJobPropertyContributor[result.size()]); + } + + public void setInfo(EnvInjectJobPropertyInfo info) { + this.info = info; + } + + public void setOn(boolean on) { + this.on = on; + } + + public void setKeepJenkinsSystemVariables(boolean keepJenkinsSystemVariables) { + this.keepJenkinsSystemVariables = keepJenkinsSystemVariables; + } + + public void setKeepBuildVariables(boolean keepBuildVariables) { + this.keepBuildVariables = keepBuildVariables; + } + + public void setOverrideBuildParameters(boolean overrideBuildParameters) { + this.overrideBuildParameters = overrideBuildParameters; + } + + public void setContributors(EnvInjectJobPropertyContributor[] jobPropertyContributors) { + this.contributors = jobPropertyContributors; + } + + @Override + public JobProperty reconfigure(StaplerRequest req, JSONObject form) throws Descriptor.FormException { + EnvInjectPrebuildJobProperty property = (EnvInjectPrebuildJobProperty) super.reconfigure(req, form); + if (property != null && property.info != null && !Jenkins.getInstance().hasPermission(Jenkins.RUN_SCRIPTS)) { + // Don't let non RUN_SCRIPT users set arbitrary groovy script + property.info = new EnvInjectJobPropertyInfo(property.info.propertiesFilePath, property.info.propertiesContent, + property.info.getScriptFilePath(), property.info.getScriptContent(), + this.info != null ? this.info.getGroovyScriptContent() : "", + property.info.isLoadFilesFromMaster()); + } + return property; + } + + @Extension + @SuppressWarnings("unused") + public static final class DescriptorImpl extends JobPropertyDescriptor { + + @Override + public String getDisplayName() { + return "[Environment Inject (Prebuild)] -" + Messages.envinject_set_displayName(); + } + + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + @Override + public String getHelpFile() { + return "/plugin/envinject/help.html"; + } + + @Override + public EnvInjectPrebuildJobProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException { + Object onObject = formData.get("on"); + + if (onObject != null) { + EnvInjectPrebuildJobProperty envInjectJobProperty = new EnvInjectPrebuildJobProperty(); + EnvInjectJobPropertyInfo info = req.bindParameters(EnvInjectJobPropertyInfo.class, "envInjectInfoPrebuildJobProperty."); + envInjectJobProperty.setInfo(info); + envInjectJobProperty.setOn(true); + if (onObject instanceof JSONObject) { + JSONObject onJSONObject = (JSONObject) onObject; + envInjectJobProperty.setKeepJenkinsSystemVariables(onJSONObject.getBoolean("keepJenkinsSystemVariables")); + envInjectJobProperty.setKeepBuildVariables(onJSONObject.getBoolean("keepBuildVariables")); + envInjectJobProperty.setOverrideBuildParameters(onJSONObject.getBoolean("overrideBuildParameters")); + + //Process contributions + setContributors(req, envInjectJobProperty, onJSONObject); + + return envInjectJobProperty; + } + } + + return null; + } + + private void setContributors(StaplerRequest req, EnvInjectPrebuildJobProperty envInjectJobProperty, JSONObject onJSONObject) { + if (!onJSONObject.containsKey("contributors")) { + envInjectJobProperty.setContributors(new EnvInjectJobPropertyContributor[0]); + } else { + JSON contribJSON; + try { + contribJSON = onJSONObject.getJSONArray("contributors"); + } catch (JSONException jsone) { + contribJSON = onJSONObject.getJSONObject("contributors"); + } + List contributions = req.bindJSONToList(EnvInjectJobPropertyContributor.class, contribJSON); + EnvInjectJobPropertyContributor[] contributionsArray = contributions.toArray(new EnvInjectJobPropertyContributor[contributions.size()]); + envInjectJobProperty.setContributors(contributionsArray); + } + } + + public DescriptorExtensionList getEnvInjectContributors() { + return EnvInjectJobPropertyContributor.all(); + } + + public @CheckForNull EnvInjectJobPropertyContributor[] getContributorsInstance() { + EnvInjectContributorManagement envInjectContributorManagement = new EnvInjectContributorManagement(); + try { + return envInjectContributorManagement.getNewContributorsInstance(); + } catch (org.jenkinsci.lib.envinject.EnvInjectException e) { + e.printStackTrace(); + } + return null; + } + + public boolean isEnvInjectContributionActivated() { + EnvInjectContributorManagement envInjectContributorManagement = new EnvInjectContributorManagement(); + return envInjectContributorManagement.isEnvInjectContributionActivated(); + } + + } + + /* + * (non-Javadoc) + * + * @see hudson.model.JobProperty#prebuild(hudson.model.AbstractBuild, + * hudson.model.BuildListener) + */ + @Override + public boolean prebuild(AbstractBuild build, BuildListener listener) { + if(!isOn()) { + return true; + } + EnvInjectLogger logger = new EnvInjectLogger(listener); + FilePath ws = build.getWorkspace(); + + try { + logger.info("Preparing an environment for the build (prebuild phase)."); + + EnvInjectVariableGetter variableGetter = new EnvInjectVariableGetter(); + EnvInjectJobPropertyInfo info = getInfo(); + assert isOn(); + + // Init infra env vars + Map previousEnvVars = variableGetter.getEnvVarsPreviousSteps(build, logger); + Map infraEnvVarsNode = new LinkedHashMap(previousEnvVars); + Map infraEnvVarsMaster = new LinkedHashMap(previousEnvVars); + + // Add workspace if not set + if (ws != null) { + if (infraEnvVarsNode.get("WORKSPACE") == null) { + infraEnvVarsNode.put("WORKSPACE", ws.getRemote()); + } + } + + //Add Jenkins System variables + if (isKeepJenkinsSystemVariables()) { + logger.info("Keeping Jenkins system variables."); + infraEnvVarsMaster.putAll(variableGetter.getJenkinsSystemVariables(true)); + infraEnvVarsNode.putAll(variableGetter.getJenkinsSystemVariables(false)); + } + + //Add build variables + if (isKeepBuildVariables()) { + logger.info("Keeping Jenkins build variables."); + Map buildVariables = variableGetter.getBuildVariables(build, logger); + infraEnvVarsMaster.putAll(buildVariables); + infraEnvVarsNode.putAll(buildVariables); + } + + final FilePath rootPath = getNodeRootPath(); + if (rootPath != null) { + + final EnvInjectEnvVars envInjectEnvVarsService = new EnvInjectEnvVars(logger); + + //Execute script + int resultCode = envInjectEnvVarsService.executeScript(info.isLoadFilesFromMaster(), + info.getScriptContent(), + rootPath, info.getScriptFilePath(), infraEnvVarsMaster, infraEnvVarsNode, rootPath.createLauncher(listener), listener); + if (resultCode != 0) { + build.setResult(Result.FAILURE); + throw new Run.RunnerAbortedException(); + } + + //Evaluate Groovy script + Map groovyMapEnvVars = envInjectEnvVarsService.executeAndGetMapGroovyScript(logger, info.getGroovyScriptContent(), infraEnvVarsNode); + + final Map propertiesVariables = envInjectEnvVarsService.getEnvVarsPropertiesJobProperty(rootPath, + logger, info.isLoadFilesFromMaster(), + info.getPropertiesFilePath(), info.getPropertiesContentMap(previousEnvVars), + infraEnvVarsMaster, infraEnvVarsNode); + + //Get variables get by contribution + Map contributionVariables = getEnvVarsByContribution(build, this, logger, listener); + + final Map mergedVariables = envInjectEnvVarsService.getMergedVariables( + infraEnvVarsNode, + propertiesVariables, + groovyMapEnvVars, + contributionVariables); + + //Add an action to share injected environment variables + new EnvInjectActionSetter(rootPath).addEnvVarsToEnvInjectBuildAction(build, mergedVariables); + } + } catch (Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + logger.error("Failure while executing scripts in a prebuild phase: " + sw.toString()); + pw.close(); + return false; + } + return true; + } + + private Node getNode() { + Computer computer = Computer.currentComputer(); + if (computer == null) { + return null; + } + return computer.getNode(); + } + + private FilePath getNodeRootPath() { + Node node = getNode(); + if (node != null) { + return node.getRootPath(); + } + return null; + } + + private Map getEnvVarsByContribution(AbstractBuild build, EnvInjectPrebuildJobProperty envInjectJobProperty, + EnvInjectLogger logger, BuildListener listener) throws EnvInjectException { + + assert envInjectJobProperty != null; + Map contributionVariables = new HashMap(); + + EnvInjectJobPropertyContributor[] contributors = envInjectJobProperty.getContributors(); + if (contributors != null) { + logger.info("Injecting contributions."); + for (EnvInjectJobPropertyContributor contributor : contributors) { + contributionVariables.putAll(contributor.getEnvVars(build, listener)); + } + } + return contributionVariables; + } + + + @Deprecated + private transient boolean injectGlobalPasswords; + @Deprecated + private transient EnvInjectPasswordEntry[] passwordEntries; + @Deprecated + private transient boolean keepSystemVariables; + + @Deprecated + public boolean isInjectGlobalPasswords() { + return injectGlobalPasswords; + } + + @Deprecated + public EnvInjectPasswordEntry[] getPasswordEntries() { + return passwordEntries; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/envinject/service/EnvInjectVariableGetter.java b/src/main/java/org/jenkinsci/plugins/envinject/service/EnvInjectVariableGetter.java index 6bf81172..9f21e82b 100644 --- a/src/main/java/org/jenkinsci/plugins/envinject/service/EnvInjectVariableGetter.java +++ b/src/main/java/org/jenkinsci/plugins/envinject/service/EnvInjectVariableGetter.java @@ -144,7 +144,12 @@ public Map getEnvVarsPreviousSteps(AbstractBuild build, EnvInjec if (environmentList != null) { for (Environment e : environmentList) { if (e != null) { - e.buildEnvVars(result); + try { + e.buildEnvVars(result); + } + catch(Exception ex) { + logger.error("Environment " + e + " could not provide it's env variables: " + ex); + } } } } diff --git a/src/main/resources/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty/config.jelly b/src/main/resources/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty/config.jelly new file mode 100644 index 00000000..2123ceac --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobProperty/config.jelly @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/help_prebuild.html b/src/main/webapp/help_prebuild.html new file mode 100644 index 00000000..6155aec6 --- /dev/null +++ b/src/main/webapp/help_prebuild.html @@ -0,0 +1,10 @@ +
+

+ If this option is checked, Jenkins makes it possible to set an environment + for the build job (or for each run on a matrix project) by executing a script + (a setup script).
+ These actions will be executed in a JobProperty "prebuild" phase, after a SCM checkout but before the build-proper starts. + This allows, for example, to setup build environment for Maven project based on SCM data, which would otherwise be impossible, + since Maven project requires Maven to be available even before any of the build wrappers are executed.
+

+
diff --git a/src/test/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobPropertyTest.java b/src/test/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobPropertyTest.java new file mode 100644 index 00000000..91e57ff8 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/envinject/EnvInjectPrebuildJobPropertyTest.java @@ -0,0 +1,130 @@ +package org.jenkinsci.plugins.envinject; + +import hudson.model.Cause; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.ParametersAction; +import hudson.model.StringParameterValue; +import hudson.model.TaskListener; +import hudson.model.queue.QueueTaskFuture; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.junit.Rule; +import org.jvnet.hudson.test.JenkinsRule; + +import static org.junit.Assert.*; +import org.junit.Ignore; +import org.junit.Test; +import org.jvnet.hudson.test.CaptureEnvironmentBuilder; +import org.jvnet.hudson.test.Issue; + +/** + * Tests for {@link EnvInjectPrebuildJobProperty} + * @author Oleg Nenashev + */ +public class EnvInjectPrebuildJobPropertyTest { + + @Rule + public JenkinsRule jenkinsRule = new JenkinsRule(); + + @Test + public void shouldInjectBuildVarsFromPropertiesContent() throws Exception { + FreeStyleProject project = jenkinsRule.createFreeStyleProject(); + EnvInjectPrebuildJobProperty prop = forPropertiesContent(project, "FOO=BAR"); + + FreeStyleBuild build = jenkinsRule.buildAndAssertSuccess(project); + assertEquals("The FOO variable has not been injected properly", "BAR", + build.getEnvironment(TaskListener.NULL).get("FOO")); + } + + @Test + public void shouldNotInjectVariablesIfPropertyIsDisabled() throws Exception { + FreeStyleProject project = jenkinsRule.createFreeStyleProject(); + + EnvInjectPrebuildJobProperty prop = new EnvInjectPrebuildJobProperty(); + prop.setInfo(new EnvInjectJobPropertyInfo(null, "FOO=BAR", null, null, null, false)); + // prop.setOn(false); // It is default + project.addProperty(prop); + + FreeStyleBuild build = jenkinsRule.buildAndAssertSuccess(project); + assertNull("The plugin should not inject properties by default", + build.getEnvironment(TaskListener.NULL).get("FOO")); + } + + @Test + public void shouldKeepBuildVariablesByDefault() throws Exception { + FreeStyleProject project = jenkinsRule.createFreeStyleProject(); + // We assign a value to another parameter just to enable the engine + EnvInjectPrebuildJobProperty prop = forPropertiesContent(project, "PARAM2=Overridden"); + + QueueTaskFuture scheduled = project.scheduleBuild2(0, new Cause.UserIdCause(), + new ParametersAction(new StringParameterValue("PARAM", "Value"))); + assertNotNull(scheduled); + FreeStyleBuild build = scheduled.get(); + jenkinsRule.assertBuildStatusSuccess(build); + assertEquals("The build parameter has been overridden", "Value", + build.getEnvironment(TaskListener.NULL).get("PARAM")); + } + + @Test + @Ignore("The value is being actually contributed by other env contributors. The feature seems to be obsolete") + public void shouldNotKeepBuildVariablesIfDisabled() throws Exception { + FreeStyleProject project = jenkinsRule.createFreeStyleProject(); + + EnvInjectPrebuildJobProperty prop = forPropertiesContent(project, "PARAM2=Overridden"); + prop.setKeepBuildVariables(false); + + QueueTaskFuture scheduled = project.scheduleBuild2(0, new Cause.UserIdCause(), + new ParametersAction(new StringParameterValue("PARAM", "Value"))); + assertNotNull(scheduled); + FreeStyleBuild build = scheduled.get(); + jenkinsRule.assertBuildStatusSuccess(build); + assertNull("We expect that the PARAM is not specified", + build.getEnvironment(TaskListener.NULL).get("PARAM")); + } + + @Test + @Ignore("It should not override vars according to the manual testing. But it does... " + + "Manual tests also show the wrong value in InjectedVarsAction") + @Issue("JENKINS-29905") + public void shouldNotOverrideBuildParametersByDefault() throws Exception { + FreeStyleProject project = jenkinsRule.createFreeStyleProject(); + EnvInjectPrebuildJobProperty prop = forPropertiesContent(project, "PARAM=Overridden"); + prop.setOverrideBuildParameters(false); + + final CaptureEnvironmentBuilder envCapture = new CaptureEnvironmentBuilder(); + project.getBuildersList().add(envCapture); + + QueueTaskFuture scheduled = project.scheduleBuild2(0, new Cause.UserIdCause(), + new ParametersAction(new StringParameterValue("PARAM", "ValueFromParameter"))); + assertNotNull(scheduled); + FreeStyleBuild build = scheduled.get(); + jenkinsRule.assertBuildStatusSuccess(build); + assertEquals("The variable has been overridden in the environment", "ValueFromParameter", envCapture.getEnvVars().get("PARAM")); + assertEquals("The variable has been overridden in the API", "ValueFromParameter", build.getEnvironment(TaskListener.NULL).get("PARAM")); + } + + @Test + public void shouldOverrideBuildParametersIfEnabled() throws Exception { + FreeStyleProject project = jenkinsRule.createFreeStyleProject(); + EnvInjectPrebuildJobProperty prop = forPropertiesContent(project, "PARAM=Overridden"); + prop.setOverrideBuildParameters(true); + + QueueTaskFuture scheduled = project.scheduleBuild2(0, new Cause.UserIdCause(), + new ParametersAction(new StringParameterValue("PARAM", "ValueFromParameter"))); + assertNotNull(scheduled); + FreeStyleBuild build = scheduled.get(); + jenkinsRule.assertBuildStatusSuccess(build); + assertEquals("The build parameter value has not been overridden", "Overridden", build.getEnvironment(TaskListener.NULL).get("PARAM")); + } + + @Nonnull + public EnvInjectPrebuildJobProperty + forPropertiesContent(@Nonnull FreeStyleProject job, @Nonnull String content) throws IOException { + final EnvInjectPrebuildJobProperty prop = new EnvInjectPrebuildJobProperty(); + prop.setInfo(new EnvInjectJobPropertyInfo(null, content, null, null, null, false)); + prop.setOn(true); // Property becomes enabled by default + job.addProperty(prop); + return prop; + } +}