Skip to content

Commit

Permalink
Merge pull request #2563 from stochmial/shell-setunstable-rebased
Browse files Browse the repository at this point in the history
[JENKINS-23786] Permit the Shell plugin to set a build result as unstable via a return code
  • Loading branch information
oleg-nenashev committed Oct 11, 2016
2 parents 74c5342 + 359548e commit 720b75f
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 15 deletions.
53 changes: 48 additions & 5 deletions core/src/main/java/hudson/tasks/BatchFile.java
Expand Up @@ -25,14 +25,21 @@

import hudson.FilePath;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractProject;
import hudson.util.FormValidation;
import hudson.util.LineEndingConversion;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

import java.io.ObjectStreamException;

import javax.annotation.CheckForNull;

/**
* Executes commands by using Windows batch file.
*
Expand All @@ -44,6 +51,8 @@ public BatchFile(String command) {
super(LineEndingConversion.convertEOL(command, LineEndingConversion.EOLType.Windows));
}

private Integer unstableReturn;

public String[] buildCommandLine(FilePath script) {
return new String[] {"cmd","/c","call",script.getRemote()};
}
Expand All @@ -56,6 +65,21 @@ protected String getFileExtension() {
return ".bat";
}

@CheckForNull
public final Integer getUnstableReturn() {
return new Integer(0).equals(unstableReturn) ? null : unstableReturn;
}

@DataBoundSetter
public void setUnstableReturn(Integer unstableReturn) {
this.unstableReturn = unstableReturn;
}

@Override
protected boolean isErrorlevelForUnstableBuild(int exitCode) {
return this.unstableReturn != null && exitCode != 0 && this.unstableReturn.equals(exitCode);
}

private Object readResolve() throws ObjectStreamException {
return new BatchFile(command);
}
Expand All @@ -71,9 +95,28 @@ public String getDisplayName() {
return Messages.BatchFile_DisplayName();
}

@Override
public Builder newInstance(StaplerRequest req, JSONObject data) {
return new BatchFile(data.getString("command"));
/**
* Performs on-the-fly validation of the errorlevel.
*/
@Restricted(DoNotUse.class)
public FormValidation doCheckUnstableReturn(@QueryParameter String value) {
value = Util.fixEmptyAndTrim(value);
if (value == null) {
return FormValidation.ok();
}
long unstableReturn;
try {
unstableReturn = Long.parseLong(value);
} catch (NumberFormatException e) {
return FormValidation.error(hudson.model.Messages.Hudson_NotANumber());
}
if (unstableReturn == 0) {
return FormValidation.warning(hudson.tasks.Messages.BatchFile_invalid_exit_code_zero());
}
if (unstableReturn < Integer.MIN_VALUE || unstableReturn > Integer.MAX_VALUE) {
return FormValidation.error(hudson.tasks.Messages.BatchFile_invalid_exit_code_range(unstableReturn));
}
return FormValidation.ok();
}

public boolean isApplicable(Class<? extends AbstractProject> jobType) {
Expand Down
21 changes: 20 additions & 1 deletion core/src/main/java/hudson/tasks/CommandInterpreter.java
Expand Up @@ -31,6 +31,7 @@
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Node;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.remoting.ChannelClosedException;

Expand Down Expand Up @@ -64,6 +65,18 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListene
return perform(build,launcher,(TaskListener)listener);
}

/**
* Determines whether a non-zero exit code from the process should change the build
* status to {@link Result#UNSTABLE} instead of default {@link Result#FAILURE}.
*
* Changing to {@link Result#UNSTABLE} does not abort the build, next steps are continued.
*
* @since TODO
*/
protected boolean isErrorlevelForUnstableBuild(int exitCode) {
return false;
}

public boolean perform(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws InterruptedException {
FilePath ws = build.getWorkspace();
if (ws == null) {
Expand Down Expand Up @@ -93,6 +106,11 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, TaskListener
envVars.put(e.getKey(),e.getValue());

r = join(launcher.launch().cmds(buildCommandLine(script)).envs(envVars).stdout(listener).pwd(ws).start());

if(isErrorlevelForUnstableBuild(r)) {
build.setResult(Result.UNSTABLE);
r = 0;
}
} catch (IOException e) {
Util.displayIOException(e, listener);
e.printStackTrace(listener.fatalError(Messages.CommandInterpreter_CommandFailed()));
Expand Down Expand Up @@ -127,7 +145,8 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, TaskListener
*
* This allows subtypes to treat the exit code differently (for example by treating non-zero exit code
* as if it's zero, or to set the status to {@link Result#UNSTABLE}). Any non-zero exit code will cause
* the build step to fail.
* the build step to fail. Use {@link #isErrorlevelForUnstableBuild(int exitCode)} to redefine the default
* behaviour.
*
* @since 1.549
*/
Expand Down
63 changes: 56 additions & 7 deletions core/src/main/java/hudson/tasks/Shell.java
Expand Up @@ -36,7 +36,10 @@
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;

Expand All @@ -46,17 +49,24 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;

/**
* Executes a series of commands by using a shell.
*
* @author Kohsuke Kawaguchi
*/
public class Shell extends CommandInterpreter {

@DataBoundConstructor
public Shell(String command) {
super(LineEndingConversion.convertEOL(command, LineEndingConversion.EOLType.Unix));
}

private Integer unstableReturn;



/**
* Older versions of bash have a bug where non-ASCII on the first line
* makes the shell think the file is a binary file and not a script. Adding
Expand All @@ -82,7 +92,7 @@ public String[] buildCommandLine(FilePath script) {
args.add(script.getRemote());
args.set(0,args.get(0).substring(2)); // trim off "#!"
return args.toArray(new String[args.size()]);
} else
} else
return new String[] { getDescriptor().getShellOrDefault(script.getChannel()), "-xe", script.getRemote()};
}

Expand All @@ -94,6 +104,21 @@ protected String getFileExtension() {
return ".sh";
}

@CheckForNull
public final Integer getUnstableReturn() {
return new Integer(0).equals(unstableReturn) ? null : unstableReturn;
}

@DataBoundSetter
public void setUnstableReturn(Integer unstableReturn) {
this.unstableReturn = unstableReturn;
}

@Override
protected boolean isErrorlevelForUnstableBuild(int exitCode) {
return this.unstableReturn != null && exitCode != 0 && this.unstableReturn.equals(exitCode);
}

@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
Expand Down Expand Up @@ -134,7 +159,7 @@ public String getShellOrDefault() {
}

public String getShellOrDefault(VirtualChannel channel) {
if (shell != null)
if (shell != null)
return shell;

String interpreter = null;
Expand All @@ -151,7 +176,7 @@ public String getShellOrDefault(VirtualChannel channel) {

return interpreter;
}

public void setShell(String shell) {
this.shell = Util.fixEmptyAndTrim(shell);
save();
Expand All @@ -161,6 +186,30 @@ public String getDisplayName() {
return Messages.Shell_DisplayName();
}

/**
* Performs on-the-fly validation of the exit code.
*/
@Restricted(DoNotUse.class)
public FormValidation doCheckUnstableReturn(@QueryParameter String value) {
value = Util.fixEmptyAndTrim(value);
if (value == null) {
return FormValidation.ok();
}
long unstableReturn;
try {
unstableReturn = Long.parseLong(value);
} catch (NumberFormatException e) {
return FormValidation.error(hudson.model.Messages.Hudson_NotANumber());
}
if (unstableReturn == 0) {
return FormValidation.warning(hudson.tasks.Messages.Shell_invalid_exit_code_zero());
}
if (unstableReturn < 1 || unstableReturn > 255) {
return FormValidation.error(hudson.tasks.Messages.Shell_invalid_exit_code_range(unstableReturn));
}
return FormValidation.ok();
}

@Override
public boolean configure(StaplerRequest req, JSONObject data) throws FormException {
req.bindJSON(this, data);
Expand All @@ -172,9 +221,9 @@ public boolean configure(StaplerRequest req, JSONObject data) throws FormExcepti
*/
public FormValidation doCheckShell(@QueryParameter String value) {
// Executable requires admin permission
return FormValidation.validateExecutable(value);
return FormValidation.validateExecutable(value);
}

private static final class Shellinterpreter extends MasterToSlaveCallable<String, IOException> {

private static final long serialVersionUID = 1L;
Expand All @@ -183,8 +232,8 @@ public String call() throws IOException {
return Functions.isWindows() ? "sh" : "/bin/sh";
}
}

}

private static final Logger LOGGER = Logger.getLogger(Shell.class.getName());
}
5 changes: 5 additions & 0 deletions core/src/main/resources/hudson/tasks/BatchFile/config.jelly
Expand Up @@ -28,4 +28,9 @@ THE SOFTWARE.
description="${%description(rootURL)}">
<f:textarea name="command" value="${instance.command}" class="fixed-width" />
</f:entry>
<f:advanced>
<f:entry title="${%ERRORLEVEL to set build unstable}" field="unstableReturn" >
<f:number value="${instance.unstableReturn}" min="-2147483648" max="2147483647" step="1" />
</f:entry>
</f:advanced>
</j:jelly>
@@ -0,0 +1,9 @@
<div>
If set, the batch errorlevel result that will be interpreted as an unstable build result.
If the final errorlevel matches the value, the build results will be set to unstable and
next steps will be continued. Supported values match the widest errorlevel range for Windows
like systems. In Windows NT4 and beyond the ERRORLEVEL is stored as a four byte, signed integer,
yielding maximum and minimum values of 2147483647 and -2147483648, respectively. Older versions
of Windows use 2 bytes. DOS like systems use single byte, yelding errorlevels between 0-255.
The value 0 is ignored and does not make the build unstable to keep the default behaviour consistent.
</div>
4 changes: 4 additions & 0 deletions core/src/main/resources/hudson/tasks/Messages.properties
Expand Up @@ -39,6 +39,8 @@ If you really did mean to archive all the files in the workspace, please specify
ArtifactArchiver.NoMatchFound=No artifacts found that match the file pattern "{0}". Configuration error?

BatchFile.DisplayName=Execute Windows batch command
BatchFile.invalid_exit_code_range=Invalid errorlevel value: {0}. Check help section
BatchFile.invalid_exit_code_zero=ERRORLEVEL zero is ignored and does not make the build unstable

BuildTrigger.Disabled={0} is disabled. Triggering skipped
BuildTrigger.DisplayName=Build other projects
Expand Down Expand Up @@ -81,3 +83,5 @@ Maven.NotMavenDirectory={0} doesn\u2019t look like a Maven directory
Maven.NoExecutable=Couldn\u2019t find any executable in {0}

Shell.DisplayName=Execute shell
Shell.invalid_exit_code_range=Invalid exit code value: {0}. Check help section
Shell.invalid_exit_code_zero=Exit code zero is ignored and does not make the build unstable
7 changes: 7 additions & 0 deletions core/src/main/resources/hudson/tasks/Shell/config.groovy
Expand Up @@ -27,3 +27,10 @@ f=namespace(lib.FormTagLib)
f.entry(title:_("Command"),description:_("description",rootURL)) {
f.textarea(name: "command", value: instance?.command, class: "fixed-width", 'codemirror-mode': 'shell', 'codemirror-config': "mode: 'text/x-sh'")
}

f.advanced() {
f.entry(title:_("Exit code to set build unstable"), field: "unstableReturn") {
f.number(clazz:"positive-number", value: instance?.unstableReturn, min:1, max:255, step:1)
}

}
Expand Up @@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

description=See <a href="{0}/env-vars.html" target=_new>the list of available environment variables</a>
description=See <a href="{0}/env-vars.html" target=_new>the list of available environment variables</a>
@@ -0,0 +1,5 @@
<div>
If set, the shell exit code that will be interpreted as an unstable build result. If the exit code matches the value,
the build results will be set to 'unstable' and next steps will be continued. On Unix-like it is a value between 0-255.
The value 0 is ignored and does not make the build unstable to keep the default behaviour consistent.
</div>

0 comments on commit 720b75f

Please sign in to comment.