Skip to content

Commit

Permalink
[FIXED JENKINS-25079] Add support for Managed PowerShell files
Browse files Browse the repository at this point in the history
  • Loading branch information
Greybird committed Apr 22, 2015
1 parent 263a7b6 commit cebf39c
Show file tree
Hide file tree
Showing 11 changed files with 567 additions and 0 deletions.
@@ -0,0 +1,243 @@
package org.jenkinsci.plugins.managedscripts;

import org.jenkinsci.plugins.managedscripts.PowerShellConfig.Arg;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import org.jenkinsci.lib.configprovider.ConfigProvider;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.bind.JavaScriptMethod;
import org.kohsuke.stapler.DataBoundConstructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.jenkinsci.lib.configprovider.model.Config;
import hudson.FilePath;
import hudson.tasks.CommandInterpreter;

/**
* A project that uses this builder can choose a build step from a list of predefined powershell files that are used as command line scripts.
* <p>
*
* @author Arnaud Tamaillon (Greybird)
* @see hudson.tasks.BatchFile
*/
public class PowerShellBuildStep extends CommandInterpreter {

private final String[] buildStepArgs;

public static class ArgValue {
public final String arg;

@DataBoundConstructor
public ArgValue(String arg) {
this.arg = arg;
}
}

public static class ScriptBuildStepArgs {
public final boolean defineArgs;
public final ArgValue[] buildStepArgs;

@DataBoundConstructor
public ScriptBuildStepArgs(boolean defineArgs, ArgValue[] buildStepArgs)
{
this.defineArgs = defineArgs;
this.buildStepArgs = buildStepArgs;
}
}

/**
* The constructor used at form submission
*
* @param buildStepId
* the Id of the config file
* @param scriptBuildStepArgs
* whether to save the args and arg values (the boolean is required because of html form submission, which also sends hidden values)
*/
@DataBoundConstructor
public PowerShellBuildStep(String buildStepId, ScriptBuildStepArgs scriptBuildStepArgs) {
super(buildStepId);
List<String> l = null;
if (scriptBuildStepArgs != null && scriptBuildStepArgs.defineArgs
&& scriptBuildStepArgs.buildStepArgs != null) {
l = new ArrayList<String>();
for (ArgValue arg : scriptBuildStepArgs.buildStepArgs) {
l.add(arg.arg);
}
}
this.buildStepArgs = l == null ? null : l.toArray(new String[l.size()]);
}

/**
* The constructor
*
* @param buildStepId
* the Id of the config file
* @param buildStepArgs
* list of arguments specified as buildStepargs
*/
public PowerShellBuildStep(String buildStepId, String[] buildStepArgs) {
super(buildStepId); // save buildStepId as command
this.buildStepArgs = buildStepArgs;
}

public String getBuildStepId() {
return getCommand();
}

public String[] getBuildStepArgs() {
return buildStepArgs;
}

@Override
public String[] buildCommandLine(FilePath script) {
List<String> cml = new ArrayList<String>();
cml.add("powershell.exe");
cml.add("-ExecutionPolicy");
cml.add("ByPass");
cml.add("& \'" + script.getRemote() + "\'");

// Add additional parameters set by user
if (buildStepArgs != null) {
for (String arg : buildStepArgs) {
cml.add(arg);
}
}

return (String[]) cml.toArray(new String[cml.size()]);
}

@Override
protected String getContents() {
Config buildStepConfig = getDescriptor().getBuildStepConfigById(getBuildStepId());
if (buildStepConfig == null) {
throw new IllegalStateException(Messages.config_does_not_exist(getBuildStepId()));
}
return buildStepConfig.content + "\r\nexit $LastExitCode";
}

@Override
protected String getFileExtension() {
return ".ps1";
}

//Overridden for better type safety.
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}

/**
* Descriptor for {@link PowerShellBuildStep}.
*/
@Extension(ordinal = 60)
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
final Logger logger = Logger.getLogger(PowerShellBuildStep.class.getName());

/**
* Enables this builder for all kinds of projects.
*/
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}

/**
* This human readable name is used in the configuration screen.
*/
@Override
public String getDisplayName() {
return Messages.powershell_buildstep_name();
}

/**
* Return all powershell files (templates) that the user can choose from when creating a build step. Ordered by name.
*
* @return A collection of batch files of type {@link PowerShellBatchConfig}.
*/
public Collection<Config> getAvailableBuildTemplates() {
List<Config> allConfigs = new ArrayList<Config>(getBuildStepConfigProvider().getAllConfigs());
Collections.sort(allConfigs, new Comparator<Config>() {
public int compare(Config o1, Config o2) {
return o1.name.compareTo(o2.name);
}
});
return allConfigs;
}

/**
* Returns a Config object for a given config file Id.
*
* @param id
* The Id of a config file.
* @return If Id can be found a Config object that represents the given Id is returned. Otherwise null.
*/
public PowerShellConfig getBuildStepConfigById(String id) {
return (PowerShellConfig) getBuildStepConfigProvider().getConfigById(id);
}

/**
* gets the argument description to be displayed on the screen when selecting a config in the dropdown
*
* @param configId
* the config id to get the arguments description for
* @return the description
*/
@JavaScriptMethod
public String getArgsDescription(String configId) {
final PowerShellConfig config = getBuildStepConfigById(configId);
if (config != null) {
if (config.args != null && !config.args.isEmpty()) {
StringBuilder sb = new StringBuilder("Required arguments: ");
int i = 1;
for (Iterator<Arg> iterator = config.args.iterator(); iterator.hasNext(); i++) {
Arg arg = iterator.next();
sb.append(i).append(". ").append(arg.name);
if (iterator.hasNext()) {
sb.append(" | ");
}
}
return sb.toString();
} else {
return "No arguments required";
}
}
return "please select a script!";
}

@JavaScriptMethod
public List<Arg> getArgs(String configId) {
final PowerShellConfig config = getBuildStepConfigById(configId);
return config.args;
}

/**
* validate that an existing config was chosen
*
* @param value
* the configId
* @return
*/
public FormValidation doCheckBuildStepId(@QueryParameter String buildStepId) {
final PowerShellConfig config = getBuildStepConfigById(buildStepId);
if (config != null) {
return FormValidation.ok();
} else {
return FormValidation.error("you must select a valid powershell file");
}
}

private ConfigProvider getBuildStepConfigProvider() {
ExtensionList<ConfigProvider> providers = ConfigProvider.all();
return providers.get(PowerShellConfig.PowerShellConfigProvider.class);
}
}
}
@@ -0,0 +1,67 @@
package org.jenkinsci.plugins.managedscripts;

import hudson.Extension;
import org.jenkinsci.lib.configprovider.AbstractConfigProviderImpl;
import org.jenkinsci.lib.configprovider.model.Config;
import org.jenkinsci.lib.configprovider.model.ContentType;
import org.kohsuke.stapler.DataBoundConstructor;

import java.util.ArrayList;
import java.util.List;

public class PowerShellConfig extends Config {

public final List<Arg> args;

@DataBoundConstructor
public PowerShellConfig(String id, String name, String comment, String content, List<Arg> args) {
super(id, name, comment, content);

if (args != null) {
List<Arg> filteredArgs = new ArrayList<PowerShellConfig.Arg>();
for (Arg arg : args) {
if (arg.name != null && arg.name.trim().length() > 0) {
filteredArgs.add(arg);
}
}
this.args = filteredArgs;
} else {
this.args = null;
}
}

public static class Arg {
public final String name;

@DataBoundConstructor
public Arg(final String name) {
this.name = name;
}
}

@Extension(ordinal = 70)
public static class PowerShellConfigProvider extends AbstractConfigProviderImpl {

public PowerShellConfigProvider() {
load();
}

@Override
public ContentType getContentType() {
return ContentType.DefinedType.HTML;
}

@Override
public String getDisplayName() {
return Messages.powershell_buildstep_provider_name();
}

@Override
public Config newConfig() {
String id = getProviderId() + System.currentTimeMillis();
return new PowerShellConfig(id, "Build Step", "", "Write-Host \"hello\";", null);
}

}

}
Expand Up @@ -26,6 +26,9 @@ buildstep_name=Execute managed script
win_buildstep_provider_name=Managed windows batch file
win_buildstep_name=Execute managed windows batch

powershell_buildstep_provider_name=Managed powershell file
powershell_buildstep_name=Execute managed powershell

config_does_not_exist=Cannot find config with Id [{0}]. Are you sure it exists? Please check the configuration.


@@ -0,0 +1,69 @@
<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">

<st:adjunct assumes="org.kohsuke.stapler.framework.prototype.prototype" includes="org.kohsuke.stapler.bind"/>

<st:once>
<script type="text/javascript" src="${rootURL}/plugin/managed-scripts/js/managed-scripts.js" />
</st:once>
<j:choose>
<j:when test="${empty(descriptor.availableBuildTemplates)}">
<f:entry title="">
<div>
No build templates are defined. Please define one
<a href="/configfiles">here</a>.
</div>
</f:entry>
</j:when>
<j:otherwise>
<f:entry title="${%Script}" field="buildStepContent">
<select name="buildStepId" onChange="ms_initDetailLink('${rootURL}', this);ms_showParams(this, this.value);">
<option value="">(Default)</option>
<j:forEach var="inst" items="${descriptor.availableBuildTemplates}" varStatus="loop">
<j:choose>
<j:when test="${inst.id == instance.buildStepId}">
<option value="${inst.id}" selected="selected">${inst.name} - ${inst.comment}</option>
</j:when>
<j:otherwise>
<option value="${inst.id}">${inst.name} - ${inst.comment}</option>
</j:otherwise>
</j:choose>
</j:forEach>
</select>
<a target="_blank" name="showDetailLink" href="" style="display:none;" onclick="window.open(this.href,'window','width=900,height=640,resizable,scrollbars,toolbar,menubar') ;return false;"> view selected script</a>
<div name="argumentDescription" id="argumentDescription"/>
<f:block>
<table name="scriptBuildStepArgs" id="scriptBuildStepArgs">
<f:optionalBlock name="defineArgs" inline="true" title="${%Define arguments}" checked="${!empty(instance.buildStepArgs)}" help="/plugin/managed-scripts/help-defineArgs_powershell.html">
<f:entry>
<f:repeatable var="arg" items="${instance.buildStepArgs}" name="buildStepArgs" noAddButton="true" minimum="1">
<table width="100%">
<f:entry>
<div name="argName"><st:nbsp/></div>
<input type="text" name="arg" value="${arg}" size="80"/>
<input type="button" name="delete_button" value="${%Delete}" class="repeatable-delete show-if-not-only" style="margin-left: 1em;" />
<input type="button" name="add_button" onClick="ms_labelArgs()" value="${%Add argument}" class="repeatable-add show-if-last" />
</f:entry>
</table>
</f:repeatable>
</f:entry>
</f:optionalBlock>
</table>
</f:block>
</f:entry>
</j:otherwise>
</j:choose>
<st:bind var="desc" value="${descriptor}"/>
<st:once>
<script type="text/javascript">
Event.observe(window, 'load', function() {
var all = new Array();
all = document.getElementsByName('buildStepId');
for(var i = 0; i &lt; all.length; i++) {
ms_initDetailLink('<j:out value="${rootURL}" />', all.item(i));
ms_showParams(all.item(i), all.item(i).value);
ms_getArgs(all.item(i), all.item(i).value);
}
});
</script>
</st:once>
</j:jelly>
@@ -0,0 +1,4 @@
<div>
This step allows to reference and execute a centrally managed powershell script within your build.
New files can be added in the <a href="/configfiles">global configuration</a>.
</div>

0 comments on commit cebf39c

Please sign in to comment.