提交 33afbcc8 编写于 作者: J Jesse Glick 提交者: Oleg Nenashev

Make all CLI commands compatible with Pipeline where possible (#2874)

* [FIX JENKINS-30785] Generalize some CLI stuff to AbstractItem

* Offering default methods on ParameterizedJob.

* Javadoc typo.

* Cleaner use of default methods in ParameterizedJob.

* Need to pick up https://github.com/infradna/bridge-method-injector/pull/15 to be able to build.

* Sketch of pulling disabled functionality into ParameterizedJob.

* EnableJobCommandTest.groovy → EnableJobCommandTest.java, and replacing deprecated Remoting-based CLI calls with CLICommandInvoker.

* All CLI commands could be broken by a missing CLI.*.shortDescription key on just one!

* Forgot to move CLI method short descriptions to new package.

* Needed a @CLIResolver for ParameterizedJob. Adding an OptionHandler while we are here.

* Trying to fix up access-modifier versions; started failing in CI today for unknown reasons.

* Introduced <p:makeDisabled/> by analogy with <p:config-disableBuild/>.

* Using new type bounds.

* access-modifier 1.11 released.

* MatrixProject and MavenModuleSet both expect to have access to makeDisabled.jelly.

* Trying to generalize some more.

* Minor simplification.

* [JENKINS-34716] Generalizing doPolling and schedulePolling.

* isBuildable

* Obsolete comment.

* Updated comments.

* bridge-method-injector 1.17

* Unfortunately AbstractProject.schedulePolling cannot delegate to SCMTriggerItem.

* Making delete-builds and list-changes commands work with Pipeline.

* [FIXED JENKINS-41527] Made console CLI command compatible with Pipeline.

* Fixed set-build-description and set-build-display-name.

* @oleg-nenashev agreed it would be clearer to explicitly mark commands as restricted, not APIs.

* Updated tests to match slight message changes.

* bridge-method-injector 1.17

* @olivergondza pointed out that RunRangeCommand is a better name than JobRangeCommand.
上级 a8994d4f
......@@ -12,7 +12,9 @@ import java.util.List;
* {@link CLICommand} that acts on a series of {@link AbstractBuild}s.
*
* @author Kohsuke Kawaguchi
* @deprecated rather use {@link RunRangeCommand}
*/
@Deprecated
public abstract class AbstractBuildRangeCommand extends CLICommand {
@Argument(metaVar="JOB",usage="Name of the job to build",required=true,index=0)
public AbstractProject<?,?> job;
......
......@@ -25,7 +25,6 @@ package hudson.cli;
import hudson.Util;
import hudson.console.ModelHyperlinkNote;
import hudson.model.AbstractProject;
import hudson.model.Cause.UserIdCause;
import hudson.model.CauseAction;
import hudson.model.Job;
......
......@@ -2,10 +2,10 @@ package hudson.cli;
import hudson.Extension;
import hudson.console.AnnotatedLargeText;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.PermalinkProjectAction.Permalink;
import hudson.model.Run;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
......@@ -29,7 +29,7 @@ public class ConsoleCommand extends CLICommand {
}
@Argument(metaVar="JOB",usage="Name of the job",required=true)
public AbstractProject<?,?> job;
public Job<?,?> job;
@Argument(metaVar="BUILD",usage="Build number or permalink to point to the build. Defaults to the last build",required=false,index=1)
public String build="lastBuild";
......@@ -43,7 +43,7 @@ public class ConsoleCommand extends CLICommand {
protected int run() throws Exception {
job.checkPermission(Item.BUILD);
AbstractBuild<?,?> run;
Run<?,?> run;
try {
int n = Integer.parseInt(build);
......@@ -54,7 +54,7 @@ public class ConsoleCommand extends CLICommand {
// maybe a permalink?
Permalink p = job.getPermalinks().get(build);
if (p!=null) {
run = (AbstractBuild)p.resolve(job);
run = p.resolve(job);
if (run==null)
throw new IllegalStateException("Permalink "+build+" produced no build");
} else {
......@@ -94,7 +94,7 @@ public class ConsoleCommand extends CLICommand {
/**
* Find the byte offset in the log input stream that marks "last N lines".
*/
private long seek(AbstractBuild<?, ?> run) throws IOException {
private long seek(Run<?, ?> run) throws IOException {
class RingBuffer {
long[] lastNlines = new long[n];
int ptr=0;
......
......@@ -24,21 +24,23 @@
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.List;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Deletes builds records in a bulk.
*
* @author Kohsuke Kawaguchi
*/
@Restricted(DoNotUse.class) // command implementation only
@Extension
public class DeleteBuildsCommand extends AbstractBuildRangeCommand {
public class DeleteBuildsCommand extends RunRangeCommand {
@Override
public String getShortDescription() {
return Messages.DeleteBuildsCommand_ShortDescription();
......@@ -52,12 +54,12 @@ public class DeleteBuildsCommand extends AbstractBuildRangeCommand {
}
@Override
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
protected int act(List<Run<?, ?>> builds) throws IOException {
job.checkPermission(Run.DELETE);
final HashSet<Integer> hsBuilds = new HashSet<Integer>();
for (AbstractBuild build : builds) {
for (Run<?, ?> build : builds) {
if (!hsBuilds.contains(build.number)) {
build.delete();
hsBuilds.add(build.number);
......
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.util.QuotedStringTokenizer;
import jenkins.scm.RunWithSCM;
import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.export.Flavor;
import org.kohsuke.stapler.export.Model;
......@@ -13,14 +14,17 @@ import org.kohsuke.stapler.export.ModelBuilder;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Retrieves a change list for the specified builds.
*
* @author Kohsuke Kawaguchi
*/
@Restricted(DoNotUse.class) // command implementation only
@Extension
public class ListChangesCommand extends AbstractBuildRangeCommand {
public class ListChangesCommand extends RunRangeCommand {
@Override
public String getShortDescription() {
return Messages.ListChangesCommand_ShortDescription();
......@@ -39,40 +43,50 @@ public class ListChangesCommand extends AbstractBuildRangeCommand {
public Format format = Format.PLAIN;
@Override
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
protected int act(List<Run<?, ?>> builds) throws IOException {
// Loading job for this CLI command requires Item.READ permission.
// No other permission check needed.
switch (format) {
case XML:
PrintWriter w = new PrintWriter(stdout);
w.println("<changes>");
for (AbstractBuild build : builds) {
w.println("<build number='"+build.getNumber()+"'>");
ChangeLogSet<?> cs = build.getChangeSet();
Model p = new ModelBuilder().get(cs.getClass());
p.writeTo(cs,Flavor.XML.createDataWriter(cs,w));
w.println("</build>");
for (Run<?, ?> build : builds) {
if (build instanceof RunWithSCM) {
w.println("<build number='" + build.getNumber() + "'>");
for (ChangeLogSet<?> cs : ((RunWithSCM<?, ?>) build).getChangeSets()) {
Model p = new ModelBuilder().get(cs.getClass());
p.writeTo(cs, Flavor.XML.createDataWriter(cs, w));
}
w.println("</build>");
}
}
w.println("</changes>");
w.flush();
break;
case CSV:
for (AbstractBuild build : builds) {
ChangeLogSet<?> cs = build.getChangeSet();
for (Entry e : cs) {
stdout.printf("%s,%s%n",
QuotedStringTokenizer.quote(e.getAuthor().getId()),
QuotedStringTokenizer.quote(e.getMsg()));
for (Run<?, ?> build : builds) {
if (build instanceof RunWithSCM) {
for (ChangeLogSet<?> cs : ((RunWithSCM<?, ?>) build).getChangeSets()) {
for (Entry e : cs) {
stdout.printf("%s,%s%n",
QuotedStringTokenizer.quote(e.getAuthor().getId()),
QuotedStringTokenizer.quote(e.getMsg()));
}
}
}
}
break;
case PLAIN:
for (AbstractBuild build : builds) {
ChangeLogSet<?> cs = build.getChangeSet();
for (Entry e : cs) {
stdout.printf("%s\t%s%n",e.getAuthor(),e.getMsg());
for (String p : e.getAffectedPaths())
stdout.println(" "+p);
for (Run<?, ?> build : builds) {
if (build instanceof RunWithSCM) {
for (ChangeLogSet<?> cs : ((RunWithSCM<?, ?>) build).getChangeSets()) {
for (Entry e : cs) {
stdout.printf("%s\t%s%n", e.getAuthor(), e.getMsg());
for (String p : e.getAffectedPaths()) {
stdout.println(" " + p);
}
}
}
}
}
break;
......
......@@ -29,6 +29,8 @@ import hudson.model.AbstractItem;
import hudson.model.AbstractProject;
import hudson.model.Item;
import hudson.model.Items;
import hudson.model.TopLevelItem;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;
......@@ -69,7 +71,6 @@ public class ReloadJobCommand extends CLICommand {
AbstractItem job = null;
try {
// TODO: JENKINS-30786
Item item = jenkins.getItemByFullName(job_s);
if (item instanceof AbstractItem) {
job = (AbstractItem) item;
......@@ -78,11 +79,10 @@ public class ReloadJobCommand extends CLICommand {
}
if(job == null) {
// TODO: JENKINS-30785
AbstractProject project = AbstractProject.findNearest(job_s);
AbstractItem project = Items.findNearest(AbstractItem.class, job_s, jenkins);
throw new IllegalArgumentException(project == null ?
"No such job \u2018" + job_s + "\u2019 exists." :
String.format("No such job \u2018%s\u2019 exists. Perhaps you meant \u2018%s\u2019?",
"No such item \u2018" + job_s + "\u2019 exists." :
String.format("No such item \u2018%s\u2019 exists. Perhaps you meant \u2018%s\u2019?",
job_s, project.getFullName()));
}
......
package hudson.cli;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Job;
import hudson.model.Run;
import org.kohsuke.args4j.Argument;
import java.io.IOException;
import java.util.List;
/**
* {@link CLICommand} that acts on a series of {@link Run}s.
* @since FIXME
*/
public abstract class RunRangeCommand extends CLICommand {
@Argument(metaVar="JOB",usage="Name of the job to build",required=true,index=0)
public Job<?,?> job;
@Argument(metaVar="RANGE",usage="Range of the build records to delete. 'N-M', 'N,M', or 'N'",required=true,index=1)
public String range;
protected int run() throws Exception {
RangeSet rs = RangeSet.fromString(range,false);
return act((List)job.getBuilds(rs));
}
protected abstract int act(List<Run<?,?>> builds) throws IOException;
}
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Run;
import java.io.Serializable;
......@@ -18,7 +18,7 @@ public class SetBuildDescriptionCommand extends CLICommand implements Serializab
}
@Argument(metaVar="JOB",usage="Name of the job to build",required=true,index=0)
public transient AbstractProject<?,?> job;
public transient Job<?,?> job;
@Argument(metaVar="BUILD#",usage="Number of the build",required=true,index=1)
public int number;
......
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Run;
import org.apache.commons.io.IOUtils;
import org.kohsuke.args4j.Argument;
......@@ -18,7 +18,7 @@ public class SetBuildDisplayNameCommand extends CLICommand implements Serializab
}
@Argument(metaVar="JOB", usage="Name of the job to build", required=true, index=0)
public transient AbstractProject<?, ?> job;
public transient Job<?, ?> job;
@Argument(metaVar="BUILD#", usage="Number of the build", required=true, index=1)
public int number;
......
......@@ -850,11 +850,11 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
*/
@CLIResolver
public static AbstractItem resolveForCLI(
@Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
@Argument(required=true,metaVar="NAME",usage="Item name") String name) throws CmdLineException {
// TODO can this (and its pseudo-override in AbstractProject) share code with GenericItemOptionHandler, used for explicit CLICommand’s rather than CLIMethod’s?
AbstractItem item = Jenkins.getInstance().getItemByFullName(name, AbstractItem.class);
if (item==null) {
AbstractProject project = AbstractProject.findNearest(name); // TODO should be Items.findNearest
AbstractItem project = Items.findNearest(AbstractItem.class, name, Jenkins.getInstance());
throw new CmdLineException(null, project == null ? Messages.AbstractItem_NoSuchJobExistsWithoutSuggestion(name)
: Messages.AbstractItem_NoSuchJobExists(name, project.getFullName()));
}
......
......@@ -91,7 +91,7 @@ public class ReloadJobCommandTest {
assertThat(result, failedWith(3));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("ERROR: No such job \u2018aProject\u2019 exists."));
assertThat(result.stderr(), containsString("ERROR: No such item ‘aProject’ exists."));
assertThat(project.scheduleBuild2(0).get().getLog(), containsString("echo 1"));
}
......@@ -121,7 +121,7 @@ public class ReloadJobCommandTest {
.invokeWithArgs("never_created");
assertThat(result, failedWith(3));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("ERROR: No such job \u2018never_created\u2019 exists."));
assertThat(result.stderr(), containsString("ERROR: No such item ‘never_created’ exists."));
}
@Test public void reloadJobShouldFailIfJobDoesNotExistButNearExists() throws Exception {
......@@ -133,7 +133,7 @@ public class ReloadJobCommandTest {
.invokeWithArgs("never_created1");
assertThat(result, failedWith(3));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("ERROR: No such job \u2018never_created1\u2019 exists. Perhaps you meant \u2018never_created\u2019?"));
assertThat(result.stderr(), containsString("ERROR: No such item ‘never_created1’ exists. Perhaps you meant ‘never_created’?"));
}
@Test public void reloadJobManyShouldSucceed() throws Exception {
......@@ -182,7 +182,7 @@ public class ReloadJobCommandTest {
assertThat(result, failedWith(5));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("never_created: No such job \u2018never_created\u2019 exists."));
assertThat(result.stderr(), containsString("never_created: No such item ‘never_created’ exists."));
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
......@@ -208,7 +208,7 @@ public class ReloadJobCommandTest {
assertThat(result, failedWith(5));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("never_created: No such job \u2018never_created\u2019 exists."));
assertThat(result.stderr(), containsString("never_created: No such item ‘never_created’ exists."));
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
......@@ -234,7 +234,7 @@ public class ReloadJobCommandTest {
assertThat(result, failedWith(5));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("never_created: No such job \u2018never_created\u2019 exists."));
assertThat(result.stderr(), containsString("never_created: No such item ‘never_created’ exists."));
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
......@@ -260,8 +260,8 @@ public class ReloadJobCommandTest {
assertThat(result, failedWith(5));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("never_created1: No such job \u2018never_created1\u2019 exists."));
assertThat(result.stderr(), containsString("never_created2: No such job \u2018never_created2\u2019 exists."));
assertThat(result.stderr(), containsString("never_created1: No such item ‘never_created1’ exists."));
assertThat(result.stderr(), containsString("never_created2: No such item ‘never_created2’ exists."));
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
......
......@@ -54,14 +54,14 @@ import static org.hamcrest.Matchers.not;
/**
* @author pjanouse
*/
public class AbstractBuildRangeCommand2Test {
public class RunRangeCommand2Test {
private CLICommandInvoker command;
@Rule public final JenkinsRule j = new JenkinsRule();
@Before public void setUp() {
command = new CLICommandInvoker(j, new AbstractBuildRangeCommandTest.DummyRangeCommand());
command = new CLICommandInvoker(j, new RunRangeCommandTest.DummyRangeCommand());
}
@Test public void dummyRangeShouldFailIfJobNameIsEmptyOnEmptyJenkins() throws Exception {
......
......@@ -25,9 +25,9 @@
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Job;
import hudson.model.Run;
import jenkins.model.Jenkins;
import org.junit.BeforeClass;
import org.junit.ClassRule;
......@@ -48,7 +48,7 @@ import static org.hamcrest.Matchers.not;
/**
* @author pjanouse
*/
public class AbstractBuildRangeCommandTest {
public class RunRangeCommandTest {
private static CLICommandInvoker command = null;
private static FreeStyleProject project = null;
......@@ -977,18 +977,18 @@ public class AbstractBuildRangeCommandTest {
}
@Extension
public static class DummyRangeCommand extends AbstractBuildRangeCommand {
public static class DummyRangeCommand extends RunRangeCommand {
@Override
public String getShortDescription() {
return "DummyRangeCommand";
}
@Override
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
protected int act(List<Run<?, ?>> builds) throws IOException {
boolean comma = false;
stdout.print("Builds: ");
for (AbstractBuild build : builds) {
for (Run<?, ?> build : builds) {
if (comma)
stdout.print(",");
else
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册