refactor(plugins): new CLI for plugins
This commit is contained in:
parent
47661042b9
commit
3a7924d0fd
|
@ -31,6 +31,9 @@
|
||||||
|
|
||||||
-export([format/2]).
|
-export([format/2]).
|
||||||
|
|
||||||
|
%% For CLI outputs
|
||||||
|
-export([best_effort_json/1]).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-include_lib("proper/include/proper.hrl").
|
-include_lib("proper/include/proper.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
@ -51,6 +54,16 @@
|
||||||
|
|
||||||
-define(IS_STRING(String), (is_list(String) orelse is_binary(String))).
|
-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().
|
-spec format(logger:log_event(), config()) -> iodata().
|
||||||
format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) ->
|
format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) ->
|
||||||
Config = add_default_config(Config0),
|
Config = add_default_config(Config0),
|
||||||
|
|
|
@ -225,46 +225,50 @@ if_valid_qos(QoS, Fun) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
plugins(["list"]) ->
|
plugins(["list"]) ->
|
||||||
lists:foreach(fun print/1, emqx_plugins:list());
|
emqx_plugins_cli:list(fun emqx_ctl:print/2);
|
||||||
|
plugins(["describe", NameVsn]) ->
|
||||||
plugins(["load", Name]) ->
|
emqx_plugins_cli:describe(NameVsn, fun emqx_ctl:print/2);
|
||||||
case emqx_plugins:load(list_to_atom(Name)) of
|
plugins(["install", NameVsn]) ->
|
||||||
ok ->
|
emqx_plugins_cli:ensure_installed(NameVsn, fun emqx_ctl:print/2);
|
||||||
emqx_ctl:print("Plugin ~ts loaded successfully.~n", [Name]);
|
plugins(["uninstall", NameVsn])->
|
||||||
{error, Reason} ->
|
emqx_plugins_cli:ensure_uninstalled(NameVsn, fun emqx_ctl:print/2);
|
||||||
emqx_ctl:print("Load plugin ~ts error: ~p.~n", [Name, Reason])
|
plugins(["start", NameVsn]) ->
|
||||||
end;
|
emqx_plugins_cli:ensure_started(NameVsn, fun emqx_ctl:print/2);
|
||||||
|
plugins(["stop", NameVsn]) ->
|
||||||
plugins(["unload", "emqx_management"])->
|
emqx_plugins_cli:ensure_stopped(NameVsn, fun emqx_ctl:print/2);
|
||||||
emqx_ctl:print("Plugin emqx_management can not be unloaded.~n");
|
plugins(["restart", NameVsn]) ->
|
||||||
|
emqx_plugins_cli:restart(NameVsn, fun emqx_ctl:print/2);
|
||||||
plugins(["unload", Name]) ->
|
plugins(["disable", NameVsn]) ->
|
||||||
case emqx_plugins:unload(list_to_atom(Name)) of
|
emqx_plugins_cli:ensure_disabled(NameVsn, fun emqx_ctl:print/2);
|
||||||
ok ->
|
plugins(["enable", NameVsn]) ->
|
||||||
emqx_ctl:print("Plugin ~ts unloaded successfully.~n", [Name]);
|
emqx_plugins_cli:ensure_enabled(NameVsn, no_move, fun emqx_ctl:print/2);
|
||||||
{error, Reason} ->
|
plugins(["enable", NameVsn, "front"]) ->
|
||||||
emqx_ctl:print("Unload plugin ~ts error: ~p.~n", [Name, Reason])
|
emqx_plugins_cli:ensure_enabled(NameVsn, front, fun emqx_ctl:print/2);
|
||||||
end;
|
plugins(["enable", NameVsn, "rear"]) ->
|
||||||
|
emqx_plugins_cli:ensure_enabled(NameVsn, rear, fun emqx_ctl:print/2);
|
||||||
plugins(["reload", Name]) ->
|
plugins(["enable", NameVsn, "before", Other]) ->
|
||||||
try list_to_existing_atom(Name) of
|
emqx_plugins_cli:ensure_enabled(NameVsn, {before, Other}, fun emqx_ctl:print/2);
|
||||||
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;
|
|
||||||
|
|
||||||
plugins(_) ->
|
plugins(_) ->
|
||||||
emqx_ctl:usage([{"plugins list", "Show loaded plugins"},
|
emqx_ctl:usage(
|
||||||
{"plugins load <Plugin>", "Load plugin"},
|
[{"plugins <command> [Name-Vsn]", "e.g. 'start emqx_plugin_template-5.0-rc.1'"},
|
||||||
{"plugins unload <Plugin>", "Unload plugin"},
|
{"plugins list", "List all installed plugins"},
|
||||||
{"plugins reload <Plugin>", "Reload plugin"}
|
{"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"}
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
, ensure_enabled/1
|
, ensure_enabled/1
|
||||||
, ensure_enabled/2
|
, ensure_enabled/2
|
||||||
, ensure_disabled/1
|
, ensure_disabled/1
|
||||||
|
, delete_package/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ ensure_started/0
|
-export([ ensure_started/0
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
, ensure_stopped/1
|
, ensure_stopped/1
|
||||||
, restart/1
|
, restart/1
|
||||||
, list/0
|
, list/0
|
||||||
, delete_package/1
|
, describe/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ get_config/2
|
-export([ get_config/2
|
||||||
|
@ -54,11 +55,16 @@
|
||||||
|
|
||||||
-type name_vsn() :: binary() | string(). %% "my_plugin-0.1.0"
|
-type name_vsn() :: binary() | string(). %% "my_plugin-0.1.0"
|
||||||
-type plugin() :: map(). %% the parse result of the JSON info file
|
-type plugin() :: map(). %% the parse result of the JSON info file
|
||||||
|
-type position() :: no_move | front | rear | {before, name_vsn()}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% APIs
|
%% 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.
|
%% @doc Install a .tar.gz package placed in install_dir.
|
||||||
-spec ensure_installed(name_vsn()) -> ok | {error, any()}.
|
-spec ensure_installed(name_vsn()) -> ok | {error, any()}.
|
||||||
ensure_installed(NameVsn) ->
|
ensure_installed(NameVsn) ->
|
||||||
|
@ -98,7 +104,7 @@ do_ensure_installed(NameVsn) ->
|
||||||
-spec ensure_uninstalled(name_vsn()) -> ok | {error, any()}.
|
-spec ensure_uninstalled(name_vsn()) -> ok | {error, any()}.
|
||||||
ensure_uninstalled(NameVsn) ->
|
ensure_uninstalled(NameVsn) ->
|
||||||
case read_plugin(NameVsn) of
|
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",
|
{error, #{reason => "bad_plugin_running_status",
|
||||||
hint => "stop_the_plugin_first"
|
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.
|
%% @doc Ensure a plugin is enabled to the end of the plugins list.
|
||||||
-spec ensure_enabled(name_vsn()) -> ok | {error, any()}.
|
-spec ensure_enabled(name_vsn()) -> ok | {error, any()}.
|
||||||
ensure_enabled(NameVsn) ->
|
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_enabled(NameVsn, Position) ->
|
||||||
ensure_state(NameVsn, Position, true).
|
ensure_state(NameVsn, Position, true).
|
||||||
|
|
||||||
%% @doc Ensure a plugin is disabled.
|
%% @doc Ensure a plugin is disabled.
|
||||||
-spec ensure_disabled(name_vsn()) -> ok | {error, any()}.
|
-spec ensure_disabled(name_vsn()) -> ok | {error, any()}.
|
||||||
ensure_disabled(NameVsn) ->
|
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(NameVsn, Position, State) when is_binary(NameVsn) ->
|
||||||
ensure_state(binary_to_list(NameVsn), Position, State);
|
ensure_state(binary_to_list(NameVsn), Position, State);
|
||||||
|
@ -147,6 +155,8 @@ ensure_configured(#{name_vsn := NameVsn} = Item, Position) ->
|
||||||
end,
|
end,
|
||||||
ok = put_configured(NewConfigured).
|
ok = put_configured(NewConfigured).
|
||||||
|
|
||||||
|
add_new_configured(Configured, no_move, Item) ->
|
||||||
|
Configured ++ [Item];
|
||||||
add_new_configured(Configured, front, Item) ->
|
add_new_configured(Configured, front, Item) ->
|
||||||
[Item | Configured];
|
[Item | Configured];
|
||||||
add_new_configured(Configured, rear, Item) ->
|
add_new_configured(Configured, rear, Item) ->
|
||||||
|
@ -232,7 +242,7 @@ restart(NameVsn) ->
|
||||||
-spec list() -> [plugin()].
|
-spec list() -> [plugin()].
|
||||||
list() ->
|
list() ->
|
||||||
Pattern = filename:join([install_dir(), "*", "release.json"]),
|
Pattern = filename:join([install_dir(), "*", "release.json"]),
|
||||||
lists:filtermap(
|
All = lists:filtermap(
|
||||||
fun(JsonFile) ->
|
fun(JsonFile) ->
|
||||||
case read_plugin({file, JsonFile}) of
|
case read_plugin({file, JsonFile}) of
|
||||||
{ok, Info} ->
|
{ok, Info} ->
|
||||||
|
@ -241,7 +251,24 @@ list() ->
|
||||||
?SLOG(warning, Reason),
|
?SLOG(warning, Reason),
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end, filelib:wildcard(Pattern)).
|
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) ->
|
do_ensure_started(NameVsn) ->
|
||||||
tryit("start_plugins",
|
tryit("start_plugins",
|
||||||
|
@ -295,7 +322,7 @@ plugin_status(NameVsn) ->
|
||||||
_ -> loaded
|
_ -> loaded
|
||||||
end;
|
end;
|
||||||
undefined ->
|
undefined ->
|
||||||
not_loaded
|
stopped
|
||||||
end,
|
end,
|
||||||
Configured = lists:filtermap(
|
Configured = lists:filtermap(
|
||||||
fun(#{name_vsn := Nv, enable := St}) ->
|
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