Skip to content

Commit

Permalink
Merge pull request #2 from edalquist/JENKINS-13209
Browse files Browse the repository at this point in the history
JENKINS-13209 System Groovy script support
  • Loading branch information
gboissinot committed Mar 25, 2012
2 parents 14b1178 + afe69ee commit 2f62504
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 38 deletions.
@@ -1,21 +1,40 @@
package org.jenkinsci.plugins.scripttrigger.groovy;

import antlr.ANTLRException;
import hudson.EnvVars;
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;
import org.jenkinsci.lib.xtrigger.XTriggerLog;
import org.jenkinsci.plugins.scripttrigger.AbstractTrigger;
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 @@ -28,14 +47,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 @@ -52,6 +74,10 @@ public String getGroovyFilePath() {
public String getPropertiesFilePath() {
return propertiesFilePath;
}

public boolean isGroovySystemScript() {
return groovySystemScript;
}

@Override
public Collection<? extends Action> getProjectActions() {
Expand Down Expand Up @@ -111,24 +137,40 @@ public Action[] invoke(File f, VirtualChannel channel) throws IOException, Inter

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

GroovyScriptTriggerExecutor executor = getGroovyScriptTriggerExecutor(log);

if (groovyExpression != null) {
boolean evaluationSucceed = executor.evaluateGroovyScript(pollingNode, getGroovyExpression());
if (evaluationSucceed) {
return true;
final Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
try {

GroovyScriptTriggerExecutor executor = getGroovyScriptTriggerExecutor(log);
final AbstractProject proj = (AbstractProject) job;

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

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

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

if (groovyFilePath != null) {
boolean evaluationSucceed = executor.evaluateGroovyScriptFilePath(pollingNode, proj, 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,16 +1,23 @@
package org.jenkinsci.plugins.scripttrigger.groovy;

import groovy.lang.GroovyShell;
import hudson.EnvVars;
import hudson.Util;
import hudson.PluginManager;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.remoting.Callable;
import hudson.util.IOUtils;

import java.io.File;
import java.io.FileInputStream;
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;

import java.io.IOException;

/**
* @author Gregory Boissinot
*/
Expand All @@ -20,41 +27,136 @@ public GroovyScriptTriggerExecutor(XTriggerLog log) {
super(log);
}

public boolean evaluateGroovyScript(Node executingNode, final String scriptContent) throws ScriptTriggerException {
public boolean evaluateGroovyScript(Node executingNode, final AbstractProject proj, 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(proj, scriptContent, envVars);
}

return executingNode.getRootPath().act(new Callable<Boolean, ScriptTriggerException>() {
public Boolean call() throws ScriptTriggerException {
final String groovyExpressionResolved = Util.replaceMacro(scriptContent, EnvVars.masterEnvVars);
log.info(String.format("Evaluating the groovy script: \n %s", scriptContent));
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 AbstractProject proj, 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("Evaluating the groovy script:");
log.info("---------- Base Script -----------------");
log.info(scriptContent);
log.info("---------- Resolved Script -------------");
log.info(groovyExpressionResolved);
log.info("----------------------------------------\n");

final ClassLoader cl = getClassLoader();

GroovyShell shell = new GroovyShell(cl);

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

//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();

public boolean evaluateGroovyScriptFilePath(Node executingNode, String scriptFilePath) throws ScriptTriggerException {
}

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, AbstractProject proj, String scriptFilePath, Map<String, String> envVars, boolean groovySystemScript) throws ScriptTriggerException {
if (scriptFilePath == null) {
throw new NullPointerException("The scriptFilePath object must be set.");
}

if (!existsScript(executingNode, scriptFilePath)) {
return false;
final String scriptContent;
if (groovySystemScript) {
String expandedScriptFile = Util.replaceMacro(scriptFilePath, envVars);

final File file = new File(expandedScriptFile);
final String scriptPath = file.getAbsolutePath();

if (!file.exists()) {
log.info(String.format("Can't load the file '%s'. It doesn't exist.", scriptPath));
return false;
}

log.info("Reading script from: " + file.getAbsolutePath());
try {
final FileInputStream fis = new FileInputStream(file);
try {
scriptContent = IOUtils.toString(fis);
}
finally {
fis.close();
}
log.info("Read " + scriptContent.length() + " character long script from: " + scriptPath);
}
catch (IOException e) {
final String msg = "Failed to read system groovy script file '" + scriptFilePath + "' from '" + scriptPath + "'";
log.info(msg);
e.printStackTrace(log.getListener().getLogger());
throw new RuntimeException(msg, e);
}
}
else {
if (!existsScript(executingNode, scriptFilePath)) {
return false;
}

scriptContent = getStringContent(executingNode, scriptFilePath);
}

String scriptContent = getStringContent(executingNode, scriptFilePath);
return evaluateGroovyScript(executingNode, scriptContent);
return evaluateGroovyScript(executingNode, proj, 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. The AbstractProject is also bound to the project variable.
<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 2f62504

Please sign in to comment.