Skip to content
Permalink
Browse files

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 infradna/bridge-method-injector#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.
  • Loading branch information...
jglick authored and oleg-nenashev committed May 19, 2017
1 parent a8994d4 commit 33afbcc87f783e4b2ea79ef1a77e9bbef6e6b837
@@ -12,7 +12,9 @@
* {@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 @@

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 @@

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 String getShortDescription() {
}

@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 String getShortDescription() {
protected int run() throws Exception {
job.checkPermission(Item.BUILD);

AbstractBuild<?,?> run;
Run<?,?> run;

try {
int n = Integer.parseInt(build);
@@ -54,7 +54,7 @@ protected int run() throws Exception {
// 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 @@ protected int run() throws Exception {
/**
* 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 @@ protected void printUsageSummary(PrintStream stderr) {
}

@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);
@@ -1,10 +1,11 @@
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 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 String getShortDescription() {
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.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 @@ protected int run() throws Exception {
AbstractItem job = null;

try {
// TODO: JENKINS-30786
Item item = jenkins.getItemByFullName(job_s);
if (item instanceof AbstractItem) {
job = (AbstractItem) item;
@@ -78,11 +79,10 @@ protected int run() throws Exception {
}

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()));
}

@@ -0,0 +1,29 @@
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;
}
@@ -1,7 +1,7 @@
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 String getShortDescription() {
}

@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;
@@ -1,7 +1,7 @@
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 String getShortDescription() {
}

@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 String getSearchName() {
*/
@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 @@

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 @@
.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 @@
.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 @@

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 @@

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 @@

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 @@

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"));

0 comments on commit 33afbcc

Please sign in to comment.
You can’t perform that action at this time.