1## This Source Code Form is subject to the terms of the Mozilla Public
2## License, v. 2.0. If a copy of the MPL was not distributed with this
3## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4##
5## Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6
7defmodule RabbitMQ.CLI.Plugins.Commands.DisableCommand do
8  alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
9  alias RabbitMQ.CLI.Core.{DocGuide, Validators}
10  import RabbitMQ.CLI.Core.{CodePath, Paths}
11
12  @behaviour RabbitMQ.CLI.CommandBehaviour
13
14  def formatter(), do: RabbitMQ.CLI.Formatters.Plugins
15
16  def merge_defaults(args, opts) do
17    {args, Map.merge(%{online: false, offline: false, all: false}, opts)}
18  end
19
20  def distribution(%{offline: true}), do: :none
21  def distribution(%{offline: false}), do: :cli
22
23  def switches(), do: [online: :boolean, offline: :boolean, all: :boolean]
24
25  def validate([], %{all: false}) do
26    {:validation_failure, :not_enough_args}
27  end
28
29  def validate([_ | _], %{all: true}) do
30    {:validation_failure, {:bad_argument, "Cannot set both --all and a list of plugins"}}
31  end
32
33  def validate(_, %{online: true, offline: true}) do
34    {:validation_failure, {:bad_argument, "Cannot set both online and offline"}}
35  end
36
37  def validate(_args, _opts) do
38    :ok
39  end
40
41  def validate_execution_environment(args, opts) do
42    Validators.chain(
43      [
44        &PluginHelpers.can_set_plugins_with_mode/2,
45        &require_rabbit_and_plugins/2,
46        &PluginHelpers.enabled_plugins_file/2,
47        &plugins_dir/2
48      ],
49      [args, opts]
50    )
51  end
52
53  def run(plugin_names, %{all: all_flag, node: node_name} = opts) do
54    plugins =
55      case all_flag do
56        false -> for s <- plugin_names, do: String.to_atom(s)
57        true -> PluginHelpers.plugin_names(PluginHelpers.list(opts))
58      end
59
60    enabled = PluginHelpers.read_enabled(opts)
61    all = PluginHelpers.list(opts)
62    implicit = :rabbit_plugins.dependencies(false, enabled, all)
63    to_disable_deps = :rabbit_plugins.dependencies(true, plugins, all)
64    plugins_to_set = MapSet.difference(MapSet.new(enabled), MapSet.new(to_disable_deps))
65
66    mode = PluginHelpers.mode(opts)
67
68    case PluginHelpers.set_enabled_plugins(MapSet.to_list(plugins_to_set), opts) do
69      {:ok, enabled_plugins} ->
70        {:stream,
71         Stream.concat([
72           [:rabbit_plugins.strictly_plugins(enabled_plugins, all)],
73           RabbitMQ.CLI.Core.Helpers.defer(fn ->
74             :timer.sleep(5000)
75
76             case PluginHelpers.update_enabled_plugins(enabled_plugins, mode, node_name, opts) do
77               %{set: new_enabled} = result ->
78                 disabled = implicit -- new_enabled
79
80                 filter_strictly_plugins(
81                   Map.put(result, :disabled, :rabbit_plugins.strictly_plugins(disabled, all)),
82                   all,
83                   [:set, :started, :stopped]
84                 )
85
86               other ->
87                 other
88             end
89           end)
90         ])}
91
92      {:error, _} = err ->
93        err
94    end
95  end
96
97  use RabbitMQ.CLI.Plugins.ErrorOutput
98
99  def banner([], %{all: true, node: node_name}) do
100    "Disabling ALL plugins on node #{node_name}"
101  end
102
103  def banner(plugins, %{node: node_name}) do
104    ["Disabling plugins on node #{node_name}:" | plugins]
105  end
106
107  def usage, do: "disable <plugin1> [ <plugin2>] | --all [--offline] [--online]"
108
109  def usage_additional() do
110    [
111      ["<plugin1> [ <plugin2>]", "names of plugins to disable separated by a space"],
112      ["--online", "contact target node to disable the plugins. Changes are applied immediately."],
113      ["--offline", "update enabled plugins file directly without contacting target node. Changes will be delayed until the node is restarted."],
114      ["--all", "disable all currently enabled plugins"]
115    ]
116  end
117
118  def usage_doc_guides() do
119    [
120      DocGuide.plugins()
121    ]
122  end
123
124  def help_section(), do: :plugin_management
125
126  def description(), do: "Disables one or more plugins"
127
128  #
129  # Implementation
130  #
131
132  defp filter_strictly_plugins(map, _all, []) do
133    map
134  end
135
136  defp filter_strictly_plugins(map, all, [head | tail]) do
137    case map[head] do
138      nil ->
139        filter_strictly_plugins(map, all, tail)
140
141      other ->
142        value = :rabbit_plugins.strictly_plugins(other, all)
143        filter_strictly_plugins(Map.put(map, head, value), all, tail)
144    end
145  end
146end
147