diff --git a/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/ClientMain.java b/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/ClientMain.java index 9ccd89b..7589ba2 100644 --- a/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/ClientMain.java +++ b/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/ClientMain.java @@ -1,10 +1,14 @@ package com.alibaba.nacos.ctl.bootstrap; +import com.alibaba.nacos.ctl.bootstrap.command.NacosBootstrapCommand; +import com.alibaba.nacos.ctl.command.NacosCommand; import com.alibaba.nacos.ctl.command.NacosCtl; +import com.alibaba.nacos.ctl.command.spi.NacosCommandLoader; import com.alibaba.nacos.ctl.core.LogicHandler; import com.alibaba.nacos.ctl.core.exception.HandlerException; import com.alibaba.nacos.ctl.intraction.input.InputGetter; import com.alibaba.nacos.ctl.bootstrap.utils.StringUtils; +import jline.console.UserInterruptException; import picocli.CommandLine; /** @@ -16,13 +20,19 @@ public class ClientMain { public static void main(String[] args) throws HandlerException { - System.out.println("NacosCtl Loading...\n"); + System.out.println("NacosCtl Loading..."); InputGetter.init(); + System.out.println("NacosCtl Load finished.\n"); - System.out.println("Loading Nacos client sdk...\n"); + System.out.println("Loading Nacos client sdk..."); LogicHandler.init(); + System.out.println("Loading Nacos client sdk finished.\n"); + + System.out.println("Loading Extension commands..."); + NacosCommandLoader.getInstance().loadCommands(); + System.out.println("Loading Extension commands finish\n"); - new CommandLine(new NacosCtl()).execute(args); + new CommandLine(new NacosBootstrapCommand()).execute(args); loopExecute(InputGetter.getInstance()); } @@ -30,24 +40,32 @@ public static void main(String[] args) throws HandlerException { private static void loopExecute(InputGetter in) { String[] args; + CommandLine commandLine = new CommandLine(new NacosCtl()); + for (NacosCommand each : NacosCommandLoader.getInstance().getLoadedCommands()) { + commandLine.getCommandSpec().addSubcommand(each.getCommandName(), new CommandLine(each)); + } // 循环执行命令 while (true) { - - String line = in.nextLine(); - args = StringUtils.parseInput(line); - // 忽略无效输入 - if (args.length < 1 || args[0].length() < 1) { - continue; - } - // 给Picocli执行命令 - int ret = new CommandLine(new NacosCtl()).execute(args); - // 特殊的流程控制,通过返回值来判断,-1是退出,-2是清屏 - if (ret == -1) { - break; - } - if (ret == -2) { - in.clear(); + + try { + String line = in.nextLine(); + args = StringUtils.parseInput(line); + // 忽略无效输入 + if (args.length < 1 || args[0].length() < 1) { + continue; + } + // 给Picocli执行命令 + + int ret = commandLine.execute(args); + // 特殊的流程控制,通过返回值来判断,-1是退出,-2是清屏 + if (ret == -1) { + break; + } + if (ret == -2) { + in.clear(); + } + } catch (UserInterruptException ignored) { } } } diff --git a/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/command/NacosBootstrapCommand.java b/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/command/NacosBootstrapCommand.java new file mode 100644 index 0000000..e17bcca --- /dev/null +++ b/bootstrap/src/main/java/com/alibaba/nacos/ctl/bootstrap/command/NacosBootstrapCommand.java @@ -0,0 +1,75 @@ +package com.alibaba.nacos.ctl.bootstrap.command; + +import com.alibaba.nacos.ctl.command.NacosClear; +import com.alibaba.nacos.ctl.command.NacosQuit; +import com.alibaba.nacos.ctl.core.LogicHandler; +import com.alibaba.nacos.ctl.core.config.ConfigLoader; +import com.alibaba.nacos.ctl.core.config.GlobalConfig; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.ctl.command.utils.HintUtils.APP_NAME; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.CLI_DESCRIPTION; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.COMMAND_LIST_HEADING; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.FOOTER; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.GREETING; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.MIXIN_STANDARD_HELP_OPTIONS; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.VERSION_NAME; + +/** + * the main command, based on picocli + * + * @author lehr + */ +@Command(name = APP_NAME, mixinStandardHelpOptions = MIXIN_STANDARD_HELP_OPTIONS, version = VERSION_NAME, description = CLI_DESCRIPTION, commandListHeading = COMMAND_LIST_HEADING, footer = FOOTER, subcommands = { + CommandLine.HelpCommand.class, NacosQuit.class, NacosClear.class}) +public class NacosBootstrapCommand implements Runnable { + + @CommandLine.Option(names = {"-e", + "--endpoint"}, paramLabel = "", description = "The Nacos-Server host Ip.") + private String host; + + @CommandLine.Option(names = {"-p", "--port"}, paramLabel = "", description = "The port of Nacos-Server.") + private Integer port; + + @CommandLine.Option(names = {"-u", + "--username"}, paramLabel = "", description = "Nacos authentication username.") + private String username; + + @CommandLine.Option(names = {"-pswd", + "--password"}, paramLabel = "", description = "Nacos password username.") + private String password; + + @CommandLine.Option(names = {"-ak", "--accessKey"}, paramLabel = "", description = "Nacos access key.") + private String accessKey; + + @CommandLine.Option(names = {"-sk", "--secretKey"}, paramLabel = "", description = "Nacos secret key.") + private String secretKey; + + @Override + public void run() { + System.out.println(GREETING); + Map confs = new HashMap<>(); + for (Field f : this.getClass().getDeclaredFields()) { + if (f.isAnnotationPresent(CommandLine.Option.class)) { + try { + Object o = f.get(this); + if (o != null) { + confs.put(f.getName(), o.toString()); + } + } catch (Exception e) { + //todo + } + } + } + if (!confs.isEmpty()) { + ConfigLoader.preload(confs); + GlobalConfig.getInstance().refresh(); + LogicHandler.refresh(); + } + } +} diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCommand.java b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCommand.java new file mode 100644 index 0000000..6bc671a --- /dev/null +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCommand.java @@ -0,0 +1,26 @@ +package com.alibaba.nacos.ctl.command; + +import picocli.CommandLine; + +/** + * Nacos command. + * + * @author xiweng.yy + */ +public abstract class NacosCommand implements Runnable { + + @CommandLine.Spec + CommandLine.Model.CommandSpec spec; + + /** + * Get command name. + * + * @return command name + */ + public abstract String getCommandName(); + + @Override + public void run() { + spec.commandLine().usage(System.err); + } +} diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCtl.java b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCtl.java index dcf1ad1..9f56d90 100644 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCtl.java +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosCtl.java @@ -5,19 +5,13 @@ import com.alibaba.nacos.ctl.command.namespace.NacosNamespace; import com.alibaba.nacos.ctl.command.service.NacosService; import com.alibaba.nacos.ctl.command.switches.NacosSwitch; -import com.alibaba.nacos.ctl.core.config.ConfigLoader; import picocli.CommandLine; import picocli.CommandLine.Command; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - import static com.alibaba.nacos.ctl.command.utils.HintUtils.APP_NAME; import static com.alibaba.nacos.ctl.command.utils.HintUtils.CLI_DESCRIPTION; import static com.alibaba.nacos.ctl.command.utils.HintUtils.COMMAND_LIST_HEADING; import static com.alibaba.nacos.ctl.command.utils.HintUtils.FOOTER; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.GREETING; import static com.alibaba.nacos.ctl.command.utils.HintUtils.MIXIN_STANDARD_HELP_OPTIONS; import static com.alibaba.nacos.ctl.command.utils.HintUtils.VERSION_NAME; @@ -29,49 +23,13 @@ @Command(name = APP_NAME, mixinStandardHelpOptions = MIXIN_STANDARD_HELP_OPTIONS, version = VERSION_NAME, description = CLI_DESCRIPTION, commandListHeading = COMMAND_LIST_HEADING, footer = FOOTER, subcommands = { CommandLine.HelpCommand.class, NacosQuit.class, NacosClear.class, NacosConfig.class, NacosNamespace.class, NacosInstance.class, NacosSwitch.class, NacosService.class, - // NacosMetrics.class, NacosUse.class, // NacosWatch.class, }) -public class NacosCtl implements Runnable { - - @CommandLine.Option(names = {"-e", - "--endpoint"}, paramLabel = "", description = "The Nacos-Server host Ip.") - private String host; - - @CommandLine.Option(names = {"-p", "--port"}, paramLabel = "", description = "The port of Nacos-Server.") - private Integer port; - - @CommandLine.Option(names = {"-u", - "--username"}, paramLabel = "", description = "Nacos authentication username.") - private String username; - - @CommandLine.Option(names = {"-pswd", - "--password"}, paramLabel = "", description = "Nacos password username.") - private String password; - - @CommandLine.Option(names = {"-ak", "--accessKey"}, paramLabel = "", description = "Nacos access key.") - private String accessKey; - - @CommandLine.Option(names = {"-sk", "--secretKey"}, paramLabel = "", description = "Nacos secret key.") - private String secretKey; +public class NacosCtl extends NacosCommand { @Override - public void run() { - System.out.println(GREETING); - Map confs = new HashMap<>(); - for (Field f : this.getClass().getDeclaredFields()) { - if (f.isAnnotationPresent(CommandLine.Option.class)) { - try { - Object o = f.get(this); - if (o != null) { - confs.put(f.getName(), o.toString()); - } - } catch (Exception e) { - //todo - } - } - } - ConfigLoader.preload(confs); + public String getCommandName() { + return APP_NAME; } } diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosMetrics.java b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosMetrics.java deleted file mode 100644 index b606c7d..0000000 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosMetrics.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.alibaba.nacos.ctl.command; - -import com.alibaba.nacos.ctl.core.LogicHandler; -import com.alibaba.nacos.ctl.core.exception.HandlerException; -import picocli.CommandLine; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Stream; - -import static com.alibaba.nacos.ctl.command.utils.HintUtils.DESCRIPTION_HEADING; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.DESCRIPTION_METRICS; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.HEADER_HEADING; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.NAME_METRICS; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.OPTION_LIST_HEADING; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.PARAMETER_LIST_HEADING; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.SORT_OPTIONS; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.SYNOPSIS_HEADING; -import static com.alibaba.nacos.ctl.command.utils.HintUtils.USAGE_METRICS; - -/** - * get metrics data from nacos prometheus http port - * - * @author lehr - */ -@CommandLine.Command(name = NAME_METRICS, sortOptions = SORT_OPTIONS, headerHeading = HEADER_HEADING, synopsisHeading = SYNOPSIS_HEADING, descriptionHeading = DESCRIPTION_HEADING, parameterListHeading = PARAMETER_LIST_HEADING, optionListHeading = OPTION_LIST_HEADING, header = USAGE_METRICS, description = DESCRIPTION_METRICS, subcommands = { - CommandLine.HelpCommand.class}) -@Deprecated -public class NacosMetrics implements Runnable { - - @CommandLine.Parameters(paramLabel = "", description = "The module name of the metrics") - public Module module; - - @Override - public void run() { - - try { - Stream metrics = LogicHandler.getMetrics(); - metrics.map(this::mapModule).filter(Objects::nonNull).sorted().forEachOrdered(System.out::println); - } catch (HandlerException e) { - System.out.println(e.getMessage()); - } - } - - private enum Module { - /** - * nacos 客户端模块参数 - */ - CLIENT, - /** - * nacos server的参数 - */ - NACOS, - /** - * jvm和gc相关信息 - */ - JVM, - /** - * 异常信息 - */ - EXCEPTION - } - - /** - * translate metrics name to chinese. - * - * @param s - * @return - */ - public String mapModule(String s) { - - String backup = s; - - Map map = enumMap.get(module); - for (Map.Entry e : map.entrySet()) { - if (s.startsWith(e.getKey())) { - s = s.replace(e.getKey(), e.getValue()); - break; - } - } - - if (backup.equals(s)) { - return null; - } else { - return s; - } - } - - private static Map clientMap; - - private static Map exceptionMap; - - private static Map nacosMap; - - private static Map jvmMap; - - private static Map> enumMap = new HashMap<>(); - - static { - jvmMap = new HashMap<>(); - jvmMap.put("system_cpu_usage", "CPU使用率"); - jvmMap.put("system_load_average_1m", "CPU负载"); - jvmMap.put("jvm_memory_used_bytes", "内存使用字节"); - jvmMap.put("jvm_memory_max_bytes", "内存最大字节"); - jvmMap.put("jvm_gc_pause_seconds_count", "GC次数"); - jvmMap.put("jvm_gc_pause_seconds_sum", "GC耗时"); - jvmMap.put("jvm_threads_daemon", "线程数"); - - clientMap = new HashMap<>(); - clientMap.put("nacos_monitor{name='subServiceCount'}", "订阅的服务数"); - clientMap.put("nacos_monitor{name='pubServiceCount'}", "发布的服务数"); - clientMap.put("nacos_monitor{name='configListenSize'}", "监听的配置数"); - clientMap.put("nacos_client_request_seconds_count", "请求次数"); - clientMap.put("nacos_client_request_seconds_sum", "请求总耗时"); - - nacosMap = new HashMap<>(); - nacosMap.put("http_server_requests_seconds_count", "http请求次数"); - nacosMap.put("http_server_requests_seconds_sum", "http请求总耗时"); - nacosMap.put("nacos_timer_seconds_sum", "Nacos config水平通知耗时"); - nacosMap.put("nacos_timer_seconds_count", "Nacos config水平通知次数"); - nacosMap.put("nacos_monitor{module=\"config\",name=\"longPolling\",}", "Nacos config长连接数"); - nacosMap.put("nacos_monitor{module=\"config\",name=\"configCount\",}", "Nacos config配置个数"); - nacosMap.put("nacos_monitor{module=\"config\",name=\"dumpTask\",}", "Nacos config配置落盘任务堆积数"); - nacosMap.put("nacos_monitor{module=\"config\",name=\"notifyTask\",}", "Nacos config配置水平通知任务堆积数"); - nacosMap.put("nacos_monitor{module=\"config\",name=\"getConfig\",}", "Nacos config读配置统计数"); - nacosMap.put("nacos_monitor{module=\"config\",name=\"publish\",}", "Nacos config写配置统计数"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"ipCount\",}", "Nacos naming ip个数"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"domCount\",}", "Nacos naming域名个数"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"failedPush\",}", "Nacos naming推送失败数"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"avgPushCost\",}", "Nacos naming平均推送耗时"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"leaderStatus\",}", "Nacos naming角色状态"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"maxPushCost\",}", "Nacos naming最大推送耗时"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"mysqlhealthCheck\",}", "Nacos naming mysql健康检查次数"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"httpHealthCheck\",}", "Nacos naming http健康检查次数"); - nacosMap.put("nacos_monitor{module=\"naming\",name=\"tcpHealthCheck\",}", "Nacos naming tcp健康检查次数"); - - exceptionMap = new HashMap<>(); - exceptionMap.put("nacos_exception_total{name='db'}", "数据库异常"); - exceptionMap.put("nacos_exception_total{name='configNotify'}", "Nacos config水平通知失败"); - exceptionMap.put("nacos_exception_total{name='unhealth'}", "Nacos config server之间健康检查异常"); - exceptionMap.put("nacos_exception_total{name='disk'}", "Nacos naming写磁盘异常"); - exceptionMap.put("nacos_exception_total{name='leaderSendBeatFailed'}", "Nacos naming leader发送心跳异常"); - exceptionMap.put("nacos_exception_total{name='illegalArgument'}", "请求参数不合法"); - exceptionMap.put("nacos_exception_total{name='nacos'}", "Nacos请求响应内部错误异常"); - - enumMap.put(Module.NACOS, nacosMap); - enumMap.put(Module.JVM, jvmMap); - enumMap.put(Module.EXCEPTION, exceptionMap); - enumMap.put(Module.CLIENT, clientMap); - } -} diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosQuit.java b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosQuit.java index 961d2a1..282af1a 100644 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/NacosQuit.java +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/NacosQuit.java @@ -10,6 +10,7 @@ import static com.alibaba.nacos.ctl.command.utils.HintUtils.DESCRIPTION_QUIT; import static com.alibaba.nacos.ctl.command.utils.HintUtils.HEADER_HEADING; import static com.alibaba.nacos.ctl.command.utils.HintUtils.NAME_QUIT; +import static com.alibaba.nacos.ctl.command.utils.HintUtils.NAME_QUIT_ALIAS; import static com.alibaba.nacos.ctl.command.utils.HintUtils.OPTION_LIST_HEADING; import static com.alibaba.nacos.ctl.command.utils.HintUtils.PARAMETER_LIST_HEADING; import static com.alibaba.nacos.ctl.command.utils.HintUtils.SORT_OPTIONS; @@ -21,7 +22,7 @@ * * @author lehr */ -@CommandLine.Command(name = NAME_QUIT, sortOptions = SORT_OPTIONS, headerHeading = HEADER_HEADING, synopsisHeading = SYNOPSIS_HEADING, descriptionHeading = DESCRIPTION_HEADING, parameterListHeading = PARAMETER_LIST_HEADING, optionListHeading = OPTION_LIST_HEADING, header = USAGE_QUIT, description = DESCRIPTION_QUIT) +@CommandLine.Command(name = NAME_QUIT, aliases = NAME_QUIT_ALIAS, sortOptions = SORT_OPTIONS, headerHeading = HEADER_HEADING, synopsisHeading = SYNOPSIS_HEADING, descriptionHeading = DESCRIPTION_HEADING, parameterListHeading = PARAMETER_LIST_HEADING, optionListHeading = OPTION_LIST_HEADING, header = USAGE_QUIT, description = DESCRIPTION_QUIT) public class NacosQuit implements Callable { @Override diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigAdd.java b/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigAdd.java index ab3ba04..49a7fee 100644 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigAdd.java +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigAdd.java @@ -55,8 +55,11 @@ public void run() { } try { - LogicHandler.postConfig(group, dataId, content, type); - System.out.println("done"); + if (LogicHandler.postConfig(group, dataId, content, type)) { + System.out.println("done"); + return; + } + System.out.printf("publish config group:%s, dataid:%s failed%n", group, dataId); } catch (HandlerException e) { System.out.println(e.getMessage()); } diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigList.java b/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigList.java index 48d89fb..17b013c 100644 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigList.java +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/config/NacosConfigList.java @@ -1,5 +1,6 @@ package com.alibaba.nacos.ctl.command.config; +import com.alibaba.nacos.api.utils.StringUtils; import com.alibaba.nacos.ctl.core.LogicHandler; import com.alibaba.nacos.ctl.core.bean.ConfigVO; import com.alibaba.nacos.ctl.core.exception.HandlerException; @@ -38,9 +39,17 @@ public class NacosConfigList implements Runnable { @Override public void run() { - + String search = "accurate"; try { - List list = LogicHandler.listConfigs(dataId, group, pageNo, pageSize); + if (StringUtils.isEmpty(group)) { + group = "**"; + search = "blur"; + } + if (StringUtils.isEmpty(dataId)) { + dataId = "**"; + search = "blur"; + } + List list = LogicHandler.listConfigs(dataId, group, pageNo, pageSize, search); int counter = 1; AsciiTable at = new AsciiTable(); at.getContext().setWidth(60); diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/instance/NacosInstance.java b/command/src/main/java/com/alibaba/nacos/ctl/command/instance/NacosInstance.java index 2d2a5dc..4858ca9 100644 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/instance/NacosInstance.java +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/instance/NacosInstance.java @@ -22,7 +22,6 @@ NacosInstanceGet.class}) public class NacosInstance implements Runnable { - @CommandLine.Spec CommandLine.Model.CommandSpec spec; diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/spi/NacosCommandLoader.java b/command/src/main/java/com/alibaba/nacos/ctl/command/spi/NacosCommandLoader.java new file mode 100644 index 0000000..fef778d --- /dev/null +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/spi/NacosCommandLoader.java @@ -0,0 +1,51 @@ +package com.alibaba.nacos.ctl.command.spi; + +import com.alibaba.nacos.ctl.command.NacosCommand; +import picocli.CommandLine; + +import java.util.Collection; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Nacos command loader. + * + * @author xiweng.yy + */ +public class NacosCommandLoader { + + private static final NacosCommandLoader INSTANCE = new NacosCommandLoader(); + + private static final Map COMMANDS = new ConcurrentHashMap<>(); + + private static final String IGNORE_INVALID_COMMANDS = "Ignore invalid command(%s) without CommandLine.Command annotation."; + + private static final String LOADED_COMMANDS = "Load command(%s) finished."; + + private NacosCommandLoader() { + } + + public static NacosCommandLoader getInstance() { + return INSTANCE; + } + + public void loadCommands() { + for (NacosCommand each : ServiceLoader.load(NacosCommand.class)) { + if (notContainCommandAnnotation(each.getClass())) { + System.out.println(String.format(IGNORE_INVALID_COMMANDS, each.getCommandName())); + continue; + } + System.out.println(String.format(LOADED_COMMANDS, each.getCommandName())); + COMMANDS.putIfAbsent(each.getCommandName(), each); + } + } + + private boolean notContainCommandAnnotation(Class clazz) { + return clazz.getAnnotationsByType(CommandLine.Command.class).length == 0; + } + + public Collection getLoadedCommands() { + return COMMANDS.values(); + } +} diff --git a/command/src/main/java/com/alibaba/nacos/ctl/command/utils/HintUtils.java b/command/src/main/java/com/alibaba/nacos/ctl/command/utils/HintUtils.java index 94869fd..4aea258 100644 --- a/command/src/main/java/com/alibaba/nacos/ctl/command/utils/HintUtils.java +++ b/command/src/main/java/com/alibaba/nacos/ctl/command/utils/HintUtils.java @@ -10,7 +10,7 @@ public class HintUtils { */ public static final String APP_NAME = "nacosctl"; - public static final String VERSION_NAME = "v1.0.1-Beta"; + public static final String VERSION_NAME = "v1.0.1"; public static final String CLI_DESCRIPTION = "NacosCtl is a fast, scalable, helpful client that can let you connect to nacos-server easily."; @@ -51,6 +51,8 @@ public class HintUtils { public static final String NAME_QUIT = "quit"; + public static final String NAME_QUIT_ALIAS = "stop"; + public static final String USAGE_QUIT = "Quit from the Cli"; public static final String DESCRIPTION_QUIT = "Quit from the cli, and close the connection with current server."; diff --git a/core/src/main/java/com/alibaba/nacos/ctl/core/LogicHandler.java b/core/src/main/java/com/alibaba/nacos/ctl/core/LogicHandler.java index 478aa5a..eb63324 100644 --- a/core/src/main/java/com/alibaba/nacos/ctl/core/LogicHandler.java +++ b/core/src/main/java/com/alibaba/nacos/ctl/core/LogicHandler.java @@ -1,10 +1,10 @@ package com.alibaba.nacos.ctl.core; import com.alibaba.nacos.api.naming.pojo.Instance; -import com.alibaba.nacos.ctl.core.config.GlobalConfig; import com.alibaba.nacos.ctl.core.bean.ConfigVO; import com.alibaba.nacos.ctl.core.bean.NamespaceVO; import com.alibaba.nacos.ctl.core.bean.ServiceVO; +import com.alibaba.nacos.ctl.core.config.GlobalConfig; import com.alibaba.nacos.ctl.core.exception.HandlerException; import com.alibaba.nacos.ctl.core.service.openapi.OpenApiService; import com.alibaba.nacos.ctl.core.service.sdk.SdkConfigService; @@ -39,6 +39,20 @@ public static void init() throws HandlerException { sdkNamingService = new SdkNamingService(); } + public static void refresh() { + try { + sdkConfigService.shutdown(); + sdkNamingService.shutdown(); + config = GlobalConfig.getInstance(); + openApiService = new OpenApiService(); + sdkConfigService = new SdkConfigService(); + sdkNamingService = new SdkNamingService(); + } catch (Exception e) { + System.out.println("refresh Logic Handler failed."); + e.printStackTrace(); + } + } + /** * 切换当前的默认Namespace ,读取到新到Namespace和Id后,需要: * @@ -73,13 +87,14 @@ public static String getConfig(String group, String dataId) throws HandlerExcept return sdkConfigService.getConfig(dataId, group); } - public static void postConfig(String group, String dataId, String content, String type) throws HandlerException { - sdkConfigService.publishConfig(dataId, group, content, type); + public static boolean postConfig(String group, String dataId, String content, String type) throws HandlerException { + return sdkConfigService.publishConfig(dataId, group, content, type); } - public static List listConfigs(String dataId, String group, Integer pageNo, Integer pageSize) + public static List listConfigs(String dataId, String group, Integer pageNo, Integer pageSize, + String search) throws HandlerException { - return openApiService.listConfigs(dataId, group, pageNo, pageSize); + return openApiService.listConfigs(dataId, group, pageNo, pageSize, search); } public static void deleteConfig(String group, String dataId) throws HandlerException { diff --git a/core/src/main/java/com/alibaba/nacos/ctl/core/config/ConfigLoader.java b/core/src/main/java/com/alibaba/nacos/ctl/core/config/ConfigLoader.java index 2e969c7..6564218 100644 --- a/core/src/main/java/com/alibaba/nacos/ctl/core/config/ConfigLoader.java +++ b/core/src/main/java/com/alibaba/nacos/ctl/core/config/ConfigLoader.java @@ -1,17 +1,19 @@ package com.alibaba.nacos.ctl.core.config; +import java.io.InputStream; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.function.Function; import java.util.stream.Collectors; @@ -28,11 +30,19 @@ public class ConfigLoader { private static void readFile() { try { - List lines = Files.readAllLines(Paths.get(CONF_PATH), StandardCharsets.UTF_8); - Map confs = lines.stream().peek(s -> s.trim()).filter(s -> !s.startsWith("#")) - .filter(s -> s.contains("=")).map(s -> s.split("=")).filter(sa -> sa.length == 2) - .collect(Collectors.toMap(s -> s[0].trim(), s -> s[1].trim())); - tinyDb.putAll(confs); + Path configFilePath = Paths.get(System.getProperty("config.dir"), CONF_PATH); + if (!Files.exists(configFilePath)) { + System.out.println( + String.format("[WARN] Can't find %s file in dir %s, skip load properties from file.", CONF_PATH, + System.getProperty("config.dir"))); + return; + } + InputStream inputStream = Files.newInputStream(configFilePath, StandardOpenOption.READ); + Properties properties = new Properties(); + properties.load(inputStream); + Map propertiesMap = properties.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString())); + tinyDb.putAll(propertiesMap); } catch (Exception e) { //...不存在就不加载 } @@ -60,7 +70,7 @@ public static void preload(Map params) { public static boolean fill(Object o) { boolean flag = true; for (Field f : o.getClass().getDeclaredFields()) { - if (f.isAnnotationPresent(FromPropertie.class)) { + if (f.isAnnotationPresent(FromProperties.class)) { String name = f.getName(); String value = tinyDb.get(name); if (value != null) { @@ -81,13 +91,19 @@ public static boolean fill(Object o) { return flag; } + public static Properties toProperties() { + Properties result = new Properties(); + result.putAll(tinyDb); + return result; + } + /** * @author lehr */ @Documented @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) - public @interface FromPropertie { + public @interface FromProperties { } } diff --git a/core/src/main/java/com/alibaba/nacos/ctl/core/config/GlobalConfig.java b/core/src/main/java/com/alibaba/nacos/ctl/core/config/GlobalConfig.java index 7a36a92..e3ad855 100644 --- a/core/src/main/java/com/alibaba/nacos/ctl/core/config/GlobalConfig.java +++ b/core/src/main/java/com/alibaba/nacos/ctl/core/config/GlobalConfig.java @@ -1,6 +1,6 @@ package com.alibaba.nacos.ctl.core.config; -import com.alibaba.nacos.ctl.core.config.ConfigLoader.FromPropertie; +import com.alibaba.nacos.ctl.core.config.ConfigLoader.FromProperties; import java.util.Properties; @@ -22,25 +22,25 @@ public static GlobalConfig getInstance() { private String namespaceId = ""; - @FromPropertie + @FromProperties private boolean confirmEnabled = true; - @FromPropertie + @FromProperties private String host = "localhost"; - @FromPropertie + @FromProperties private Integer port = 8848; - @FromPropertie + @FromProperties private String username = "nacos"; - @FromPropertie + @FromProperties private String password = "nacos"; - @FromPropertie + @FromProperties private String accessKey = "accessKey"; - @FromPropertie + @FromProperties private String secretKey = "secretKey"; @@ -57,9 +57,13 @@ public static Properties getAsProperties() { } private GlobalConfig() { + refresh(); + } + + public void refresh() { boolean fillAll = ConfigLoader.fill(this); if (fillAll) { - System.out.println("Successfully load all configuration from file.\n"); + System.out.println("Successfully load all configuration.\n"); } System.out.println(this); } diff --git a/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/OpenApiService.java b/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/OpenApiService.java index 6967caf..ba324ba 100644 --- a/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/OpenApiService.java +++ b/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/OpenApiService.java @@ -1,5 +1,6 @@ package com.alibaba.nacos.ctl.core.service.openapi; +import com.alibaba.nacos.api.utils.StringUtils; import com.alibaba.nacos.ctl.core.config.GlobalConfig; import com.alibaba.nacos.ctl.core.bean.ConfigVO; import com.alibaba.nacos.ctl.core.bean.NamespaceVO; @@ -58,7 +59,7 @@ public String updateSwitch(String entry, String value, Boolean debug) throws Han return httpProvider.nacosRequest(PUT, SWITCH_URL, params); } - public List listConfigs(String dataId, String group, Integer pageNo, Integer pageSize) + public List listConfigs(String dataId, String group, Integer pageNo, Integer pageSize, String search) throws HandlerException { Map params = new HashMap<>(); @@ -66,9 +67,10 @@ public List listConfigs(String dataId, String group, Integer pageNo, I params.put("group", group); params.put("pageNo", pageNo); params.put("pageSize", pageSize); - params.put("search", "accurate"); - params.put("search", "accurate"); - params.put("tenant", config.getNamespaceId()); + params.put("search", search); + if (!StringUtils.isEmpty(config.getNamespaceId())) { + params.put("tenant", config.getNamespaceId()); + } String ret = httpProvider.nacosRequest(GET, CONF_URL, params); JsonObject data = new JsonParser().parse(ret).getAsJsonObject(); diff --git a/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/network/HttpProvider.java b/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/network/HttpProvider.java index d7d04bc..cef02dc 100644 --- a/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/network/HttpProvider.java +++ b/core/src/main/java/com/alibaba/nacos/ctl/core/service/openapi/network/HttpProvider.java @@ -1,8 +1,11 @@ package com.alibaba.nacos.ctl.core.service.openapi.network; +import com.alibaba.nacos.common.http.param.Header; +import com.alibaba.nacos.ctl.core.config.ConfigLoader; import com.alibaba.nacos.ctl.core.config.GlobalConfig; import com.alibaba.nacos.ctl.core.exception.HandlerException; import com.alibaba.nacos.ctl.core.service.openapi.network.bean.HttpDelete; +import com.alibaba.nacos.ctl.core.utils.AuthUtils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.apache.http.NameValuePair; @@ -24,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import static com.alibaba.nacos.ctl.core.service.openapi.network.HttpProvider.Method.DELETE; import static com.alibaba.nacos.ctl.core.service.openapi.network.HttpProvider.Method.GET; @@ -40,6 +44,10 @@ public class HttpProvider { private String authStr = ""; + private String serverIdentityKey; + + private String serverIdentityValue; + public HttpProvider() { String username = config.getUsername(); String password = config.getPassword(); @@ -48,6 +56,9 @@ public HttpProvider() { } catch (HandlerException e) { System.out.println("nacos login failed:" + e.getMessage()); } + Properties properties = ConfigLoader.toProperties(); + serverIdentityKey = properties.getProperty("serverIdentityKey", ""); + serverIdentityValue = properties.getProperty("serverIdentityValue", ""); } private static final String LOGIN_FAILED = "unknown user!"; @@ -118,18 +129,26 @@ public String nacosRequest(Method method, String path, Map param // 拼接完整url String url = getNacosUrl() + "/v1" + path; List params = new ArrayList<>(); + String accessKey = GlobalConfig.getInstance().getAccessKey(); + String secretKey = GlobalConfig.getInstance().getSecretKey(); + Map tempStringParameters = new HashMap<>(); if (parameterMap != null) { - parameterMap.entrySet().forEach(e -> { - if (e.getValue() != null) { - params.add(new BasicNameValuePair(e.getKey(), String.valueOf(e.getValue()))); - } - }); + parameterMap.forEach((key, value) -> tempStringParameters.put(key, String.valueOf(value))); } + Header header = AuthUtils.buildConfigSpas(tempStringParameters, accessKey, secretKey); + AuthUtils.injectNamingSpas(tempStringParameters, accessKey, secretKey); + AuthUtils.injectIdentity(header, serverIdentityKey, serverIdentityValue); + tempStringParameters.forEach((key, value) -> { + if (value != null) { + params.add(new BasicNameValuePair(key, value)); + } + }); // 根据请求类型不同,构造请求,拼接参数,发送请求 try { HttpRequestBase req = generateRequest(method, url, params); + header.getHeader().forEach(req::addHeader); String ret = sendRequest(req); if (ret != null && ret.contains(FORBIDDEN)) { throw new HandlerException("403 Forbidden! Please check your permission."); diff --git a/core/src/main/java/com/alibaba/nacos/ctl/core/utils/AuthUtils.java b/core/src/main/java/com/alibaba/nacos/ctl/core/utils/AuthUtils.java new file mode 100644 index 0000000..c0857b6 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/ctl/core/utils/AuthUtils.java @@ -0,0 +1,63 @@ +package com.alibaba.nacos.ctl.core.utils; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.client.config.impl.SpasAdapter; +import com.alibaba.nacos.client.naming.utils.SignUtil; +import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.common.http.param.Header; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.common.utils.UuidUtils; + +import java.util.Map; + +/** + * Auth utils + * + * @author xiweng.yy + */ +public class AuthUtils { + + public static Header buildConfigSpas(Map paramValues, String accessKey, String secretKey) { + Header header = Header.newInstance(); + header.getHeader().remove(HttpHeaderConsts.CONTENT_TYPE); + if (StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey)) { + header.addParam("Spas-AccessKey", accessKey); + Map signHeaders = SpasAdapter.getSignHeaders(paramValues, secretKey); + if (signHeaders != null) { + header.addAll(signHeaders); + } + } + String ts = String.valueOf(System.currentTimeMillis()); + + header.addParam(Constants.CLIENT_REQUEST_TS_HEADER, ts); + header.addParam("exConfigInfo", "true"); + header.addParam(HttpHeaderConsts.REQUEST_ID, UuidUtils.generateUuid()); + return header; + } + + public static void injectNamingSpas(Map paramValues, String accessKey, String secretKey) { + if (StringUtils.isNotBlank(accessKey) && StringUtils.isNotBlank(secretKey)) { + try { + String signData = getSignData(paramValues.get("serviceName")); + String signature = SignUtil.sign(signData, secretKey); + paramValues.put("signature", signature); + paramValues.put("data", signData); + paramValues.put("ak", accessKey); + } catch (Exception e) { + System.out.println("inject ak/sk failed."); + e.printStackTrace(); + } + } + } + + private static String getSignData(String serviceName) { + return StringUtils.isNotEmpty(serviceName) ? System.currentTimeMillis() + "@@" + serviceName + : String.valueOf(System.currentTimeMillis()); + } + + public static void injectIdentity(Header header, String identityKey, String identityValue) { + if (StringUtils.isNotBlank(identityKey) && StringUtils.isNotBlank(identityValue)) { + header.addParam(identityKey, identityValue); + } + } +} diff --git a/distrobution/bin/nacosctl.sh b/distrobution/bin/nacosctl.sh index f150c7f..ccef427 100755 --- a/distrobution/bin/nacosctl.sh +++ b/distrobution/bin/nacosctl.sh @@ -43,6 +43,7 @@ export SERVER="nacosctl" export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" export BASE_DIR=`cd $(dirname $0)/..; pwd` +export CONFIG_DIR=${BASE_DIR}/conf/ #=========================================================================================== # JVM Configuration @@ -57,9 +58,9 @@ else JAVA_OPT_EXT_FIX="-Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext" fi +JAVA_OPT="${JAVA_OPT} -Dconfig.dir=${CONFIG_DIR}" JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/${SERVER}.jar" JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" -JAVA_OPT="${JAVA_OPT} --logging.config=${BASE_DIR}/conf/logback.xml" if [ ! -d "${BASE_DIR}/logs" ]; then mkdir ${BASE_DIR}/logs diff --git a/distrobution/conf/conf.properties b/distrobution/conf/conf.properties index 0b43126..7670b3a 100644 --- a/distrobution/conf/conf.properties +++ b/distrobution/conf/conf.properties @@ -1,7 +1,7 @@ #username = nacos #password = nacos #confirmEnabled = true -#accessKey = ? -#secrectKey = ? -#host=localhost -#port=8848 \ No newline at end of file +#accessKey = +#secretKey = +#host = localhost +#port = 8848 diff --git a/distrobution/pom.xml b/distrobution/pom.xml index e003414..f680a00 100644 --- a/distrobution/pom.xml +++ b/distrobution/pom.xml @@ -20,6 +20,7 @@ + nacos-ctl org.apache.maven.plugins diff --git a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/InputGetter.java b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/InputGetter.java index 64129e9..e1e8a81 100644 --- a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/InputGetter.java +++ b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/InputGetter.java @@ -31,6 +31,7 @@ public static InputGetter getInstance() { public static void init() throws HandlerException { try { instance.console = new ConsoleReader(); + instance.console.setHandleUserInterrupt(true); instance.console.addCompleter(new NacosCtlCompleter()); } catch (IOException e) { throw new HandlerException("Failed to load JLine", e); diff --git a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/CompleterFactory.java b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/CompleterFactory.java index 46e28c6..7c3afd5 100644 --- a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/CompleterFactory.java +++ b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/CompleterFactory.java @@ -13,7 +13,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; import java.util.List; +import java.util.ServiceLoader; /** * 提供各种命令用到的completer group @@ -22,6 +25,8 @@ */ public class CompleterFactory { + private static final String LOADED_COMPLETER = "LOAD completer(%s) finished."; + /** * @author lehr */ @@ -30,6 +35,7 @@ public class CompleterFactory { @Retention(RetentionPolicy.RUNTIME) private @interface Enabled { + } public static List loadAll() throws InvocationTargetException, IllegalAccessException { @@ -45,6 +51,15 @@ public static List loadAll() throws InvocationTargetException, Illega return completers; } + public static Collection loadExtensionCompleter() { + List result = new LinkedList<>(); + for (NacosCompleterBuilder each : ServiceLoader.load(NacosCompleterBuilder.class)) { + result.add(each.build()); + System.out.println(String.format(LOADED_COMPLETER, each.getCompleterName())); + } + return result; + } + /** * 对于不提供后续补全的几个基本指令,简单提供String补全组 * diff --git a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCompleterBuilder.java b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCompleterBuilder.java new file mode 100644 index 0000000..d45a244 --- /dev/null +++ b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCompleterBuilder.java @@ -0,0 +1,25 @@ +package com.alibaba.nacos.ctl.intraction.input.completer; + +import jline.console.completer.Completer; + +/** + * Nacos completer builder + * + * @author xiweng.yy + */ +public interface NacosCompleterBuilder { + + /** + * Get completer name. + * + * @return completer name + */ + String getCompleterName(); + + /** + * Build target completer. + * + * @return completer + */ + Completer build(); +} diff --git a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCtlCompleter.java b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCtlCompleter.java index dae1687..b4a0f23 100644 --- a/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCtlCompleter.java +++ b/interaction/src/main/java/com/alibaba/nacos/ctl/intraction/input/completer/NacosCtlCompleter.java @@ -23,6 +23,7 @@ public class NacosCtlCompleter implements Completer { public NacosCtlCompleter() throws HandlerException { try { completers = CompleterFactory.loadAll(); + completers.addAll(CompleterFactory.loadExtensionCompleter()); } catch (Exception e) { throw new HandlerException("failed to load completer groups", e); } diff --git a/pom.xml b/pom.xml index 70c3bd9..04d3569 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ - 1.0.1-SNAPSHOT + 1.0.1 UTF-8 8 8