Skip to content

Commit

Permalink
Split GroovyScript for agent and controller
Browse files Browse the repository at this point in the history
Make GroovyScript simpler by separating the parts meant only for the
controller into a new class that extends GroovyScript. This also makes
the intention of each class and the differences between them clearer
from the names themselves.
  • Loading branch information
mtughan committed Jun 23, 2023
1 parent 74a851a commit 5e05896
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.jenkinsci.plugins.scriptler.config.Parameter;
import org.jenkinsci.plugins.scriptler.config.Script;
import org.jenkinsci.plugins.scriptler.config.ScriptlerConfiguration;
import org.jenkinsci.plugins.scriptler.util.ControllerGroovyScript;
import org.jenkinsci.plugins.scriptler.util.GroovyScript;
import org.jenkinsci.plugins.scriptler.util.ScriptHelper;
import org.jenkinsci.plugins.scriptler.util.UIHelper;
Expand Down Expand Up @@ -246,7 +247,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
final Object output;
if (script.onlyMaster) {
// When run on master, make build, launcher, listener available to script
output = FilePath.localChannel.call(new GroovyScript(script.script, expandedParams, true, listener, launcher, build));
output = FilePath.localChannel.call(new ControllerGroovyScript(script.script, expandedParams, true, listener, launcher, build));
} else {
VirtualChannel channel = launcher.getChannel();
if (channel == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import hudson.Extension;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.remoting.ChannelClosedException;
import hudson.remoting.VirtualChannel;

import jenkins.model.Jenkins;
import org.jenkinsci.plugins.scriptler.Messages;
import org.jenkinsci.plugins.scriptler.config.Script;
import org.jenkinsci.plugins.scriptler.util.ControllerGroovyScript;
import org.jenkinsci.plugins.scriptler.util.GroovyScript;
import org.jenkinsci.plugins.scriptler.util.ScriptHelper;
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro;
Expand Down Expand Up @@ -40,20 +42,19 @@ public String evaluate(AbstractBuild<?, ?> context, TaskListener listener, Strin
throw new MacroEvaluationException(Messages.tokenmacro_AdminScriptOnly(scriptId));
}

VirtualChannel channel;
if (script.onlyMaster) {
channel = FilePath.localChannel;
Object output;
Node builder = context.getBuiltOn();
if (script.onlyMaster || Jenkins.get().equals(builder)) {
output = FilePath.localChannel.call(new ControllerGroovyScript(script.script, Collections.emptyList(), true, listener, null, context));
} else {
FilePath remoteFilePath = context.getWorkspace();
if (remoteFilePath == null) {
// the remote node has apparently disconnected, so we can't run our script
throw new ChannelClosedException((Channel) null, null);
}
channel = remoteFilePath.getChannel();
output = remoteFilePath.getChannel().call(new GroovyScript(script.script, Collections.emptyList(), true, listener));
}

Object output = channel.call(new GroovyScript(script.script, Collections.emptyList(), true, listener, null, context));

return output != null ? output.toString() : "";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.jenkinsci.plugins.scriptler.util;

import edu.umd.cs.findbugs.annotations.NonNull;
import groovy.lang.GroovyShell;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.TaskListener;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.scriptler.config.Parameter;

import java.util.Collection;

public class ControllerGroovyScript extends GroovyScript {
private static final long serialVersionUID = 1L;
private transient final AbstractBuild<?, ?> build;
private transient final Launcher launcher;

/**
* This constructor can only be used when the script is executed on the controller, because launcher and build can not be transferred to an agent and therefore the execution will fail
* @param script the script to be executed
* @param parameters the parameters to be passed to the script
* @param failWithException should the job fail with an exception
* @param listener access to logging via listener
* @param launcher the launcher
* @param build the current build
*/
public ControllerGroovyScript(String script, @NonNull Collection<Parameter> parameters, boolean failWithException, TaskListener listener, Launcher launcher, AbstractBuild<?, ?> build) {
super(script, parameters, failWithException, listener);
this.build = build;
this.launcher = launcher;
}

@Override
public ClassLoader getClassLoader() {
return Jenkins.get().getPluginManager().uberClassLoader;
}

@Override
protected void setShellVariables(@NonNull GroovyShell shell) {
super.setShellVariables(shell);
if (build != null) {
shell.setVariable("build", build);
}
if (launcher != null) {
shell.setVariable("launcher", launcher);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.TaskListener;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.security.Roles;
import org.apache.commons.collections.map.LRUMap;
import org.jenkinsci.plugins.scriptler.Messages;
import org.jenkinsci.plugins.scriptler.config.Parameter;
import org.jenkinsci.remoting.Role;
import org.jenkinsci.remoting.RoleChecker;

import java.io.PrintStream;
import java.util.*;
Expand All @@ -29,9 +23,6 @@ public class GroovyScript extends MasterToSlaveCallable<Object, RuntimeException
private final Collection<Parameter> parameters;
private final boolean failWithException;
private final TaskListener listener;
private transient final AbstractBuild<?, ?> build;
private transient final Launcher launcher;
private transient ClassLoader cl;

@SuppressWarnings("unchecked")
private static Map<String, ConcurrentLinkedQueue<Script>> cache = Collections.synchronizedMap(new LRUMap(10));
Expand All @@ -45,44 +36,25 @@ public class GroovyScript extends MasterToSlaveCallable<Object, RuntimeException
}

/**
* This constructor can only be used when the script is executed on the master, because launcher and build can not be transered to a slave and the therefore the execution will fail
* Constructor
* @param script the script to be executed
* @param parameters the parameters to be passed to the script
* @param failWithException should the job fail with an exception
* @param listener access to logging via listener
* @param launcher the launcher
* @param build the current build
*/
public GroovyScript(String script, @NonNull Collection<Parameter> parameters, boolean failWithException, TaskListener listener, Launcher launcher, AbstractBuild<?, ?> build) {
public GroovyScript(String script, @NonNull Collection<Parameter> parameters, boolean failWithException, TaskListener listener) {
this.script = script;
this.parameters = new ArrayList<>(parameters);
this.failWithException = failWithException;
this.listener = listener;
this.cl = getClassLoader();
this.build = build;
this.launcher = launcher;
}

/**
* Constructor
* @param script the script to be executed
* @param parameters the parameters to be passed to the script
* @param failWithException should the job fail with an exception
* @param listener access to logging via listener
*/
public GroovyScript(String script, @NonNull Collection<Parameter> parameters, boolean failWithException, TaskListener listener) {
this(script, parameters, failWithException, listener, null, null);
}

public ClassLoader getClassLoader() {
return Jenkins.get().getPluginManager().uberClassLoader;
return Thread.currentThread().getContextClassLoader();
}

public Object call() {
// if we run locally, cl!=null. Otherwise the delegating classloader will be available as context classloader.
if (cl == null) {
cl = Thread.currentThread().getContextClassLoader();
}
ClassLoader cl = getClassLoader();
PrintStream logger = listener.getLogger();
GroovyShell shell = new GroovyShell(cl);

Expand All @@ -97,9 +69,7 @@ public Object call() {

// set default variables
shell.setVariable("out", logger);
shell.setVariable("listener", listener);
if(build != null) shell.setVariable("build", build);
if(launcher != null) shell.setVariable("launcher", launcher);
setShellVariables(shell);

ConcurrentLinkedQueue<Script> scriptPool = cache.get(script);
if (scriptPool == null) {
Expand Down Expand Up @@ -137,13 +107,8 @@ public Object call() {
}
}

@Override
public void checkRoles(RoleChecker roleChecker) throws SecurityException {
if(launcher != null && build != null){
roleChecker.check(this, Roles.MASTER);
}else{
roleChecker.check(this, Role.UNKNOWN);
}
protected void setShellVariables(@NonNull GroovyShell shell) {
shell.setVariable("listener", listener);
}

private static final class ScriptlerExecutionException extends RuntimeException {
Expand Down

0 comments on commit 5e05896

Please sign in to comment.