diff --git a/core/src/main/java/hudson/cli/UpdateNodeCommand.java b/core/src/main/java/hudson/cli/UpdateNodeCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..5af2b54eb8c9aa9a5b9a70a1cd982c6a74272f77 --- /dev/null +++ b/core/src/main/java/hudson/cli/UpdateNodeCommand.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * + * Copyright 2013 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package hudson.cli; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import hudson.Extension; +import hudson.model.Node; + +import org.kohsuke.args4j.Argument; + +/** + * @author ogondza + * @since XXX + */ +@Extension +public class UpdateNodeCommand extends CLICommand { + + @Argument(metaVar="NODE", usage="Name of the node", required=true) + public Node node; + + @Override + public String getShortDescription() { + + return Messages.UpdateNodeCommand_ShortDescription(); + } + + @Override + protected int run() throws IOException, ServletException { + + node.toComputer().updateByXml(stdin); + + return 0; + } +} diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 07787414357476a4f383486a7f0cd14e62d9e409..493098782ff0240faa875e21c07a3021fa6d5777 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -77,6 +77,7 @@ import javax.servlet.ServletException; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; @@ -1194,9 +1195,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } if (req.getMethod().equals("POST")) { // submission - Node result = (Node)Jenkins.XSTREAM2.fromXML(req.getReader()); - - replaceBy(result); + updateByXml(req.getInputStream()); return; } @@ -1223,6 +1222,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } } + /** + * Updates Job by its XML definition. + * + * @since XXX + */ + public void updateByXml(final InputStream source) throws IOException, ServletException { + checkPermission(Jenkins.ADMINISTER); + Node result = (Node)Jenkins.XSTREAM2.fromXML(source); + replaceBy(result); + } + /** * Really deletes the slave. */ diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties index 4b71adcca7b9a8e5ffebe7222064bfe320cc18b0..5346fd8040c26e9d885a178481e036a1ee64aa92 100644 --- a/core/src/main/resources/hudson/cli/Messages.properties +++ b/core/src/main/resources/hudson/cli/Messages.properties @@ -52,4 +52,6 @@ WhoAmICommand.ShortDescription=\ Reports your credential and permissions UpdateJobCommand.ShortDescription=\ Updates the job definition XML from stdin. The opposite of the get-job command +UpdateNodeCommand.ShortDescription=\ + Updates the node definition XML from stdin. The opposite of the get-node command BuildCommand.CLICause.ShortDescription=Started by command line by {0} diff --git a/test/src/test/java/hudson/cli/UpdateNodeCommandTest.java b/test/src/test/java/hudson/cli/UpdateNodeCommandTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7423ba8853177a0248e7cd70284959a07e7b49bf --- /dev/null +++ b/test/src/test/java/hudson/cli/UpdateNodeCommandTest.java @@ -0,0 +1,129 @@ +/* + * The MIT License + * + * Copyright 2013 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package hudson.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.text.IsEmptyString.isEmptyString; +import hudson.model.Node; +import hudson.model.User; +import hudson.security.Permission; +import hudson.security.GlobalMatrixAuthorizationStrategy; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Locale; + +import jenkins.model.Jenkins; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class UpdateNodeCommandTest { + + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + private final ByteArrayOutputStream err = new ByteArrayOutputStream(); + private InputStream in; + + private UpdateNodeCommand command; + + @Rule public final JenkinsRule j = new JenkinsRule(); + + @Before public void setUp() { + + command = new UpdateNodeCommand(); + } + + @Test public void updateNodeShouldFailWithoutAdministerPermision() throws Exception { + + forUser("user"); + + j.createSlave("MySlave", null, null); + + final int result = execute("MySlave"); + + assertThat(err.toString(), containsString("user is missing the Administer permission")); + assertThat("No output expected", out.toString(), isEmptyString()); + assertThat("Command is expected to fail", result, equalTo(-1)); + } + + @Test public void updateNodeShouldModifyNodeConfiguration() throws Exception { + + forUser("administrator"); + + j.createSlave("MySlave", null, null); + + in = getClass().getResourceAsStream("node.xml"); + final int result = execute("MySlave"); + + assertThat("No error output expected", err.toString(), isEmptyString()); + assertThat("Command is expected to succeed", result, equalTo(0)); + + assertThat("A slave with old name should not exist", j.jenkins.getNode("MySlave"), nullValue()); + + final Node updatedSlave = j.jenkins.getNode("SlaveFromXML"); + assertThat(updatedSlave.getNodeName(), equalTo("SlaveFromXML")); + assertThat(updatedSlave.getNumExecutors(), equalTo(42)); + } + + @Test public void updateNodeShouldFailIfNodeDoesNotExist() throws Exception { + + forUser("administrator"); + + final int result = execute("MySlave"); + + assertThat(err.toString(), containsString("No such node 'MySlave'")); + assertThat("No output expected", out.toString(), isEmptyString()); + assertThat("Command is expected to fail", result, equalTo(-1)); + } + + private void forUser(final String user) { + + JenkinsRule.DummySecurityRealm realm = j.createDummySecurityRealm(); + realm.addGroups("user", "group"); + realm.addGroups("administrator", "administrator"); + j.jenkins.setSecurityRealm(realm); + + GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); + auth.add(Permission.READ, "group"); + auth.add(Jenkins.ADMINISTER, "administrator"); + j.jenkins.setAuthorizationStrategy(auth); + + command.setTransportAuth(User.get(user).impersonate()); + } + + private int execute(final String... args) { + + return command.main( + Arrays.asList(args), Locale.ENGLISH, in, new PrintStream(out), new PrintStream(err) + ); + } +} diff --git a/test/src/test/resources/hudson/cli/node.xml b/test/src/test/resources/hudson/cli/node.xml new file mode 100644 index 0000000000000000000000000000000000000000..65685361ebff25ea503b04fdd3e54ba8a59e7192 --- /dev/null +++ b/test/src/test/resources/hudson/cli/node.xml @@ -0,0 +1,19 @@ + + + SlaveFromXML + XML slave description + /user/hudson/wokspace + 42 + NORMAL + + + + + + + "/opt/java6/jre/bin/java" -jar "slave.jar" + + + + SYSTEM +