/* * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, 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.AbortException; import hudson.Extension; import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.cli.declarative.CLIMethod; import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson; import hudson.cli.declarative.OptionHandlerExtension; import jenkins.model.Jenkins; import hudson.remoting.Callable; import hudson.remoting.Channel; import hudson.remoting.ChannelProperty; import hudson.security.CliAuthenticator; import hudson.security.SecurityRealm; import org.acegisecurity.Authentication; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.apache.commons.discovery.ResourceClassIterator; import org.apache.commons.discovery.ResourceNameIterator; import org.apache.commons.discovery.resource.ClassLoaders; import org.apache.commons.discovery.resource.classes.DiscoverClasses; import org.apache.commons.discovery.resource.names.DiscoverServiceNames; import org.jvnet.hudson.annotation_indexer.Index; import org.jvnet.tiger_types.Types; import org.kohsuke.args4j.ClassParser; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.spi.OptionHandler; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.List; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; /** * Base class for Hudson CLI. * *
* The users starts {@linkplain CLI the "CLI agent"} on a remote system, by specifying arguments, like * "java -jar jenkins-cli.jar command arg1 arg2 arg3". The CLI agent creates * a remoting channel with the server, and it sends the entire arguments to the server, along with * the remoted stdin/out/err. * *
* The Hudson master then picks the right {@link CLICommand} to execute, clone it, and * calls {@link #main(List, Locale, InputStream, PrintStream, PrintStream)} method. * *
* (In contrast, calling {@code System.out.println(...)} would print out * the message to the server log file, which is probably not what you want. */ public transient PrintStream stdout,stderr; /** * Connected to stdin of the CLI agent. * *
* This input stream is buffered to hide the latency in the remoting. */ public transient InputStream stdin; /** * {@link Channel} that represents the CLI JVM. You can use this to * execute {@link Callable} on the CLI JVM, among other things. * *
* Starting 1.445, CLI transports are not required to provide a channel * (think of sshd, telnet, etc), so in such a case this field is null. * *
* See {@link #checkChannel()} to get a channel and throw an user-friendly * exception */ public transient Channel channel; /** * The locale of the client. Messages should be formatted with this resource. */ public transient Locale locale; /** * Set by the caller of the CLI system if the transport already provides * authentication. Due to the compatibility issue, we still allow the user * to use command line switches to authenticate as other users. */ private transient Authentication transportAuth; /** * Gets the command name. * *
* For example, if the CLI is invoked as java -jar cli.jar foo arg1 arg2 arg4, * on the server side {@link CLICommand} that returns "foo" from {@link #getName()} * will be invoked. * *
* By default, this method creates "foo-bar-zot" from "FooBarZotCommand". */ public String getName() { String name = getClass().getName(); name = name.substring(name.lastIndexOf('.') + 1); // short name name = name.substring(name.lastIndexOf('$')+1); if(name.endsWith("Command")) name = name.substring(0,name.length()-7); // trim off the command // convert "FooBarZot" into "foo-bar-zot" // Locale is fixed so that "CreateInstance" always become "create-instance" no matter where this is run. return name.replaceAll("([a-z0-9])([A-Z])","$1-$2").toLowerCase(Locale.ENGLISH); } /** * Gets the quick summary of what this command does. * Used by the help command to generate the list of commands. */ public abstract String getShortDescription(); /** * Entry point to the CLI command. * *
* The default implementation uses args4j to parse command line arguments and call {@link #run()},
* but if that processing is undesirable, subtypes can directly override this method and leave {@link #run()}
* to an empty method.
*
* @param args
* Arguments to the sub command. For example, if the CLI is invoked like "java -jar cli.jar foo bar zot",
* then "foo" is the sub-command and the argument list is ["bar","zot"].
* @param locale
* Locale of the client (which can be different from that of the server.) Good behaving command implementation
* would use this locale for formatting messages.
* @param stdin
* Connected to the stdin of the CLI client.
* @param stdout
* Connected to the stdout of the CLI client.
* @param stderr
* Connected to the stderr of the CLI client.
* @return
* Exit code from the command.
*/
public int main(List
* If your command doesn't require any authentication whatsoever, and if you don't even want to let the user
* authenticate, then override this method to always return false — doing so will result in all the commands
* running as anonymous user credential.
*
*
* Note that even if this method returns true, the user can still skip aut
*
* @param auth
* Always non-null.
* If the underlying transport had already performed authentication, this object is something other than
* {@link jenkins.model.Jenkins#ANONYMOUS}.
*/
protected boolean shouldPerformAuthentication(Authentication auth) {
return auth== Jenkins.ANONYMOUS;
}
/**
* Returns the identity of the client as determined at the CLI transport level.
*
*
* When the CLI connection to the server is tunneled over HTTP, that HTTP connection
* can authenticate the client, just like any other HTTP connections to the server
* can authenticate the client. This method returns that information, if one is available.
* By generalizing it, this method returns the identity obtained at the transport-level authentication.
*
*
* For example, imagine if the current {@link SecurityRealm} is doing Kerberos authentication,
* then this method can return a valid identity of the client.
*
*
* If the transport doesn't do authentication, this method returns {@link jenkins.model.Jenkins#ANONYMOUS}.
*/
public Authentication getTransportAuthentication() {
Authentication a = transportAuth;
if (a==null) a = Jenkins.ANONYMOUS;
return a;
}
public void setTransportAuth(Authentication transportAuth) {
this.transportAuth = transportAuth;
}
/**
* Executes the command, and return the exit code.
*
*
* This is an internal contract between {@link CLICommand} and its subtype.
* To execute CLI method from outside, use {@link #main(List, Locale, InputStream, PrintStream, PrintStream)}
*
* @return
* 0 to indicate a success, otherwise an error code.
* @throws AbortException
* If the processing should be aborted. Hudson will report the error message
* without stack trace, and then exits this command.
* @throws Exception
* All the other exceptions cause the stack trace to be dumped, and then
* the command exits with an error code.
*/
protected abstract int run() throws Exception;
protected void printUsage(PrintStream stderr, CmdLineParser p) {
stderr.println("java -jar jenkins-cli.jar "+getName()+" args...");
printUsageSummary(stderr);
p.printUsage(stderr);
}
/**
* Called while producing usage. This is a good method to override
* to render the general description of the command that goes beyond
* a single-line summary.
*/
protected void printUsageSummary(PrintStream stderr) {
stderr.println(getShortDescription());
}
/**
* Convenience method for subtypes to obtain the system property of the client.
*/
protected String getClientSystemProperty(String name) throws IOException, InterruptedException {
return checkChannel().call(new GetSystemProperty(name));
}
private static final class GetSystemProperty implements Callable