Skip to content

Commit

Permalink
💥 kcctl#17 Added global execution exception handler to handle KafkaCo…
Browse files Browse the repository at this point in the history
…nnectExceptions
  • Loading branch information
tonyfosterdev committed Oct 7, 2021
1 parent 20758de commit a9bb7b1
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/main/java/dev/morling/kccli/command/ApplyCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ private int createOrUpdateConnector(KafkaConnectApi kafkaConnectApi, String cont
GetPluginsCommand getPlugins = new GetPluginsCommand();
getPlugins.context = context;
getPlugins.run();
} else {
}
else {
System.out.println(kce.getMessage());
}

Expand Down
10 changes: 9 additions & 1 deletion src/main/java/dev/morling/kccli/command/KcCtlCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
*/
package dev.morling.kccli.command;

import javax.enterprise.inject.Produces;

import org.eclipse.microprofile.config.ConfigProvider;

import dev.morling.kccli.service.ExecutionExceptionHandler;
import io.quarkus.picocli.runtime.PicocliCommandLineFactory;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;
import picocli.CommandLine.IVersionProvider;

@TopCommand
@TopCommand()
@CommandLine.Command(name = "kcctl", mixinStandardHelpOptions = true, versionProvider = VersionProviderWithConfigProvider.class, subcommands = {
InfoCommand.class,
ConfigCommand.class,
Expand All @@ -42,6 +46,10 @@
)

public class KcCtlCommand {
@Produces
CommandLine getCommandLineInstance(PicocliCommandLineFactory factory) {
return factory.create().setExecutionExceptionHandler(new ExecutionExceptionHandler());
}
}

class VersionProviderWithConfigProvider implements IVersionProvider {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2021 The original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.morling.kccli.service;

import org.apache.http.HttpStatus;

import dev.morling.kccli.util.Colors;
import picocli.CommandLine;
import picocli.CommandLine.IExecutionExceptionHandler;
import picocli.CommandLine.ParseResult;

public class ExecutionExceptionHandler implements IExecutionExceptionHandler {

public static class ExitCodeErrorMessagePair {
private int exitCode;
private String errorMessage;

public ExitCodeErrorMessagePair(int exitCode, String errorMessage) {
this.exitCode = exitCode;
this.errorMessage = errorMessage;
}

public int getExitCode() {
return this.exitCode;
}

public String getErrorMessage() {
return this.errorMessage;
}
}

@Override
public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) {
var exitCodeErrorMessagePair = loadMessageAndExitCode(ex);
System.err.println(Colors.ANSI_RED + exitCodeErrorMessagePair.getErrorMessage() + Colors.ANSI_RESET);
return exitCodeErrorMessagePair.getExitCode();
}

public static ExitCodeErrorMessagePair loadMessageAndExitCode(Exception ex) {
if (ex instanceof KafkaConnectException) {
var kafkaConnectException = (KafkaConnectException) ex;
switch (kafkaConnectException.getErrorCode()) {
case HttpStatus.SC_UNAUTHORIZED: {
return new ExitCodeErrorMessagePair(
CommandLine.ExitCode.SOFTWARE,
"Kafka Connect returned an error indicating the configured user is unauthorized.");
}
}
}

return new ExitCodeErrorMessagePair(CommandLine.ExitCode.SOFTWARE, "An error occured.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2021 The original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.morling.kccli.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import dev.morling.kccli.util.Colors;
import picocli.CommandLine;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayNameGeneration(ReplaceUnderscores.class)
class ExecutionExceptionHandlerTest {
@TempDir
File tempDir;

@Nested
class HandleExecutionException {
// Thanks to https://stackoverflow.com/a/1119559
private final ByteArrayOutputStream fakeSystemErr = new ByteArrayOutputStream();
private final PrintStream originalSystemErr = System.err;

@BeforeEach
public void setupSystemErr() {
System.setErr(new PrintStream(fakeSystemErr));
}

@AfterEach
public void restoreSystemErr() {
System.setErr(originalSystemErr);
}

public String errorPrintLnFormatted(String str) {
return Colors.ANSI_RED + str + Colors.ANSI_RESET + System.getProperty("line.separator");
}

@Test
void should_handle_unauthorized_errors() throws IOException {
var handler = new ExecutionExceptionHandler();
int exitCode = handler.handleExecutionException(
new KafkaConnectException("Unauthorized", 401), null, null);

assertThat(exitCode).isEqualTo(CommandLine.ExitCode.SOFTWARE);
assertThat(fakeSystemErr.toString())
.isEqualTo(errorPrintLnFormatted(
"Kafka Connect returned an error indicating the configured user is unauthorized."));
}

@Test
void should_use_default_error_message_and_code_for_other_kc_exceptions() throws IOException {
var handler = new ExecutionExceptionHandler();
int exitCode = handler.handleExecutionException(
new KafkaConnectException("Woa", 999), null, null);

assertThat(exitCode).isEqualTo(CommandLine.ExitCode.SOFTWARE);
assertThat(fakeSystemErr.toString())
.isEqualTo(errorPrintLnFormatted("An error occured."));
}

@Test
void should_use_default_error_message_and_code_for_non_kc_exceptions() throws IOException {
var handler = new ExecutionExceptionHandler();
int exitCode = handler.handleExecutionException(
new Exception("Woa"), null, null);

assertThat(exitCode).isEqualTo(CommandLine.ExitCode.SOFTWARE);
assertThat(fakeSystemErr.toString())
.isEqualTo(errorPrintLnFormatted("An error occured."));
}
}
}

0 comments on commit a9bb7b1

Please sign in to comment.