File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\cli\CLICommand.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.cli;
25
26 import hudson.AbortException;
27 import hudson.Extension;
28 import hudson.ExtensionList;
29 import hudson.ExtensionPoint;
30 import hudson.cli.declarative.CLIMethod;
31 import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson;
32 import hudson.cli.declarative.OptionHandlerExtension;
33 import jenkins.model.Jenkins;
34 import hudson.remoting.Callable;
35 import hudson.remoting.Channel;
36 import hudson.remoting.ChannelProperty;
37 import hudson.security.CliAuthenticator;
38 import hudson.security.SecurityRealm;
39 import org.acegisecurity.Authentication;
40 import org.acegisecurity.context.SecurityContext;
41 import org.acegisecurity.context.SecurityContextHolder;
42 import org.apache.commons.discovery.ResourceClassIterator;
43 import org.apache.commons.discovery.ResourceNameIterator;
44 import org.apache.commons.discovery.resource.ClassLoaders;
45 import org.apache.commons.discovery.resource.classes.DiscoverClasses;
46 import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
47 import org.jvnet.hudson.annotation_indexer.Index;
48 import org.jvnet.tiger_types.Types;
49 import org.kohsuke.args4j.ClassParser;
50 import org.kohsuke.args4j.CmdLineException;
51 import org.kohsuke.args4j.CmdLineParser;
52 import org.kohsuke.args4j.spi.OptionHandler;
53
54 import java.io.BufferedInputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.PrintStream;
58 import java.lang.reflect.Type;
59 import java.nio.charset.Charset;
60 import java.nio.charset.UnsupportedCharsetException;
61 import java.util.List;
62 import java.util.Locale;
63 import java.util.logging.Level;
64 import java.util.logging.Logger;
65
66 /**
67 * Base class for Hudson CLI.
68 *
69 * <h2>How does a CLI command work</h2>
70 * <p>
71 * The users starts {@linkplain CLI the "CLI agent"} on a remote system, by specifying arguments, like
72 * <tt>"java -jar jenkins-cli.jar command arg1 arg2 arg3"</tt>. The CLI agent creates
73 * a remoting channel with the server, and it sends the entire arguments to the server, along with
74 * the remoted stdin/out/err.
75 *
76 * <p>
77 * The Hudson master then picks the right {@link CLICommand} to execute, clone it, and
78 * calls {@link #main(List, Locale, InputStream, PrintStream, PrintStream)} method.
79 *
80 * <h2>Note for CLI command implementor</h2>
81 * Start with <a href="http://wiki.jenkins-ci.org/display/JENKINS/Writing+CLI+commands">this document</a>
82 * to get the general idea of CLI.
83 *
84 * <ul>
85 * <li>
86 * Put {@link Extension} on your implementation to have it discovered by Hudson.
87 *
88 * <li>
89 * Use <a href="https://github.com/kohsuke/args4j">args4j</a> annotation on your implementation to define
90 * options and arguments (however, if you don't like that, you could override
91 * the {@link #main(List, Locale, InputStream, PrintStream, PrintStream)} method directly.
92 *
93 * <li>
94 * stdin, stdout, stderr are remoted, so proper buffering is necessary for good user experience.
95 *
96 * <li>
97 * Send {@link Callable} to a CLI agent by using {@link #channel} to get local interaction,
98 * such as uploading a file, asking for a password, etc.
99 *
100 * </ul>
101 *
102 * @author Kohsuke Kawaguchi
103 * @since 1.302
104 * @see CLIMethod
105 */
106 @LegacyInstancesAreScopedToHudson
107 public abstract class CLICommand implements ExtensionPoint, Cloneable {
108 /**
109 * Connected to stdout and stderr of the CLI agent that initiated the session.
110 * IOW, if you write to these streams, the person who launched the CLI command
111 * will see the messages in his terminal.
112 *
113 * <p>
114 * (In contrast, calling {@code System.out.println(...)} would print out
115 * the message to the server log file, which is probably not what you want.
116 */
117 public transient PrintStream stdout,stderr;
118
119 /**
120 * Connected to stdin of the CLI agent.
121 *
122 * <p>
123 * This input stream is buffered to hide the latency in the remoting.
124 */
125 public transient InputStream stdin;
126
127 /**
128 * {@link Channel} that represents the CLI JVM. You can use this to
129 * execute {@link Callable} on the CLI JVM, among other things.
130 *
131 * <p>
132 * Starting 1.445, CLI transports are not required to provide a channel
133 * (think of sshd, telnet, etc), so in such a case this field is null.
134 *
135 * <p>
136 * See {@link #checkChannel()} to get a channel and throw an user-friendly
137 * exception
138 */
139 public transient Channel channel;
140
141 /**
142 * The locale of the client. Messages should be formatted with this resource.
143 */
144 public transient Locale locale;
145
146 /**
147 * Set by the caller of the CLI system if the transport already provides
148 * authentication. Due to the compatibility issue, we still allow the user
149 * to use command line switches to authenticate as other users.
150 */
151 private transient Authentication transportAuth;
152
153 /**
154 * Gets the command name.
155 *
156 * <p>
157 * For example, if the CLI is invoked as <tt>java -jar cli.jar foo arg1 arg2 arg4</tt>,
158 * on the server side {@link CLICommand} that returns "foo" from {@link #getName()}
159 * will be invoked.
160 *
161 * <p>
162 * By default, this method creates "foo-bar-zot" from "FooBarZotCommand".
163 */
164 public String getName() {
165 String name = getClass().getName();
166 name = name.substring(name.lastIndexOf('.') + 1); // short name
167 name = name.substring(name.lastIndexOf('$')+1);
168 if(name.endsWith("Command"))
169 name = name.substring(0,name.length()-7); // trim off the command
170
171 // convert "FooBarZot" into "foo-bar-zot"
172 // Locale is fixed so that "CreateInstance" always become "create-instance" no matter where this is run.
173 return name.replaceAll("([a-z0-9])([A-Z])","$1-$2").toLowerCase(Locale.ENGLISH);
174 }
175
176 /**
177 * Gets the quick summary of what this command does.
178 * Used by the help command to generate the list of commands.
179 */
180 public abstract String getShortDescription();
181
182 /**
183 * Entry point to the CLI command.
184 *
185 * <p>
186 * The default implementation uses args4j to parse command line arguments and call {@link #run()},
187 * but if that processing is undesirable, subtypes can directly override this method and leave {@link #run()}
188 * to an empty method.
189 *
190 * @param args
191 * Arguments to the sub command. For example, if the CLI is invoked like "java -jar cli.jar foo bar zot",
192 * then "foo" is the sub-command and the argument list is ["bar","zot"].
193 * @param locale
194 * Locale of the client (which can be different from that of the server.) Good behaving command implementation
195 * would use this locale for formatting messages.
196 * @param stdin
197 * Connected to the stdin of the CLI client.
198 * @param stdout
199 * Connected to the stdout of the CLI client.
200 * @param stderr
201 * Connected to the stderr of the CLI client.
202 * @return
203 * Exit code from the command.
204 */
205 public int main(List<String> args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) {
206 this.stdin = new BufferedInputStream(stdin);
207 this.stdout = stdout;
208 this.stderr = stderr;
209 this.locale = locale;
210 registerOptionHandlers();
211 CmdLineParser p = new CmdLineParser(this);
212
213 // add options from the authenticator
214 SecurityContext sc = SecurityContextHolder.getContext();
215 Authentication old = sc.getAuthentication();
216
217 CliAuthenticator authenticator = Jenkins.getInstance().getSecurityRealm().createCliAuthenticator(this);
218 sc.setAuthentication(getTransportAuthentication());
219 new ClassParser().parse(authenticator,p);
220
221 try {
222 p.parseArgument(args.toArray(new String[args.size()]));
223 Authentication auth = authenticator.authenticate();
224 if (auth==Jenkins.ANONYMOUS)
225 auth = loadStoredAuthentication();
226 sc.setAuthentication(auth); // run the CLI with the right credential
227 if (!(this instanceof LoginCommand || this instanceof HelpCommand))
228 Jenkins.getInstance().checkPermission(Jenkins.READ);
229 return run();
230 } catch (CmdLineException e) {
231 stderr.println(e.getMessage());
232 printUsage(stderr, p);
233 return -1;
234 } catch (AbortException e) {
235 // signals an error without stack trace
236 stderr.println(e.getMessage());
237 return -1;
238 } catch (Exception e) {
239 e.printStackTrace(stderr);
240 return -1;
241 } finally {
242 sc.setAuthentication(old); // restore
243 }
244 }
245
246 public Channel checkChannel() throws AbortException {
247 if (channel==null)
248 throw new AbortException("This command can only run with Jenkins CLI. See https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI");
249 return channel;
250 }
251
252 /**
253 * Loads the persisted authentication information from {@link ClientAuthenticationCache}
254 * if the current transport provides {@link Channel}.
255 */
256 protected Authentication loadStoredAuthentication() throws InterruptedException {
257 try {
258 if (channel!=null)
259 return new ClientAuthenticationCache(channel).get();
260 } catch (IOException e) {
261 stderr.println("Failed to access the stored credential");
262 e.printStackTrace(stderr); // recover
263 }
264 return Jenkins.ANONYMOUS;
265 }
266
267 /**
268 * Determines if the user authentication is attempted through CLI before running this command.
269 *
270 * <p>
271 * If your command doesn't require any authentication whatsoever, and if you don't even want to let the user
272 * authenticate, then override this method to always return false &mdash; doing so will result in all the commands
273 * running as anonymous user credential.
274 *
275 * <p>
276 * Note that even if this method returns true, the user can still skip aut
277 *
278 * @param auth
279 * Always non-null.
280 * If the underlying transport had already performed authentication, this object is something other than
281 * {@link jenkins.model.Jenkins#ANONYMOUS}.
282 */
283 protected boolean shouldPerformAuthentication(Authentication auth) {
284 return auth== Jenkins.ANONYMOUS;
285 }
286
287 /**
288 * Returns the identity of the client as determined at the CLI transport level.
289 *
290 * <p>
291 * When the CLI connection to the server is tunneled over HTTP, that HTTP connection
292 * can authenticate the client, just like any other HTTP connections to the server
293 * can authenticate the client. This method returns that information, if one is available.
294 * By generalizing it, this method returns the identity obtained at the transport-level authentication.
295 *
296 * <p>
297 * For example, imagine if the current {@link SecurityRealm} is doing Kerberos authentication,
298 * then this method can return a valid identity of the client.
299 *
300 * <p>
301 * If the transport doesn't do authentication, this method returns {@link jenkins.model.Jenkins#ANONYMOUS}.
302 */
303 public Authentication getTransportAuthentication() {
304 Authentication a = transportAuth;
305 if (a==null) a = Jenkins.ANONYMOUS;
306 return a;
307 }
308
309 public void setTransportAuth(Authentication transportAuth) {
310 this.transportAuth = transportAuth;
311 }
312
313 /**
314 * Executes the command, and return the exit code.
315 *
316 * <p>
317 * This is an internal contract between {@link CLICommand} and its subtype.
318 * To execute CLI method from outside, use {@link #main(List, Locale, InputStream, PrintStream, PrintStream)}
319 *
320 * @return
321 * 0 to indicate a success, otherwise an error code.
322 * @throws AbortException
323 * If the processing should be aborted. Hudson will report the error message
324 * without stack trace, and then exits this command.
325 * @throws Exception
326 * All the other exceptions cause the stack trace to be dumped, and then
327 * the command exits with an error code.
328 */
329 protected abstract int run() throws Exception;
330
331 protected void printUsage(PrintStream stderr, CmdLineParser p) {
332 stderr.println("java -jar jenkins-cli.jar "+getName()+" args...");
333 printUsageSummary(stderr);
334 p.printUsage(stderr);
335 }
336
337 /**
338 * Called while producing usage. This is a good method to override
339 * to render the general description of the command that goes beyond
340 * a single-line summary.
341 */
342 protected void printUsageSummary(PrintStream stderr) {
343 stderr.println(getShortDescription());
344 }
345
346 /**
347 * Convenience method for subtypes to obtain the system property of the client.
348 */
349 protected String getClientSystemProperty(String name) throws IOException, InterruptedException {
350 return checkChannel().call(new GetSystemProperty(name));
351 }
352
353 private static final class GetSystemProperty implements Callable<String, IOException> {
354 private final String name;
355
356 private GetSystemProperty(String name) {
357 this.name = name;
358 }
359
360 public String call() throws IOException {
361 return System.getProperty(name);
362 }
363
364 private static final long serialVersionUID = 1L;
365 }
366
367 protected Charset getClientCharset() throws IOException, InterruptedException {
368 if (channel==null)
369 // for SSH, assume the platform default encoding
370 // this is in-line with the standard SSH behavior
371 return Charset.defaultCharset();
372
373 String charsetName = checkChannel().call(new GetCharset());
374 try {
375 return Charset.forName(charsetName);
376 } catch (UnsupportedCharsetException e) {
377 LOGGER.log(Level.FINE,"Server doesn't have charset "+charsetName);
378 return Charset.defaultCharset();
379 }
380 }
381
382 private static final class GetCharset implements Callable<String, IOException> {
383 public String call() throws IOException {
384 return Charset.defaultCharset().name();
385 }
386
387 private static final long serialVersionUID = 1L;
388 }
389
390 /**
391 * Convenience method for subtypes to obtain environment variables of the client.
392 */
393 protected String getClientEnvironmentVariable(String name) throws IOException, InterruptedException {
394 return checkChannel().call(new GetEnvironmentVariable(name));
395 }
396
397 private static final class GetEnvironmentVariable implements Callable<String, IOException> {
398 private final String name;
399
400 private GetEnvironmentVariable(String name) {
401 this.name = name;
402 }
403
404 public String call() throws IOException {
405 return System.getenv(name);
406 }
407
408 private static final long serialVersionUID = 1L;
409 }
410
411 /**
412 * Creates a clone to be used to execute a command.
413 */
414 protected CLICommand createClone() {
415 try {
416 return getClass().newInstance();
417 } catch (IllegalAccessException e) {
418 throw new AssertionError(e);
419 } catch (InstantiationException e) {
420 throw new AssertionError(e);
421 }
422 }
423
424 /**
425 * Auto-discovers {@link OptionHandler}s and add them to the given command line parser.
426 */
427 protected void registerOptionHandlers() {
428 try {
429 for (Class c : Index.list(OptionHandlerExtension.class, Jenkins.getInstance().pluginManager.uberClassLoader,Class.class)) {
430 Type t = Types.getBaseClass(c, OptionHandler.class);
431 CmdLineParser.registerHandler(Types.erasure(Types.getTypeArgument(t,0)), c);
432 }
433 } catch (IOException e) {
434 throw new Error(e);
435 }
436 }
437
438 /**
439 * Returns all the registered {@link CLICommand}s.
440 */
441 public static ExtensionList<CLICommand> all() {
442 return Jenkins.getInstance().getExtensionList(CLICommand.class);
443 }
444
445 /**
446 * Obtains a copy of the command for invocation.
447 */
448 public static CLICommand clone(String name) {
449 for (CLICommand cmd : all())
450 if(name.equals(cmd.getName()))
451 return cmd.createClone();
452 return null;
453 }
454
455 private static final Logger LOGGER = Logger.getLogger(CLICommand.class.getName());
456
457 /**
458 * Key for {@link Channel#getProperty(Object)} that links to the {@link Authentication} object
459 * which captures the identity of the client given by the transport layer.
460 */
461 public static final ChannelProperty<Authentication> TRANSPORT_AUTHENTICATION = new ChannelProperty<Authentication>(Authentication.class,"transportAuthentication");
462
463 private static final ThreadLocal<CLICommand> CURRENT_COMMAND = new ThreadLocal<CLICommand>();
464
465 /*package*/ static CLICommand setCurrent(CLICommand cmd) {
466 CLICommand old = getCurrent();
467 CURRENT_COMMAND.set(cmd);
468 return old;
469 }
470
471 /**
472 * If the calling thread is in the middle of executing a CLI command, return it. Otherwise null.
473 */
474 public static CLICommand getCurrent() {
475 return CURRENT_COMMAND.get();
476 }
477
478 static {
479 // register option handlers that are defined
480 ClassLoaders cls = new ClassLoaders();
481 Jenkins j = Jenkins.getInstance();
482 if (j!=null) {// only when running on the master
483 cls.put(j.getPluginManager().uberClassLoader);
484
485 ResourceNameIterator servicesIter =
486 new DiscoverServiceNames(cls).findResourceNames(OptionHandler.class.getName());
487 final ResourceClassIterator itr =
488 new DiscoverClasses(cls).findResourceClasses(servicesIter);
489
490 while(itr.hasNext()) {
491 Class h = itr.nextResourceClass().loadClass();
492 Class c = Types.erasure(Types.getTypeArgument(Types.getBaseClass(h, OptionHandler.class), 0));
493 CmdLineParser.registerHandler(c,h);
494 }
495 }
496 }
497 }
498