KEYCLOAK-3767 kcreg should show hint for help if required arguments are missing

This commit is contained in:
Marko Strukelj 2016-10-27 16:10:13 +02:00
parent b6b567f948
commit 408850e7bd
16 changed files with 141 additions and 60 deletions

View file

@ -61,14 +61,14 @@ public class AttrsCmd extends AbstractGlobalOptionsCmd {
if (args != null) { if (args != null) {
if (args.size() > 1) { if (args.size() > 1) {
throw new RuntimeException("Invalid option: " + args.get(1)); throw new IllegalArgumentException("Invalid option: " + args.get(1));
} }
attr = args.get(0); attr = args.get(0);
} }
Class type = regType == EndpointType.DEFAULT ? ClientRepresentation.class : (regType == EndpointType.OIDC ? OIDCClientRepresentation.class : null); Class type = regType == EndpointType.DEFAULT ? ClientRepresentation.class : (regType == EndpointType.OIDC ? OIDCClientRepresentation.class : null);
if (type == null) { if (type == null) {
throw new RuntimeException("Endpoint not supported: " + regType); throw new IllegalArgumentException("Endpoint not supported: " + regType);
} }
AttributeKey key = attr == null ? new AttributeKey() : new AttributeKey(attr); AttributeKey key = attr == null ? new AttributeKey() : new AttributeKey(attr);

View file

@ -23,12 +23,14 @@ import org.jboss.aesh.console.command.CommandException;
import org.jboss.aesh.console.command.Command; import org.jboss.aesh.console.command.Command;
import org.jboss.aesh.console.command.CommandResult; import org.jboss.aesh.console.command.CommandResult;
import org.jboss.aesh.console.command.invocation.CommandInvocation; import org.jboss.aesh.console.command.invocation.CommandInvocation;
import org.keycloak.client.registration.cli.util.OsUtil;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.List; import java.util.List;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
/** /**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a> * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/ */
@ -69,7 +71,7 @@ public class ConfigCmd extends AbstractAuthOptionsCmd implements Command {
if (printHelp()) { if (printHelp()) {
return help ? CommandResult.SUCCESS : CommandResult.FAILURE; return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
} }
throw new RuntimeException("Unknown sub-command: " + cmd); throw new IllegalArgumentException("Unknown sub-command: " + cmd + suggestHelp());
} }
} }
} }
@ -78,13 +80,17 @@ public class ConfigCmd extends AbstractAuthOptionsCmd implements Command {
return help ? CommandResult.SUCCESS : CommandResult.FAILURE; return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
} }
throw new RuntimeException("Sub-command required by '" + OsUtil.CMD + " config' - one of: 'credentials', 'truststore', 'initial-token', 'registration-token'"); throw new IllegalArgumentException("Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore', 'initial-token', 'registration-token'");
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help config' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }
@ -92,13 +98,13 @@ public class ConfigCmd extends AbstractAuthOptionsCmd implements Command {
public static String usage() { public static String usage() {
StringWriter sb = new StringWriter(); StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb); PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + OsUtil.CMD + " config SUB_COMMAND [ARGUMENTS]"); out.println("Usage: " + CMD + " config SUB_COMMAND [ARGUMENTS]");
out.println(); out.println();
out.println("Where SUB_COMMAND is one of: 'credentials', 'truststore', 'initial-token', 'registration-token'"); out.println("Where SUB_COMMAND is one of: 'credentials', 'truststore', 'initial-token', 'registration-token'");
out.println(); out.println();
out.println(); out.println();
out.println("Use '" + OsUtil.CMD + " help config SUB_COMMAND' for more info."); out.println("Use '" + CMD + " help config SUB_COMMAND' for more info.");
out.println("Use '" + OsUtil.CMD + " help' for general information and a list of commands."); out.println("Use '" + CMD + " help' for general information and a list of commands.");
return sb.toString(); return sb.toString();
} }
} }

View file

@ -25,6 +25,7 @@ import static org.keycloak.client.registration.cli.util.ConfigUtil.saveTokens;
import static org.keycloak.client.registration.cli.util.IoUtil.printErr; import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret; import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH; import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
@ -69,6 +70,8 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
processGlobalOptions(); processGlobalOptions();
return process(commandInvocation); return process(commandInvocation);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -83,7 +86,7 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
// check server // check server
if (server == null) { if (server == null) {
throw new RuntimeException("Required option not specified: --server"); throw new IllegalArgumentException("Required option not specified: --server");
} }
try { try {
@ -93,7 +96,7 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
} }
if (realm == null) if (realm == null)
throw new RuntimeException("Required option not specified: --realm"); throw new IllegalArgumentException("Required option not specified: --realm");
String signedRequestToken = null; String signedRequestToken = null;
boolean clientSet = clientId != null; boolean clientSet = clientId != null;
@ -122,7 +125,7 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
if (keystore != null) { if (keystore != null) {
if (secret != null) { if (secret != null) {
throw new RuntimeException("Can't use both --keystore and --secret"); throw new IllegalArgumentException("Can't use both --keystore and --secret");
} }
if (!new File(keystore).isFile()) { if (!new File(keystore).isFile()) {
@ -174,6 +177,10 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help config credentials' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -19,6 +19,7 @@ import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFI
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig; import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut; import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH; import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
@ -47,6 +48,8 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Com
} }
return process(commandInvocation); return process(commandInvocation);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -85,13 +88,13 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Com
} }
if (args.size() > 1) { if (args.size() > 1) {
throw new RuntimeException("Invalid option: " + args.get(1)); throw new IllegalArgumentException("Invalid option: " + args.get(1));
} }
String token = args.size() == 1 ? args.get(0) : null; String token = args.size() == 1 ? args.get(0) : null;
if (realm == null) { if (realm == null) {
throw new RuntimeException("Realm not specified"); throw new IllegalArgumentException("Realm not specified");
} }
if (token != null && token.startsWith("-")) { if (token != null && token.startsWith("-")) {
@ -138,6 +141,10 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Com
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help config initial-token' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -17,6 +17,7 @@ import java.util.List;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING; import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig; import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH; import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
@ -44,6 +45,9 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implement
} }
return process(commandInvocation); return process(commandInvocation);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -77,21 +81,21 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implement
} }
if (args.size() > 1) { if (args.size() > 1) {
throw new RuntimeException("Invalid option: " + args.get(1)); throw new IllegalArgumentException("Invalid option: " + args.get(1));
} }
String token = args.size() == 1 ? args.get(0) : null; String token = args.size() == 1 ? args.get(0) : null;
if (server == null) { if (server == null) {
throw new RuntimeException("Required option not specified: --server"); throw new IllegalArgumentException("Required option not specified: --server");
} }
if (realm == null) { if (realm == null) {
throw new RuntimeException("Required option not specified: --realm"); throw new IllegalArgumentException("Required option not specified: --realm");
} }
if (clientId == null) { if (clientId == null) {
throw new RuntimeException("Required option not specified: --client"); throw new IllegalArgumentException("Required option not specified: --client");
} }
checkUnsupportedOptions( checkUnsupportedOptions(
@ -128,6 +132,10 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implement
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help config registration-token' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -17,6 +17,7 @@ import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFI
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig; import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret; import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH; import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
@ -44,6 +45,9 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
} }
return process(commandInvocation); return process(commandInvocation);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -77,7 +81,7 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
} }
if (args.size() > 1) { if (args.size() > 1) {
throw new RuntimeException("Invalid option: " + args.get(1)); throw new IllegalArgumentException("Invalid option: " + args.get(1));
} }
String truststore = null; String truststore = null;
@ -105,7 +109,7 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
if (!delete) { if (!delete) {
if (truststore == null) { if (truststore == null) {
throw new RuntimeException("No truststore specified"); throw new IllegalArgumentException("No truststore specified");
} }
if (!new File(truststore).isFile()) { if (!new File(truststore).isFile()) {
@ -121,10 +125,10 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
} else { } else {
if (truststore != null) { if (truststore != null) {
throw new RuntimeException("Option --delete is mutually exclusive with specifying a TRUSTSTORE"); throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
} }
if (trustPass != null) { if (trustPass != null) {
throw new RuntimeException("Options --trustpass and --delete are mutually exclusive"); throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
} }
store = null; store = null;
pass = null; pass = null;
@ -138,6 +142,10 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help config truststore' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -56,6 +56,7 @@ import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
import static org.keycloak.client.registration.cli.util.IoUtil.readFully; import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret; import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH; import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes; import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
@ -114,25 +115,25 @@ public class CreateCmd extends AbstractAuthOptionsCmd implements Command {
case "-s": case "-s":
case "--set": { case "--set": {
if (!it.hasNext()) { if (!it.hasNext()) {
throw new RuntimeException("Option " + option + " requires a value"); throw new IllegalArgumentException("Option " + option + " requires a value");
} }
String[] keyVal = parseKeyVal(it.next()); String[] keyVal = parseKeyVal(it.next());
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1])); attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
break; break;
} }
default: { default: {
throw new RuntimeException("Unsupported option: " + option); throw new IllegalArgumentException("Unsupported option: " + option);
} }
} }
} }
} }
if (file == null && attrs.size() == 0) { if (file == null && attrs.size() == 0) {
throw new RuntimeException("No file nor attribute values specified"); throw new IllegalArgumentException("No file nor attribute values specified");
} }
if (outputClient && returnClientId) { if (outputClient && returnClientId) {
throw new RuntimeException("Options -o and -i can't be used together"); throw new IllegalArgumentException("Options -o and -i are mutually exclusive");
} }
// if --token is specified read it // if --token is specified read it
@ -211,6 +212,8 @@ public class CreateCmd extends AbstractAuthOptionsCmd implements Command {
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -235,6 +238,9 @@ public class CreateCmd extends AbstractAuthOptionsCmd implements Command {
return noOptions() && regType == null && file == null && (args == null || args.size() == 0); return noOptions() && regType == null && file == null && (args == null || args.size() == 0);
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help create' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();

View file

@ -39,6 +39,7 @@ import static org.keycloak.client.registration.cli.util.HttpUtil.doDelete;
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode; import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr; import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
@ -61,11 +62,11 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
processGlobalOptions(); processGlobalOptions();
if (args == null || args.isEmpty()) { if (args == null || args.isEmpty()) {
throw new RuntimeException("CLIENT not specified"); throw new IllegalArgumentException("CLIENT not specified");
} }
if (args.size() > 1) { if (args.size() > 1) {
throw new RuntimeException("Invalid option: " + args.get(1)); throw new IllegalArgumentException("Invalid option: " + args.get(1));
} }
String clientId = args.get(0); String clientId = args.get(0);
@ -108,6 +109,8 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
}); });
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -118,6 +121,10 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
return noOptions() && (args == null || args.size() == 0); return noOptions() && (args == null || args.size() == 0);
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help delete' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -51,6 +51,7 @@ import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut; import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.IoUtil.readFully; import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
/** /**
@ -79,11 +80,11 @@ public class GetCmd extends AbstractAuthOptionsCmd {
processGlobalOptions(); processGlobalOptions();
if (args == null || args.isEmpty()) { if (args == null || args.isEmpty()) {
throw new RuntimeException("CLIENT not specified"); throw new IllegalArgumentException("CLIENT not specified");
} }
if (args.size() > 1) { if (args.size() > 1) {
throw new RuntimeException("Invalid option: " + args.get(1)); throw new IllegalArgumentException("Invalid option: " + args.get(1));
} }
String clientId = args.get(0); String clientId = args.get(0);
@ -170,6 +171,8 @@ public class GetCmd extends AbstractAuthOptionsCmd {
} }
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -180,6 +183,10 @@ public class GetCmd extends AbstractAuthOptionsCmd {
return noOptions() && endpoint == null && (args == null || args.size() == 0); return noOptions() && endpoint == null && (args == null || args.size() == 0);
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help get' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -62,6 +62,7 @@ import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.registration.cli.util.IoUtil.readFully; import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON; import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes; import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin; import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
@ -112,7 +113,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
if (args != null) { if (args != null) {
Iterator<String> it = args.iterator(); Iterator<String> it = args.iterator();
if (!it.hasNext()) { if (!it.hasNext()) {
throw new RuntimeException("CLIENT_ID not specified"); throw new IllegalArgumentException("CLIENT_ID not specified");
} }
clientId = it.next(); clientId = it.next();
@ -127,7 +128,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
case "-s": case "-s":
case "--set": { case "--set": {
if (!it.hasNext()) { if (!it.hasNext()) {
throw new RuntimeException("Option " + option + " requires a value"); throw new IllegalArgumentException("Option " + option + " requires a value");
} }
String[] keyVal = parseKeyVal(it.next()); String[] keyVal = parseKeyVal(it.next());
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1])); attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
@ -139,14 +140,14 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
break; break;
} }
default: { default: {
throw new RuntimeException("Unsupported option: " + option); throw new IllegalArgumentException("Unsupported option: " + option);
} }
} }
} }
} }
if (file == null && attrs.size() == 0) { if (file == null && attrs.size() == 0) {
throw new RuntimeException("No file nor attribute values specified"); throw new IllegalArgumentException("No file nor attribute values specified");
} }
// We have several options for update: // We have several options for update:
@ -318,6 +319,8 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -338,6 +341,10 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
return noOptions() && regType == null && file == null && (args == null || args.size() == 0); return noOptions() && regType == null && file == null && (args == null || args.size() == 0);
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help update' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -45,6 +45,7 @@ import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut; import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut; import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD; import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT; import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
/** /**
@ -67,7 +68,7 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
processGlobalOptions(); processGlobalOptions();
if (args == null || args.isEmpty()) { if (args == null || args.isEmpty()) {
throw new RuntimeException("CLIENT not specified"); throw new IllegalArgumentException("CLIENT not specified");
} }
String clientId = args.get(0); String clientId = args.get(0);
@ -127,6 +128,8 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
//System.out.println("Token updated for client " + clientId); //System.out.println("Token updated for client " + clientId);
return CommandResult.SUCCESS; return CommandResult.SUCCESS;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
} finally { } finally {
commandInvocation.stop(); commandInvocation.stop();
} }
@ -137,6 +140,10 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
return noOptions() && (args == null || args.size() == 0); return noOptions() && (args == null || args.size() == 0);
} }
protected String suggestHelp() {
return EOL + "Try '" + CMD + " help update-token' for more information";
}
protected String help() { protected String help() {
return usage(); return usage();
} }

View file

@ -39,6 +39,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.cli.KcRegExec.WORK_DIR;
import static org.keycloak.testsuite.cli.KcRegExec.execute; import static org.keycloak.testsuite.cli.KcRegExec.execute;
/** /**
@ -351,7 +352,7 @@ public abstract class AbstractCliTest extends AbstractKeycloakTest {
FileConfigHandler initCustomConfigFile() { FileConfigHandler initCustomConfigFile() {
String filename = UUID.randomUUID().toString() + ".config"; String filename = UUID.randomUUID().toString() + ".config";
File cfgFile = new File(KcRegExec.WORK_DIR + "/" + filename); File cfgFile = new File(WORK_DIR + "/" + filename);
FileConfigHandler handler = new FileConfigHandler(); FileConfigHandler handler = new FileConfigHandler();
handler.setConfigFile(cfgFile.getAbsolutePath()); handler.setConfigFile(cfgFile.getAbsolutePath());
return handler; return handler;
@ -363,7 +364,7 @@ public abstract class AbstractCliTest extends AbstractKeycloakTest {
File initTempFile(String extension, String content) throws IOException { File initTempFile(String extension, String content) throws IOException {
String filename = UUID.randomUUID().toString() + extension; String filename = UUID.randomUUID().toString() + extension;
File file = new File(KcRegExec.WORK_DIR + "/" + filename); File file = new File(WORK_DIR + "/" + filename);
if (content != null) { if (content != null) {
OutputStream os = new FileOutputStream(file); OutputStream os = new FileOutputStream(file);
os.write(content.getBytes(Charset.forName("iso_8859_1"))); os.write(content.getBytes(Charset.forName("iso_8859_1")));

View file

@ -8,6 +8,7 @@ import org.keycloak.testsuite.util.TempFileResource;
import java.io.IOException; import java.io.IOException;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL; import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.testsuite.cli.KcRegExec.execute; import static org.keycloak.testsuite.cli.KcRegExec.execute;
@ -25,18 +26,21 @@ public class KcRegConfigTest extends AbstractCliTest {
// without --server // without --server
KcRegExec exe = execute("config registration-token --config '" + configFile.getName() + "' "); KcRegExec exe = execute("config registration-token --config '" + configFile.getName() + "' ");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("error message", "Required option not specified: --server", exe.stderrLines().get(0)); Assert.assertEquals("error message", "Required option not specified: --server", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
// without --realm // without --realm
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth"); exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("error message", "Required option not specified: --realm", exe.stderrLines().get(0)); Assert.assertEquals("error message", "Required option not specified: --realm", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
// without --client // without --client
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test"); exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("error message", "Required option not specified: --client", exe.stderrLines().get(0)); Assert.assertEquals("error message", "Required option not specified: --client", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
// specify token on cmdline // specify token on cmdline
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test --client my_client NEWTOKEN"); exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test --client my_client NEWTOKEN");

View file

@ -5,7 +5,6 @@ import org.junit.Test;
import org.keycloak.client.registration.cli.config.ConfigData; import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.config.FileConfigHandler; import org.keycloak.client.registration.cli.config.FileConfigHandler;
import org.keycloak.client.registration.cli.config.RealmConfigData; import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.client.registration.cli.util.OsUtil;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testsuite.cli.KcRegExec; import org.keycloak.testsuite.cli.KcRegExec;
import org.keycloak.testsuite.util.TempFileResource; import org.keycloak.testsuite.util.TempFileResource;
@ -48,51 +47,51 @@ public class KcRegTest extends AbstractCliTest {
exe = execute("config"); exe = execute("config");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 1);
Assert.assertEquals("error message", Assert.assertEquals("error message",
"Sub-command required by '" + OsUtil.CMD + " config' - one of: 'credentials', 'truststore', 'initial-token', 'registration-token'", "Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore', 'initial-token', 'registration-token'",
exe.stderrLines().get(0)); exe.stderrLines().get(0));
exe = execute("config credentials"); exe = execute("config credentials");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]", exe.stdoutLines().get(0));
exe = execute("config initial-token"); exe = execute("config initial-token");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " config initial-token --server SERVER --realm REALM [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " config initial-token --server SERVER --realm REALM [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0));
exe = execute("config registration-token"); exe = execute("config registration-token");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " config registration-token --server SERVER --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " config registration-token --server SERVER --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0));
exe = execute("config truststore"); exe = execute("config truststore");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWOD] [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWOD] [ARGUMENTS]", exe.stdoutLines().get(0));
exe = execute("create"); exe = execute("create");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " create [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " create [ARGUMENTS]", exe.stdoutLines().get(0));
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0)); //Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
exe = execute("get"); exe = execute("get");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " get CLIENT [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " get CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
exe = execute("update"); exe = execute("update");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " update CLIENT [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " update CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0)); //Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
exe = execute("delete"); exe = execute("delete");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " delete CLIENT [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " delete CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
exe = execute("attrs"); exe = execute("attrs");
@ -103,7 +102,7 @@ public class KcRegTest extends AbstractCliTest {
exe = execute("update-token"); exe = execute("update-token");
assertExitCodeAndStdErrSize(exe, 1, 0); assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
Assert.assertEquals("help message", "Usage: " + OsUtil.CMD + " update-token CLIENT [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("help message", "Usage: " + CMD + " update-token CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
exe = execute("help"); exe = execute("help");
@ -150,7 +149,7 @@ public class KcRegTest extends AbstractCliTest {
exe = execute("config --help"); exe = execute("config --help");
assertExitCodeAndStdErrSize(exe, 0, 0); assertExitCodeAndStdErrSize(exe, 0, 0);
Assert.assertEquals("stdout first line", "Usage: " + OsUtil.CMD + " config SUB_COMMAND [ARGUMENTS]", exe.stdoutLines().get(0)); Assert.assertEquals("stdout first line", "Usage: " + CMD + " config SUB_COMMAND [ARGUMENTS]", exe.stdoutLines().get(0));
exe = execute("config credentials --help"); exe = execute("config credentials --help");
assertExitCodeAndStdErrSize(exe, 0, 0); assertExitCodeAndStdErrSize(exe, 0, 0);
@ -208,8 +207,9 @@ public class KcRegTest extends AbstractCliTest {
KcRegExec exe = execute("get my_client --nonexistent"); KcRegExec exe = execute("get my_client --nonexistent");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0)); Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help get' for more information", exe.stderrLines().get(1));
} }
@Test @Test
@ -229,8 +229,9 @@ public class KcRegTest extends AbstractCliTest {
*/ */
KcRegExec exe = execute("config credentials --realm master --user admin --password admin"); KcRegExec exe = execute("config credentials --realm master --user admin --password admin");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0)); Assert.assertEquals("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
} }
@Test @Test
@ -240,8 +241,9 @@ public class KcRegTest extends AbstractCliTest {
*/ */
KcRegExec exe = execute("config credentials --server " + serverUrl + " --user admin --password admin"); KcRegExec exe = execute("config credentials --server " + serverUrl + " --user admin --password admin");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0)); Assert.assertEquals("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
} }
@Test @Test

View file

@ -4,13 +4,14 @@ import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.client.registration.cli.config.ConfigData; import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.config.FileConfigHandler; import org.keycloak.client.registration.cli.config.FileConfigHandler;
import org.keycloak.client.registration.cli.util.ConfigUtil;
import org.keycloak.testsuite.cli.KcRegExec; import org.keycloak.testsuite.cli.KcRegExec;
import org.keycloak.testsuite.util.TempFileResource; import org.keycloak.testsuite.util.TempFileResource;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_PATH;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.EOL; import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
import static org.keycloak.testsuite.cli.KcRegExec.execute; import static org.keycloak.testsuite.cli.KcRegExec.execute;
@ -90,15 +91,17 @@ public class KcRegTruststoreTest extends AbstractCliTest {
assertExitCodeAndStreamSizes(exe, 0, 0, 0); assertExitCodeAndStreamSizes(exe, 0, 0, 0);
exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'"); exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0)); Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
exe = execute("config truststore --delete --trustpass secret"); exe = execute("config truststore --delete --trustpass secret");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0)); Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
FileConfigHandler cfghandler = new FileConfigHandler(); FileConfigHandler cfghandler = new FileConfigHandler();
cfghandler.setConfigFile(ConfigUtil.DEFAULT_CONFIG_FILE_PATH); cfghandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
ConfigData config = cfghandler.loadConfig(); ConfigData config = cfghandler.loadConfig();
Assert.assertNull("truststore null", config.getTruststore()); Assert.assertNull("truststore null", config.getTruststore());
Assert.assertNull("trustpass null", config.getTrustpass()); Assert.assertNull("trustpass null", config.getTrustpass());

View file

@ -12,6 +12,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.testsuite.cli.KcRegExec.execute; import static org.keycloak.testsuite.cli.KcRegExec.execute;
/** /**
@ -90,9 +91,9 @@ public class KcRegUpdateTest extends AbstractCliTest {
// check that using an invalid attribute key is not ignored // check that using an invalid attribute key is not ignored
exe = execute("update my_client --nonexisting --config '" + configFile.getName() + "'"); exe = execute("update my_client --nonexisting --config '" + configFile.getName() + "'");
assertExitCodeAndStreamSizes(exe, 1, 0, 1); assertExitCodeAndStreamSizes(exe, 1, 0, 2);
Assert.assertEquals("error message", "Unsupported option: --nonexisting", exe.stderrLines().get(0)); Assert.assertEquals("error message", "Unsupported option: --nonexisting", exe.stderrLines().get(0));
Assert.assertEquals("try help", "Try '" + CMD + " help update' for more information", exe.stderrLines().get(1));
// try use incompatible endpoint // try use incompatible endpoint