Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split GroovyScript for agent and controller #67

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,18 @@ 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;
if (script.onlyMaster || Jenkins.get().equals(context.getBuiltOn())) {
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,46 +36,26 @@ 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();
}
PrintStream logger = listener.getLogger();
GroovyShell shell = new GroovyShell(cl);
GroovyShell shell = new GroovyShell(getClassLoader());

for (Parameter param : parameters) {
final String paramName = param.getName();
Expand All @@ -97,9 +68,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 +106,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