mirror of https://github.com/jenkinsci/jenkins.git
Merge branch 'master' into improve-tooltips-dropdowns
This commit is contained in:
commit
5060044fcd
|
@ -48,10 +48,10 @@ import java.lang.reflect.Type;
|
|||
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.cli.listeners.CLIContext;
|
||||
import jenkins.cli.listeners.CLIListener;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.util.Listeners;
|
||||
import jenkins.util.SystemProperties;
|
||||
import org.jvnet.hudson.annotation_indexer.Index;
|
||||
import org.jvnet.tiger_types.Types;
|
||||
|
@ -242,70 +242,73 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
|
|||
this.locale = locale;
|
||||
CmdLineParser p = getCmdLineParser();
|
||||
|
||||
Authentication auth = getTransportAuthentication2();
|
||||
CLIContext context = new CLIContext(getName(), args, auth);
|
||||
|
||||
// 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(auth);
|
||||
|
||||
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()});
|
||||
|
||||
Listeners.notify(CLIListener.class, true, listener -> listener.onExecution(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});
|
||||
Listeners.notify(CLIListener.class, true, listener -> listener.onCompleted(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);
|
||||
Listeners.notify(CLIListener.class, true, listener -> listener.onThrowable(context, 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) {
|
||||
exitCode = 7;
|
||||
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);
|
||||
|
||||
private void printError(String errorMessage) {
|
||||
this.stderr.println();
|
||||
this.stderr.println("ERROR: " + errorMessage);
|
||||
}
|
||||
|
@ -541,8 +544,6 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
|
|||
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) {
|
||||
|
|
|
@ -27,11 +27,9 @@ package hudson.cli.declarative;
|
|||
import static java.util.logging.Level.SEVERE;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.AbortException;
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionComponent;
|
||||
import hudson.ExtensionFinder;
|
||||
import hudson.Functions;
|
||||
import hudson.Util;
|
||||
import hudson.cli.CLICommand;
|
||||
import hudson.cli.CloneableCLICommand;
|
||||
|
@ -49,19 +47,17 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Stack;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.ExtensionComponentSet;
|
||||
import jenkins.ExtensionRefreshException;
|
||||
import jenkins.cli.listeners.CLIContext;
|
||||
import jenkins.cli.listeners.CLIListener;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.util.Listeners;
|
||||
import org.jvnet.hudson.annotation_indexer.Index;
|
||||
import org.jvnet.localizer.ResourceBundleHolder;
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.kohsuke.args4j.CmdLineParser;
|
||||
import org.kohsuke.args4j.ParserProperties;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
@ -202,6 +198,9 @@ public class CLIRegisterer extends ExtensionFinder {
|
|||
|
||||
List<MethodBinder> binders = new ArrayList<>();
|
||||
|
||||
Authentication auth = getTransportAuthentication2();
|
||||
CLIContext context = new CLIContext(getName(), args, auth);
|
||||
|
||||
CmdLineParser parser = bindMethod(binders);
|
||||
try {
|
||||
// TODO this could probably use ACL.as; why is it calling SecurityContext.setAuthentication rather than SecurityContextHolder.setContext?
|
||||
|
@ -211,19 +210,19 @@ public class CLIRegisterer extends ExtensionFinder {
|
|||
// fill up all the binders
|
||||
parser.parseArgument(args);
|
||||
|
||||
Authentication auth = getTransportAuthentication2();
|
||||
sc.setAuthentication(auth); // run the CLI with the right credential
|
||||
jenkins.checkPermission(Jenkins.READ);
|
||||
|
||||
Listeners.notify(CLIListener.class, true, listener -> listener.onExecution(context));
|
||||
|
||||
// resolve them
|
||||
Object instance = null;
|
||||
for (MethodBinder binder : binders)
|
||||
instance = binder.call(instance);
|
||||
|
||||
if (instance instanceof Integer)
|
||||
return (Integer) instance;
|
||||
else
|
||||
return 0;
|
||||
Integer exitCode = (instance instanceof Integer) ? (Integer) instance : 0;
|
||||
Listeners.notify(CLIListener.class, true, listener -> listener.onCompleted(context, exitCode));
|
||||
return exitCode;
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable t = e.getTargetException();
|
||||
if (t instanceof Exception)
|
||||
|
@ -232,47 +231,13 @@ public class CLIRegisterer extends ExtensionFinder {
|
|||
} finally {
|
||||
sc.setAuthentication(old); // restore
|
||||
}
|
||||
} catch (CmdLineException e) {
|
||||
printError(e.getMessage());
|
||||
printUsage(stderr, parser);
|
||||
return 2;
|
||||
} catch (IllegalStateException e) {
|
||||
printError(e.getMessage());
|
||||
return 4;
|
||||
} catch (IllegalArgumentException e) {
|
||||
printError(e.getMessage());
|
||||
return 3;
|
||||
} catch (AbortException e) {
|
||||
printError(e.getMessage());
|
||||
return 5;
|
||||
} catch (AccessDeniedException e) {
|
||||
printError(e.getMessage());
|
||||
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) {
|
||||
final 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, parser);
|
||||
Listeners.notify(CLIListener.class, true, listener -> listener.onThrowable(context, e));
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
private void printError(String errorMessage) {
|
||||
this.stderr.println();
|
||||
this.stderr.println("ERROR: " + errorMessage);
|
||||
}
|
||||
|
||||
private void logAndPrintError(Throwable e, String errorMessage, String logMessage, Level logLevel) {
|
||||
LOGGER.log(logLevel, logMessage, e);
|
||||
printError(errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int run() throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2025, CloudBees, 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 jenkins.cli.listeners;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||
import java.util.List;
|
||||
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 {@code correlationId} in order to group related events to the same command.
|
||||
*
|
||||
* @since TODO
|
||||
*/
|
||||
public class CLIContext {
|
||||
private final String correlationId = UUID.randomUUID().toString();
|
||||
private final String command;
|
||||
private final List<String> args;
|
||||
private final Authentication auth;
|
||||
|
||||
/**
|
||||
* @param command The command being executed.
|
||||
* @param args Arguments passed to the command.
|
||||
* @param auth Authenticated user performing the execution.
|
||||
*/
|
||||
public CLIContext(@NonNull String command, @CheckForNull List<String> args, @Nullable Authentication auth) {
|
||||
this.command = command;
|
||||
this.args = args != null ? args : List.of();
|
||||
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 Arguments passed to the command.
|
||||
*/
|
||||
@NonNull
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Authenticated user performing the execution.
|
||||
*/
|
||||
@CheckForNull
|
||||
public Authentication getAuth() {
|
||||
return auth;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2025, CloudBees, 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 jenkins.cli.listeners;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.ExtensionPoint;
|
||||
import hudson.cli.CLICommand;
|
||||
|
||||
/**
|
||||
* Allows implementations to listen to {@link CLICommand#run()} execution events.
|
||||
*
|
||||
* @since TODO
|
||||
*/
|
||||
public interface CLIListener extends ExtensionPoint {
|
||||
|
||||
/**
|
||||
* Invoked before command execution.
|
||||
*
|
||||
* @param context Information about the command being executed.
|
||||
* */
|
||||
default void onExecution(@NonNull CLIContext context) {}
|
||||
|
||||
/**
|
||||
* Invoked after command execution.
|
||||
*
|
||||
* @param context Information about the command being executed.
|
||||
* @param exitCode Exit code returned by the implementation of {@link CLICommand#run()}.
|
||||
* */
|
||||
default void onCompleted(@NonNull CLIContext context, int exitCode) {}
|
||||
|
||||
/**
|
||||
* Invoked when an exception or error occurs during command execution.
|
||||
*
|
||||
* @param context Information about the command being executed.
|
||||
* @param t Any error during the execution of the command.
|
||||
* */
|
||||
default void onThrowable(@NonNull CLIContext context, @NonNull Throwable t) {}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2025, CloudBees, 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 jenkins.cli.listeners;
|
||||
|
||||
import hudson.AbortException;
|
||||
import hudson.Extension;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* Basic default implementation of {@link CLIListener} that just logs.
|
||||
*/
|
||||
@Extension
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class DefaultCLIListener implements CLIListener {
|
||||
private static final Logger LOGGER = Logger.getLogger(DefaultCLIListener.class.getName());
|
||||
|
||||
@Override
|
||||
public void onExecution(CLIContext context) {
|
||||
LOGGER.log(Level.FINE, "Invoking CLI command {0}, with {1} arguments, as user {2}.", new Object[] {
|
||||
context.getCommand(), context.getArgs().size(), authName(context.getAuth()),
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted(CLIContext context, int exitCode) {
|
||||
LOGGER.log(
|
||||
Level.FINE, "Executed CLI command {0}, with {1} arguments, as user {2}, return code {3}", new Object[] {
|
||||
context.getCommand(), context.getArgs().size(), authName(context.getAuth()), exitCode,
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThrowable(CLIContext context, Throwable t) {
|
||||
if (t instanceof BadCredentialsException) {
|
||||
// to the caller (stderr), we can't reveal whether the user didn't exist or the password didn't match.
|
||||
// do that to the server log instead
|
||||
LOGGER.log(Level.INFO, "CLI login attempt failed: " + context.getCorrelationId(), t);
|
||||
} else if (t instanceof CmdLineException
|
||||
|| t instanceof IllegalArgumentException
|
||||
|| t instanceof IllegalStateException
|
||||
|| t instanceof AbortException
|
||||
|| t instanceof AccessDeniedException) {
|
||||
// covered cases on CLICommand#handleException
|
||||
LOGGER.log(
|
||||
Level.FINE,
|
||||
String.format(
|
||||
"Failed call to CLI command %s, with %d arguments, as user %s.",
|
||||
context.getCommand(), context.getArgs().size(), authName(context.getAuth())),
|
||||
t);
|
||||
} else {
|
||||
LOGGER.log(
|
||||
Level.WARNING,
|
||||
"Unexpected exception occurred while performing " + context.getCommand() + " command.",
|
||||
t);
|
||||
}
|
||||
}
|
||||
|
||||
private static String authName(Authentication auth) {
|
||||
return auth != null ? auth.getName() : "<unknown>";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2025, CloudBees, 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 jenkins.cli;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
import hudson.cli.CLICommand;
|
||||
import hudson.cli.CLICommandInvoker;
|
||||
import hudson.cli.ListJobsCommand;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import jenkins.cli.listeners.DefaultCLIListener;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
import org.jvnet.hudson.test.LoggerRule;
|
||||
import org.jvnet.hudson.test.MockAuthorizationStrategy;
|
||||
import org.jvnet.hudson.test.TestExtension;
|
||||
|
||||
public class DefaultCLIListenerTest {
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Rule
|
||||
public LoggerRule logging = new LoggerRule();
|
||||
|
||||
private static final String USER = "cli-user";
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
|
||||
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
|
||||
.grant(Jenkins.ADMINISTER)
|
||||
.everywhere()
|
||||
.to(USER));
|
||||
j.createFreeStyleProject("p");
|
||||
logging.record(DefaultCLIListener.class, Level.FINE).capture(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commandOnCompletedIsLogged() throws Exception {
|
||||
CLICommandInvoker command = new CLICommandInvoker(j, new ListJobsCommand());
|
||||
command.asUser(USER).invoke();
|
||||
|
||||
List<String> messages = logging.getMessages();
|
||||
assertThat(messages, hasSize(2));
|
||||
assertThat(
|
||||
messages.get(0),
|
||||
containsString("Invoking CLI command list-jobs, with 0 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
messages.get(1),
|
||||
containsString(
|
||||
"Executed CLI command list-jobs, with 0 arguments, as user %s, return code 0".formatted(USER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commandOnThrowableIsLogged() throws Exception {
|
||||
CLICommandInvoker command = new CLICommandInvoker(j, new ListJobsCommand());
|
||||
command.asUser(USER).invokeWithArgs("view-not-found");
|
||||
|
||||
List<String> messages = logging.getMessages();
|
||||
assertThat(messages, hasSize(2));
|
||||
assertThat(
|
||||
messages.get(0),
|
||||
containsString("Invoking CLI command list-jobs, with 1 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
messages.get(1),
|
||||
containsString("Failed call to CLI command list-jobs, with 1 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
logging.getRecords().get(0).getThrown().getMessage(),
|
||||
containsString("No view or item group with the given name 'view-not-found' found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commandOnThrowableUnexpectedIsLogged() throws Exception {
|
||||
CLICommandInvoker command = new CLICommandInvoker(j, new ThrowsTestCommand());
|
||||
command.asUser(USER).invoke();
|
||||
|
||||
List<String> messages = logging.getMessages();
|
||||
assertThat(messages, hasSize(2));
|
||||
assertThat(
|
||||
messages.get(0),
|
||||
containsString(
|
||||
"Invoking CLI command throws-test-command, with 0 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
messages.get(1),
|
||||
containsString("Unexpected exception occurred while performing throws-test-command command."));
|
||||
assertThat(logging.getRecords().get(0).getThrown().getMessage(), containsString("unexpected"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodOnCompletedIsLogged() throws Exception {
|
||||
CLICommandInvoker command = new CLICommandInvoker(j, "disable-job");
|
||||
command.asUser(USER).invokeWithArgs("p");
|
||||
|
||||
List<String> messages = logging.getMessages();
|
||||
assertThat(messages, hasSize(2));
|
||||
assertThat(
|
||||
messages.get(0),
|
||||
containsString("Invoking CLI command disable-job, with 1 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
messages.get(1),
|
||||
containsString("Executed CLI command disable-job, with 1 arguments, as user %s, return code 0"
|
||||
.formatted(USER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodOnThrowableIsLogged() throws Exception {
|
||||
CLICommandInvoker command = new CLICommandInvoker(j, "disable-job");
|
||||
command.asUser(USER).invokeWithArgs("job-not-found");
|
||||
|
||||
List<String> messages = logging.getMessages();
|
||||
assertThat(messages, hasSize(2));
|
||||
assertThat(
|
||||
messages.get(0),
|
||||
containsString("Invoking CLI command disable-job, with 1 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
messages.get(1),
|
||||
containsString(
|
||||
"Failed call to CLI command disable-job, with 1 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(
|
||||
logging.getRecords().get(0).getThrown().getMessage(),
|
||||
containsString("No such job ‘job-not-found’ exists."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodOnThrowableUnexpectedIsLogged() throws Exception {
|
||||
CLICommandInvoker command = new CLICommandInvoker(j, "restart");
|
||||
command.asUser(USER).invoke();
|
||||
|
||||
List<String> messages = logging.getMessages();
|
||||
assertThat(messages, hasSize(2));
|
||||
assertThat(
|
||||
messages.get(0),
|
||||
containsString("Invoking CLI command restart, with 0 arguments, as user %s.".formatted(USER)));
|
||||
assertThat(messages.get(1), containsString("Unexpected exception occurred while performing restart command."));
|
||||
assertThat(logging.getRecords().get(0).getThrown(), notNullValue());
|
||||
}
|
||||
|
||||
@TestExtension
|
||||
public static class ThrowsTestCommand extends CLICommand {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "throws-test-command";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortDescription() {
|
||||
return "throws test command";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int run() {
|
||||
throw new RuntimeException("unexpected");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue