summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniil Fedotov <hairyhum@gmail.com>2016-11-25 10:44:30 +0000
committerGitHub <noreply@github.com>2016-11-25 10:44:30 +0000
commite184cb08323a7ff14706aaefd033a0ff92dce2e5 (patch)
treead80e6c2b31248c6745bd004bdc737e1d67bb38e /src
parentb4d8d27e4bb6157d4781e167ab764dff37fabd07 (diff)
parent05ca97d35e9be8e298008f264a9b317f218e2c2c (diff)
downloadrabbitmq-server-git-e184cb08323a7ff14706aaefd033a0ff92dce2e5.tar.gz
Merge pull request #1016 from binarin/multiple-plugins-dir
Support multiple plugins directories
Diffstat (limited to 'src')
-rw-r--r--src/rabbit_plugins.erl140
-rw-r--r--src/rabbit_plugins_main.erl39
2 files changed, 135 insertions, 44 deletions
diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl
index 4d8966f7e2..bde4ff5388 100644
--- a/src/rabbit_plugins.erl
+++ b/src/rabbit_plugins.erl
@@ -84,42 +84,16 @@ active() ->
lists:member(App, InstalledPlugins)].
%% @doc Get the list of plugins which are ready to be enabled.
-list(PluginsDir) ->
- list(PluginsDir, false).
+list(PluginsPath) ->
+ list(PluginsPath, false).
-list(PluginsDir, IncludeRequiredDeps) ->
- EZs = [{ez, EZ} || EZ <- filelib:wildcard("*.ez", PluginsDir)],
- FreeApps = [{app, App} ||
- App <- filelib:wildcard("*/ebin/*.app", PluginsDir)],
- %% We load the "rabbit" application to be sure we can get the
- %% "applications" key. This is required for rabbitmq-plugins for
- %% instance.
- application:load(rabbit),
- {ok, RabbitDeps} = application:get_key(rabbit, applications),
- {AvailablePlugins, Problems} =
- lists:foldl(fun ({error, EZ, Reason}, {Plugins1, Problems1}) ->
- {Plugins1, [{EZ, Reason} | Problems1]};
- (Plugin = #plugin{name = Name}, {Plugins1, Problems1}) ->
- %% Applications RabbitMQ depends on (eg.
- %% "rabbit_common") can't be considered
- %% plugins, otherwise rabbitmq-plugins would
- %% list them and the user may believe he can
- %% disable them.
- case IncludeRequiredDeps orelse
- not lists:member(Name, RabbitDeps) of
- true -> {[Plugin|Plugins1], Problems1};
- false -> {Plugins1, Problems1}
- end
- end, {[], []},
- [plugin_info(PluginsDir, Plug) || Plug <- EZs ++ FreeApps]),
- case Problems of
- [] -> ok;
- _ -> rabbit_log:warning(
- "Problem reading some plugins: ~p~n", [Problems])
- end,
- Plugins = lists:filter(fun(P) -> not plugin_provided_by_otp(P) end,
- AvailablePlugins),
- ensure_dependencies(Plugins).
+list(PluginsPath, IncludeRequiredDeps) ->
+ {AllPlugins, LoadingProblems} = discover_plugins(split_path(PluginsPath)),
+ {UniquePlugins, DuplicateProblems} = remove_duplicate_plugins(AllPlugins),
+ Plugins1 = maybe_keep_required_deps(IncludeRequiredDeps, UniquePlugins),
+ Plugins2 = remove_otp_overrideable_plugins(Plugins1),
+ maybe_report_plugin_loading_problems(LoadingProblems ++ DuplicateProblems),
+ ensure_dependencies(Plugins2).
%% @doc Read the list of enabled plugins from the supplied term file.
read_enabled(PluginsFile) ->
@@ -259,14 +233,12 @@ prepare_plugin(#plugin{type = dir, name = Name, location = Location},
ExpandDir) ->
rabbit_file:recursive_copy(Location, filename:join([ExpandDir, Name])).
-plugin_info(Base, {ez, EZ0}) ->
- EZ = filename:join([Base, EZ0]),
+plugin_info({ez, EZ}) ->
case read_app_file(EZ) of
{application, Name, Props} -> mkplugin(Name, Props, ez, EZ);
{error, Reason} -> {error, EZ, Reason}
end;
-plugin_info(Base, {app, App0}) ->
- App = filename:join([Base, App0]),
+plugin_info({app, App}) ->
case rabbit_file:read_term_file(App) of
{ok, [{application, Name, Props}]} ->
mkplugin(Name, Props, dir,
@@ -317,3 +289,93 @@ plugin_names(Plugins) ->
lookup_plugins(Names, AllPlugins) ->
[P || P = #plugin{name = Name} <- AllPlugins, lists:member(Name, Names)].
+
+%% Split PATH-like value into its components.
+split_path(PathString) ->
+ Delimiters = case os:type() of
+ {unix, _} -> ":";
+ {win32, _} -> ";"
+ end,
+ string:tokens(PathString, Delimiters).
+
+%% Search for files using glob in a given dir. Returns full filenames of those files.
+full_path_wildcard(Glob, Dir) ->
+ [filename:join([Dir, File]) || File <- filelib:wildcard(Glob, Dir)].
+
+%% Returns list off all .ez files in a given set of directories
+list_ezs([]) ->
+ [];
+list_ezs([Dir|Rest]) ->
+ [{ez, EZ} || EZ <- full_path_wildcard("*.ez", Dir)] ++ list_ezs(Rest).
+
+%% Returns list of all files that look like OTP applications in a
+%% given set of directories.
+list_free_apps([]) ->
+ [];
+list_free_apps([Dir|Rest]) ->
+ [{app, App} || App <- full_path_wildcard("*/ebin/*.app", Dir)]
+ ++ list_free_apps(Rest).
+
+compare_by_name_and_version(#plugin{name = Name, version = VersionA},
+ #plugin{name = Name, version = VersionB}) ->
+ ec_semver:lte(VersionA, VersionB);
+compare_by_name_and_version(#plugin{name = NameA},
+ #plugin{name = NameB}) ->
+ NameA =< NameB.
+
+-spec discover_plugins([Directory]) -> {[#plugin{}], [Problem]} when
+ Directory :: file:name(),
+ Problem :: {file:name(), term()}.
+discover_plugins(PluginsDirs) ->
+ EZs = list_ezs(PluginsDirs),
+ FreeApps = list_free_apps(PluginsDirs),
+ read_plugins_info(EZs ++ FreeApps, {[], []}).
+
+read_plugins_info([], Acc) ->
+ Acc;
+read_plugins_info([Path|Paths], {Plugins, Problems}) ->
+ case plugin_info(Path) of
+ #plugin{} = Plugin ->
+ read_plugins_info(Paths, {[Plugin|Plugins], Problems});
+ {error, Location, Reason} ->
+ read_plugins_info(Paths, {Plugins, [{Location, Reason}|Problems]})
+ end.
+
+remove_duplicate_plugins(Plugins) ->
+ %% Reverse order ensures that if there are several versions of the
+ %% same plugin, the most recent one comes first.
+ Sorted = lists:reverse(
+ lists:sort(fun compare_by_name_and_version/2, Plugins)),
+ remove_duplicate_plugins(Sorted, {[], []}).
+
+remove_duplicate_plugins([], Acc) ->
+ Acc;
+remove_duplicate_plugins([Best = #plugin{name = Name}, Offender = #plugin{name = Name} | Rest],
+ {Plugins0, Problems0}) ->
+ Problems1 = [{Offender#plugin.location, duplicate_plugin}|Problems0],
+ remove_duplicate_plugins([Best|Rest], {Plugins0, Problems1});
+remove_duplicate_plugins([Plugin|Rest], {Plugins0, Problems0}) ->
+ Plugins1 = [Plugin|Plugins0],
+ remove_duplicate_plugins(Rest, {Plugins1, Problems0}).
+
+maybe_keep_required_deps(true, Plugins) ->
+ Plugins;
+maybe_keep_required_deps(false, Plugins) ->
+ %% We load the "rabbit" application to be sure we can get the
+ %% "applications" key. This is required for rabbitmq-plugins for
+ %% instance.
+ application:load(rabbit),
+ {ok, RabbitDeps} = application:get_key(rabbit, applications),
+ lists:filter(fun(#plugin{name = Name}) ->
+ not lists:member(Name, RabbitDeps)
+ end,
+ Plugins).
+
+remove_otp_overrideable_plugins(Plugins) ->
+ lists:filter(fun(P) -> not plugin_provided_by_otp(P) end,
+ Plugins).
+
+maybe_report_plugin_loading_problems([]) ->
+ ok;
+maybe_report_plugin_loading_problems(Problems) ->
+ rabbit_log:warning("Problem reading some plugins: ~p~n", [Problems]).
diff --git a/src/rabbit_plugins_main.erl b/src/rabbit_plugins_main.erl
index ff516268c6..758a9acaa4 100644
--- a/src/rabbit_plugins_main.erl
+++ b/src/rabbit_plugins_main.erl
@@ -169,9 +169,9 @@ format_plugins(Node, Pattern, Opts, #cli{all = All,
EnabledImplicitly = Implicit -- Enabled,
{StatusMsg, Running} =
- case rabbit_misc:rpc_call(Node, rabbit_plugins, active, []) of
- {badrpc, _} -> {"[failed to contact ~s - status not shown]", []};
- Active -> {"* = running on ~s", Active}
+ case remote_running_plugins(Node) of
+ {ok, Active} -> {"* = running on ~s", Active};
+ error -> {"[failed to contact ~s - status not shown]", []}
end,
{ok, RE} = re:compile(Pattern),
Plugins = [ Plugin ||
@@ -196,7 +196,7 @@ format_plugins(Node, Pattern, Opts, #cli{all = All,
Format, MaxWidth) || P <- Plugins1],
ok.
-format_plugin(#plugin{name = Name, version = Version,
+format_plugin(#plugin{name = Name, version = OnDiskVersion,
description = Description, dependencies = Deps},
Enabled, EnabledImplicitly, Running, Format,
MaxWidth) ->
@@ -206,7 +206,7 @@ format_plugin(#plugin{name = Name, version = Version,
{false, true} -> "e";
_ -> " "
end,
- RunningGlyph = case lists:member(Name, Running) of
+ RunningGlyph = case lists:keymember(Name, 1, Running) of
true -> "*";
false -> " "
end,
@@ -214,6 +214,7 @@ format_plugin(#plugin{name = Name, version = Version,
Opt = fun (_F, A, A) -> ok;
( F, A, _) -> io:format(F, [A])
end,
+ Version = format_running_plugin_version(Name, OnDiskVersion, Running),
case Format of
minimal -> io:format("~s~n", [Name]);
normal -> io:format("~s ~-" ++ integer_to_list(MaxWidth) ++ "w ",
@@ -305,3 +306,31 @@ rpc_call(Node, Online, Mod, Fun, Args) ->
plur([_]) -> "";
plur(_) -> "s".
+
+-spec remote_running_plugins(node()) -> [{atom(), Vsn :: string()}].
+remote_running_plugins(Node) ->
+ case rabbit_misc:rpc_call(Node, rabbit_plugins, active, []) of
+ {badrpc, _} -> error;
+ Active -> maybe_augment_with_versions(Node, Active)
+ end.
+
+-spec maybe_augment_with_versions(node(), [atom()]) -> [{atom(), Vsn :: string()}].
+maybe_augment_with_versions(Node, Plugins) ->
+ case rabbit_misc:rpc_call(Node, rabbit_misc, which_applications, []) of
+ {badrpc, _} ->
+ error;
+ All ->
+ {ok, [{App, Vsn} || {App, _, Vsn} <- All,
+ lists:member(App, Plugins)]}
+ end.
+
+-spec format_running_plugin_version(atom(), string(), [{atom(), Vsn :: string()}]) -> string().
+format_running_plugin_version(Name, OnDiskVersion, RunningPlugins) ->
+ case lists:keyfind(Name, 1, RunningPlugins) of
+ false ->
+ OnDiskVersion;
+ {_, OnDiskVersion} ->
+ OnDiskVersion;
+ {_, RunningVersion} ->
+ io_lib:format("~s (pending upgrade to ~s)", [RunningVersion, OnDiskVersion])
+ end.