agave_validator/commands/plugin/
mod.rs1use {
2 crate::{
3 admin_rpc_service,
4 commands::{FromClapArgMatches, Result},
5 },
6 clap::{value_t, App, AppSettings, Arg, ArgMatches, SubCommand},
7 std::path::Path,
8};
9
10const COMMAND: &str = "plugin";
11
12#[derive(Debug, PartialEq)]
13pub struct PluginUnloadArgs {
14 pub name: String,
15}
16
17impl FromClapArgMatches for PluginUnloadArgs {
18 fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
19 Ok(PluginUnloadArgs {
20 name: value_t!(matches, "name", String)?,
21 })
22 }
23}
24
25#[derive(Debug, PartialEq)]
26pub struct PluginLoadArgs {
27 pub config: String,
28}
29
30impl FromClapArgMatches for PluginLoadArgs {
31 fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
32 Ok(PluginLoadArgs {
33 config: value_t!(matches, "config", String)?,
34 })
35 }
36}
37
38#[derive(Debug, PartialEq)]
39pub struct PluginReloadArgs {
40 pub name: String,
41 pub config: String,
42}
43
44impl FromClapArgMatches for PluginReloadArgs {
45 fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
46 Ok(PluginReloadArgs {
47 name: value_t!(matches, "name", String)?,
48 config: value_t!(matches, "config", String)?,
49 })
50 }
51}
52
53pub fn command<'a>() -> App<'a, 'a> {
54 let name_arg = Arg::with_name("name").required(true).takes_value(true);
55 let config_arg = Arg::with_name("config").required(true).takes_value(true);
56
57 SubCommand::with_name(COMMAND)
58 .about("Manage and view geyser plugins")
59 .setting(AppSettings::SubcommandRequiredElseHelp)
60 .setting(AppSettings::InferSubcommands)
61 .subcommand(SubCommand::with_name("list").about("List all current running geyser plugins"))
62 .subcommand(
63 SubCommand::with_name("unload")
64 .about("Unload a particular geyser plugin. You must specify the geyser plugin name")
65 .arg(&name_arg),
66 )
67 .subcommand(
68 SubCommand::with_name("reload")
69 .about(
70 "Reload a particular geyser plugin. You must specify the geyser plugin name \
71 and the new config path",
72 )
73 .arg(&name_arg)
74 .arg(&config_arg),
75 )
76 .subcommand(
77 SubCommand::with_name("load")
78 .about(
79 "Load a new geyser plugin. You must specify the config path. Fails if \
80 overwriting (use reload)",
81 )
82 .arg(&config_arg),
83 )
84}
85
86pub fn execute(matches: &ArgMatches, ledger_path: &Path) -> Result<()> {
87 match matches.subcommand() {
88 ("list", _) => {
89 let admin_client = admin_rpc_service::connect(ledger_path);
90 let plugins = admin_rpc_service::runtime()
91 .block_on(async move { admin_client.await?.list_plugins().await })?;
92 if !plugins.is_empty() {
93 println!("Currently the following plugins are loaded:");
94 for (plugin, i) in plugins.into_iter().zip(1..) {
95 println!(" {i}) {plugin}");
96 }
97 } else {
98 println!("There are currently no plugins loaded");
99 }
100 }
101 ("unload", Some(subcommand_matches)) => {
102 let PluginUnloadArgs { name } =
103 PluginUnloadArgs::from_clap_arg_match(subcommand_matches)?;
104
105 let admin_client = admin_rpc_service::connect(ledger_path);
106 admin_rpc_service::runtime()
107 .block_on(async { admin_client.await?.unload_plugin(name.clone()).await })?;
108 println!("Successfully unloaded plugin: {name}");
109 }
110 ("load", Some(subcommand_matches)) => {
111 let PluginLoadArgs { config } =
112 PluginLoadArgs::from_clap_arg_match(subcommand_matches)?;
113
114 let admin_client = admin_rpc_service::connect(ledger_path);
115 let name = admin_rpc_service::runtime()
116 .block_on(async { admin_client.await?.load_plugin(config.clone()).await })?;
117 println!("Successfully loaded plugin: {name}");
118 }
119 ("reload", Some(subcommand_matches)) => {
120 let PluginReloadArgs { name, config } =
121 PluginReloadArgs::from_clap_arg_match(subcommand_matches)?;
122
123 let admin_client = admin_rpc_service::connect(ledger_path);
124 admin_rpc_service::runtime().block_on(async {
125 admin_client
126 .await?
127 .reload_plugin(name.clone(), config.clone())
128 .await
129 })?;
130 println!("Successfully reloaded plugin: {name}");
131 }
132 _ => unreachable!(),
133 }
134
135 Ok(())
136}
137
138#[cfg(test)]
139mod tests {
140 use {super::*, crate::commands::tests::verify_args_struct_by_command_is_error};
141
142 #[test]
143 fn verify_args_struct_by_command_plugin_unload_default() {
144 verify_args_struct_by_command_is_error::<PluginUnloadArgs>(
145 command(),
146 vec![COMMAND, "unload"],
147 );
148 }
149
150 #[test]
151 fn verify_args_struct_by_command_plugin_unload_with_name() {
152 let app = command();
153 let matches = app.get_matches_from(vec![COMMAND, "unload", "testname"]);
154 let subcommand_matches = matches.subcommand_matches("unload").unwrap();
155 let args = PluginUnloadArgs::from_clap_arg_match(subcommand_matches).unwrap();
156 assert_eq!(
157 args,
158 PluginUnloadArgs {
159 name: "testname".to_string(),
160 }
161 );
162 }
163
164 #[test]
165 fn verify_args_struct_by_command_plugin_load_default() {
166 verify_args_struct_by_command_is_error::<PluginLoadArgs>(command(), vec![COMMAND, "load"]);
167 }
168
169 #[test]
170 fn verify_args_struct_by_command_plugin_load_with_config() {
171 let app = command();
172 let matches = app.get_matches_from(vec![COMMAND, "load", "testconfig"]);
173 let subcommand_matches = matches.subcommand_matches("load").unwrap();
174 let args = PluginLoadArgs::from_clap_arg_match(subcommand_matches).unwrap();
175 assert_eq!(
176 args,
177 PluginLoadArgs {
178 config: "testconfig".to_string(),
179 }
180 );
181 }
182
183 #[test]
184 fn verify_args_struct_by_command_plugin_reload_default() {
185 verify_args_struct_by_command_is_error::<PluginReloadArgs>(
186 command(),
187 vec![COMMAND, "reload"],
188 );
189 }
190
191 #[test]
192 fn verify_args_struct_by_command_plugin_reload_with_name() {
193 verify_args_struct_by_command_is_error::<PluginReloadArgs>(
194 command(),
195 vec![COMMAND, "reload", "testname"],
196 );
197 }
198
199 #[test]
200 fn verify_args_struct_by_command_plugin_reload_with_name_and_config() {
201 let app = command();
202 let matches = app.get_matches_from(vec![COMMAND, "reload", "testname", "testconfig"]);
203 let subcommand_matches = matches.subcommand_matches("reload").unwrap();
204 let args = PluginReloadArgs::from_clap_arg_match(subcommand_matches).unwrap();
205 assert_eq!(
206 args,
207 PluginReloadArgs {
208 name: "testname".to_string(),
209 config: "testconfig".to_string(),
210 }
211 );
212 }
213}