diff --git a/job/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowJob.java b/job/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowJob.java index 471007868..94330e2a4 100644 --- a/job/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowJob.java +++ b/job/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowJob.java @@ -58,6 +58,7 @@ import hudson.search.SearchIndexBuilder; import hudson.security.ACL; import hudson.slaves.WorkspaceList; +import hudson.tasks.LogRotator; import hudson.triggers.SCMTrigger; import hudson.triggers.Trigger; import hudson.triggers.TriggerDescriptor; @@ -73,6 +74,7 @@ import java.util.Map; import javax.annotation.CheckForNull; import javax.servlet.ServletException; +import jenkins.model.BuildDiscarder; import jenkins.model.Jenkins; import jenkins.model.ParameterizedJobMixIn; import jenkins.model.lazy.LazyBuildMixIn; @@ -81,6 +83,7 @@ import net.sf.json.JSONObject; import org.acegisecurity.Authentication; import org.jenkinsci.plugins.workflow.flow.FlowDefinition; +import org.jenkinsci.plugins.workflow.job.properties.BuildDiscarderProperty; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.QueryParameter; @@ -521,6 +524,29 @@ public void addTrigger(Trigger trigger) { // TODO call SCM.processWorkspaceBeforeDeletion } + /** Actually it does, but we want to suppress this section of {@code Job/configure.jelly} in favor of {@link BuildDiscarderProperty}. */ + @Override public boolean supportsLogRotator() { + return false; + } + + @SuppressWarnings("deprecation") + @Override public LogRotator getLogRotator() { + BuildDiscarder buildDiscarder = getBuildDiscarder(); + return buildDiscarder instanceof LogRotator ? (LogRotator) buildDiscarder : null; + } + + @Override public void setBuildDiscarder(BuildDiscarder bd) throws IOException { + removeProperty(BuildDiscarderProperty.class); + if (bd != null) { + addProperty(new BuildDiscarderProperty(bd)); + } + } + + @Override public BuildDiscarder getBuildDiscarder() { + BuildDiscarderProperty prop = getProperty(BuildDiscarderProperty.class); + return prop != null ? prop.getStrategy() : /* settings compatibility */ super.getBuildDiscarder(); + } + @Initializer(before=InitMilestone.EXTENSIONS_AUGMENTED) public static void alias() { Items.XSTREAM2.alias("flow-definition", WorkflowJob.class); diff --git a/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowParameterDefinitionBranchProperty.java b/job/src/main/java/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty.java similarity index 57% rename from multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowParameterDefinitionBranchProperty.java rename to job/src/main/java/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty.java index a9534a794..71d601579 100644 --- a/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowParameterDefinitionBranchProperty.java +++ b/job/src/main/java/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty.java @@ -22,35 +22,35 @@ * THE SOFTWARE. */ -package org.jenkinsci.plugins.workflow.multibranch; +package org.jenkinsci.plugins.workflow.job.properties; import hudson.Extension; -import hudson.model.Job; -import hudson.model.Run; -import jenkins.branch.BranchPropertyDescriptor; -import jenkins.branch.MultiBranchProjectDescriptor; -import jenkins.branch.ParameterDefinitionBranchProperty; +import jenkins.model.BuildDiscarder; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.kohsuke.stapler.DataBoundConstructor; -public class WorkflowParameterDefinitionBranchProperty extends ParameterDefinitionBranchProperty { +/** + * Defines a {@link BuildDiscarder}. + * TODO consider whether this should be moved upstream to core. + */ +public class BuildDiscarderProperty extends OptionalJobProperty { + + private final BuildDiscarder strategy; - @DataBoundConstructor public WorkflowParameterDefinitionBranchProperty() {} + @DataBoundConstructor public BuildDiscarderProperty(BuildDiscarder strategy) { + this.strategy = strategy; + } - @Override protected

, B extends Run> boolean isApplicable(Class

clazz) { - return clazz == WorkflowJob.class; + public BuildDiscarder getStrategy() { + return strategy; } - @Extension public static class DescriptorImpl extends BranchPropertyDescriptor { + @Extension public static class DescriptorImpl extends OptionalJobPropertyDescriptor { - @Override protected boolean isApplicable(MultiBranchProjectDescriptor projectDescriptor) { - return WorkflowMultiBranchProject.class.isAssignableFrom(projectDescriptor.getClazz()); + @Override public String getDisplayName() { + return "Discard Old Builds"; } - @Override - public String getDisplayName() { - return "Parameters"; - } } } diff --git a/job/src/main/java/org/jenkinsci/plugins/workflow/job/properties/OptionalJobProperty.java b/job/src/main/java/org/jenkinsci/plugins/workflow/job/properties/OptionalJobProperty.java new file mode 100644 index 000000000..ac6a74f65 --- /dev/null +++ b/job/src/main/java/org/jenkinsci/plugins/workflow/job/properties/OptionalJobProperty.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.plugins.workflow.job.properties; + +import hudson.model.Job; +import hudson.model.JobProperty; +import hudson.model.JobPropertyDescriptor; +import hudson.model.ParametersDefinitionProperty; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.StaplerRequest; + +/** + * Job property which may or may not be present. + * Must define {@code config-details.jelly} or {@code config-details.groovy}. + * TODO should be moved into core and implemented by {@link ParametersDefinitionProperty}. + */ +abstract class OptionalJobProperty> extends JobProperty { + + @Override + public OptionalJobPropertyDescriptor getDescriptor() { + return (OptionalJobPropertyDescriptor) super.getDescriptor(); + } + + public static abstract class OptionalJobPropertyDescriptor extends JobPropertyDescriptor { + + protected OptionalJobPropertyDescriptor(Class> clazz) { + super(clazz); + } + + protected OptionalJobPropertyDescriptor() {} + + @Override + public JobProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException { + return formData.optBoolean("specified") ? super.newInstance(req, formData) : null; + } + + } + +} diff --git a/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty/config-details.jelly b/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty/config-details.jelly new file mode 100644 index 000000000..a5f3d0a4d --- /dev/null +++ b/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty/config-details.jelly @@ -0,0 +1,29 @@ + + + + + + + diff --git a/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty/help.html b/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty/help.html new file mode 100644 index 000000000..3d1e66957 --- /dev/null +++ b/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/BuildDiscarderProperty/help.html @@ -0,0 +1,19 @@ +

+ This controls the disk consumption of Jenkins by managing how long you'd like to keep + records of the builds (such as console output, build artifacts, and so on.) + Jenkins offers two criteria: + +
    +
  1. + Driven by age. You can have Jenkins delete a record if it reaches a certain age + (for example, 7 days old.) +
  2. + Driven by number. You can have Jenkins make sure that it only maintains up to + N build records. If a new build is started, the oldest record will + be simply removed. +
+ + Jenkins also allows you to mark an individual build as 'Keep this log forever', to + exclude certain important builds from being discarded automatically. + The last stable and last successful build are always kept as well. +
diff --git a/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/OptionalJobProperty/config.jelly b/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/OptionalJobProperty/config.jelly new file mode 100644 index 000000000..07345d446 --- /dev/null +++ b/job/src/main/resources/org/jenkinsci/plugins/workflow/job/properties/OptionalJobProperty/config.jelly @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/multibranch/pom.xml b/multibranch/pom.xml index 56159f8bc..fbd622d4f 100644 --- a/multibranch/pom.xml +++ b/multibranch/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.plugins.workflow workflow-pom - 1.11-beta-1 + 1.11-SNAPSHOT 1.11-beta-2-SNAPSHOT @@ -58,7 +58,7 @@ THE SOFTWARE. org.jenkins-ci.plugins branch-api - 0.2-beta-5 + 0.2-beta-6-SNAPSHOT ${project.groupId} @@ -66,6 +66,13 @@ THE SOFTWARE. ${workflow.version} test + + ${project.groupId} + workflow-step-api + ${workflow.version} + tests + test + ${project.groupId} workflow-support diff --git a/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep.java b/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep.java new file mode 100644 index 000000000..ba06ef6e4 --- /dev/null +++ b/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep.java @@ -0,0 +1,177 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.plugins.workflow.multibranch; + +import hudson.AbortException; +import hudson.BulkChange; +import hudson.Extension; +import hudson.ExtensionList; +import hudson.model.Descriptor; +import hudson.model.DescriptorVisibilityFilter; +import hudson.model.Job; +import hudson.model.JobProperty; +import hudson.model.JobPropertyDescriptor; +import hudson.model.Run; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import jenkins.branch.BuildRetentionBranchProperty; +import jenkins.branch.RateLimitBranchProperty; +import jenkins.model.Jenkins; +import net.sf.json.JSONObject; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; +import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; +import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution; +import org.jenkinsci.plugins.workflow.steps.Step; +import org.jenkinsci.plugins.workflow.steps.StepContextParameter; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.StaplerRequest; + +/** + * Resets the properties of the current job. + */ +@SuppressWarnings("rawtypes") // TODO JENKINS-26535: cannot bind List> +public class JobPropertyStep extends AbstractStepImpl { + + private final List properties; + + @DataBoundConstructor public JobPropertyStep(List properties) { + this.properties = properties; + } + + public List getProperties() { + return properties; + } + + public Map getPropertiesMap() { + return Descriptor.toMap(properties); + } + + public static class Execution extends AbstractSynchronousStepExecution { + + @Inject transient JobPropertyStep step; + @StepContextParameter Run build; + + @SuppressWarnings("unchecked") // untypable + @Override protected Void run() throws Exception { + Job job = build.getParent(); + for (JobProperty prop : step.properties) { + if (!prop.getDescriptor().isApplicable(job.getClass())) { + throw new AbortException("cannot apply " + prop.getDescriptor().getId() + " to a " + job.getClass().getSimpleName()); + } + } + BulkChange bc = new BulkChange(job); + try { + for (JobProperty prop : job.getAllProperties()) { + if (prop instanceof BranchJobProperty) { + // TODO do we need to define an API for other properties which should not be removed? + continue; + } + job.removeProperty(prop); + } + for (JobProperty prop : step.properties) { + job.addProperty(prop); + } + bc.commit(); + } finally { + bc.abort(); + } + return null; + } + + } + + @Extension public static class DescriptorImpl extends AbstractStepDescriptorImpl { + + public DescriptorImpl() { + super(Execution.class); + } + + @Override public String getFunctionName() { + return "properties"; + } + + @Override public String getDisplayName() { + return "Set job properties"; + } + + @Override public Step newInstance(StaplerRequest req, JSONObject formData) throws FormException { + // A modified version of RequestImpl.TypePair.convertJSON. + // Works around the fact that Stapler does not call back into Descriptor.newInstance for nested objects. + List properties = new ArrayList(); + ClassLoader cl = req.getStapler().getWebApp().getClassLoader(); + @SuppressWarnings("unchecked") Set> entrySet = formData.getJSONObject("propertiesMap").entrySet(); + for (Map.Entry e : entrySet) { + if (e.getValue() instanceof JSONObject) { + String className = e.getKey().replace('-', '.'); // decode JSON-safe class name escaping + Class itemType; + try { + itemType = cl.loadClass(className).asSubclass(JobProperty.class); + } catch (ClassNotFoundException x) { + throw new FormException(x, "propertiesMap"); + } + JobPropertyDescriptor d = (JobPropertyDescriptor) Jenkins.getActiveInstance().getDescriptorOrDie(itemType); + JSONObject more = (JSONObject) e.getValue(); + JobProperty property = d.newInstance(req, more); + if (property != null) { + properties.add(property); + } + } + } + return new JobPropertyStep(properties); + } + + @Restricted(DoNotUse.class) // f:repeatableHeteroProperty + public Collection> getPropertyDescriptors() { + List> result = new ArrayList>(); + for (JobPropertyDescriptor p : ExtensionList.lookup(JobPropertyDescriptor.class)) { + if (p.isApplicable(WorkflowJob.class)) { + result.add(p); + } + } + return result; + } + + } + + @Extension public static class HideSuperfluousBranchProperties extends DescriptorVisibilityFilter { + + @Override public boolean filter(Object context, Descriptor descriptor) { + if (context instanceof WorkflowMultiBranchProject && (descriptor.clazz == RateLimitBranchProperty.class || descriptor.clazz == BuildRetentionBranchProperty.class)) { + // These are both adequately handled by declarative job properties. + return false; + } + return true; + } + + } + +} diff --git a/multibranch/src/main/resources/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep/config.jelly b/multibranch/src/main/resources/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep/config.jelly new file mode 100644 index 000000000..b669eeedf --- /dev/null +++ b/multibranch/src/main/resources/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep/config.jelly @@ -0,0 +1,29 @@ + + + + + + + diff --git a/multibranch/src/main/resources/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep/help.html b/multibranch/src/main/resources/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep/help.html new file mode 100644 index 000000000..dd8353d80 --- /dev/null +++ b/multibranch/src/main/resources/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStep/help.html @@ -0,0 +1,4 @@ +
+ Updates the properties of the job which runs this step. + Mainly useful from multibranch workflows, so that Jenkinsfile itself can encode what would otherwise be static job configuration. +
diff --git a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStepTest.java b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStepTest.java new file mode 100644 index 000000000..1274605f8 --- /dev/null +++ b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/JobPropertyStepTest.java @@ -0,0 +1,143 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.plugins.workflow.multibranch; + +import hudson.model.BooleanParameterDefinition; +import hudson.model.JobProperty; +import hudson.model.ParametersAction; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.StringParameterValue; +import hudson.tasks.LogRotator; +import java.util.Collections; +import java.util.List; +import jenkins.branch.BranchProperty; +import jenkins.branch.BranchSource; +import jenkins.branch.DefaultBranchPropertyStrategy; +import jenkins.model.BuildDiscarder; +import jenkins.plugins.git.GitSCMSource; +import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.jenkinsci.plugins.workflow.job.properties.BuildDiscarderProperty; +import org.jenkinsci.plugins.workflow.steps.StepConfigTester; +import org.jenkinsci.plugins.workflow.steps.scm.GitSampleRepoRule; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.ClassRule; +import org.junit.Rule; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import static org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject; +import org.junit.Ignore; + +@Issue("JENKINS-30519") +public class JobPropertyStepTest { + + @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); + @Rule public JenkinsRule r = new JenkinsRule(); + @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); + + @Ignore("TODO fails with a JS TypeError, which goes away on new cores, maybe due to new HtmlUnit (1.627+); and needs https://github.com/jenkinsci/branch-api-plugin/pull/9") + @SuppressWarnings("rawtypes") + @Test public void configRoundTripParameters() throws Exception { + StepConfigTester tester = new StepConfigTester(r); + assertEquals(Collections.emptyList(), tester.configRoundTrip(new JobPropertyStep(Collections.emptyList())).getProperties()); + List properties = tester.configRoundTrip(new JobPropertyStep(Collections.singletonList(new ParametersDefinitionProperty(new BooleanParameterDefinition("flag", true, null))))).getProperties(); + assertEquals(1, properties.size()); + assertEquals(ParametersDefinitionProperty.class, properties.get(0).getClass()); + ParametersDefinitionProperty pdp = (ParametersDefinitionProperty) properties.get(0); + assertEquals(1, pdp.getParameterDefinitions().size()); + assertEquals(BooleanParameterDefinition.class, pdp.getParameterDefinitions().get(0).getClass()); + BooleanParameterDefinition bpd = (BooleanParameterDefinition) pdp.getParameterDefinitions().get(0); + assertEquals("flag", bpd.getName()); + assertTrue(bpd.isDefaultValue()); + // TODO JENKINS-29711 means it seems to omit the required () but we are not currently testing the Snippetizer output anyway + } + + @Ignore("TODO as above") + @SuppressWarnings("rawtypes") + @Test public void configRoundTripBuildDiscarder() throws Exception { + StepConfigTester tester = new StepConfigTester(r); + assertEquals(Collections.emptyList(), tester.configRoundTrip(new JobPropertyStep(Collections.emptyList())).getProperties()); + List properties = tester.configRoundTrip(new JobPropertyStep(Collections.singletonList(new BuildDiscarderProperty(new LogRotator(1, 2, -1, 3))))).getProperties(); + assertEquals(1, properties.size()); + assertEquals(BuildDiscarderProperty.class, properties.get(0).getClass()); + BuildDiscarderProperty bdp = (BuildDiscarderProperty) properties.get(0); + BuildDiscarder strategy = bdp.getStrategy(); + assertNotNull(strategy); + assertEquals(LogRotator.class, strategy.getClass()); + LogRotator lr = (LogRotator) strategy; + assertEquals(1, lr.getDaysToKeep()); + assertEquals(2, lr.getNumToKeep()); + assertEquals(-1, lr.getArtifactDaysToKeep()); + assertEquals(3, lr.getArtifactNumToKeep()); + } + + @Issue("JENKINS-30206") + @Test public void useParameter() throws Exception { + sampleRepo.init(); + ScriptApproval.get().approveSignature("method groovy.lang.Binding hasVariable java.lang.String"); // TODO add to generic whitelist + sampleRepo.write("Jenkinsfile", + "properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [[$class: 'StringParameterDefinition', name: 'myparam', defaultValue: 'default value']]]])\n" + + "echo \"received ${binding.hasVariable('myparam') ? myparam : 'undefined'}\""); + sampleRepo.git("add", "Jenkinsfile"); + sampleRepo.git("commit", "--all", "--message=flow"); + WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0]))); + WorkflowJob p = scheduleAndFindBranchProject(mp, "master"); + assertEquals(1, mp.getItems().size()); + r.waitUntilNoActivity(); + WorkflowRun b1 = p.getLastBuild(); + assertEquals(1, b1.getNumber()); + // TODO not all that satisfactory since it means you cannot rely on a default value; would be a little easier given JENKINS-27295 + r.assertLogContains("received undefined", b1); + WorkflowRun b2 = r.assertBuildStatusSuccess(p.scheduleBuild2(0, new ParametersAction(new StringParameterValue("myparam", "special value")))); + assertEquals(2, b2.getNumber()); + r.assertLogContains("received special value", b2); + } + + @SuppressWarnings("deprecation") // RunList.size + @Test public void useBuildDiscarder() throws Exception { + sampleRepo.init(); + sampleRepo.write("Jenkinsfile", "properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '1']]])"); + sampleRepo.git("add", "Jenkinsfile"); + sampleRepo.git("commit", "--all", "--message=flow"); + WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0]))); + WorkflowJob p = scheduleAndFindBranchProject(mp, "master"); + assertEquals(1, mp.getItems().size()); + r.waitUntilNoActivity(); // #1 built automatically + assertEquals(1, p.getBuilds().size()); + r.assertBuildStatusSuccess(p.scheduleBuild2(0)); // #2 + assertEquals(1, p.getBuilds().size()); + r.assertBuildStatusSuccess(p.scheduleBuild2(0)); // #3 + assertEquals(1, p.getBuilds().size()); + WorkflowRun b3 = p.getLastBuild(); + assertEquals(3, b3.getNumber()); + assertNull(b3.getPreviousBuild()); + } + +} diff --git a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowMultiBranchProjectTest.java b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowMultiBranchProjectTest.java index 296641e3b..8f3c624eb 100644 --- a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowMultiBranchProjectTest.java +++ b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowMultiBranchProjectTest.java @@ -24,8 +24,13 @@ package org.jenkinsci.plugins.workflow.multibranch; +import hudson.model.DescriptorVisibilityFilter; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import javax.annotation.Nonnull; import jenkins.branch.BranchProperty; +import jenkins.branch.BranchPropertyDescriptor; import jenkins.branch.BranchSource; import jenkins.branch.DefaultBranchPropertyStrategy; import jenkins.plugins.git.GitSCMSource; @@ -92,4 +97,15 @@ public class WorkflowMultiBranchProjectTest { return p; } + @Test public void visibleBranchProperties() throws Exception { + WorkflowMultiBranchProject p = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + Set> clazzes = new HashSet>(); + for (BranchPropertyDescriptor d : DescriptorVisibilityFilter.apply(p, BranchPropertyDescriptor.all())) { + clazzes.add(d.clazz); + } + // RateLimitBranchProperty & BuildRetentionBranchProperty hidden by JobPropertyStep.HideSuperfluousBranchProperties. + // UntrustedBranchProperty hidden because it applies only to Project. + assertEquals(Collections.>emptySet(), clazzes); + } + } diff --git a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowParameterDefinitionBranchPropertyTest.java b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowParameterDefinitionBranchPropertyTest.java deleted file mode 100644 index e67d5c0a3..000000000 --- a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/WorkflowParameterDefinitionBranchPropertyTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * The MIT License - * - * Copyright 2015 CloudBees, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.jenkinsci.plugins.workflow.multibranch; - -import hudson.model.DescriptorVisibilityFilter; -import hudson.model.ParameterDefinition; -import hudson.model.ParametersAction; -import hudson.model.StringParameterDefinition; -import hudson.model.StringParameterValue; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import jenkins.branch.BranchProperty; -import jenkins.branch.BranchPropertyDescriptor; -import jenkins.branch.BranchSource; -import jenkins.branch.BuildRetentionBranchProperty; -import jenkins.branch.DefaultBranchPropertyStrategy; -import jenkins.branch.RateLimitBranchProperty; -import jenkins.plugins.git.GitSCMSource; -import org.jenkinsci.plugins.workflow.job.WorkflowJob; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; -import org.jenkinsci.plugins.workflow.steps.scm.GitSampleRepoRule; -import org.junit.Test; -import static org.junit.Assert.*; -import org.junit.ClassRule; -import org.junit.Rule; -import org.jvnet.hudson.test.BuildWatcher; -import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.JenkinsRule; -import static org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject; - -public class WorkflowParameterDefinitionBranchPropertyTest { - - @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); - @Rule public JenkinsRule r = new JenkinsRule(); - @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); - - @Test public void propertyVisible() throws Exception { - WorkflowMultiBranchProject p = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); - Set> clazzes = new HashSet>(); - for (BranchPropertyDescriptor d : DescriptorVisibilityFilter.apply(p, BranchPropertyDescriptor.all())) { - clazzes.add(d.clazz); - } - @SuppressWarnings("unchecked") - Set> expected = new HashSet>(Arrays.asList( - WorkflowParameterDefinitionBranchProperty.class, - RateLimitBranchProperty.class, - BuildRetentionBranchProperty.class - /* UntrustedBranchProperty should not be here! */)); - assertEquals(expected, clazzes); - } - - @Issue("JENKINS-30206") - @Test public void useParameter() throws Exception { - sampleRepo.init(); - sampleRepo.write("Jenkinsfile", "echo \"received ${myparam}\""); - sampleRepo.git("add", "Jenkinsfile"); - sampleRepo.git("commit", "--all", "--message=flow"); - WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); - WorkflowParameterDefinitionBranchProperty prop = new WorkflowParameterDefinitionBranchProperty(); - prop.setParameterDefinitions(Collections.singletonList(new StringParameterDefinition("myparam", "default value"))); - mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[] {prop}))); - WorkflowJob p = scheduleAndFindBranchProject(mp, "master"); - assertEquals(1, mp.getItems().size()); - r.waitUntilNoActivity(); - WorkflowRun b1 = p.getLastBuild(); - assertEquals(1, b1.getNumber()); - r.assertLogContains("received default value", b1); - WorkflowRun b2 = r.assertBuildStatusSuccess(p.scheduleBuild2(0, new ParametersAction(new StringParameterValue("myparam", "special value")))); - assertEquals(2, b2.getNumber()); - r.assertLogContains("received special value", b2); - } - -} diff --git a/step-api/src/main/java/org/jenkinsci/plugins/workflow/structs/DescribableHelper.java b/step-api/src/main/java/org/jenkinsci/plugins/workflow/structs/DescribableHelper.java index 5c29101cd..bd9e15b54 100644 --- a/step-api/src/main/java/org/jenkinsci/plugins/workflow/structs/DescribableHelper.java +++ b/step-api/src/main/java/org/jenkinsci/plugins/workflow/structs/DescribableHelper.java @@ -30,6 +30,7 @@ import hudson.model.Descriptor; import hudson.model.ParameterDefinition; import hudson.model.ParameterValue; +import hudson.model.ParametersDefinitionProperty; import java.beans.Introspector; import java.lang.reflect.Array; import java.lang.reflect.Constructor; @@ -85,8 +86,7 @@ public class DescribableHelper { * and only one subtype is registered (as a {@link Descriptor}) with that simple name. */ public static T instantiate(Class clazz, Map arguments) throws Exception { - ClassDescriptor d = new ClassDescriptor(clazz); - String[] names = d.loadConstructorParamNames(); + String[] names = loadConstructorParamNames(clazz); Constructor c = findConstructor(clazz, names.length); Object[] args = buildArguments(clazz, arguments, c.getGenericParameterTypes(), names, true); T o = c.newInstance(args); @@ -103,10 +103,9 @@ public static T instantiate(Class clazz, Map argument public static Map uninstantiate(Object o) throws UnsupportedOperationException { Class clazz = o.getClass(); Map r = new TreeMap(); - ClassDescriptor d = new ClassDescriptor(clazz); String[] names; try { - names = d.loadConstructorParamNames(); + names = loadConstructorParamNames(clazz); } catch (NoStaplerConstructorException x) { throw new UnsupportedOperationException(x); } @@ -219,7 +218,7 @@ private static Object coerce(String context, Type type, @Nonnull Object o) throw Jenkins j = Jenkins.getInstance(); ClassLoader loader = j != null ? j.getPluginManager().uberClassLoader : DescribableHelper.class.getClassLoader(); clazz = loader.loadClass(clazzS); - } else { + } else if (type instanceof Class) { clazz = null; for (Class c : findSubtypes((Class) type)) { if (c.getSimpleName().equals(clazzS)) { @@ -232,6 +231,8 @@ private static Object coerce(String context, Type type, @Nonnull Object o) throw if (clazz == null) { throw new UnsupportedOperationException("no known implementation of " + type + " is named " + clazzS); } + } else { + throw new UnsupportedOperationException("JENKINS-26535: do not know how to handle " + type); } return instantiate(clazz.asSubclass((Class) type), m); } else if (o instanceof String && type instanceof Class && ((Class) type).isEnum()) { @@ -265,9 +266,24 @@ private static List mapList(String context, Type type, List list) thr return r; } - // copied from RequestImpl + private static String[] loadConstructorParamNames(Class clazz) { + if (clazz == ParametersDefinitionProperty.class) { // TODO pending core fix + return new String[] {"parameterDefinitions"}; + } + return new ClassDescriptor(clazz).loadConstructorParamNames(); + } + + // adapted from RequestImpl + @SuppressWarnings("unchecked") private static Constructor findConstructor(Class clazz, int length) { - @SuppressWarnings("unchecked") Constructor[] ctrs = (Constructor[]) clazz.getConstructors(); + try { // may work without this, but only if the JVM happens to return the right overload first + if (clazz == ParametersDefinitionProperty.class && length == 1) { // TODO pending core fix + return (Constructor) ParametersDefinitionProperty.class.getConstructor(List.class); + } + } catch (NoSuchMethodException x) { + throw new AssertionError(x); + } + Constructor[] ctrs = (Constructor[]) clazz.getConstructors(); for (Constructor c : ctrs) { if (c.getAnnotation(DataBoundConstructor.class) != null) { if (c.getParameterTypes().length != length) { @@ -318,7 +334,7 @@ private static void inspect(Map r, Object o, Class clazz, Str AtomicReference type = new AtomicReference(); Object value = inspect(o, clazz, field, type); try { - String[] names = new ClassDescriptor(clazz).loadConstructorParamNames(); + String[] names = loadConstructorParamNames(clazz); int idx = Arrays.asList(names).indexOf(field); if (idx >= 0) { Type ctorType = findConstructor(clazz, names.length).getGenericParameterTypes()[idx]; diff --git a/step-api/src/test/java/org/jenkinsci/plugins/workflow/structs/DescribableHelperTest.java b/step-api/src/test/java/org/jenkinsci/plugins/workflow/structs/DescribableHelperTest.java index 17601e8a6..c0ebcc67d 100644 --- a/step-api/src/test/java/org/jenkinsci/plugins/workflow/structs/DescribableHelperTest.java +++ b/step-api/src/test/java/org/jenkinsci/plugins/workflow/structs/DescribableHelperTest.java @@ -30,6 +30,7 @@ import hudson.model.BooleanParameterValue; import hudson.model.Descriptor; import hudson.model.ParameterValue; +import hudson.model.ParametersDefinitionProperty; import hudson.plugins.git.GitSCM; import hudson.plugins.git.extensions.impl.CleanBeforeCheckout; import java.net.URL; @@ -463,6 +464,10 @@ public static final class TakesParams { } } + @Test public void parametersDefinitionProperty() throws Exception { + roundTrip(ParametersDefinitionProperty.class, map("parameterDefinitions", Arrays.asList(map("$class", "BooleanParameterDefinition", "name", "flag", "defaultValue", false), map("$class", "StringParameterDefinition", "name", "text")))); + } + @Issue("JENKINS-26619") @Test public void getterDescribableList() throws Exception { roundTrip(GitSCM.class, map(