-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
[JENKINS-75378] Adding a CLI command listener #10382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
b41633f
99ba771
5cb32e0
7b9cb0a
b1e15c4
32ead10
b8f360a
206147e
5433f59
751313c
dcbbdf6
b5c3f70
1138c0d
4d7e35a
3679a4d
676835c
bdf2895
737c08d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,8 @@ | |
| import hudson.Functions; | ||
| import hudson.cli.declarative.CLIMethod; | ||
| import hudson.cli.declarative.OptionHandlerExtension; | ||
| import hudson.cli.listeners.CliContext; | ||
| import hudson.cli.listeners.CliListener; | ||
| import hudson.remoting.Channel; | ||
| import hudson.security.SecurityRealm; | ||
| import java.io.BufferedInputStream; | ||
|
|
@@ -48,9 +50,6 @@ | |
| import java.nio.charset.Charset; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.UUID; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import jenkins.model.Jenkins; | ||
| import jenkins.util.SystemProperties; | ||
| import org.jvnet.hudson.annotation_indexer.Index; | ||
|
|
@@ -239,70 +238,75 @@ public int main(List<String> args, Locale locale, InputStream stdin, PrintStream | |
| this.locale = locale; | ||
| CmdLineParser p = getCmdLineParser(); | ||
|
|
||
| final CliContext context = new CliContext(getName(), args.size(), getTransportAuthentication2()); | ||
|
|
||
| // add options from the authenticator | ||
| SecurityContext sc = null; | ||
| Authentication old = null; | ||
| Authentication auth; | ||
| try { | ||
| // TODO as in CLIRegisterer this may be doing too much work | ||
| sc = SecurityContextHolder.getContext(); | ||
| old = sc.getAuthentication(); | ||
|
|
||
| sc.setAuthentication(auth = getTransportAuthentication2()); | ||
| sc.setAuthentication(getTransportAuthentication2()); | ||
apuig marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (!(this instanceof HelpCommand || this instanceof WhoAmICommand)) | ||
| Jenkins.get().checkPermission(Jenkins.READ); | ||
| p.parseArgument(args.toArray(new String[0])); | ||
| LOGGER.log(Level.FINE, "Invoking CLI command {0}, with {1} arguments, as user {2}.", | ||
| new Object[] {getName(), args.size(), auth.getName()}); | ||
|
|
||
| CliListener.fireExecution(context); | ||
| int res = run(); | ||
| LOGGER.log(Level.FINE, "Executed CLI command {0}, with {1} arguments, as user {2}, return code {3}", | ||
| new Object[] {getName(), args.size(), auth.getName(), res}); | ||
| CliListener.fireCompleted(context, res); | ||
|
|
||
| return res; | ||
| } catch (CmdLineException e) { | ||
| logFailedCommandAndPrintExceptionErrorMessage(args, e); | ||
| printUsage(stderr, p); | ||
| return 2; | ||
| } catch (IllegalStateException e) { | ||
| logFailedCommandAndPrintExceptionErrorMessage(args, e); | ||
| return 4; | ||
| } catch (IllegalArgumentException e) { | ||
| logFailedCommandAndPrintExceptionErrorMessage(args, e); | ||
| return 3; | ||
| } catch (AbortException e) { | ||
| logFailedCommandAndPrintExceptionErrorMessage(args, e); | ||
| return 5; | ||
| } catch (AccessDeniedException e) { | ||
| logFailedCommandAndPrintExceptionErrorMessage(args, e); | ||
| return 6; | ||
| } catch (BadCredentialsException e) { | ||
| // to the caller, we can't reveal whether the user didn't exist or the password didn't match. | ||
| // do that to the server log instead | ||
| String id = UUID.randomUUID().toString(); | ||
| logAndPrintError(e, "Bad Credentials. Search the server log for " + id + " for more details.", | ||
| "CLI login attempt failed: " + id, Level.INFO); | ||
| return 7; | ||
| } catch (Throwable e) { | ||
| String errorMsg = "Unexpected exception occurred while performing " + getName() + " command."; | ||
| logAndPrintError(e, errorMsg, errorMsg, Level.WARNING); | ||
| Functions.printStackTrace(e, stderr); | ||
| return 1; | ||
| int exitCode = handleException(e, context, p); | ||
| CliListener.fireError(context, exitCode, e); | ||
| return exitCode; | ||
| } finally { | ||
| if (sc != null) | ||
| sc.setAuthentication(old); // restore | ||
| } | ||
| } | ||
|
|
||
| private void logFailedCommandAndPrintExceptionErrorMessage(List<String> args, Throwable e) { | ||
| Authentication auth = getTransportAuthentication2(); | ||
| String logMessage = String.format("Failed call to CLI command %s, with %d arguments, as user %s.", | ||
| getName(), args.size(), auth != null ? auth.getName() : "<unknown>"); | ||
|
|
||
| logAndPrintError(e, e.getMessage(), logMessage, Level.FINE); | ||
| /** | ||
| * Determines command stderr output and return the exit code as described on {@link #main(List, Locale, InputStream, PrintStream, PrintStream)} | ||
| * */ | ||
| protected int handleException(Throwable e, CliContext context, CmdLineParser p) { | ||
| int exitCode; | ||
| if (e instanceof CmdLineException) { | ||
| exitCode = 2; | ||
| printError(e.getMessage()); | ||
| printUsage(stderr, p); | ||
| } else if (e instanceof IllegalArgumentException) { | ||
| exitCode = 3; | ||
| printError(e.getMessage()); | ||
| } else if (e instanceof IllegalStateException) { | ||
| exitCode = 4; | ||
| printError(e.getMessage()); | ||
| } else if (e instanceof AbortException) { | ||
| exitCode = 5; | ||
| printError(e.getMessage()); | ||
| } else if (e instanceof AccessDeniedException) { | ||
| exitCode = 6; | ||
| printError(e.getMessage()); | ||
| } else if (e instanceof BadCredentialsException) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like
b1803a9 I can't find CliAuthenticationTest, may not apply anymore. EDIT: this branch is only possible if a CLICommand implementation throws a |
||
| exitCode = 7; | ||
| // to the caller, we can't reveal whether the user didn't exist or the password didn't match. | ||
| // do that to the server log instead | ||
apuig marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| printError( | ||
| "Bad Credentials. Search the server log for " + context.getCorrelationId() + " for more details."); | ||
| } else { | ||
| exitCode = 1; | ||
| printError("Unexpected exception occurred while performing " + getName() + " command."); | ||
| Functions.printStackTrace(e, stderr); | ||
| } | ||
| return exitCode; | ||
| } | ||
|
|
||
| private void logAndPrintError(Throwable e, String errorMessage, String logMessage, Level logLevel) { | ||
| LOGGER.log(logLevel, logMessage, e); | ||
|
|
||
krisstern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| private void printError(String errorMessage) { | ||
| this.stderr.println(); | ||
| this.stderr.println("ERROR: " + errorMessage); | ||
| } | ||
|
|
@@ -538,8 +542,6 @@ public static CLICommand clone(String name) { | |
| return null; | ||
| } | ||
|
|
||
| private static final Logger LOGGER = Logger.getLogger(CLICommand.class.getName()); | ||
|
|
||
| private static final ThreadLocal<CLICommand> CURRENT_COMMAND = new ThreadLocal<>(); | ||
|
|
||
| /*package*/ static CLICommand setCurrent(CLICommand cmd) { | ||
|
|
||
apuig marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package hudson.cli.listeners; | ||
apuig marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
| import edu.umd.cs.findbugs.annotations.NonNull; | ||
| import edu.umd.cs.findbugs.annotations.Nullable; | ||
| import java.util.UUID; | ||
| import org.springframework.security.core.Authentication; | ||
|
|
||
| /** | ||
| * Holds information of a command execution. Same instance is used to all {@link CliListener} invocations. | ||
| * Use `correlationId` in order to group related events. | ||
apuig marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * | ||
| * @since TODO | ||
| */ | ||
| public class CliContext { | ||
| private final String correlationId = UUID.randomUUID().toString(); | ||
| private final String command; | ||
| private final int argsSize; | ||
| private final Authentication auth; | ||
|
|
||
| /** | ||
| * @param command The command being executed. | ||
daniel-beck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @param argsSize Number of arguments passed to the command. | ||
apuig marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * @param auth Authenticated user performing the execution. | ||
| */ | ||
| public CliContext(@NonNull String command, int argsSize, @Nullable Authentication auth) { | ||
| this.command = command; | ||
| this.argsSize = argsSize; | ||
| this.auth = auth; | ||
| } | ||
|
|
||
| /** | ||
| * @return Correlate this command event to other, related command events. | ||
| */ | ||
| @NonNull | ||
| public String getCorrelationId() { | ||
| return correlationId; | ||
| } | ||
|
|
||
| /** | ||
| * @return Command being executed. | ||
| */ | ||
| @NonNull | ||
| public String getCommand() { | ||
| return command; | ||
| } | ||
|
|
||
| /** | ||
| * @return Number of arguments passed to the command. | ||
| */ | ||
| public int getArgsSize() { | ||
| return argsSize; | ||
| } | ||
|
|
||
| /** | ||
| * @return Authenticated user performing the execution. | ||
| */ | ||
| @CheckForNull | ||
| public Authentication getAuth() { | ||
| return auth; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.