Skip to content

Commit

Permalink
IGNITE-23459 Add console input if argument presented without value fo…
Browse files Browse the repository at this point in the history
…r ./control.sh
  • Loading branch information
Положаев Денис Александрович committed Jan 27, 2025
1 parent fce02b4 commit 6b9c352
Show file tree
Hide file tree
Showing 13 changed files with 381 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* limitations under the License.
*/


package org.apache.ignite.internal.commandline;

import java.lang.reflect.Field;
Expand Down Expand Up @@ -50,7 +49,7 @@
import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_PORT;
import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
import static org.apache.ignite.internal.commandline.argument.parser.CLIArgument.optionalArg;
import static org.apache.ignite.internal.commandline.argument.parser.CLIArgument.command;
import static org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
import static org.apache.ignite.internal.management.api.CommandUtils.NAME_PREFIX;
import static org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
Expand Down Expand Up @@ -143,6 +142,9 @@ public class ArgumentParser {
/** */
private final List<CLIArgument<?>> common = new ArrayList<>();

/** Console instance */
protected final GridConsole console;

static {
SENSITIVE_ARGUMENTS.add(CMD_PASSWORD);
SENSITIVE_ARGUMENTS.add(CMD_KEYSTORE_PASSWORD);
Expand All @@ -160,47 +162,49 @@ public static boolean isSensitiveArgument(String arg) {
/**
* @param log Logger.
* @param registry Supported commands.
* @param console Supported commands.
*/
public ArgumentParser(IgniteLogger log, IgniteCommandRegistry registry) {
public ArgumentParser(IgniteLogger log, IgniteCommandRegistry registry, GridConsole console) {
this.log = log;
this.registry = registry;
this.console = console;

BiConsumer<String, ?> securityWarn = (name, val) -> log.info(String.format("Warning: %s is insecure. " +
"Whenever possible, use interactive prompt for password (just discard %s option).", name, name));

arg(CMD_HOST, "HOST_OR_IP", String.class, DFLT_HOST);
arg(CMD_PORT, "PORT", Integer.class, DFLT_PORT, PORT_VALIDATOR);
arg(CMD_USER, "USER", String.class, null);
arg(CMD_PASSWORD, "PASSWORD", String.class, null, (BiConsumer<String, String>)securityWarn);
arg(CMD_VERBOSE, CMD_VERBOSE, boolean.class, false);
arg(CMD_SSL_PROTOCOL, "SSL_PROTOCOL[, SSL_PROTOCOL_2, ..., SSL_PROTOCOL_N]", String[].class, new String[] {DFLT_SSL_PROTOCOL});
arg(CMD_SSL_CIPHER_SUITES, "SSL_CIPHER_1[, SSL_CIPHER_2, ..., SSL_CIPHER_N]", String[].class, null);
arg(CMD_SSL_KEY_ALGORITHM, "SSL_KEY_ALGORITHM", String.class, SslContextFactory.DFLT_KEY_ALGORITHM);
arg(CMD_SSL_FACTORY, "SSL_FACTORY_PATH", String.class, null);
arg(CMD_KEYSTORE_TYPE, "KEYSTORE_TYPE", String.class, SslContextFactory.DFLT_STORE_TYPE);
arg(CMD_KEYSTORE, "KEYSTORE_PATH", String.class, null);
arg(CMD_KEYSTORE_PASSWORD, "KEYSTORE_PASSWORD", char[].class, null, (BiConsumer<String, char[]>)securityWarn);
arg(CMD_TRUSTSTORE_TYPE, "TRUSTSTORE_TYPE", String.class, SslContextFactory.DFLT_STORE_TYPE);
arg(CMD_TRUSTSTORE, "TRUSTSTORE_PATH", String.class, null);
arg(CMD_TRUSTSTORE_PASSWORD, "TRUSTSTORE_PASSWORD", char[].class, null, (BiConsumer<String, char[]>)securityWarn);
arg(CMD_AUTO_CONFIRMATION, CMD_AUTO_CONFIRMATION, boolean.class, false);
arg(
CMD_ENABLE_EXPERIMENTAL,
CMD_ENABLE_EXPERIMENTAL, Boolean.class,
IgniteSystemProperties.getBoolean(IGNITE_ENABLE_EXPERIMENTAL_COMMAND)
common.addAll(List.of(
command().withName(CMD_HOST).withUsage("HOST_OR_IP").withIsOptional(true).withType(String.class)
.withDfltValSupplier(t -> DFLT_HOST).build(),
command().withName(CMD_PORT).withUsage("PORT").withIsOptional(true).withType(Integer.class)
.withDfltValSupplier(t -> DFLT_PORT).withValidator(PORT_VALIDATOR).build(),
command().withName(CMD_USER).withUsage("USER").withIsOptional(true).withType(String.class).build(),
command().withName(CMD_PASSWORD).withUsage("PASSWORD").withIsOptional(true).withType(String.class)
.withValidator(securityWarn).withInteractive(true).build(),
command().withName(CMD_VERBOSE).withUsage(CMD_VERBOSE).withIsOptional(true).withType(boolean.class).build(),
command().withName(CMD_SSL_PROTOCOL).withUsage("SSL_PROTOCOL[, SSL_PROTOCOL_2, ..., SSL_PROTOCOL_N]")
.withIsOptional(true).withType(String[].class).withDfltValSupplier(t -> new String[] {DFLT_SSL_PROTOCOL}).build(),
command().withName(CMD_SSL_CIPHER_SUITES).withUsage("SSL_CIPHER_1[, SSL_CIPHER_2, ..., SSL_CIPHER_N]")
.withIsOptional(true).withType(String[].class).build(),
command().withName(CMD_SSL_KEY_ALGORITHM).withUsage("SSL_KEY_ALGORITHM").withIsOptional(true)
.withType(String.class).withDfltValSupplier(t -> SslContextFactory.DFLT_KEY_ALGORITHM).build(),
command().withName(CMD_SSL_FACTORY).withUsage("SSL_FACTORY_PATH").withIsOptional(true).withType(String.class).build(),
command().withName(CMD_KEYSTORE_TYPE).withUsage("KEYSTORE_TYPE").withIsOptional(true).withType(String.class)
.withDfltValSupplier(t -> SslContextFactory.DFLT_STORE_TYPE).build(),
command().withName(CMD_KEYSTORE).withUsage("KEYSTORE_PATH").withIsOptional(true).withType(String.class).build(),
command().withName(CMD_KEYSTORE_PASSWORD).withUsage("KEYSTORE_PASSWORD").withIsOptional(true)
.withType(char[].class).withValidator(securityWarn).withInteractive(true).build(),
command().withName(CMD_TRUSTSTORE_TYPE).withUsage("TRUSTSTORE_TYPE").withIsOptional(true)
.withType(String.class).withDfltValSupplier(t -> SslContextFactory.DFLT_STORE_TYPE).build(),
command().withName(CMD_TRUSTSTORE).withUsage("TRUSTSTORE_PATH").withIsOptional(true).withType(String.class).build(),
command().withName(CMD_TRUSTSTORE_PASSWORD).withUsage("TRUSTSTORE_PASSWORD").withIsOptional(true)
.withType(char[].class).withValidator(securityWarn).withInteractive(true).build(),
command().withName(CMD_AUTO_CONFIRMATION).withUsage(CMD_AUTO_CONFIRMATION).withIsOptional(true)
.withType(boolean.class).build(),
command().withName(CMD_ENABLE_EXPERIMENTAL).withUsage(CMD_ENABLE_EXPERIMENTAL).withIsOptional(true).withType(Boolean.class)
.withDfltValSupplier(t -> IgniteSystemProperties.getBoolean(IGNITE_ENABLE_EXPERIMENTAL_COMMAND)).build())
);
}

/** */
private <T> void arg(String name, String usage, Class<T> type, T dflt, BiConsumer<String, T> validator) {
common.add(optionalArg(name, usage, type, t -> dflt, validator));
}

/** */
private <T> void arg(String name, String usage, Class<T> type, T dflt) {
common.add(optionalArg(name, usage, type, () -> dflt));
}

/**
* Creates list of common utility options.
*
Expand Down Expand Up @@ -236,7 +240,7 @@ public <A extends IgniteDataTransferObject> ConnectionAndSslParameters<A> parseA

CLIArgumentParser parser = createArgumentParser();

parser.parse(args.iterator());
parser.parse(args.listIterator());

A arg = (A)argument(
cmdPath.peek().argClass(),
Expand Down Expand Up @@ -323,14 +327,13 @@ private CLIArgumentParser createArgumentParser() {
List<CLIArgument<?>> positionalArgs = new ArrayList<>();
List<CLIArgument<?>> namedArgs = new ArrayList<>();

BiFunction<Field, Boolean, CLIArgument<?>> toArg = (fld, optional) -> new CLIArgument<>(
toFormattedFieldName(fld).toLowerCase(),
null,
optional,
fld.getType(),
null,
(name, val) -> {}
);
BiFunction<Field, Boolean, CLIArgument<?>> toArg = (fld, optional) -> command()
.withName(toFormattedFieldName(fld).toLowerCase())
.withIsOptional(optional)
.withType(fld.getType())
.withValidator((name, val) -> {})
.withInteractive(fld.getAnnotation(Argument.class).interactive())
.build();

List<Set<String>> grpdFlds = CommandUtils.argumentGroupsValues(cmdPath.peek().argClass());

Expand All @@ -339,14 +342,13 @@ private CLIArgumentParser createArgumentParser() {
|| fld.getAnnotation(Argument.class).optional())
);

Consumer<Field> positionalArgCb = fld -> positionalArgs.add(new CLIArgument<>(
fld.getName().toLowerCase(),
null,
fld.getAnnotation(Argument.class).optional(),
fld.getType(),
null,
(name, val) -> {}
));
Consumer<Field> positionalArgCb = fld -> positionalArgs.add(command()
.withName(fld.getName().toLowerCase())
.withIsOptional(fld.getAnnotation(Argument.class).optional())
.withType(fld.getType())
.withValidator((name, val) -> {})
.build()
);

BiConsumer<ArgumentGroup, List<Field>> argGrpCb = (argGrp0, flds) -> flds.forEach(fld -> {
if (fld.isAnnotationPresent(Positional.class))
Expand All @@ -359,6 +361,6 @@ private CLIArgumentParser createArgumentParser() {

namedArgs.addAll(common);

return new CLIArgumentParser(positionalArgs, namedArgs);
return new CLIArgumentParser(positionalArgs, namedArgs, console);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public <A extends IgniteDataTransferObject> int execute(List<String> rawArgs) {

verbose = F.exist(rawArgs, CMD_VERBOSE::equalsIgnoreCase);

ConnectionAndSslParameters<A> args = new ArgumentParser(logger, registry).parseAndValidate(rawArgs);
ConnectionAndSslParameters<A> args = new ArgumentParser(logger, registry, console).parseAndValidate(rawArgs);

cmdName = toFormattedCommandName(args.cmdPath().peekLast().getClass()).toUpperCase();

Expand Down Expand Up @@ -651,7 +651,8 @@ private void printHelp(List<String> rawArgs) {
"The command has the following syntax:");
logger.info("");

logger.info(INDENT + join(" ", join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, registry).getCommonOptions())),
logger.info(INDENT + join(" ",
join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, registry, null).getCommonOptions())),
asOptional("command", true), "<command_parameters>"));
logger.info("");
logger.info("");
Expand Down Expand Up @@ -717,8 +718,8 @@ private void printCacheHelpHeader(IgniteLogger logger) {
logger.info(INDENT + "The '--cache subcommand' is used to get information about and perform actions" +
" with caches. The command has the following syntax:");
logger.info("");
logger.info(INDENT + join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, null).getCommonOptions())) + " " +
"--cache [subcommand] <subcommand_parameters>");
logger.info(INDENT + join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, null, null).getCommonOptions())) +
" " + "--cache [subcommand] <subcommand_parameters>");
logger.info("");
logger.info(INDENT + "The subcommands that take [nodeId] as an argument ('list', 'find_garbage', " +
"'contention' and 'validate_indexes') will be executed on the given node or on all server nodes" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* Command line argument.
Expand Down Expand Up @@ -48,48 +47,19 @@ public class CLIArgument<T> {
private final BiConsumer<String, T> validator;

/** */
public static <T> CLIArgument<T> optionalArg(String name, String usage, Class<T> type) {
return new CLIArgument<>(name, usage, true, type, null, null);
}

/** */
public static <T> CLIArgument<T> optionalArg(String name, String usage, Class<T> type, Supplier<T> dfltValSupplier) {
return new CLIArgument<>(name, usage, true, type, p -> dfltValSupplier.get(), null);
}

/** */
public static <T> CLIArgument<T> optionalArg(
String name,
String usage,
Class<T> type,
Function<CLIArgumentParser, T> dfltValSupplier,
BiConsumer<String, T> validator
) {
return new CLIArgument<>(name, usage, true, type, dfltValSupplier, validator);
}

/** */
public static <T> CLIArgument<T> mandatoryArg(String name, String usage, Class<T> type) {
return new CLIArgument<>(name, usage, false, type, null, null);
}
private final boolean isConsoleReq;

/** */
public CLIArgument(
String name,
String usage,
boolean isOptional,
Class<T> type,
Function<CLIArgumentParser, T> dfltValSupplier,
BiConsumer<String, T> validator
private CLIArgument(
CLIArgumentBuilder<T> builder
) {
this.name = name;
this.usage = usage;
this.isOptional = isOptional;
this.type = type;
this.dfltValSupplier = dfltValSupplier == null
? (type.equals(Boolean.class) ? p -> (T)Boolean.FALSE : p -> null)
: dfltValSupplier;
this.validator = validator;
name = builder.name;
usage = builder.usage;
isOptional = builder.isOptional;
type = builder.type;
dfltValSupplier = builder.dfltValSupplier;
validator = builder.validator;
isConsoleReq = builder.interactive;
}

/** */
Expand Down Expand Up @@ -121,4 +91,107 @@ public Function<CLIArgumentParser, T> defaultValueSupplier() {
public BiConsumer<String, T> validator() {
return validator == null ? EMPTY : validator;
}

/** */
public boolean isConsoleReq() {
return isConsoleReq;
}

/** */
public boolean isFlag() {
return type.equals(Boolean.class) || type.equals(boolean.class);
}

/** */
public static CLIArgumentBuilder<?> command() {
return new CLIArgumentBuilder<>();
}

/**
* Command line argument builder.
* @param <T> Value type.
*/
public static class CLIArgumentBuilder<T> {
/** */
private String name;

/** */
private String usage;

/** */
private boolean isOptional;

/** */
private Class<T> type;

/** */
private Function<CLIArgumentParser, T> dfltValSupplier;

/** */
private BiConsumer<String, T> validator;

/** */
private boolean interactive;

/** */
public CLIArgumentBuilder<T> withName(String name) {
this.name = name;
return this;
}

/** */
public CLIArgumentBuilder<T> withUsage(String usage) {
this.usage = usage;
return this;
}

/** */
public CLIArgumentBuilder<T> withIsOptional(boolean isOptional) {
this.isOptional = isOptional;
return this;
}

/** */
public CLIArgumentBuilder<T> withType(Class<?> type) {
this.type = (Class<T>)type;
return this;
}

/** */
public CLIArgumentBuilder<T> withDfltValSupplier(Function<CLIArgumentParser, ?> dfltValSupplier) {
this.dfltValSupplier = (Function<CLIArgumentParser, T>)dfltValSupplier;
return this;
}

/** */
public CLIArgumentBuilder<T> withValidator(BiConsumer<String, ?> validator) {
this.validator = (BiConsumer<String, T>)validator;
return this;
}

/** */
public CLIArgumentBuilder<T> withInteractive(boolean interactive) {
this.interactive = interactive;
return this;
}

/** */
public CLIArgument<T> build() {
assert name != null;
assert type != null;

if (isFlag() && interactive)
throw new IllegalArgumentException("Flag argument can't be interactive");

if (dfltValSupplier == null)
dfltValSupplier = isFlag() ? p -> (T)Boolean.FALSE : p -> null;

return new CLIArgument<>(this);
}

/** */
private boolean isFlag() {
return type.equals(Boolean.class) || type.equals(boolean.class);
}
}
}
Loading

0 comments on commit 6b9c352

Please sign in to comment.