Skip to content

Commit

Permalink
JENKINS-13209 System Groovy script support
Browse files Browse the repository at this point in the history
Add system script checkbox in configuration UI
Add log/out/job variables to groovy shell
Setup the SYSTEM user during script execution
Log envvars used for script re-writing
Log messages and stack traces if the groovy script fails
  • Loading branch information
edalquist committed Mar 23, 2012
1 parent 726ad01 commit 0e8ba3d
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 43 deletions.
@@ -1,12 +1,30 @@
package org.jenkinsci.plugins.scripttrigger.groovy;

import antlr.ANTLRException;
import hudson.Extension;
import hudson.FilePath;
import hudson.Util;
import hudson.model.*;
import hudson.model.Action;
import hudson.model.Item;
import hudson.model.ParameterValue;
import hudson.model.AbstractProject;
import hudson.model.Node;
import hudson.model.ParametersAction;
import hudson.model.StringParameterValue;
import hudson.remoting.VirtualChannel;
import hudson.security.ACL;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContextHolder;
import org.jenkinsci.lib.envinject.EnvInjectException;
import org.jenkinsci.lib.envinject.service.EnvVarsResolver;
import org.jenkinsci.lib.xtrigger.XTriggerDescriptor;
Expand All @@ -15,10 +33,7 @@
import org.jenkinsci.plugins.scripttrigger.ScriptTriggerException;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
import antlr.ANTLRException;


/**
Expand All @@ -31,14 +46,17 @@ public class GroovyScriptTrigger extends AbstractTrigger {
private String groovyFilePath;

private String propertiesFilePath;

private boolean groovySystemScript;

@DataBoundConstructor
@SuppressWarnings("unused")
public GroovyScriptTrigger(String cronTabSpec, String groovyExpression, String groovyFilePath, String propertiesFilePath) throws ANTLRException {
public GroovyScriptTrigger(String cronTabSpec, String groovyExpression, String groovyFilePath, String propertiesFilePath, boolean groovySystemScript) throws ANTLRException {
super(cronTabSpec);
this.groovyExpression = Util.fixEmpty(groovyExpression);
this.groovyFilePath = Util.fixEmpty(groovyFilePath);
this.propertiesFilePath = Util.fixEmpty(propertiesFilePath);
this.groovySystemScript = groovySystemScript;
}

@SuppressWarnings("unused")
Expand All @@ -55,6 +73,10 @@ public String getGroovyFilePath() {
public String getPropertiesFilePath() {
return propertiesFilePath;
}

public boolean isGroovySystemScript() {
return groovySystemScript;
}

@Override
public Collection<? extends Action> getProjectActions() {
Expand Down Expand Up @@ -114,32 +136,39 @@ public Action[] invoke(File f, VirtualChannel channel) throws IOException, Inter

@Override
protected boolean checkIfModified(Node pollingNode, XTriggerLog log) throws ScriptTriggerException {

GroovyScriptTriggerExecutor executor = getGroovyScriptTriggerExecutor(log);

EnvVarsResolver envVarsResolver = new EnvVarsResolver();
Map<String, String> envVars;
final Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
try {
envVars = envVarsResolver.getPollingEnvVars((AbstractProject) job, pollingNode);
} catch (EnvInjectException e) {
throw new ScriptTriggerException(e);
}

if (groovyExpression != null) {
boolean evaluationSucceed = executor.evaluateGroovyScript(pollingNode, getGroovyExpression(), envVars);
if (evaluationSucceed) {
return true;
GroovyScriptTriggerExecutor executor = getGroovyScriptTriggerExecutor(log);

EnvVarsResolver envVarsResolver = new EnvVarsResolver();
Map<String, String> envVars;
try {
envVars = envVarsResolver.getPollingEnvVars((AbstractProject) job, pollingNode);
} catch (EnvInjectException e) {
throw new ScriptTriggerException(e);
}
}

if (groovyFilePath != null) {
boolean evaluationSucceed = executor.evaluateGroovyScriptFilePath(pollingNode, groovyFilePath, envVars);
if (evaluationSucceed) {
return true;

if (groovyExpression != null) {
boolean evaluationSucceed = executor.evaluateGroovyScript(pollingNode, job, getGroovyExpression(), envVars, groovySystemScript);
if (evaluationSucceed) {
return true;
}
}

if (groovyFilePath != null) {
boolean evaluationSucceed = executor.evaluateGroovyScriptFilePath(pollingNode, job, groovyFilePath, envVars, groovySystemScript);
if (evaluationSucceed) {
return true;
}
}

return false;
}
finally {
SecurityContextHolder.getContext().setAuthentication(existingAuth);
}

return false;
}

private GroovyScriptTriggerExecutor getGroovyScriptTriggerExecutor(XTriggerLog log) throws ScriptTriggerException {
Expand Down
@@ -1,17 +1,20 @@
package org.jenkinsci.plugins.scripttrigger.groovy;

import groovy.lang.GroovyShell;
import hudson.EnvVars;
import hudson.Util;
import hudson.PluginManager;
import hudson.model.Item;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.remoting.Callable;
import org.jenkinsci.lib.xtrigger.XTriggerLog;
import org.jenkinsci.plugins.scripttrigger.ScriptTriggerException;
import org.jenkinsci.plugins.scripttrigger.ScriptTriggerExecutor;

import java.io.IOException;
import java.util.Map;

import org.jenkinsci.lib.xtrigger.XTriggerLog;
import org.jenkinsci.plugins.scripttrigger.ScriptTriggerException;
import org.jenkinsci.plugins.scripttrigger.ScriptTriggerExecutor;

/**
* @author Gregory Boissinot
*/
Expand All @@ -21,32 +24,88 @@ public GroovyScriptTriggerExecutor(XTriggerLog log) {
super(log);
}

public boolean evaluateGroovyScript(Node executingNode, final String scriptContent, final Map<String, String> envVars) throws ScriptTriggerException {
public boolean evaluateGroovyScript(Node executingNode, final Item job, final String scriptContent, final Map<String, String> envVars, boolean groovySystemScript) throws ScriptTriggerException {

if (scriptContent == null) {
throw new NullPointerException("The script content object must be set.");
}
try {
if (groovySystemScript) {
log.info("Running as system script");
return evaluateGroovyScript(job, scriptContent, envVars);
}

return executingNode.getRootPath().act(new Callable<Boolean, ScriptTriggerException>() {
public Boolean call() throws ScriptTriggerException {
log.info("Replacing script vars with: " + envVars);
final String groovyExpressionResolved = Util.replaceMacro(scriptContent, envVars);
log.info(String.format("Evaluating the groovy script: \n %s", groovyExpressionResolved));
GroovyShell shell = new GroovyShell();
//Evaluate the new script content
Object result = shell.evaluate(groovyExpressionResolved);
//Return the evaluated result
return Boolean.valueOf(String.valueOf(result));
log.info("Running as node script");
return evaluateGroovyScript(null, scriptContent, envVars);
}
});
} catch (IOException ioe) {
log.info("Script execition failed: " + ioe.getClass().getName());
ioe.printStackTrace(log.getListener().getLogger());
throw new ScriptTriggerException(ioe);
} catch (InterruptedException ie) {
log.info("Script execition failed: " + ie.getClass().getName());
ie.printStackTrace(log.getListener().getLogger());
throw new ScriptTriggerException(ie);
} catch (RuntimeException e) {
log.info("Script execition failed: " + e.getClass().getName());
e.printStackTrace(log.getListener().getLogger());
throw e;
}
}

private boolean evaluateGroovyScript(final Item job, final String scriptContent, final Map<String, String> envVars) {
final StringBuilder envDebug = new StringBuilder("Replacing script vars using:");
for (final Map.Entry<String, String> envEntry : envVars.entrySet()) {
envDebug.append("\n\t").append(envEntry.getKey()).append("=").append(envEntry.getValue());
}
log.info(envDebug.toString());

final String groovyExpressionResolved = Util.replaceMacro(scriptContent, envVars);
log.info(String.format("Evaluating the groovy script: \n----------------------------------------\n%s\n----------------------------------------\n\n", groovyExpressionResolved));

final ClassLoader cl = getClassLoader();

GroovyShell shell = new GroovyShell(cl);

shell.setVariable("log", log);
shell.setVariable("out", log.getListener().getLogger());
if (job != null) {
shell.setVariable("job", job);
}

//Evaluate the new script content
Object result = shell.evaluate(groovyExpressionResolved);
//Return the evaluated result
return Boolean.valueOf(String.valueOf(result));
}

protected ClassLoader getClassLoader() {
final Hudson instance = Hudson.getInstance();
if (instance == null) {
log.info("No Hudson Instance available, returning thread context classloader");
return Thread.currentThread().getContextClassLoader();

}

final PluginManager pluginManager = instance.getPluginManager();
if (pluginManager == null) {
log.info("No PluginManager available, returning thread context classloader");
return Thread.currentThread().getContextClassLoader();
}

final ClassLoader cl = pluginManager.uberClassLoader;
if (cl == null) {
log.info("No uberClassLoader available, returning thread context classloader");
return Thread.currentThread().getContextClassLoader();
}

return cl;
}

public boolean evaluateGroovyScriptFilePath(Node executingNode, String scriptFilePath, final Map<String, String> envVars) throws ScriptTriggerException {
public boolean evaluateGroovyScriptFilePath(Node executingNode, Item job, String scriptFilePath, Map<String, String> envVars, boolean groovySystemScript) throws ScriptTriggerException {
if (scriptFilePath == null) {
throw new NullPointerException("The scriptFilePath object must be set.");
}
Expand All @@ -56,7 +115,7 @@ public boolean evaluateGroovyScriptFilePath(Node executingNode, String scriptFil
}

String scriptContent = getStringContent(executingNode, scriptFilePath);
return evaluateGroovyScript(executingNode, scriptContent, envVars);
return evaluateGroovyScript(executingNode, job, scriptContent, envVars, groovySystemScript);
}

}
@@ -1,5 +1,9 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">

<f:entry field="groovySystemScript" title="${%Groovy System Script}">
<f:checkbox name="groovySystemScript" value="${instance.groovySystemScript}" default="false"/>
</f:entry>

<f:entry field="groovyExpression" title="${%Groovy Script Content}">
<f:textarea name="groovyExpression" value="${instance.groovyExpression}"/>
</f:entry>
Expand Down
@@ -0,0 +1,9 @@
<div>
<p>
If checked run the groovy script as a system script, the script will have access to the same
variables as the Groovy Console including the hudson and job models.
<br/>
If not checked run the groovy script on the executor node, the script will not have access
to the hudson or job model.
</p>
</div>

0 comments on commit 0e8ba3d

Please sign in to comment.