diff --git a/changelog.html b/changelog.html index b56e0f7cfee5caba6cfc07836854027c013908da..8ab44b45cb2cda64989c9c4486711907d9a4e6ec 100644 --- a/changelog.html +++ b/changelog.html @@ -68,6 +68,8 @@ Upcoming changes Display class loading statistics in /computer/name/systemInfo.
  • Added list-plugins CLI command. +
  • + Added console CLI command that dumps console output from a build. diff --git a/core/src/main/java/hudson/cli/ConsoleCommand.java b/core/src/main/java/hudson/cli/ConsoleCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..b6edd0be80f6f4ffa931658bfdfaae7ea76e0a6e --- /dev/null +++ b/core/src/main/java/hudson/cli/ConsoleCommand.java @@ -0,0 +1,147 @@ +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.PermalinkProjectAction.Permalink; +import hudson.util.IOUtils; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintStream; + +/** + * cat/tail/head of the console output. + * + * @author Kohsuke Kawaguchi + */ +@Extension +public class ConsoleCommand extends CLICommand { + @Override + public String getShortDescription() { + return "Retrieves console output of a build"; + } + + @Argument(metaVar="JOB",usage="Name of the job",required=true) + public AbstractProject 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"; + + @Option(name="-f",usage="If the build is in progress, stay around and append console output as it comes, like 'tail -f'") + public boolean follow = false; + + @Option(name="-n",metaVar="N",usage="Display the last N lines") + public int n = -1; + + protected int run() throws Exception { + job.checkPermission(Item.BUILD); + + AbstractBuild run; + + try { + int n = Integer.parseInt(build); + run = job.getBuildByNumber(n); + if (run==null) + throw new CmdLineException("No such build #"+n); + } catch (NumberFormatException e) { + // maybe a permalink? + Permalink p = job.getPermalinks().get(build); + if (p!=null) { + run = (AbstractBuild)p.resolve(job); + if (run==null) + throw new CmdLineException("Permalink "+build+" produced no build"); + } else { + Permalink nearest = job.getPermalinks().findNearest(build); + throw new CmdLineException(String.format("Not sure what you meant by \"%s\". Did you mean \"%s\"?", build, nearest.getId())); + } + } + + OutputStreamWriter w = new OutputStreamWriter(stdout, getClientCharset()); + try { + long pos = n>=0 ? seek(run) : 0; + + if (follow) { + AnnotatedLargeText logText; + do { + logText = run.getLogText(); + pos = logText.writeLogTo(pos, w); + } while (!logText.isComplete()); + } else { + InputStream in = run.getLogInputStream(); + IOUtils.skip(in,pos); + IOUtils.copy(new InputStreamReader(in,run.getCharset()),w); + } + } finally { + w.flush(); // this pointless flush needed to work around SSHD-154 + w.close(); + } + + return 0; + } + + /** + * Find the byte offset in the log input stream that marks "last N lines". + */ + private long seek(AbstractBuild run) throws IOException { + class RingBuffer { + long[] lastNlines = new long[n]; + int ptr=0; + + RingBuffer() { + for (int i=0; i=0) { + for (int i=0; i