refactor(plugins): new CLI for plugins
This commit is contained in:
parent
47661042b9
commit
3a7924d0fd
|
@ -31,6 +31,9 @@
|
|||
|
||||
-export([format/2]).
|
||||
|
||||
%% For CLI outputs
|
||||
-export([best_effort_json/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("proper/include/proper.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
@ -51,6 +54,16 @@
|
|||
|
||||
-define(IS_STRING(String), (is_list(String) orelse is_binary(String))).
|
||||
|
||||
%% @doc Format a list() or map() to JSON object.
|
||||
%% This is used for CLI result prints,
|
||||
%% or HTTP API result formatting.
|
||||
%% The JSON object is pretty-printed.
|
||||
%% NOTE: do not use this function for logging.
|
||||
best_effort_json(Input) ->
|
||||
Config = #{depth => unlimited, single_line => true},
|
||||
JsonReady = best_effort_json_obj(Input, Config),
|
||||
jsx:encode(JsonReady, [space, {indent, 4}]).
|
||||
|
||||
-spec format(logger:log_event(), config()) -> iodata().
|
||||
format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) ->
|
||||
Config = add_default_config(Config0),
|
||||
|
|
|
@ -225,47 +225,51 @@ if_valid_qos(QoS, Fun) ->
|
|||
end.
|
||||
|
||||
plugins(["list"]) ->
|
||||
lists:foreach(fun print/1, emqx_plugins:list());
|
||||
|
||||
plugins(["load", Name]) ->
|
||||
case emqx_plugins:load(list_to_atom(Name)) of
|
||||
ok ->
|
||||
emqx_ctl:print("Plugin ~ts loaded successfully.~n", [Name]);
|
||||
{error, Reason} ->
|
||||
emqx_ctl:print("Load plugin ~ts error: ~p.~n", [Name, Reason])
|
||||
end;
|
||||
|
||||
plugins(["unload", "emqx_management"])->
|
||||
emqx_ctl:print("Plugin emqx_management can not be unloaded.~n");
|
||||
|
||||
plugins(["unload", Name]) ->
|
||||
case emqx_plugins:unload(list_to_atom(Name)) of
|
||||
ok ->
|
||||
emqx_ctl:print("Plugin ~ts unloaded successfully.~n", [Name]);
|
||||
{error, Reason} ->
|
||||
emqx_ctl:print("Unload plugin ~ts error: ~p.~n", [Name, Reason])
|
||||
end;
|
||||
|
||||
plugins(["reload", Name]) ->
|
||||
try list_to_existing_atom(Name) of
|
||||
PluginName ->
|
||||
case emqx_mgmt:reload_plugin(node(), PluginName) of
|
||||
ok ->
|
||||
emqx_ctl:print("Plugin ~ts reloaded successfully.~n", [Name]);
|
||||
{error, Reason} ->
|
||||
emqx_ctl:print("Reload plugin ~ts error: ~p.~n", [Name, Reason])
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
emqx_ctl:print("Reload plugin ~ts error: The plugin doesn't exist.~n", [Name])
|
||||
end;
|
||||
|
||||
emqx_plugins_cli:list(fun emqx_ctl:print/2);
|
||||
plugins(["describe", NameVsn]) ->
|
||||
emqx_plugins_cli:describe(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["install", NameVsn]) ->
|
||||
emqx_plugins_cli:ensure_installed(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["uninstall", NameVsn])->
|
||||
emqx_plugins_cli:ensure_uninstalled(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["start", NameVsn]) ->
|
||||
emqx_plugins_cli:ensure_started(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["stop", NameVsn]) ->
|
||||
emqx_plugins_cli:ensure_stopped(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["restart", NameVsn]) ->
|
||||
emqx_plugins_cli:restart(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["disable", NameVsn]) ->
|
||||
emqx_plugins_cli:ensure_disabled(NameVsn, fun emqx_ctl:print/2);
|
||||
plugins(["enable", NameVsn]) ->
|
||||
emqx_plugins_cli:ensure_enabled(NameVsn, no_move, fun emqx_ctl:print/2);
|
||||
plugins(["enable", NameVsn, "front"]) ->
|
||||
emqx_plugins_cli:ensure_enabled(NameVsn, front, fun emqx_ctl:print/2);
|
||||
plugins(["enable", NameVsn, "rear"]) ->
|
||||
emqx_plugins_cli:ensure_enabled(NameVsn, rear, fun emqx_ctl:print/2);
|
||||
plugins(["enable", NameVsn, "before", Other]) ->
|
||||
emqx_plugins_cli:ensure_enabled(NameVsn, {before, Other}, fun emqx_ctl:print/2);
|
||||
plugins(_) ->
|
||||
emqx_ctl:usage([{"plugins list", "Show loaded plugins"},
|
||||
{"plugins load <Plugin>", "Load plugin"},
|
||||
{"plugins unload <Plugin>", "Unload plugin"},
|
||||
{"plugins reload <Plugin>", "Reload plugin"}
|
||||
]).
|
||||
emqx_ctl:usage(
|
||||
[{"plugins <command> [Name-Vsn]", "e.g. 'start emqx_plugin_template-5.0-rc.1'"},
|
||||
{"plugins list", "List all installed plugins"},
|
||||
{"plugins describe Name-Vsn", "Describe an installed plugins"},
|
||||
{"plugins install Name-Vsn", "Install a plugin package placed\n"
|
||||
"in plugin'sinstall_dir"},
|
||||
{"plugins uninstall Name-Vsn", "Uninstall a plugin. NOTE: it deletes\n"
|
||||
"all files in install_dir/Name-Vsn"},
|
||||
{"plugins start Name-Vsn", "Start a plugin"},
|
||||
{"plugins stop Name-Vsn", "Stop a plugin"},
|
||||
{"plugins restart Name-Vsn", "Stop then start a plugin"},
|
||||
{"plugins disable Name-Vsn", "Disable auto-boot"},
|
||||
{"plugins enable Name-Vsn [Position]",
|
||||
"Enable auto-boot at Position in the boot list, where Position could be\n"
|
||||
"'front', 'rear', or 'before Other-Vsn' to specify a relative position.\n"
|
||||
"The Position parameter can be used to adjust the boot order.\n"
|
||||
"If no Position is given, an already configured plugin\n"
|
||||
"will stary at is old position; a newly plugin is appended to the rear\n"
|
||||
"e.g. plugins disable foo-0.1.0 front\n"
|
||||
" plugins enable bar-0.2.0 before foo-0.1.0"}
|
||||
]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc vm command
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
, ensure_enabled/1
|
||||
, ensure_enabled/2
|
||||
, ensure_disabled/1
|
||||
, delete_package/1
|
||||
]).
|
||||
|
||||
-export([ ensure_started/0
|
||||
|
@ -32,7 +33,7 @@
|
|||
, ensure_stopped/1
|
||||
, restart/1
|
||||
, list/0
|
||||
, delete_package/1
|
||||
, describe/1
|
||||
]).
|
||||
|
||||
-export([ get_config/2
|
||||
|
@ -54,11 +55,16 @@
|
|||
|
||||
-type name_vsn() :: binary() | string(). %% "my_plugin-0.1.0"
|
||||
-type plugin() :: map(). %% the parse result of the JSON info file
|
||||
-type position() :: no_move | front | rear | {before, name_vsn()}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% APIs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% @doc Describe a plugin.
|
||||
-spec describe(name_vsn()) -> {ok, plugin()} | {error, any()}.
|
||||
describe(NameVsn) -> read_plugin(NameVsn).
|
||||
|
||||
%% @doc Install a .tar.gz package placed in install_dir.
|
||||
-spec ensure_installed(name_vsn()) -> ok | {error, any()}.
|
||||
ensure_installed(NameVsn) ->
|
||||
|
@ -98,7 +104,7 @@ do_ensure_installed(NameVsn) ->
|
|||
-spec ensure_uninstalled(name_vsn()) -> ok | {error, any()}.
|
||||
ensure_uninstalled(NameVsn) ->
|
||||
case read_plugin(NameVsn) of
|
||||
{ok, #{running_status := RunningSt}} when RunningSt =/= not_loaded ->
|
||||
{ok, #{running_status := RunningSt}} when RunningSt =/= stopped ->
|
||||
{error, #{reason => "bad_plugin_running_status",
|
||||
hint => "stop_the_plugin_first"
|
||||
}};
|
||||
|
@ -113,15 +119,17 @@ ensure_uninstalled(NameVsn) ->
|
|||
%% @doc Ensure a plugin is enabled to the end of the plugins list.
|
||||
-spec ensure_enabled(name_vsn()) -> ok | {error, any()}.
|
||||
ensure_enabled(NameVsn) ->
|
||||
ensure_enabled(NameVsn, rear).
|
||||
ensure_enabled(NameVsn, no_move).
|
||||
|
||||
%% @doc Ensure a plugin is enabled at the given position of the plugin list.
|
||||
-spec ensure_enabled(name_vsn(), position()) -> ok | {error, any()}.
|
||||
ensure_enabled(NameVsn, Position) ->
|
||||
ensure_state(NameVsn, Position, true).
|
||||
|
||||
%% @doc Ensure a plugin is disabled.
|
||||
-spec ensure_disabled(name_vsn()) -> ok | {error, any()}.
|
||||
ensure_disabled(NameVsn) ->
|
||||
ensure_state(NameVsn, rear, false).
|
||||
ensure_state(NameVsn, no_move, false).
|
||||
|
||||
ensure_state(NameVsn, Position, State) when is_binary(NameVsn) ->
|
||||
ensure_state(binary_to_list(NameVsn), Position, State);
|
||||
|
@ -147,6 +155,8 @@ ensure_configured(#{name_vsn := NameVsn} = Item, Position) ->
|
|||
end,
|
||||
ok = put_configured(NewConfigured).
|
||||
|
||||
add_new_configured(Configured, no_move, Item) ->
|
||||
Configured ++ [Item];
|
||||
add_new_configured(Configured, front, Item) ->
|
||||
[Item | Configured];
|
||||
add_new_configured(Configured, rear, Item) ->
|
||||
|
@ -232,16 +242,33 @@ restart(NameVsn) ->
|
|||
-spec list() -> [plugin()].
|
||||
list() ->
|
||||
Pattern = filename:join([install_dir(), "*", "release.json"]),
|
||||
lists:filtermap(
|
||||
fun(JsonFile) ->
|
||||
case read_plugin({file, JsonFile}) of
|
||||
{ok, Info} ->
|
||||
{true, Info};
|
||||
{error, Reason} ->
|
||||
?SLOG(warning, Reason),
|
||||
false
|
||||
end
|
||||
end, filelib:wildcard(Pattern)).
|
||||
All = lists:filtermap(
|
||||
fun(JsonFile) ->
|
||||
case read_plugin({file, JsonFile}) of
|
||||
{ok, Info} ->
|
||||
{true, Info};
|
||||
{error, Reason} ->
|
||||
?SLOG(warning, Reason),
|
||||
false
|
||||
end
|
||||
end, filelib:wildcard(Pattern)),
|
||||
list(configured(), All).
|
||||
|
||||
%% Make sure configured ones are ordered in front.
|
||||
list([], All) -> All;
|
||||
list([#{name_vsn := NameVsn} | Rest], All) ->
|
||||
SplitF = fun(#{<<"name">> := Name, <<"rel_vsn">> := Vsn}) ->
|
||||
bin([Name, "-", Vsn]) =/= bin(NameVsn)
|
||||
end,
|
||||
case lists:splitwith(SplitF, All) of
|
||||
{_, []} ->
|
||||
?SLOG(warning, #{msg => "configured_plugin_not_installed",
|
||||
name_vsn => NameVsn
|
||||
}),
|
||||
list(Rest, All);
|
||||
{Front, [I | Rear]} ->
|
||||
[I | list(Rest, Front ++ Rear)]
|
||||
end.
|
||||
|
||||
do_ensure_started(NameVsn) ->
|
||||
tryit("start_plugins",
|
||||
|
@ -295,7 +322,7 @@ plugin_status(NameVsn) ->
|
|||
_ -> loaded
|
||||
end;
|
||||
undefined ->
|
||||
not_loaded
|
||||
stopped
|
||||
end,
|
||||
Configured = lists:filtermap(
|
||||
fun(#{name_vsn := Nv, enable := St}) ->
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_plugins_cli).
|
||||
|
||||
-export([ list/1
|
||||
, describe/2
|
||||
, ensure_installed/2
|
||||
, ensure_uninstalled/2
|
||||
, ensure_started/2
|
||||
, ensure_stopped/2
|
||||
, restart/2
|
||||
, ensure_disabled/2
|
||||
, ensure_enabled/3
|
||||
]).
|
||||
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
|
||||
-define(PRINT(EXPR, LOG_FUN),
|
||||
print(NameVsn, fun()-> EXPR end(), LOG_FUN, ?FUNCTION_NAME)).
|
||||
|
||||
list(LogFun) ->
|
||||
LogFun("~ts~n", [to_json(emqx_plugins:list())]).
|
||||
|
||||
describe(NameVsn, LogFun) ->
|
||||
case emqx_plugins:describe(NameVsn) of
|
||||
{ok, Plugin} ->
|
||||
LogFun("~ts~n", [to_json(Plugin)]);
|
||||
{error, Reason} ->
|
||||
%% this should not happend unless the package is manually installed
|
||||
%% corrupted packages installed from emqx_plugins:ensure_installed
|
||||
%% should not leave behind corrupted files
|
||||
?SLOG(error, #{msg => "failed_to_describe_plugin",
|
||||
name_vsn => NameVsn,
|
||||
cause => Reason}),
|
||||
%% do nothing to the CLI console
|
||||
ok
|
||||
end.
|
||||
|
||||
ensure_installed(NameVsn, LogFun) ->
|
||||
?PRINT(emqx_plugins:ensure_installed(NameVsn), LogFun).
|
||||
|
||||
ensure_uninstalled(NameVsn, LogFun) ->
|
||||
?PRINT(emqx_plugins:ensure_uninstalled(NameVsn), LogFun).
|
||||
|
||||
ensure_started(NameVsn, LogFun) ->
|
||||
?PRINT(emqx_plugins:ensure_started(NameVsn), LogFun).
|
||||
|
||||
ensure_stopped(NameVsn, LogFun) ->
|
||||
?PRINT(emqx_plugins:ensure_stopped(NameVsn), LogFun).
|
||||
|
||||
restart(NameVsn, LogFun) ->
|
||||
?PRINT(emqx_plugins:restart(NameVsn), LogFun).
|
||||
|
||||
ensure_enabled(NameVsn, Position, LogFun) ->
|
||||
?PRINT(emqx_plugins:ensure_enabled(NameVsn, Position), LogFun).
|
||||
|
||||
ensure_disabled(NameVsn, LogFun) ->
|
||||
?PRINT(emqx_plugins:ensure_disabled(NameVsn), LogFun).
|
||||
|
||||
to_json(Input) ->
|
||||
emqx_logger_jsonfmt:best_effort_json(Input).
|
||||
|
||||
print(NameVsn, Res, LogFun, Action) ->
|
||||
Obj = #{action => Action,
|
||||
name_vsn => NameVsn},
|
||||
JsonReady =
|
||||
case Res of
|
||||
ok ->
|
||||
Obj#{result => ok};
|
||||
{error, Reason} ->
|
||||
Obj#{result => not_ok,
|
||||
cause => Reason}
|
||||
end,
|
||||
LogFun("~ts~n", [to_json(JsonReady)]).
|
Loading…
Reference in New Issue