Skip to content

Commit 4810a9a

Browse files
New command group: plugins
1 parent 2dcad26 commit 4810a9a

File tree

6 files changed

+149
-1
lines changed

6 files changed

+149
-1
lines changed

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@
22

33
## v2.16.0 (in development)
44

5-
No changes yet.
5+
### Enhancements
6+
7+
* `plugins` is a new command group for listing enabled plugins:
8+
9+
```shell
10+
# List plugins across all cluster nodes
11+
rabbitmqadmin plugins list_all
12+
13+
# List plugins on a specific node
14+
rabbitmqadmin plugins list_on_node --node rabbit@hostname
15+
```
16+
17+
### Upgrades
18+
19+
* RabbitMQ HTTP API client was upgraded to [`0.62.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.62.0)
620

721

822
## v2.15.0 (Sep 30, 2025)

src/cli.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,17 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command {
252252
.subcommand_value_name("permission")
253253
.arg_required_else_help(true)
254254
.subcommands(permissions_subcommands(pre_flight_settings.clone()));
255+
let plugins_group = Command::new("plugins")
256+
.about("List enabled plugins")
257+
.infer_subcommands(pre_flight_settings.infer_subcommands)
258+
.infer_long_args(pre_flight_settings.infer_long_options)
259+
.after_help(color_print::cformat!(
260+
"<bold>Doc guide</bold>: {}",
261+
PLUGIN_GUIDE_URL
262+
))
263+
.subcommand_value_name("plugin")
264+
.arg_required_else_help(true)
265+
.subcommands(plugins_subcommands(pre_flight_settings.clone()));
255266
let policies_group = Command::new("policies")
256267
.about("Operations on policies")
257268
.infer_subcommands(pre_flight_settings.infer_subcommands)
@@ -398,6 +409,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command {
398409
parameters_group,
399410
passwords_group,
400411
permissions_group,
412+
plugins_group,
401413
policies_group,
402414
publish_group,
403415
purge_group,
@@ -2797,6 +2809,36 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) -
27972809
.collect()
27982810
}
27992811

2812+
pub fn plugins_subcommands(pre_flight_settings: PreFlightSettings) -> Vec<Command> {
2813+
let list_all_cmd = Command::new("list_all")
2814+
.about("Lists plugins across all cluster nodes")
2815+
.after_help(color_print::cformat!(
2816+
"<bold>Doc guide</bold>: {}",
2817+
PLUGIN_GUIDE_URL
2818+
));
2819+
2820+
let list_on_node_cmd = Command::new("list_on_node")
2821+
.about("Lists plugins enabled on a specific node")
2822+
.arg(
2823+
Arg::new("node")
2824+
.long("node")
2825+
.help("target node, must be a cluster member")
2826+
.required(true),
2827+
)
2828+
.after_help(color_print::cformat!(
2829+
"<bold>Doc guide</bold>: {}",
2830+
PLUGIN_GUIDE_URL
2831+
));
2832+
2833+
[
2834+
list_all_cmd,
2835+
list_on_node_cmd,
2836+
]
2837+
.into_iter()
2838+
.map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options))
2839+
.collect()
2840+
}
2841+
28002842
pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> Vec<Command> {
28012843
let list_cmd = Command::new("list")
28022844
.long_about("Lists cluster nodes")

src/commands.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use serde_json::Value;
4646
use std::fs;
4747
use std::io;
4848
use std::process;
49+
use tabled::Tabled;
4950

5051
type APIClient = Client<String, String, String>;
5152

@@ -1090,6 +1091,52 @@ pub fn list_deprecated_features_in_use(
10901091
client.list_deprecated_features_in_use()
10911092
}
10921093

1094+
//
1095+
// Plugins
1096+
//
1097+
1098+
#[derive(Debug, Clone, Tabled)]
1099+
pub struct PluginOnNode {
1100+
pub node: String,
1101+
pub name: String,
1102+
pub state: String,
1103+
}
1104+
1105+
pub fn list_plugins_on_node(
1106+
client: APIClient,
1107+
command_args: &ArgMatches,
1108+
) -> ClientResult<Vec<PluginOnNode>> {
1109+
let node = command_args.get_one::<String>("node").cloned().unwrap();
1110+
let plugins = client.list_node_plugins(&node)?;
1111+
1112+
Ok(plugins
1113+
.into_iter()
1114+
.map(|plugin_name| PluginOnNode {
1115+
node: node.clone(),
1116+
name: plugin_name,
1117+
state: "Enabled".to_string(),
1118+
})
1119+
.collect())
1120+
}
1121+
1122+
pub fn list_plugins_across_cluster(client: APIClient) -> ClientResult<Vec<PluginOnNode>> {
1123+
let nodes = client.list_nodes()?;
1124+
let mut result = Vec::new();
1125+
1126+
for node in nodes {
1127+
let plugins = client.list_node_plugins(&node.name)?;
1128+
for plugin_name in plugins {
1129+
result.push(PluginOnNode {
1130+
node: node.name.clone(),
1131+
name: plugin_name,
1132+
state: "Enabled".to_string(),
1133+
});
1134+
}
1135+
}
1136+
1137+
Ok(result)
1138+
}
1139+
10931140
//
10941141
// Declaration of core resources
10951142
//

src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,14 @@ fn dispatch_common_subcommand(
823823
let result = commands::show_memory_breakdown(client, second_level_args);
824824
res_handler.memory_breakdown_in_percent_result(result)
825825
}
826+
("plugins", "list_all") => {
827+
let result = commands::list_plugins_across_cluster(client);
828+
res_handler.tabular_result(result)
829+
}
830+
("plugins", "list_on_node") => {
831+
let result = commands::list_plugins_on_node(client, second_level_args);
832+
res_handler.tabular_result(result)
833+
}
826834
("operator_policies", "declare") => {
827835
let result = commands::declare_operator_policy(client, &vhost, second_level_args);
828836
res_handler.no_output_on_success(result);

src/static_urls.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub(crate) const OPERATOR_POLICY_GUIDE_URL: &str =
6060
"https://rabbitmq.com/docs/parameters#operator-policies";
6161
pub(crate) const USER_LIMIT_GUIDE_URL: &str = "https://rabbitmq.com/docs/user-limits";
6262
pub(crate) const PASSWORD_GUIDE_URL: &str = "https://rabbitmq.com/docs/passwords";
63+
pub(crate) const PLUGIN_GUIDE_URL: &str = "https://rabbitmq.com/docs/plugins";
6364
pub(crate) const SHOVEL_GUIDE_URL: &str = "https://rabbitmq.com/docs/shovel";
6465
pub(crate) const FEDERATION_GUIDE_URL: &str = "https://rabbitmq.com/docs/federation";
6566
pub(crate) const FEDERATED_QUEUES_GUIDE_URL: &str = "https://rabbitmq.com/docs/federated-queues";

tests/plugins_tests.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com)
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::error::Error;
16+
mod test_helpers;
17+
use crate::test_helpers::*;
18+
19+
#[test]
20+
fn test_plugins_list_all_succeeds() -> Result<(), Box<dyn Error>> {
21+
run_succeeds(["plugins", "list_all"]).stdout(output_includes("rabbitmq_management"));
22+
23+
Ok(())
24+
}
25+
26+
#[test]
27+
fn test_plugins_list_on_node_succeeds() -> Result<(), Box<dyn Error>> {
28+
let rc = api_client();
29+
let nodes = rc.list_nodes()?;
30+
let first = nodes.first().unwrap();
31+
32+
run_succeeds(["plugins", "list_on_node", "--node", first.name.as_str()])
33+
.stdout(output_includes("rabbitmq_management"));
34+
35+
Ok(())
36+
}

0 commit comments

Comments
 (0)