240 lines
6.3 KiB
Elixir
240 lines
6.3 KiB
Elixir
## This Source Code Form is subject to the terms of the Mozilla Public
|
|
## License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
##
|
|
## Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
|
|
|
defmodule RabbitMQ.CLI.Plugins.Helpers do
|
|
import RabbitMQ.CLI.Core.DataCoercion
|
|
import RabbitCommon.Records
|
|
import RabbitMQ.CLI.Core.Platform, only: [path_separator: 0]
|
|
import RabbitMQ.CLI.Core.{CodePath, Paths}
|
|
alias RabbitMQ.CLI.Core.{Config, Validators}
|
|
|
|
def mode(opts) do
|
|
%{online: online, offline: offline} = opts
|
|
|
|
case {online, offline} do
|
|
{true, false} -> :online
|
|
{false, true} -> :offline
|
|
{false, false} -> :best_effort
|
|
end
|
|
end
|
|
|
|
def can_set_plugins_with_mode(args, opts) do
|
|
case mode(opts) do
|
|
# can always set offline plugins list
|
|
:offline ->
|
|
:ok
|
|
|
|
# assume online mode, fall back to offline mode in case of errors
|
|
:best_effort ->
|
|
:ok
|
|
|
|
# a running node is required
|
|
:online ->
|
|
Validators.chain(
|
|
[&Validators.node_is_running/2, &Validators.rabbit_is_running/2],
|
|
[args, opts]
|
|
)
|
|
end
|
|
end
|
|
|
|
def list(opts) do
|
|
case plugins_dir(opts) do
|
|
{:ok, dir} ->
|
|
add_all_to_path(dir)
|
|
:lists.usort(:rabbit_plugins.list(to_charlist(dir)))
|
|
|
|
{:error, _} ->
|
|
[]
|
|
end
|
|
end
|
|
|
|
def list_names(opts) do
|
|
list(opts) |> plugin_names
|
|
end
|
|
|
|
def read_enabled(opts) do
|
|
case enabled_plugins_file(opts) do
|
|
{:ok, enabled} ->
|
|
:rabbit_plugins.read_enabled(to_charlist(enabled))
|
|
|
|
# Existence of enabled_plugins_file should be validated separately
|
|
{:error, :no_plugins_file} ->
|
|
[]
|
|
end
|
|
end
|
|
|
|
def enabled_plugins_file(opts) do
|
|
case Config.get_option(:enabled_plugins_file, opts) do
|
|
nil -> {:error, :no_plugins_file}
|
|
file -> {:ok, file}
|
|
end
|
|
end
|
|
|
|
def enabled_plugins_file(_, opts) do
|
|
enabled_plugins_file(opts)
|
|
end
|
|
|
|
def set_enabled_plugins(plugins, opts) do
|
|
plugin_atoms = :lists.usort(for plugin <- plugins, do: to_atom(plugin))
|
|
_ = require_rabbit_and_plugins(opts)
|
|
{:ok, plugins_file} = enabled_plugins_file(opts)
|
|
write_enabled_plugins(plugin_atoms, plugins_file, opts)
|
|
end
|
|
|
|
@spec update_enabled_plugins(
|
|
[atom()],
|
|
:online | :offline | :best_effort,
|
|
node(),
|
|
map()
|
|
) :: map() | {:error, any()}
|
|
def update_enabled_plugins(enabled_plugins, mode, node_name, opts) do
|
|
{:ok, plugins_file} = enabled_plugins_file(opts)
|
|
|
|
case mode do
|
|
:online ->
|
|
case update_enabled_plugins(node_name, plugins_file) do
|
|
{:ok, started, stopped} ->
|
|
%{
|
|
mode: :online,
|
|
started: Enum.sort(started),
|
|
stopped: Enum.sort(stopped),
|
|
set: Enum.sort(enabled_plugins)
|
|
}
|
|
|
|
{:error, _} = err ->
|
|
err
|
|
end
|
|
|
|
:best_effort ->
|
|
case update_enabled_plugins(node_name, plugins_file) do
|
|
{:ok, started, stopped} ->
|
|
%{
|
|
mode: :online,
|
|
started: Enum.sort(started),
|
|
stopped: Enum.sort(stopped),
|
|
set: Enum.sort(enabled_plugins)
|
|
}
|
|
|
|
{:error, :offline} ->
|
|
%{mode: :offline, set: Enum.sort(enabled_plugins)}
|
|
|
|
{:error, {:enabled_plugins_mismatch, _, _}} = err ->
|
|
err
|
|
end
|
|
|
|
:offline ->
|
|
%{mode: :offline, set: Enum.sort(enabled_plugins)}
|
|
end
|
|
end
|
|
|
|
def validate_plugins(requested_plugins, opts) do
|
|
## Maybe check all plugins
|
|
plugins =
|
|
case opts do
|
|
%{all: true} -> plugin_names(list(opts))
|
|
_ -> requested_plugins
|
|
end
|
|
|
|
all = list(opts)
|
|
deps = :rabbit_plugins.dependencies(false, plugins, all)
|
|
|
|
deps_plugins =
|
|
Enum.filter(all, fn plugin ->
|
|
name = plugin_name(plugin)
|
|
Enum.member?(deps, name)
|
|
end)
|
|
|
|
case :rabbit_plugins.validate_plugins(deps_plugins) do
|
|
{_, []} -> :ok
|
|
{_, invalid} -> {:error, :rabbit_plugins.format_invalid_plugins(invalid)}
|
|
end
|
|
end
|
|
|
|
def plugin_name(plugin) when is_binary(plugin) do
|
|
plugin
|
|
end
|
|
|
|
def plugin_name(plugin) when is_atom(plugin) do
|
|
Atom.to_string(plugin)
|
|
end
|
|
|
|
def plugin_name(plugin) do
|
|
plugin(name: name) = plugin
|
|
name
|
|
end
|
|
|
|
def plugin_names(plugins) do
|
|
for plugin <- plugins, do: plugin_name(plugin)
|
|
end
|
|
|
|
def comma_separated_names(plugins) do
|
|
Enum.join(plugin_names(plugins), ", ")
|
|
end
|
|
|
|
#
|
|
# Implementation
|
|
#
|
|
|
|
defp to_list(str) when is_binary(str) do
|
|
:erlang.binary_to_list(str)
|
|
end
|
|
|
|
defp to_list(lst) when is_list(lst) do
|
|
lst
|
|
end
|
|
|
|
defp to_list(atm) when is_atom(atm) do
|
|
to_list(Atom.to_string(atm))
|
|
end
|
|
|
|
defp write_enabled_plugins(plugins, plugins_file, opts) do
|
|
all = list(opts)
|
|
all_plugin_names = Enum.map(all, &plugin_name/1)
|
|
missing = MapSet.difference(MapSet.new(plugins), MapSet.new(all_plugin_names))
|
|
|
|
hard_write = Map.get(opts, :hard_write, false)
|
|
|
|
case Enum.empty?(missing) or hard_write do
|
|
true ->
|
|
case :rabbit_file.write_term_file(to_charlist(plugins_file), [plugins]) do
|
|
:ok ->
|
|
all_enabled = :rabbit_plugins.dependencies(false, plugins, all)
|
|
{:ok, Enum.sort(all_enabled)}
|
|
|
|
{:error, reason} ->
|
|
{:error, {:cannot_write_enabled_plugins_file, plugins_file, reason}}
|
|
end
|
|
|
|
false ->
|
|
{:error, {:plugins_not_found, Enum.to_list(missing)}}
|
|
end
|
|
end
|
|
|
|
defp update_enabled_plugins(node_name, plugins_file) do
|
|
case :rabbit_misc.rpc_call(node_name, :rabbit_plugins, :ensure, [to_list(plugins_file)]) do
|
|
{:badrpc, :nodedown} -> {:error, :offline}
|
|
{:error, :rabbit_not_running} -> {:error, :offline}
|
|
{:ok, start, stop} -> {:ok, start, stop}
|
|
{:error, _} = err -> err
|
|
end
|
|
end
|
|
|
|
defp add_all_to_path(plugins_directories) do
|
|
directories = String.split(to_string(plugins_directories), path_separator())
|
|
|
|
Enum.each(directories, fn directory ->
|
|
with {:ok, subdirs} <- File.ls(directory) do
|
|
for subdir <- subdirs do
|
|
Path.join([directory, subdir, "ebin"])
|
|
|> Code.append_path()
|
|
end
|
|
end
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
end
|