diff options
| author | Daniil Fedotov <hairyhum@gmail.com> | 2016-11-25 10:44:30 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-11-25 10:44:30 +0000 |
| commit | e184cb08323a7ff14706aaefd033a0ff92dce2e5 (patch) | |
| tree | ad80e6c2b31248c6745bd004bdc737e1d67bb38e /src | |
| parent | b4d8d27e4bb6157d4781e167ab764dff37fabd07 (diff) | |
| parent | 05ca97d35e9be8e298008f264a9b317f218e2c2c (diff) | |
| download | rabbitmq-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.erl | 140 | ||||
| -rw-r--r-- | src/rabbit_plugins_main.erl | 39 |
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. |
