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

JENKINS-25079 : Add support for Managed PowerShell files #5

Merged
merged 1 commit into from
May 11, 2015
Merged
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
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}

}

}
Original file line number Diff line number Diff line change
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.


Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Loading