Skip to content

Commit

Permalink
[JENKINS-28041] Extended delete-node CLI command to accept multiple n…
Browse files Browse the repository at this point in the history
…ames to delete
  • Loading branch information
pjanouse committed May 7, 2015
1 parent 4ddc5aa commit 67e4e88
Show file tree
Hide file tree
Showing 21 changed files with 255 additions and 12 deletions.
90 changes: 90 additions & 0 deletions core/src/main/java/hudson/cli/DeleteNodeCommand.java
@@ -0,0 +1,90 @@
/*
* The MIT License
*
* Copyright (c) 2015 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 hudson.Extension;
import hudson.model.Computer;
import hudson.model.Node;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;

import java.util.HashSet;
import java.util.List;

/**
* @author pjanouse
* @since TODO
*/
@Extension
public class DeleteNodeCommand extends CLICommand {

@Argument(usage="Nodes name to delete", required=true, multiValued=true)
private List<String> nodes;

@Override
public String getShortDescription() {

return Messages.DeleteNodeCommand_ShortDescription();
}

@Override
protected int run() throws Exception {

boolean errorOccurred = false;

HashSet<String> hs = new HashSet<String>();
hs.addAll(nodes);

for (String node_s : hs) {

Node node = Jenkins.getInstance().getNode(node_s);

if(node == null) {
stderr.format("No such node '%s'\n", node_s);
errorOccurred = true;
continue;
}

try {
node.checkPermission(Computer.DELETE);
} catch (Exception e) {
stderr.println(e.getMessage());
errorOccurred = true;
continue;
}

try {
Jenkins.getInstance().removeNode(node);
} catch (Exception e) {
stderr.format("Unexpected exception occurred during deletion of node '%s': %s\n",
node.getDisplayName(),
e.getMessage()
);
errorOccurred = true;
continue;
}
}
return errorOccurred ? -1 : 0;
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/model/Computer.java
Expand Up @@ -1292,7 +1292,7 @@ public void updateByXml(final InputStream source) throws IOException, ServletExc
/**
* Really deletes the slave.
*/
@CLIMethod(name="delete-node")
// @CLIMethod(name="delete-node")
@RequirePOST
public HttpResponse doDoDelete() throws IOException {
checkPermission(DELETE);
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/resources/hudson/cli/Messages.properties
Expand Up @@ -78,3 +78,7 @@ BuildCommand.CLICause.CannotBuildConfigNotSaved=\
Cannot build {0} because its configuration has not been saved.
BuildCommand.CLICause.CannotBuildUnknownReasons=\
Cannot build {0} for unknown reasons.

DeleteNodeCommand.ShortDescription=\
Deletes node(s)

1 change: 1 addition & 0 deletions core/src/main/resources/hudson/cli/Messages_da.properties
@@ -0,0 +1 @@
DeleteNodeCommand.ShortDescription=Sletter en node
1 change: 1 addition & 0 deletions core/src/main/resources/hudson/cli/Messages_de.properties
@@ -0,0 +1 @@
DeleteNodeCommand.ShortDescription=Knoten l\u00f6schen.
1 change: 1 addition & 0 deletions core/src/main/resources/hudson/cli/Messages_es.properties
Expand Up @@ -48,3 +48,4 @@ UpdateJobCommand.ShortDescription=Actualiza el fichero XML de la definici
GroovyshCommand.ShortDescription=Ejecuta una shell interactiva de groovy.
SetBuildDescriptionCommand.ShortDescription=Establece la descripción de una ejecución.

DeleteNodeCommand.ShortDescription=Borrar un nodo
1 change: 1 addition & 0 deletions core/src/main/resources/hudson/cli/Messages_it.properties
@@ -0,0 +1 @@
DeleteNodeCommand.ShortDescription=Cancella un nodo
4 changes: 3 additions & 1 deletion core/src/main/resources/hudson/cli/Messages_ja.properties
Expand Up @@ -68,4 +68,6 @@ BuildCommand.CLICause.CannotBuildDisabled=\
BuildCommand.CLICause.CannotBuildConfigNotSaved=\
\u8a2d\u5b9a\u304c\u4fdd\u5b58\u3055\u308c\u3066\u3044\u306a\u3044\u306e\u3067{0}\u3092\u30d3\u30eb\u30c9\u3067\u304d\u307e\u305b\u3093\u3002
BuildCommand.CLICause.CannotBuildUnknownReasons=\
\u30d3\u30eb\u30c9\u3067\u304d\u307e\u305b\u3093(\u539f\u56e0\u4e0d\u660e)\u3002
\u30d3\u30eb\u30c9\u3067\u304d\u307e\u305b\u3093(\u539f\u56e0\u4e0d\u660e)\u3002

DeleteNodeCommand.ShortDescription=\u30ce\u30fc\u30c9\u3092\u524a\u9664\u3057\u307e\u3059\u3002
3 changes: 3 additions & 0 deletions core/src/main/resources/hudson/cli/Messages_pt_BR.properties
Expand Up @@ -110,3 +110,6 @@ SessionIdCommand.ShortDescription=Exibe o ID de sess\u00e3o, que muda toda vez q
InstallPluginCommand.InstallingPluginFromUrl=Instalando um plugin de {0}
# Installs a plugin either from a file, an URL, or from update center.
InstallPluginCommand.ShortDescription=Instala um plugin a partir de um arquivo, uma URL, ou da central de atualiza\u00e7\u00f5es.

# Deletes a node
CLI.delete-node.shortDescription=Remover o n\u00f3
@@ -0,0 +1 @@
DeleteNodeCommand.ShortDescription=Deletes a node
2 changes: 2 additions & 0 deletions core/src/main/resources/hudson/cli/Messages_zh_TW.properties
Expand Up @@ -72,3 +72,5 @@ WhoAmICommand.ShortDescription=\
UpdateJobCommand.ShortDescription=\
\u7531 stdin \u66f4\u65b0\u4f5c\u696d\u5b9a\u7fa9 XML\u3002get-job \u6307\u4ee4\u7684\u76f8\u53cd
BuildCommand.CLICause.ShortDescription=\u7531 {0} \u7684\u547d\u4ee4\u5217\u4ecb\u9762\u555f\u52d5

DeleteNodeCommand.ShortDescription=\u522a\u9664\u6307\u5b9a\u7bc0\u9ede\u3002
1 change: 0 additions & 1 deletion core/src/main/resources/hudson/model/Messages.properties
Expand Up @@ -97,7 +97,6 @@ CLI.delete-job.shortDescription=Deletes a job.
CLI.reload-job.shortDescription=Reloads this job from disk.
CLI.disable-job.shortDescription=Disables a job.
CLI.enable-job.shortDescription=Enables a job.
CLI.delete-node.shortDescription=Deletes a node.
CLI.disconnect-node.shortDescription=Disconnects from a node.
CLI.connect-node.shortDescription=Reconnect to a node.
CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
Expand Down
Expand Up @@ -144,7 +144,6 @@ UpdateCenter.PluginCategory.scm=Kildekodestyring (SCM)
View.ConfigurePermission.Description=Denne rettighed tillader brugere at \u00e6ndre konfigurationen af visninger.
AbstractProject.NewBuildForWorkspace=Skedulerer et nyt byg for at f\u00e5 et arbejdsomr\u00e5de
Node.LabelMissing={0} har ikke etiket {1}
CLI.delete-node.shortDescription=Sletter en node
Queue.BlockedBy=Blokeret af {0}
Node.BecauseNodeIsReserved={0} er reserveret til jobs bundet(tied) til den
Job.minutes=min
Expand Down
Expand Up @@ -88,7 +88,6 @@ CLI.disable-job.shortDescription=Job deaktivieren.
CLI.enable-job.shortDescription=Job aktivieren.
CLI.connect-node.shortDescription=Erneut mit Knoten verbinden.
CLI.disconnect-node.shortDescription=Knoten trennen.
CLI.delete-node.shortDescription=Knoten l\u00f6schen.
CLI.offline-node.shortDescription=Knoten wird bis zum n\u00e4chsten "online-node"-Kommando f\u00fcr keine neuen Builds verwendet.
CLI.online-node.shortDescription=Knoten wird wieder f\u00fcr neue Builds verwendet. Hebt ein vorausgegangenes "offline-node"-Kommando auf.
CLI.safe-restart.shortDescription=Startet Jenkins neu.
Expand Down
Expand Up @@ -62,7 +62,6 @@ CLI.clear-queue.shortDescription=Limpiar la cola de trabajos
CLI.delete-job.shortDescription=Borrar una tarea
CLI.disable-job.shortDescription=Desactivar una tarea
CLI.enable-job.shortDescription=Activar una tarea
CLI.delete-node.shortDescription=Borrar un nodo
CLI.disconnect-node.shortDescription=Desconectarse de un nodo
CLI.connect-node.shortDescription=Reconectarse con un nodo
CLI.online-node.shortDescription=Continuar usando un nodo y candelar el comando "offline-node" mas reciente.
Expand Down
Expand Up @@ -80,7 +80,6 @@ CLI.clear-queue.shortDescription=Pulisce la coda di lavoro
CLI.delete-job.shortDescription=Cancella un job
CLI.disable-job.shortDescription=Disabilita un job
CLI.enable-job.shortDescription=Abilita un job
CLI.delete-node.shortDescription=Cancella un nodo
CLI.disconnect-node.shortDescription=Disconnects from a node
CLI.connect-node.shortDescription=Riconnettersi ad un nodo
CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
Expand Down
Expand Up @@ -323,7 +323,6 @@ CLI.clear-queue.shortDescription=\u30d3\u30eb\u30c9\u30ad\u30e5\u30fc\u3092\u30a
CLI.delete-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u524a\u9664\u3057\u307e\u3059\u3002
CLI.disable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u7121\u52b9\u5316\u3057\u307e\u3059\u3002
CLI.enable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u6709\u52b9\u5316\u3057\u307e\u3059\u3002
CLI.delete-node.shortDescription=\u30ce\u30fc\u30c9\u3092\u524a\u9664\u3057\u307e\u3059\u3002
CLI.disconnect-node.shortDescription=\u30ce\u30fc\u30c9\u3068\u306e\u63a5\u7d9a\u3092\u5207\u65ad\u3057\u307e\u3059\u3002
CLI.connect-node.shortDescription=\u30ce\u30fc\u30c9\u3068\u518d\u63a5\u7d9a\u3057\u307e\u3059\u3002
CLI.online-node.shortDescription=\u76f4\u524d\u306b\u5b9f\u884c\u3057\u305f"online-node"\u30b3\u30de\u30f3\u30c9\u3092\u53d6\u308a\u6d88\u3057\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u518d\u958b\u3057\u307e\u3059\u3002
Expand Down
Expand Up @@ -278,8 +278,6 @@ View.ConfigurePermission.Description=Permite aos usu\u00e1rios configurar as vie
AbstractProject.NewBuildForWorkspace=Agendando uma novo build para obter um novo workspace
# {0} doesn''t have label {1}
Node.LabelMissing=R\u00f3tulo n\u00e3o encontrado
# Deletes a node
CLI.delete-node.shortDescription=Remover o n\u00f3
# {0} is reserved for jobs tied to it
Node.BecauseNodeIsReserved={0} foi reservado para trabalhos vinculados
# Started by remote host {0} with note: {1}
Expand Down
Expand Up @@ -66,7 +66,6 @@ CLI.clear-queue.shortDescription=Clears the build queue
CLI.delete-job.shortDescription=Deletes a job
CLI.disable-job.shortDescription=Disables a job
CLI.enable-job.shortDescription=Enables a job
CLI.delete-node.shortDescription=Deletes a node
CLI.disconnect-node.shortDescription=Disconnects from a node
CLI.connect-node.shortDescription=Reconnect to a node
CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
Expand Down
Expand Up @@ -85,7 +85,6 @@ CLI.clear-queue.shortDescription=\u6e05\u9664\u5efa\u7f6e\u4f47\u5217\u3002
CLI.delete-job.shortDescription=\u522a\u9664\u4f5c\u696d\u3002
CLI.disable-job.shortDescription=\u505c\u7528\u4f5c\u696d\u3002
CLI.enable-job.shortDescription=\u555f\u7528\u4f5c\u696d\u3002
CLI.delete-node.shortDescription=\u522a\u9664\u6307\u5b9a\u7bc0\u9ede\u3002
CLI.disconnect-node.shortDescription=\u4e2d\u65b7\u8207\u6307\u5b9a\u7bc0\u9ede\u7684\u9023\u7dda\u3002
CLI.connect-node.shortDescription=\u9023\u7dda\u5230\u6307\u5b9a\u7bc0\u9ede\u3002
CLI.online-node.shortDescription=\u7e7c\u7e8c\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u53d6\u6d88\u5148\u524d\u7684 "offline-node" \u6307\u4ee4\u3002
Expand Down
147 changes: 147 additions & 0 deletions test/src/test/java/hudson/cli/DeleteNodeCommandTest.java
@@ -0,0 +1,147 @@
/*
* The MIT License
*
* Copyright 2015 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.
*/

/**
* @author pjanouse
* @since TODO
*/

package hudson.cli;

import hudson.model.Computer;
import hudson.slaves.DumbSlave;
import hudson.slaves.NodeProperty;
import jenkins.model.Jenkins;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

import java.util.ArrayList;

import static hudson.cli.CLICommandInvoker.Matcher.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.notNullValue;

public class DeleteNodeCommandTest {

private CLICommandInvoker command;

@Rule public final JenkinsRule j = new JenkinsRule();

@Before public void setUp() {

command = new CLICommandInvoker(j, new DeleteNodeCommand());
}

@Test public void deleteNodeShouldFailWithoutNodeDeletePermission() throws Exception {

j.createSlave("aNode", "", null);

final CLICommandInvoker.Result result = command
.authorizedTo(Jenkins.READ)
.invokeWithArgs("aNode")
;

assertThat(result, failedWith(-1));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("user is missing the Slave/Delete permission"));
}

@Test public void deleteNodeShouldSucceed() throws Exception {

j.createSlave("aNode", "", null);

final CLICommandInvoker.Result result = command
.authorizedTo(Computer.DELETE, Jenkins.READ)
.invokeWithArgs("aNode")
;

assertThat(result, succeededSilently());
assertThat(j.jenkins.getNode("aNode"), nullValue());
}

@Test public void deleteNodeShouldFailIfNodeDoesNotExist() {

final CLICommandInvoker.Result result = command
.authorizedTo(Computer.DELETE, Jenkins.READ)
.invokeWithArgs("never_created")
;

assertThat(result, failedWith(-1));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("No such node 'never_created'"));
}

@Test public void deleteNodeManyShouldSucceed() throws Exception {

j.createSlave("aNode1", "", null);
j.createSlave("aNode2", "", null);
j.createSlave("aNode3", "", null);

final CLICommandInvoker.Result result = command
.authorizedTo(Computer.DELETE, Jenkins.READ)
.invokeWithArgs("aNode1", "aNode2", "aNode3");

assertThat(result, succeededSilently());
assertThat(j.jenkins.getView("aNode1"), nullValue());
assertThat(j.jenkins.getView("aNode2"), nullValue());
assertThat(j.jenkins.getView("aNode3"), nullValue());
}

@Test public void deleteNodeManyShouldFailIfANodeDoesNotExist() throws Exception {

j.createSlave("aNode1", "", null);
j.createSlave("aNode2", "", null);

final CLICommandInvoker.Result result = command
.authorizedTo(Computer.DELETE, Jenkins.READ)
.invokeWithArgs("aNode1", "aNode2", "never_created");

assertThat(result, failedWith(-1));
assertThat(result, hasNoStandardOutput());
assertThat(result.stderr(), containsString("No such node 'never_created'"));

assertThat(j.jenkins.getView("aNode1"), nullValue());
assertThat(j.jenkins.getView("aNode2"), nullValue());
assertThat(j.jenkins.getView("never_created"), nullValue());
}

@Test public void deleteNodeManyShouldSucceedEvenANodeIsSpecifiedTwice() throws Exception {

j.createSlave("aNode1", "", null);
j.createSlave("aNode2", "", null);

final CLICommandInvoker.Result result = command
.authorizedTo(Computer.DELETE, Jenkins.READ)
.invokeWithArgs("aNode1", "aNode2", "aNode1");

assertThat(result, succeededSilently());
assertThat(j.jenkins.getView("aNode1"), nullValue());
assertThat(j.jenkins.getView("aNode2"), nullValue());
}
}

0 comments on commit 67e4e88

Please sign in to comment.