diff --git a/core/src/main/java/hudson/util/RemotingDiagnostics.java b/core/src/main/java/hudson/util/RemotingDiagnostics.java
index ba383e2081551bf455f0e3eec60c8d90a0b1cf68..8a2a51aa28608fc251652dbd90ef4f7715a26b16 100644
--- a/core/src/main/java/hudson/util/RemotingDiagnostics.java
+++ b/core/src/main/java/hudson/util/RemotingDiagnostics.java
@@ -34,15 +34,18 @@ import hudson.remoting.Future;
import hudson.remoting.VirtualChannel;
import hudson.security.AccessControlled;
import jenkins.security.MasterToSlaveCallable;
+
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;
+import javax.annotation.Nonnull;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@@ -104,7 +107,7 @@ public final class RemotingDiagnostics {
/**
* Executes Groovy script remotely.
*/
- public static String executeGroovy(String script, VirtualChannel channel) throws IOException, InterruptedException {
+ public static String executeGroovy(String script, @Nonnull VirtualChannel channel) throws IOException, InterruptedException {
return channel.call(new Script(script));
}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 8694f7a1f7bb22e84cc4c6524587b526d46a684a..8b024f018eab55ebef8abdb46dcf7e3998bf36ab 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -3527,6 +3527,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if (!"POST".equals(req.getMethod())) {
throw HttpResponses.error(HttpURLConnection.HTTP_BAD_METHOD, "requires POST");
}
+
+ if (channel == null) {
+ throw HttpResponses.error(HttpURLConnection.HTTP_NOT_FOUND, "Node is offline");
+ }
+
try {
req.setAttribute("output",
RemotingDiagnostics.executeGroovy(text, channel));
diff --git a/core/src/main/resources/hudson/model/Computer/sidepanel.jelly b/core/src/main/resources/hudson/model/Computer/sidepanel.jelly
index 7d81d64346332571927305f64a676785c731ceb2..2a74c154e76ab2050b44ae38808e9a37c0a71610 100644
--- a/core/src/main/resources/hudson/model/Computer/sidepanel.jelly
+++ b/core/src/main/resources/hudson/model/Computer/sidepanel.jelly
@@ -36,7 +36,9 @@ THE SOFTWARE.
-
+
+
+
@@ -45,4 +47,4 @@ THE SOFTWARE.
-
\ No newline at end of file
+
diff --git a/core/src/main/resources/lib/hudson/scriptConsole.jelly b/core/src/main/resources/lib/hudson/scriptConsole.jelly
index 205ea555e96a0a1c8e0fb4d7080596e1e0d602dd..bf2195db5b53b79b0867dd2b9a108f1e96133235 100644
--- a/core/src/main/resources/lib/hudson/scriptConsole.jelly
+++ b/core/src/main/resources/lib/hudson/scriptConsole.jelly
@@ -31,29 +31,36 @@ THE SOFTWARE.
- ${%Script Console}
-
-
- ${%description}
-
-
-
-
- ${%description2}
-
-
-
-
-
-
- ${%Result}
-
-
+
${%Script Console}
+
+
+
+
+ ${%description}
+
+
+
+
+ ${%description2}
+
+
+
+
+
+
+ ${%Result}
+
+
+
+
+ ${%It is not possible to run scripts when slave is offline.}
+
+
-
\ No newline at end of file
+
diff --git a/test/src/test/java/jenkins/model/JenkinsTest.java b/test/src/test/java/jenkins/model/JenkinsTest.java
index 18d42ad4b11127dff1245911099bded66de4972c..4b4bc7422d655efe635e155b95659dc0fe39361c 100644
--- a/test/src/test/java/jenkins/model/JenkinsTest.java
+++ b/test/src/test/java/jenkins/model/JenkinsTest.java
@@ -23,6 +23,9 @@
*/
package jenkins.model;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
@@ -31,7 +34,9 @@ import static org.junit.Assert.fail;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
+import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
@@ -50,6 +55,7 @@ import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.LegacySecurityRealm;
import hudson.security.Permission;
import hudson.slaves.ComputerListener;
+import hudson.slaves.DumbSlave;
import hudson.slaves.OfflineCause;
import hudson.util.FormValidation;
@@ -427,4 +433,20 @@ public class JenkinsTest {
@TestExtension(value = "testComputerListenerNotifiedOnRestart")
public static final ComputerListener listenerMock = Mockito.mock(ComputerListener.class);
+
+ @Test
+ public void runScriptOnOfflineComputer() throws Exception {
+ DumbSlave slave = j.createSlave();
+ URL url = new URL(j.getURL(), "computer/" + slave.getNodeName() + "/scriptText?script=println(42)");
+
+ WebClient wc = j.createWebClient();
+ wc.setThrowExceptionOnFailingStatusCode(false);
+
+ WebRequestSettings req = new WebRequestSettings(url, HttpMethod.POST);
+ Page page = wc.getPage(wc.addCrumb(req));
+ WebResponse rsp = page.getWebResponse();
+
+ assertThat(rsp.getContentAsString(), containsString("Node is offline"));
+ assertThat(rsp.getStatusCode(), equalTo(404));
+ }
}