Skip to content
Draft
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<spotless.check.skip>false</spotless.check.skip>
<ban-junit4-imports.skip>false</ban-junit4-imports.skip>
<!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->
<jenkins.baseline>2.504</jenkins.baseline>
<jenkins.baseline>2.541</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
</properties>

Expand All @@ -38,7 +38,7 @@
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-${jenkins.baseline}.x</artifactId>
<version>5983.v443959746f1f</version>
<version>6509.v993a_c958a_e35</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,603 +200,603 @@
}

for (ScriptInfoCatalog<ScriptInfo> scriptInfoCatalog : getCatalogs()) {
if (catalogName.equals(scriptInfoCatalog.getInfo().name)) {
if (catalogName.equals(scriptInfoCatalog.getInfo().name())) {
final ScriptInfo info = scriptInfoCatalog.getEntryById(id);
final String source = scriptInfoCatalog.getScriptSource(scriptInfoCatalog.getEntryById(id));
final List<Parameter> paramList = new ArrayList<>();
for (String paramName : info.getParameters()) {
paramList.add(new Parameter(paramName, null));
}

final String finalName = saveScriptAndForward(
id, info.getName(), info.getComment(), source, false, false, catalogName, id, paramList);
return new HttpRedirect("editScript?id=" + finalName);
}
}
final ForwardToView view = new ForwardToView(this, "catalog.jelly");
view.with("message", Messages.download_failed(id, catalogName));
view.with("catName", catalogName);
return view;
}

/**
* Saves a script snippet as file to the system.
*
* @param req
* response
* @param rsp
* request
* @param id
* the script id (fileName)
* @param name
* the name of the script
* @param comment
* a comment
* @param script
* script code
* @param nonAdministerUsing
* allow usage in Scriptler build step
* @param onlyBuiltIn
* this script is only allowed to run on the built-in node
* @param originCatalogName
* (optional) the name of the catalog the script is loaded/added from
* @param originId
* (optional) the original id the script had at the catalog
* @return forward to 'index'
*/
@RequirePOST
public HttpResponse doScriptAdd(
StaplerRequest2 req,
StaplerResponse2 rsp,
@QueryParameter("id") String id,
@QueryParameter("name") String name,
@QueryParameter("comment") String comment,
@QueryParameter(SCRIPT) String script,
@QueryParameter("nonAdministerUsing") boolean nonAdministerUsing,
@QueryParameter("onlyBuiltIn") boolean onlyBuiltIn,
String originCatalogName,
String originId)
throws IOException, ServletException {

checkPermission(ScriptlerPermissions.CONFIGURE);

List<Parameter> parameters = UIHelper.extractParameters(req.getSubmittedForm());

saveScriptAndForward(
id, name, comment, script, nonAdministerUsing, onlyBuiltIn, originCatalogName, originId, parameters);
return new HttpRedirect(INDEX);
}

/**
* Save the script details and return the forward to index
*
* @return the final name of the saved script - which is also the id of the script!
*/
private String saveScriptAndForward(
String id,
String name,
String comment,
String script,
boolean nonAdministerUsing,
boolean onlyBuiltIn,
String originCatalogName,
String originId,
@NonNull List<Parameter> parameters)
throws IOException {
script = script == null ? "TODO" : script;
if (id == null || id.isEmpty()) {
throw new IllegalArgumentException("'id' must not be empty!");
}

final String displayName = name == null ? id : name;
final String finalFileName = fixFileName(originCatalogName, id);

// save (overwrite) the file/script
Path scriptDirectory = getScriptDirectory2();
Path newScriptFile = scriptDirectory.resolve(finalFileName);

if (!Util.isDescendant(scriptDirectory.toFile(), newScriptFile.toFile())) {
LOGGER.log(
Level.WARNING,
"Folder traversal detected, file path received: {0}, after fixing: {1}",
new Object[] {id, finalFileName});
throw new IOException(INVALID_PATH + id);
}

ScriptHelper.writeScriptToFile(newScriptFile, script);

commitFileToGitRepo(finalFileName);

ScriptHelper.putScriptInApprovalQueueIfRequired(script);

final Script newScript;
if (originId != null && !originId.isEmpty()) {
newScript = new Script(
finalFileName,
displayName,
comment,
true,
originCatalogName,
originId,
new SimpleDateFormat("dd MMM yyyy HH:mm:ss").format(new Date()),
parameters);
} else {
// save (overwrite) the meta information
newScript = new Script(finalFileName, displayName, comment, nonAdministerUsing, parameters, onlyBuiltIn);
}
ScriptlerConfiguration cfg = getConfiguration();
cfg.addOrReplace(newScript);
cfg.save();
return finalFileName;
}

/**
* adds/commits the given file to the local git repo - file must be written to scripts directory!
*/
private void commitFileToGitRepo(final String finalFileName) {
getGitRepo().addSingleFileToRepo(finalFileName);
}

@Restricted(NoExternalUse.class)
public GitScriptlerRepository getGitRepo() {
return ExtensionList.lookupSingleton(GitScriptlerRepository.class);
}

/**
* Triggers a hard reset on the git repo
* @return redirects to the repo entry page at <code>/scriptler.git</code>
*/
@RequirePOST
public HttpResponse doHardResetGit() throws IOException {
checkPermission(ScriptlerPermissions.CONFIGURE);
getGitRepo().hardReset();
return new HttpRedirect("../scriptler.git");
}

/**
* Removes a script from the config and filesystem.
*
* @param res
* response
* @param rsp
* request
* @param id
* the id of the file to be removed
* @return forward to 'index'
*/
@RequirePOST
public HttpResponse doRemoveScript(StaplerRequest2 res, StaplerResponse2 rsp, @QueryParameter("id") String id)
throws IOException {
checkPermission(ScriptlerPermissions.CONFIGURE);

// remove the file
Path scriptDirectory = getScriptDirectory2();
Path oldScript = scriptDirectory.resolve(id);
if (!Util.isDescendant(scriptDirectory.toFile(), oldScript.toFile())) {
LOGGER.log(
Level.WARNING,
"Folder traversal detected, file path received: {0}, after fixing: {1}",
new Object[] {id, oldScript});
throw new Failure(INVALID_PATH + id);
}
try {
Files.delete(oldScript);
} catch (IOException e) {
Failure failure = new Failure("not able to delete " + oldScript);
failure.initCause(e);
throw failure;
}

try {
getGitRepo().rmSingleFileToRepo(id);
} catch (IllegalStateException e) {
throw new IOException("failed to update git repo", e);
}

// remove the meta information
ScriptlerConfiguration cfg = getConfiguration();
cfg.removeScript(id);
cfg.save();

return new HttpRedirect(INDEX);
}

/**
* Uploads a script and stores it with the given filename to the configuration. It will be stored on the filessytem.
*
* @param req
* request
* @return forward to index page.
*/
@RequirePOST
public HttpResponse doUploadScript(StaplerRequest2 req) throws IOException, ServletException {
checkPermission(ScriptlerPermissions.CONFIGURE);
try {

FileItem<?> fileItem = req.getFileItem2("file");
boolean nonAdministerUsing = req.getSubmittedForm().getBoolean("nonAdministerUsing");
String fileName = Util.getFileName(fileItem.getName());
if (fileName.isEmpty()) {
return new HttpRedirect(".");
}
saveScript(fileItem, nonAdministerUsing, fileName);

return new HttpRedirect(INDEX);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}

/**
* Protected only for testing
*/
/*private*/ void saveScript(FileItem<?> fileItem, boolean nonAdministerUsing, String fileName) throws IOException {
// upload can only be to/from local catalog
String fixedFileName = fixFileName(null, fileName);

Path fixedFile;
try {
fixedFile = Paths.get(fixedFileName);
} catch (InvalidPathException e) {
throw new IOException(INVALID_PATH + fileName, e);
}

if (fixedFile.isAbsolute()) {
LOGGER.log(
Level.WARNING,
"Folder traversal detected, file path received: {0}, after fixing: {1}. Seems to be an attempt to use absolute path instead of relative one",
new Object[] {fileName, fixedFileName});
throw new IOException(INVALID_PATH + fileName);
}

Path rootDir = getScriptDirectory2();
final Path f = rootDir.resolve(fixedFileName);

if (!Util.isDescendant(rootDir.toFile(), f.toFile())) {
LOGGER.log(
Level.WARNING,
"Folder traversal detected, file path received: {0}, after fixing: {1}. Seems to be an attempt to use folder escape.",
new Object[] {fileName, fixedFileName});
throw new IOException(INVALID_PATH + fileName);
}

fileItem.write(f);

commitFileToGitRepo(fixedFileName);

Script script = ScriptHelper.getScript(fixedFileName, false);
if (script == null) {
script = new Script(fixedFileName, fixedFileName, true, nonAdministerUsing, false);
}

String scriptSource = ScriptHelper.readScriptFromFile(f);
ScriptHelper.putScriptInApprovalQueueIfRequired(scriptSource);

ScriptlerConfiguration config = getConfiguration();
config.addOrReplace(script);
}

/**
* Display the screen to trigger a script. The source of the script get loaded from the filesystem and placed in the request to display it on the page before execution.
*
* @param req
* request
* @param rsp
* response
* @param id
* the id of the script to be executed
*/
public void doRunScript(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter("id") String id)
throws IOException, ServletException {
checkPermission(ScriptlerPermissions.RUN_SCRIPTS);

Script script = ScriptHelper.getScript(id, true);
if (script == null) {
// TODO check if we cannot do better here
throw new IOException(Messages.scriptNotFound(id));
}
if (script.getScriptText() == null) {
req.setAttribute("scriptNotFound", true);
} else {
boolean canByPassScriptApproval = Jenkins.get().hasPermission(ScriptlerPermissions.BYPASS_APPROVAL);

// we do not want user with approval right to auto-approve script when landing on that page
if (!ScriptHelper.isApproved(script.getScriptText(), false)) {
req.setAttribute(NOT_APPROVED_YET, true);
}

req.setAttribute(CAN_BYPASS_APPROVAL, canByPassScriptApproval);
}

req.setAttribute(SCRIPT, script);
// set default selection
req.setAttribute("currentNode", NodeNames.BUILT_IN);
req.getView(this, "runScript.jelly").forward(req, rsp);
}

/**
* Trigger/run/execute the script on an agent and show the result/output. The request then gets forward to <code>runScript.jelly</code> (This is usually also where the request came from). The
* script passed to this method gets restored in the request again (and not loaded from the system). This way one is able to modify the script before execution and reuse the modified version for
* further executions.
*
* @param req
* request
* @param rsp
* response
* @param id
* the id of the script
* @param scriptSrc
* the script code (groovy)
* @param node
* the node to execute the code on.
*/
@RequirePOST
public void doTriggerScript(
StaplerRequest2 req,
StaplerResponse2 rsp,
@QueryParameter("id") String id,
@QueryParameter(SCRIPT) String scriptSrc,
@QueryParameter("node") String node)
throws IOException, ServletException {
checkPermission(ScriptlerPermissions.RUN_SCRIPTS);

final List<Parameter> parameters = UIHelper.extractParameters(req.getSubmittedForm());

boolean canByPassScriptApproval = Jenkins.get().hasPermission(ScriptlerPermissions.BYPASS_APPROVAL);

// set the script info back to the request, to display it together with the output.
Script originalScript = ScriptHelper.getScript(id, true);
if (originalScript == null) {
rsp.sendError(404, "No script found for id=" + id);
return;
}

String originalScriptSourceCode = originalScript.getScriptText();

Script tempScript = originalScript.copy();
if (originalScriptSourceCode != null && originalScriptSourceCode.equals(scriptSrc)) {
// not copied by default
tempScript.setScriptText(originalScriptSourceCode);
} else {
tempScript.setScriptText(scriptSrc);
ScriptHelper.putScriptInApprovalQueueIfRequired(scriptSrc);
}

String output;
if (ScriptHelper.isApproved(scriptSrc)) {
List<String> computers = resolveComputerNames(node);
output = ScriptHelper.runScript(computers, scriptSrc, parameters);
} else {
LOGGER.log(
Level.WARNING,
"Script {0} was not approved yet, consider asking your administrator to approve it.",
id);
output = null;
req.setAttribute(NOT_APPROVED_YET, true);
}

tempScript.setParameters(parameters); // show the same parameters to the user
req.setAttribute(SCRIPT, tempScript);
req.setAttribute("currentNode", node);
req.setAttribute("output", output);
req.setAttribute(CAN_BYPASS_APPROVAL, canByPassScriptApproval);
req.getView(this, "runScript.jelly").forward(req, rsp);
}

/**
* Trigger/run/execute the script on an agent and directly forward the result/output to the response.
*
* @param req
* request
* @param rsp
* response
* @param script
* the script code (groovy)
* @param node
* the node, to execute the code on, defaults to {@value NodeNames#BUILT_IN}
* @param contentType
* the contentType to use in the response, defaults to text/plain
*/
@RequirePOST
public void doRun(
StaplerRequest2 req,
StaplerResponse2 rsp,
@QueryParameter(fixEmpty = true) String script,
@QueryParameter(fixEmpty = true) String node,
@QueryParameter(fixEmpty = true) String contentType)
throws IOException, ServletException {

checkPermission(ScriptlerPermissions.RUN_SCRIPTS);

String id = req.getRestOfPath();
if (id.startsWith("/")) {
id = id.substring(1);
}

if (id.isEmpty()) {
throw new IOException("Please specify a script id. Use /scriptler/run/<yourScriptId>");
}

Script tempScript = ScriptHelper.getScript(id, true);

if (tempScript == null) {
throw new IOException("Unknown script: " + id + ". Use /scriptler/run/<yourScriptId>");
}

if (script == null) {
// use original script
script = tempScript.getScriptText();
}

if (!ScriptHelper.isApproved(script)) {
LOGGER.log(
Level.WARNING,
"Script {0} was not approved yet, consider asking your administrator to approve it.",
id);
rsp.sendError(
HttpServletResponse.SC_FORBIDDEN,
"Script not approved yet, consider asking your administrator to approve it.");
return;
}

Collection<Parameter> paramArray = prepareParameters(req, tempScript);

rsp.setContentType(contentType == null ? "text/plain" : contentType);

final List<String> computers = resolveComputerNames(node == null ? NodeNames.BUILT_IN : node);
if (computers.size() > 1) {
rsp.getOutputStream().print(ScriptHelper.runScript(computers, script, paramArray));
} else {
rsp.getOutputStream().print(ScriptHelper.runScript(computers.get(0), script, paramArray));
}
}

@NonNull
private Collection<Parameter> prepareParameters(StaplerRequest2 req, Script tempScript) {
// retain default parameter values
Map<String, Parameter> params = new HashMap<>();
for (Parameter param : tempScript.getParameters()) {
params.put(param.getName(), param);
}

// overwrite parameters that are passed as parameters
for (Map.Entry<String, String[]> param : req.getParameterMap().entrySet()) {
if (params.containsKey(param.getKey())) {
params.put(param.getKey(), new Parameter(param.getKey(), param.getValue()[0]));
}
}
return params.values();
}

private List<String> resolveComputerNames(String rawNameAlias) {
final String nameAlias = NodeNames.normalizeNodeName(rawNameAlias);
final List<String> computers;
if (nameAlias.equalsIgnoreCase(NodeNames.ALL) || nameAlias.equalsIgnoreCase(NodeNames.ALL_AGENTS)) {
computers = getComputerNames();
if (nameAlias.equalsIgnoreCase(NodeNames.ALL)) {
computers.add(NodeNames.BUILT_IN);
}
} else {
computers = List.of(nameAlias);
}
return computers;
}

/**
* Loads the script by its name and forwards the request to "show.jelly".
*
* @param req
* request
* @param rsp
* response
* @param id
* the id of the script to be loaded in to the show view.
*/
public void doShowScript(
StaplerRequest2 req, StaplerResponse2 rsp, @AncestorInPath Item item, @QueryParameter("id") String id)
throws IOException, ServletException {
// action directly accessible to any people configuring job, so use a more lenient permission check
Jenkins jenkins = Jenkins.get();
if (!jenkins.hasAnyPermission(ScriptlerPermissions.RUN_SCRIPTS, ScriptlerPermissions.CONFIGURE)) {
AccessControlled parent = item == null ? jenkins : item;
parent.checkPermission(Item.CONFIGURE);
}
Script script = ScriptHelper.getScript(id, true);
req.setAttribute(SCRIPT, script);
req.getView(this, "show.jelly").forward(req, rsp);
}

/**
* Loads the script by its name and forwards the request to "edit.jelly".
*
* @param req
* request
* @param rsp
* response
* @param id
* the id of the script to be loaded in to the edit view.
*/
public void doEditScript(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter("id") String id)
throws IOException, ServletException {
checkPermission(ScriptlerPermissions.CONFIGURE);

Script script = ScriptHelper.getScript(id, true);
if (script == null || script.getScriptText() == null) {
req.setAttribute("scriptNotFound", true);
} else {
boolean canByPassScriptApproval = Jenkins.get().hasPermission(ScriptlerPermissions.BYPASS_APPROVAL);

// we do not want user with approval right to auto-approve script when landing on that page
if (!ScriptHelper.isApproved(script.getScriptText(), false)) {
req.setAttribute(NOT_APPROVED_YET, true);
}

req.setAttribute(CAN_BYPASS_APPROVAL, canByPassScriptApproval);
}

req.setAttribute(SCRIPT, script);
req.getView(this, "edit.jelly").forward(req, rsp);
}

/**
* @deprecated Use {@link #getComputerAliases(Script)} instead.
*/
@Deprecated(since = "381")
public List<String> getSlaveAlias(Script script) {
return getComputerAliases(script);
}

/**
* Gets the names of all configured computers, regardless whether they are online, including alias of ALL and ALL_AGENTS
*
* @return list with all computer names
*/
public List<String> getComputerAliases(Script script) {
if (script.onlyBuiltIn) {
return List.of(NodeNames.BUILT_IN);
}
final List<String> computerNames = getComputerNames();
// add 'magic' name for the built-in node, so all nodes can be handled the same way
computerNames.addAll(0, List.of(NodeNames.BUILT_IN, NodeNames.ALL, NodeNames.ALL_AGENTS));
return computerNames;
}

private List<String> getComputerNames() {
return Arrays.stream(Jenkins.get().getComputers())
// remove the built-in's computer as it has an empty name
.filter(Predicate.not(Jenkins.MasterComputer.class::isInstance))
.map(Computer::getName)
.collect(Collectors.toCollection(ArrayList::new));
}

/**
* Gets the remote catalogs containing the available scripts for download.
*
* @return the catalog
*/
public List<ScriptInfoCatalog<ScriptInfo>> getCatalogs() {
return ScriptInfoCatalog.all();
}

public ScriptInfoCatalog<ScriptInfo> getCatalogByName(String catalogName) {
if (catalogName != null && !catalogName.isBlank()) {
for (ScriptInfoCatalog<ScriptInfo> sic : getCatalogs()) {
final CatalogInfo info = sic.getInfo();
if (catalogName.equals(info.name)) {
if (catalogName.equals(info.name())) {
return sic;
}
}
}
return null;
}

public CatalogInfo getCatalogInfoByName(String catalogName) {
if (catalogName != null && !catalogName.isBlank()) {
for (ScriptInfoCatalog<ScriptInfo> sic : getCatalogs()) {
final CatalogInfo info = sic.getInfo();
if (catalogName.equals(info.name)) {
if (catalogName.equals(info.name())) {

Check warning on line 799 in src/main/java/org/jenkinsci/plugins/scriptler/ScriptlerManagement.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 203-799 are not covered by tests
return info;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,20 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import java.text.MessageFormat;

public class CatalogInfo {

@NonNull
public final String name;

@NonNull
public final String catalogLocation;

@NonNull
public final String scriptDownloadUrl;

@NonNull
public final String scriptDetailUrl;
public record CatalogInfo(
@NonNull String name,
@NonNull String catalogLocation,
@NonNull String scriptDetailUrl,
@NonNull String scriptDownloadUrl) {

/**
* Holds the informations used to connect to a catalog location
*
* @param name
* symbolic name of the catalog, must be unique.
* @param catLocation
* where to download the catalog file from (including file name, e.g. <code>http://myserver.com/scriptler/my-scriptler-catalog.xml</code> )
* @param scriptDownloadUrl
* the url to download a script by its name. Use <code>{0}</code> to mark the position for the file name in the url (e.g. <code>http://myserver.com/scriptler/{0}</code>)
* @param name symbolic name of the catalog, must be unique.
* @param catalogLocation where to download the catalog file from (including file name, e.g. <code>http://myserver.com/scriptler/my-scriptler-catalog.xml</code> )
* @param scriptDownloadUrl the url to download a script by its name. Use <code>{0}</code> to mark the position for the file name in the url (e.g. <code>http://myserver.com/scriptler/{0}</code>)
*/
public CatalogInfo(
@NonNull String name,
@NonNull String catLocation,
@NonNull String scriptDetailUrl,
@NonNull String scriptDownloadUrl) {
this.name = name;
this.catalogLocation = catLocation;
this.scriptDownloadUrl = scriptDownloadUrl;
this.scriptDetailUrl = scriptDetailUrl;
}
public CatalogInfo {}

public String getReplacedDownloadUrl(String scriptName, String id) {
if (scriptDownloadUrl.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,22 @@
import java.util.Objects;
import net.sf.json.JSONObject;
import org.htmlunit.HttpMethod;
import org.htmlunit.WebClient;
import org.htmlunit.WebRequest;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.util.NameValuePair;
import org.htmlunit.xml.XmlPage;
import org.jenkinsci.plugins.scriptler.ScriptlerManagementHelper;
import org.jenkinsci.plugins.scriptler.config.Parameter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
import org.jvnet.hudson.test.junit.jupiter.JenkinsSessionExtension;

/**
* Warning: a user without RUN_SCRIPT can currently only clone an existing builder INSIDE a project.
* You can search CLONE_NOTE inside this test to see the cases
*/
@WithJenkins
class ScriptlerBuilderWithRestartTest {
private static final String SCRIPT_CONTENTS = "print 'Hello World!'";
private static final String SCRIPT_USABLE_1 = "script_usable_1.groovy";
Expand All @@ -59,141 +60,148 @@ class ScriptlerBuilderWithRestartTest {

private static final String SCRIPT_NOT_USABLE = "not_usable.groovy";

@RegisterExtension
public JenkinsSessionExtension jenkins = new JenkinsSessionExtension();

@Test
void configRoundTrip(JenkinsRule r) throws Throwable {
r.jenkins.setCrumbIssuer(null);

ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_1, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_2, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_3, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_4, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_NOT_USABLE, SCRIPT_CONTENTS, false);

FreeStyleProject project = r.createFreeStyleProject("test");

try (JenkinsRule.WebClient wc = r.createWebClient()) {

WebRequest request =
new WebRequest(new URL(r.getURL() + project.getShortUrl() + "configSubmit"), HttpMethod.POST);

final String projectName = project.getName();
request.setRequestParameters(List.of(new NameValuePair(
"json",
JSONObject.fromObject(Map.of(
"name",
projectName,
"builder",
Map.of(
"kind",
ScriptlerBuilder.class.getName(),
"builderId",
"",
"scriptlerScriptId",
SCRIPT_USABLE_1,
"propagateParams",
true,
"defineParams",
Map.of(
"parameters",
List.of(
Map.of("name", "param1", "value", "value1"),
Map.of("name", "param2", "value", "value2"))))))
.toString())));
HtmlPage page = wc.getPage(request);
r.assertGoodStatus(page);

ScriptlerBuilder scriptlerBuilder = project.getBuildersList().get(ScriptlerBuilder.class);
void configRoundTrip() throws Throwable {
jenkins.then(r -> {
r.jenkins.setCrumbIssuer(null);

ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_1, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_2, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_3, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_4, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_NOT_USABLE, SCRIPT_CONTENTS, false);

FreeStyleProject project = r.createFreeStyleProject("test");

try (WebClient wc = r.createWebClient()) {

WebRequest request =
new WebRequest(new URL(r.getURL() + project.getShortUrl() + "configSubmit"), HttpMethod.POST);

final String projectName = project.getName();
request.setRequestParameters(List.of(new NameValuePair(
"json",
JSONObject.fromObject(Map.of(
"name",
projectName,
"builder",
Map.of(
"kind",
ScriptlerBuilder.class.getName(),
"builderId",
"",
"scriptlerScriptId",
SCRIPT_USABLE_1,
"propagateParams",
true,
"defineParams",
Map.of(
"parameters",
List.of(
Map.of("name", "param1", "value", "value1"),
Map.of("name", "param2", "value", "value2"))))))
.toString())));
HtmlPage page = wc.getPage(request);
r.assertGoodStatus(page);

ScriptlerBuilder scriptlerBuilder = project.getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertTrue(scriptlerBuilder.isPropagateParams());
assertIterableEquals(
List.of(new Parameter("param1", "value1"), new Parameter("param2", "value2")),
scriptlerBuilder.getParametersList());
}
});

jenkins.then(r -> {
FreeStyleProject p = r.jenkins.getItemByFullName("test", FreeStyleProject.class);

ScriptlerBuilder scriptlerBuilder =
Objects.requireNonNull(p).getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertTrue(scriptlerBuilder.isPropagateParams());
assertIterableEquals(
List.of(new Parameter("param1", "value1"), new Parameter("param2", "value2")),
scriptlerBuilder.getParametersList());
}

r.restart();

FreeStyleProject p = r.jenkins.getItemByFullName("test", FreeStyleProject.class);

ScriptlerBuilder scriptlerBuilder =
Objects.requireNonNull(p).getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertTrue(scriptlerBuilder.isPropagateParams());
assertIterableEquals(
List.of(new Parameter("param1", "value1"), new Parameter("param2", "value2")),
scriptlerBuilder.getParametersList());
});
}

@Test
void configRoundTripConfigXml(JenkinsRule r) throws Throwable {
r.jenkins.setCrumbIssuer(null);

ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_1, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_2, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_3, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_4, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_NOT_USABLE, SCRIPT_CONTENTS, false);

FreeStyleProject project = r.createFreeStyleProject("test");

try (JenkinsRule.WebClient wc = r.createWebClient()) {

XmlPage xmlPage = wc.goToXml(project.getShortUrl() + "config.xml");
r.assertGoodStatus(xmlPage);
String xml = xmlPage.getWebResponse().getContentAsString();

String modifiedXml = xml.replace("<builders/>", String.format("""
<builders>
<org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder>
<builderId></builderId>
<scriptId>%1s</scriptId>
<propagateParams>true</propagateParams>
<parameters>
<org.jenkinsci.plugins.scriptler.config.Parameter>
<name>param1</name>
<value>value1</value>
</org.jenkinsci.plugins.scriptler.config.Parameter>
<org.jenkinsci.plugins.scriptler.config.Parameter>
<name>param2</name>
<value>value2</value>
</org.jenkinsci.plugins.scriptler.config.Parameter>
</parameters>
</org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder>
</builders>""", SCRIPT_USABLE_1));

WebRequest request = new WebRequest(new URL(project.getAbsoluteUrl() + "config.xml"), HttpMethod.POST);
request.setRequestBody(modifiedXml);
request.setEncodingType(null);
HtmlPage page = wc.getPage(request);
r.assertGoodStatus(page);

project = r.jenkins.getItemByFullName(project.getFullName(), FreeStyleProject.class);
void configRoundTripConfigXml() throws Throwable {
jenkins.then(r -> {
r.jenkins.setCrumbIssuer(null);

ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_1, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_2, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_3, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_USABLE_4, SCRIPT_CONTENTS, true);
ScriptlerManagementHelper.saveScript(SCRIPT_NOT_USABLE, SCRIPT_CONTENTS, false);

FreeStyleProject project = r.createFreeStyleProject("test");

try (JenkinsRule.WebClient wc = r.createWebClient()) {

XmlPage xmlPage = wc.goToXml(project.getShortUrl() + "config.xml");
r.assertGoodStatus(xmlPage);
String xml = xmlPage.getWebResponse().getContentAsString();

String modifiedXml = xml.replace("<builders/>", String.format("""
<builders>
<org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder>
<builderId></builderId>
<scriptId>%1s</scriptId>
<propagateParams>true</propagateParams>
<parameters>
<org.jenkinsci.plugins.scriptler.config.Parameter>
<name>param1</name>
<value>value1</value>
</org.jenkinsci.plugins.scriptler.config.Parameter>
<org.jenkinsci.plugins.scriptler.config.Parameter>
<name>param2</name>
<value>value2</value>
</org.jenkinsci.plugins.scriptler.config.Parameter>
</parameters>
</org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder>
</builders>""", SCRIPT_USABLE_1));

WebRequest request = new WebRequest(new URL(project.getAbsoluteUrl() + "config.xml"), HttpMethod.POST);
request.setRequestBody(modifiedXml);
request.setEncodingType(null);
HtmlPage page = wc.getPage(request);
r.assertGoodStatus(page);

project = r.jenkins.getItemByFullName(project.getFullName(), FreeStyleProject.class);
ScriptlerBuilder scriptlerBuilder =
Objects.requireNonNull(project).getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertEquals("", scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertTrue(scriptlerBuilder.isPropagateParams());
assertIterableEquals(
List.of(new Parameter("param1", "value1"), new Parameter("param2", "value2")),
scriptlerBuilder.getParametersList());
}
});

jenkins.then(r -> {
FreeStyleProject p = r.jenkins.getItemByFullName("test", FreeStyleProject.class);

ScriptlerBuilder scriptlerBuilder =
Objects.requireNonNull(project).getBuildersList().get(ScriptlerBuilder.class);
Objects.requireNonNull(p).getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertEquals("", scriptlerBuilder.getBuilderId());
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertTrue(scriptlerBuilder.isPropagateParams());
assertIterableEquals(
List.of(new Parameter("param1", "value1"), new Parameter("param2", "value2")),
scriptlerBuilder.getParametersList());
}

r.restart();

FreeStyleProject p = r.jenkins.getItemByFullName("test", FreeStyleProject.class);

ScriptlerBuilder scriptlerBuilder =
Objects.requireNonNull(p).getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertTrue(scriptlerBuilder.isPropagateParams());
assertIterableEquals(
List.of(new Parameter("param1", "value1"), new Parameter("param2", "value2")),
scriptlerBuilder.getParametersList());
});
}
}
Loading