Skip to content

Commit 110014c

Browse files
Add rest api to create agent from xml (#11229)
* Create agent from xml via rest api One can create jobs from xml via the rest api or from cli. One can also create agents from xml via the cli, but creating an agent via rest from xml is not possible so far. * add tests * fix whitespace --------- Co-authored-by: Mark Waite <mark.earl.waite@gmail.com>
1 parent bdf22a2 commit 110014c

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

core/src/main/java/hudson/model/ComputerSet.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import hudson.util.FormApply;
4545
import hudson.util.FormValidation;
4646
import jakarta.servlet.ServletException;
47+
import jakarta.servlet.http.HttpServletResponse;
4748
import java.io.File;
4849
import java.io.IOException;
4950
import java.lang.reflect.InvocationTargetException;
@@ -281,6 +282,12 @@ public synchronized void doCreateItem(StaplerRequest2 req, StaplerResponse2 rsp,
281282
final Jenkins app = Jenkins.get();
282283
app.checkPermission(Computer.CREATE);
283284

285+
String requestContentType = req.getContentType();
286+
287+
boolean isXmlSubmission = requestContentType != null
288+
&& (requestContentType.startsWith("application/xml")
289+
|| requestContentType.startsWith("text/xml"));
290+
284291
if (mode != null && mode.equals("copy")) {
285292
name = checkName(name);
286293

@@ -319,6 +326,22 @@ public synchronized void doCreateItem(StaplerRequest2 req, StaplerResponse2 rsp,
319326
// send the browser to the config page
320327
rsp.sendRedirect2(result.getNodeName() + "/configure");
321328
} else {
329+
if (isXmlSubmission) {
330+
final Node newNode = (Node) Jenkins.XSTREAM2.fromXML(req.getInputStream());
331+
name = Util.fixEmptyAndTrim(name);
332+
333+
if (name != null) {
334+
newNode.setNodeName(name);
335+
}
336+
337+
if (app.getNode(newNode.getNodeName()) != null) {
338+
throw new Failure("Node '" + newNode.getNodeName() + "' already exists");
339+
}
340+
341+
app.addNode(newNode);
342+
rsp.setStatus(HttpServletResponse.SC_OK);
343+
return;
344+
}
322345
// proceed to step 2
323346
if (mode == null) {
324347
throw new Failure("No mode given");
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
3+
<h2>Create Node</h2>
4+
<p>
5+
To create a new node, post <code>config.xml</code> to <a href="../createItem">this URL</a>. Optionally include the query
6+
parameter <code>name=<i>NODENAME</i></code> if you want to overwrite the node name set in the xml. You need to send
7+
a <code>Content-Type: application/xml header</code>. You will get a 200 status code if the creation
8+
is successful, or 4xx/5xx code if it fails. <code>config.xml</code> is the format Jenkins uses to store the node in the file
9+
system, so you can see examples of them in the Jenkins home directory, or by retrieving the XML configuration of
10+
existing nodes from <code>/computer/<i>NODENAME</i>/config.xml</code>.
11+
</p>
12+
</j:jelly>

test/src/test/java/hudson/model/ComputerSetTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,24 @@
2828
import static org.hamcrest.Matchers.contains;
2929
import static org.hamcrest.Matchers.containsString;
3030
import static org.hamcrest.Matchers.empty;
31+
import static org.hamcrest.Matchers.instanceOf;
3132
import static org.hamcrest.Matchers.is;
3233
import static org.hamcrest.Matchers.not;
34+
import static org.hamcrest.Matchers.notNullValue;
3335
import static org.junit.jupiter.api.Assertions.assertEquals;
3436

3537
import hudson.cli.CLICommandInvoker;
3638
import hudson.slaves.DumbSlave;
3739
import hudson.slaves.OfflineCause;
40+
import hudson.slaves.RetentionStrategy;
3841
import java.net.HttpURLConnection;
3942
import jenkins.model.Jenkins;
4043
import jenkins.widgets.ExecutorsWidget;
4144
import jenkins.widgets.HasWidgetHelper;
45+
import org.htmlunit.HttpMethod;
4246
import org.htmlunit.Page;
47+
import org.htmlunit.WebRequest;
48+
import org.htmlunit.WebResponse;
4349
import org.htmlunit.html.HtmlForm;
4450
import org.htmlunit.html.HtmlPage;
4551
import org.junit.jupiter.api.BeforeEach;
@@ -186,4 +192,60 @@ void testTerminatedNodeAjaxExecutorsDoesNotShowTrace() throws Exception {
186192

187193
j.assertBuildStatus(Result.FAILURE, j.waitForCompletion(b));
188194
}
195+
196+
@Test
197+
void createItemFromXmlNoName() throws Exception {
198+
createItemTest(null);
199+
}
200+
201+
@Test
202+
void createItemFromXmlWithName() throws Exception {
203+
createItemTest("new-name");
204+
}
205+
206+
void createItemTest(String name) throws Exception {
207+
String USER = "user";
208+
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
209+
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
210+
// Grant computer create
211+
.grant(Jenkins.READ).everywhere().to(USER)
212+
.grant(Computer.CREATE).everywhere().to(USER)
213+
);
214+
215+
String xml = """
216+
<slave>
217+
<name>agent-from-xml</name>
218+
<description></description>
219+
<remoteFS>/home/jenkins</remoteFS>
220+
<numExecutors>2</numExecutors>
221+
<mode>NORMAL</mode>
222+
<retentionStrategy class="hudson.slaves.RetentionStrategy$Always"/>
223+
<launcher class="hudson.slaves.JNLPLauncher"/>
224+
<label>linux</label>
225+
<nodeProperties/>
226+
</slave>
227+
""";
228+
try (JenkinsRule.WebClient wc = j.createWebClient().withThrowExceptionOnFailingStatusCode(false)) {
229+
wc.login(USER);
230+
String agentCreateUrl = "computer/createItem";
231+
if (name != null) {
232+
agentCreateUrl += "?name=" + name;
233+
}
234+
WebRequest req = new WebRequest(wc.createCrumbedUrl(agentCreateUrl), HttpMethod.POST);
235+
req.setAdditionalHeader("Content-Type", "application/xml");
236+
req.setRequestBody(xml);
237+
WebResponse rsp = wc.getPage(req).getWebResponse();
238+
assertThat(rsp.getStatusCode(), is(200));
239+
if (name == null) {
240+
name = "agent-from-xml";
241+
}
242+
Node node = j.jenkins.getNode(name);
243+
assertThat(node, is(notNullValue()));
244+
DumbSlave agent = (DumbSlave) node;
245+
assertThat(agent.remoteFS, is("/home/jenkins"));
246+
assertThat(agent.getNumExecutors(), is(2));
247+
assertThat(agent.getLabelString(), is("linux"));
248+
assertThat(agent.getRetentionStrategy(), is(instanceOf(RetentionStrategy.Always.class)));
249+
}
250+
}
189251
}

0 commit comments

Comments
 (0)