1use clap::{Arg, ArgAction, Command};
10
11pub use bel7_cli::CompletionShell;
12
13use crate::commands::{CONFIG_FILES, EtcFile};
14use crate::common::cli_tools::RABBITMQ_CLI_TOOLS;
15use crate::shell::Shell;
16
17pub fn build_cli() -> Command {
18 Command::new("frm")
19 .version(env!("CARGO_PKG_VERSION"))
20 .disable_version_flag(true)
21 .author("Michael S. Klishin")
22 .about("Frakking RabbitMQ version Manager")
23 .help_template("{name} {version}\n{about}\n\n{usage-heading} {usage}\n\n{all-args}")
24 .arg_required_else_help(true)
25 .subcommand(status_command())
26 .subcommand(releases_command())
27 .subcommand(alphas_command())
28 .subcommand(tanzu_command())
29 .subcommand(conf_command())
30 .subcommand(default_command())
31 .subcommand(cli_command())
32 .subcommand(fg_command())
33 .subcommand(bg_command())
34 .subcommand(inspect_command())
35 .subcommand(shell_command())
36}
37
38fn status_command() -> Command {
39 Command::new("status")
40 .about("Show frm status: active version, default, installed versions")
41 .long_about(
42 "Show frm status: active version, default, installed versions.\n\n\
43 🟢 active in current shell (via 'frm releases use', 'frm alphas use', or 'frm tanzu use')\n\
44 ⚪ default version",
45 )
46}
47
48fn releases_command() -> Command {
49 Command::new("releases")
50 .about("Install or manage RabbitMQ releases (GA, RCs, betas); for alphas, see the 'alphas' command group")
51 .arg_required_else_help(true)
52 .subcommand(releases_list_command())
53 .subcommand(releases_path_command())
54 .subcommand(releases_logs_command())
55 .subcommand(releases_install_command())
56 .subcommand(releases_reinstall_command())
57 .subcommand(releases_uninstall_command())
58 .subcommand(releases_use_command())
59 .subcommand(releases_cp_etc_file_command())
60 .subcommand(releases_check_signature_command())
61 .subcommand(releases_completions_command())
62}
63
64fn releases_use_command() -> Command {
65 const HELP: &str = "Version to use (e.g., 4.2.3 or 'latest')";
66 Command::new("use")
67 .about("Output shell commands to use a specific release version")
68 .long_about(
69 "Output shell commands to use a specific release version.\n\n\
70 Use 'latest' to select the most recent installed GA version.\n\n\
71 bash/zsh: eval \"$(frm releases use [version])\"\n\
72 nushell: Use 'frm shell env nu' init script, then call 'frm-use [version]'",
73 )
74 .arg(positional_version_arg(HELP))
75 .arg(version_opt_arg(HELP))
76 .arg(
77 Arg::new("shell")
78 .long("shell")
79 .short('s')
80 .help("Shell type (bash, zsh, nu)")
81 .value_parser(clap::value_parser!(Shell)),
82 )
83}
84
85fn releases_completions_command() -> Command {
86 Command::new("completions")
87 .about("Output installed release versions for shell completion")
88 .hide(true)
89 .arg(
90 Arg::new("shell")
91 .long("shell")
92 .short('s')
93 .help("Shell type (bash, zsh, nu)")
94 .value_parser(clap::value_parser!(Shell)),
95 )
96}
97
98fn releases_list_command() -> Command {
99 Command::new("list")
100 .visible_alias("ls")
101 .about("List installed stable RabbitMQ releases")
102}
103
104fn releases_path_command() -> Command {
105 Command::new("path")
106 .about("Show the local path of an installed release")
107 .arg(version_arg())
108}
109
110fn releases_logs_command() -> Command {
111 Command::new("logs")
112 .about("Show RabbitMQ log file information for a release")
113 .arg_required_else_help(true)
114 .subcommand(
115 Command::new("path")
116 .about("Show the path to the log file")
117 .arg(version_arg()),
118 )
119 .subcommand(
120 Command::new("tail")
121 .about("Show the last lines of the log file")
122 .arg(version_arg())
123 .arg(
124 Arg::new("lines")
125 .long("lines")
126 .short('n')
127 .help("Number of lines to show")
128 .default_value("10")
129 .value_parser(clap::value_parser!(usize)),
130 ),
131 )
132}
133
134fn releases_install_command() -> Command {
135 const HELP: &str = "Version to install (e.g., 4.2.3 or 4.2.0-rc.1)";
136 Command::new("install")
137 .visible_alias("i")
138 .about("Install a stable RabbitMQ release")
139 .long_about(
140 "Install a stable RabbitMQ release (beta, rc, or GA).\n\n\
141 Alpha versions are not allowed; use 'frm alphas install' instead.",
142 )
143 .arg(positional_version_arg(HELP))
144 .arg(version_opt_arg(HELP))
145 .arg(
146 Arg::new("force")
147 .long("force")
148 .short('f')
149 .help("Force reinstallation if version exists")
150 .action(ArgAction::SetTrue),
151 )
152}
153
154fn releases_reinstall_command() -> Command {
155 const HELP: &str = "Version to reinstall (e.g., 4.2.3)";
156 Command::new("reinstall")
157 .about("Reinstall a stable RabbitMQ release")
158 .long_about(
159 "Reinstall a stable RabbitMQ release.\n\n\
160 Removes the existing installation and downloads a fresh copy.",
161 )
162 .arg(positional_version_arg(HELP))
163 .arg(version_opt_arg(HELP))
164}
165
166fn releases_uninstall_command() -> Command {
167 const HELP: &str = "Version to uninstall (e.g., 4.2.3 or 'latest')";
168 Command::new("uninstall")
169 .visible_alias("rm")
170 .about("Uninstall a stable RabbitMQ release")
171 .long_about(
172 "Uninstall a stable RabbitMQ release.\n\n\
173 Use 'latest' to uninstall the most recent installed GA version.",
174 )
175 .arg(positional_version_arg(HELP))
176 .arg(version_opt_arg(HELP))
177}
178
179fn releases_cp_etc_file_command() -> Command {
180 cp_etc_file_command("Copy a configuration file to a stable release's etc/rabbitmq directory")
181}
182
183fn releases_check_signature_command() -> Command {
184 Command::new("check-signature")
185 .about("Verify the GPG signature of an installed release")
186 .long_about(
187 "Verify the GPG signature of an installed release.\n\n\
188 Downloads the signature file from GitHub and verifies it using GPG.\n\
189 Requires gpg to be installed and available in PATH.\n\n\
190 Note: Alpha versions are not signed and cannot be verified.",
191 )
192 .arg(version_arg())
193}
194
195fn alphas_cp_etc_file_command() -> Command {
196 cp_etc_file_command("Copy a configuration file to an alpha release's etc/rabbitmq directory")
197}
198
199fn cp_etc_file_command(about: &'static str) -> Command {
200 Command::new("cp-etc-file")
201 .about(about)
202 .long_about(format!(
203 "{}\n\n\
204 Copies a local file to the version's etc/rabbitmq directory.\n\n\
205 Supported files: {}",
206 about,
207 EtcFile::all_names().join(", ")
208 ))
209 .arg(
210 Arg::new("local_file_path")
211 .long("local-file-path")
212 .help("Path to the local file to copy")
213 .required(true)
214 .value_name("PATH"),
215 )
216 .arg(
217 Arg::new("etc_file")
218 .long("etc-file")
219 .help("Target configuration file name")
220 .required(true)
221 .value_name("FILE")
222 .value_parser(EtcFile::all_names()),
223 )
224 .arg(version_arg())
225}
226
227fn alphas_command() -> Command {
228 Command::new("alphas")
229 .about("Install, manage, rotate alpha RabbitMQ releases")
230 .arg_required_else_help(true)
231 .subcommand(alphas_list_command())
232 .subcommand(alphas_path_command())
233 .subcommand(alphas_logs_command())
234 .subcommand(alphas_install_command())
235 .subcommand(alphas_reinstall_command())
236 .subcommand(alphas_uninstall_command())
237 .subcommand(alphas_use_command())
238 .subcommand(alphas_cp_etc_file_command())
239 .subcommand(alphas_prune_command())
240 .subcommand(alphas_clean_command())
241 .subcommand(alphas_completions_command())
242}
243
244fn alphas_use_command() -> Command {
245 const HELP: &str = "Alpha version to use (e.g., 4.3.0-alpha.132057c7 or 'latest')";
246 Command::new("use")
247 .about("Output shell commands to use a specific alpha version")
248 .long_about(
249 "Output shell commands to use a specific alpha version.\n\n\
250 Use 'latest' to select the most recent installed alpha version.\n\n\
251 bash/zsh: eval \"$(frm alphas use [version])\"\n\
252 nushell: Use 'frm shell env nu' init script, then call 'frm-use [version]'",
253 )
254 .arg(positional_version_arg(HELP))
255 .arg(version_opt_arg(HELP))
256 .arg(
257 Arg::new("shell")
258 .long("shell")
259 .short('s')
260 .help("Shell type (bash, zsh, nu)")
261 .value_parser(clap::value_parser!(Shell)),
262 )
263}
264
265fn alphas_completions_command() -> Command {
266 Command::new("completions")
267 .about("Output installed alpha versions for shell completion")
268 .hide(true)
269 .arg(
270 Arg::new("shell")
271 .long("shell")
272 .short('s')
273 .help("Shell type (bash, zsh, nu)")
274 .value_parser(clap::value_parser!(Shell)),
275 )
276}
277
278fn alphas_list_command() -> Command {
279 Command::new("list")
280 .visible_alias("ls")
281 .about("List installed alpha RabbitMQ releases")
282}
283
284fn alphas_path_command() -> Command {
285 Command::new("path")
286 .about("Show the local path of an installed alpha release")
287 .arg(version_arg())
288}
289
290fn alphas_logs_command() -> Command {
291 Command::new("logs")
292 .about("Show RabbitMQ log file information for an alpha release")
293 .arg_required_else_help(true)
294 .subcommand(
295 Command::new("path")
296 .about("Show the path to the log file")
297 .arg(version_arg()),
298 )
299 .subcommand(
300 Command::new("tail")
301 .about("Show the last lines of the log file")
302 .arg(version_arg())
303 .arg(
304 Arg::new("lines")
305 .long("lines")
306 .short('n')
307 .help("Number of lines to show")
308 .default_value("10")
309 .value_parser(clap::value_parser!(usize)),
310 ),
311 )
312}
313
314fn alphas_install_command() -> Command {
315 const HELP: &str = "Alpha version to install (e.g., 4.3.0-alpha.132057c7 or 'latest')";
316 Command::new("install")
317 .visible_alias("i")
318 .about("Install an alpha RabbitMQ release")
319 .long_about(
320 "Install an alpha RabbitMQ release from rabbitmq/server-packages.\n\n\
321 Use 'latest' to automatically install the most recent alpha release.",
322 )
323 .arg(positional_version_arg(HELP))
324 .arg(version_opt_arg(HELP))
325 .arg(
326 Arg::new("force")
327 .long("force")
328 .short('f')
329 .help("Force reinstallation if version exists")
330 .action(ArgAction::SetTrue),
331 )
332}
333
334fn alphas_reinstall_command() -> Command {
335 const HELP: &str = "Alpha version to reinstall (e.g., 4.3.0-alpha.132057c7 or 'latest')";
336 Command::new("reinstall")
337 .about("Reinstall an alpha RabbitMQ release")
338 .long_about(
339 "Reinstall an alpha RabbitMQ release.\n\n\
340 Removes the existing installation and downloads a fresh copy.\n\n\
341 Use 'latest' to reinstall the most recent installed alpha version.",
342 )
343 .arg(positional_version_arg(HELP))
344 .arg(version_opt_arg(HELP))
345}
346
347fn alphas_uninstall_command() -> Command {
348 const HELP: &str = "Alpha version to uninstall (e.g., 4.3.0-alpha.132057c7 or 'latest')";
349 Command::new("uninstall")
350 .visible_alias("rm")
351 .about("Uninstall an alpha RabbitMQ release")
352 .long_about(
353 "Uninstall an alpha RabbitMQ release.\n\n\
354 Use 'latest' to uninstall the most recent installed alpha version.",
355 )
356 .arg(positional_version_arg(HELP))
357 .arg(version_opt_arg(HELP))
358}
359
360fn alphas_prune_command() -> Command {
361 Command::new("prune")
362 .about("Remove all installed alpha releases")
363 .long_about("Remove all installed alpha releases to free up disk space.")
364}
365
366fn alphas_clean_command() -> Command {
367 Command::new("clean")
368 .about("Remove alpha releases older than a specified time")
369 .long_about(
370 "Remove alpha releases older than a specified time.\n\n\
371 The --older-than flag accepts human-readable time strings like:\n\
372 - \"2 weeks ago\"\n\
373 - \"1 month ago\"\n\
374 - \"yesterday\"\n\
375 - \"2025-01-01\" (absolute date)",
376 )
377 .arg(
378 Arg::new("older_than")
379 .long("older-than")
380 .help("Remove alphas installed before this time (e.g., \"2 weeks ago\")")
381 .required(true)
382 .value_name("TIME"),
383 )
384}
385
386fn tanzu_command() -> Command {
387 Command::new("tanzu")
388 .about("Install and manage Tanzu RabbitMQ from local tarballs")
389 .arg_required_else_help(true)
390 .subcommand(tanzu_install_command())
391 .subcommand(tanzu_use_command())
392}
393
394fn tanzu_use_command() -> Command {
395 const HELP: &str = "Version to use (e.g., 4.2.3 or 'latest')";
396 Command::new("use")
397 .about("Output shell commands to use a specific Tanzu RabbitMQ version")
398 .long_about(
399 "Output shell commands to use a specific Tanzu RabbitMQ version.\n\n\
400 Use 'latest' to select the most recent installed GA version.\n\n\
401 bash/zsh: eval \"$(frm tanzu use [version])\"\n\
402 nushell: Use 'frm shell env nu' init script, then call 'frm-use [version]'",
403 )
404 .arg(positional_version_arg(HELP))
405 .arg(version_opt_arg(HELP))
406 .arg(
407 Arg::new("shell")
408 .long("shell")
409 .short('s')
410 .help("Shell type (bash, zsh, nu)")
411 .value_parser(clap::value_parser!(Shell)),
412 )
413}
414
415fn tanzu_install_command() -> Command {
416 Command::new("install")
417 .visible_alias("i")
418 .about("Install Tanzu RabbitMQ from a local tarball")
419 .long_about(
420 "Install Tanzu RabbitMQ from a local tarball.\n\n\
421 Requires both the tarball path and the expected version.\n\
422 The version in the tarball filename must match the specified version.\n\n\
423 Supported formats: .tar.xz, .tar.gz, .tgz",
424 )
425 .arg(
426 Arg::new("tarball_path")
427 .long("local-tanzu-rabbitmq-tarball-path")
428 .help("Path to the local Tanzu RabbitMQ tarball")
429 .required(true)
430 .value_name("PATH"),
431 )
432 .arg(
433 Arg::new("version")
434 .long("version")
435 .short('V')
436 .help("Expected RabbitMQ version (e.g., 4.2.3 or 4.2.3-rc.1)")
437 .required(true)
438 .value_name("VERSION"),
439 )
440 .arg(
441 Arg::new("force")
442 .long("force")
443 .short('f')
444 .help("Force reinstallation if version exists")
445 .action(ArgAction::SetTrue),
446 )
447}
448
449fn conf_command() -> Command {
450 Command::new("conf")
451 .about("Manage RabbitMQ configuration files")
452 .arg_required_else_help(true)
453 .subcommand(conf_get_key_command())
454 .subcommand(conf_set_key_command())
455}
456
457fn conf_get_key_command() -> Command {
458 Command::new("get-key")
459 .about("Get a configuration key value from rabbitmq.conf")
460 .long_about(
461 "Get a configuration key value from rabbitmq.conf.\n\n\
462 Supports pattern matching with * as a wildcard for a single segment:\n\n \
463 * `listeners.tcp.*` matches `listeners.tcp.default`, `listeners.tcp.amqp`, etc.\n \
464 * `log.*.level` matches `log.console.level`, `log.file.level`, etc.",
465 )
466 .arg(
467 Arg::new("key")
468 .help("Configuration key or pattern (e.g., listeners.tcp.* or heartbeat)")
469 .required(true)
470 .index(1),
471 )
472 .arg(version_arg())
473}
474
475fn conf_set_key_command() -> Command {
476 Command::new("set-key")
477 .about("Set a configuration key value in rabbitmq.conf")
478 .long_about(
479 "Set a configuration key value in rabbitmq.conf.\n\n\
480 Keys are validated against the known RabbitMQ configuration schema.\n\
481 Use --force to set unknown keys.",
482 )
483 .arg(
484 Arg::new("key")
485 .help("Configuration key (e.g., listeners.tcp.default)")
486 .required(true)
487 .index(1),
488 )
489 .arg(
490 Arg::new("value")
491 .help("Value to set")
492 .required(true)
493 .index(2),
494 )
495 .arg(version_arg())
496 .arg(
497 Arg::new("force")
498 .long("force")
499 .short('f')
500 .help("Set the key even if it's not recognized")
501 .action(ArgAction::SetTrue),
502 )
503}
504
505fn default_command() -> Command {
506 const HELP: &str = "Version to set as default (e.g., 4.2.3 or 'latest')";
507 Command::new("default")
508 .about("Set the default RabbitMQ version")
509 .long_about(
510 "Set the default RabbitMQ version.\n\n\
511 Use 'latest' to select the most recent installed GA version.",
512 )
513 .arg(positional_version_arg(HELP))
514 .arg(version_opt_arg(HELP))
515}
516
517fn shell_command() -> Command {
518 Command::new("shell")
519 .about("Shell-related operations")
520 .arg_required_else_help(true)
521 .subcommand(shell_completions_command())
522 .subcommand(shell_env_command())
523}
524
525fn shell_env_command() -> Command {
526 Command::new("env")
527 .about("Output shell initialization script")
528 .long_about(
529 "Output shell initialization script.\n\n\
530 Add to your shell profile:\n\
531 - bash: eval \"$(frm shell env bash)\" in ~/.bashrc\n\
532 - zsh: eval \"$(frm shell env zsh)\" in ~/.zshrc\n\
533 - nu: frm shell env nu | save -f ~/.local/frm/env.nu, then source in config.nu\n\n\
534 After setup, use 'frm-use <version>' to switch versions.",
535 )
536 .arg(
537 Arg::new("shell")
538 .help("Shell type (bash, zsh, nu)")
539 .required(true)
540 .index(1)
541 .value_parser(clap::value_parser!(Shell)),
542 )
543}
544
545fn shell_completions_command() -> Command {
546 Command::new("completions")
547 .about("Generate shell completions")
548 .long_about(
549 "Generate shell completions.\n\n\
550 If no shell is specified, attempts to detect the current shell from \
551 environment variables (SHELL, NU_VERSION, etc.).",
552 )
553 .arg(
554 Arg::new("shell")
555 .help("Target shell (bash, elvish, fish, nushell, powershell, zsh); auto-detected if omitted")
556 .index(1)
557 .value_parser(clap::value_parser!(CompletionShell)),
558 )
559}
560
561fn cli_command() -> Command {
562 Command::new("cli")
563 .about("Run a RabbitMQ CLI tool")
564 .long_about(format!(
565 "Run a RabbitMQ CLI tool from the specified version.\n\n\
566 Available tools: {}\n\n\
567 Use -- to separate tool arguments from frm options:\n\
568 frm cli rabbitmqctl -V 4.2.3 -- status",
569 RABBITMQ_CLI_TOOLS.join(", ")
570 ))
571 .trailing_var_arg(true)
572 .arg(Arg::new("tool").help("Tool to run").required(true).index(1))
573 .arg(version_arg())
574 .arg(
575 Arg::new("args")
576 .help("Arguments to pass to the tool (after --)")
577 .num_args(1..)
578 .index(2),
579 )
580}
581
582fn fg_command() -> Command {
583 Command::new("fg")
584 .about("Run RabbitMQ nodes in foreground")
585 .arg_required_else_help(true)
586 .subcommand(
587 Command::new("node")
588 .about("Start RabbitMQ server in foreground")
589 .arg(version_arg()),
590 )
591}
592
593fn bg_command() -> Command {
594 Command::new("bg")
595 .about("Start and stop RabbitMQ nodes in background")
596 .arg_required_else_help(true)
597 .subcommand(
598 Command::new("start")
599 .about("Start RabbitMQ server in background (detached)")
600 .arg(version_arg()),
601 )
602 .subcommand(
603 Command::new("stop")
604 .about("Stop a running RabbitMQ node")
605 .arg(version_arg()),
606 )
607}
608
609fn inspect_command() -> Command {
610 Command::new("inspect")
611 .about("Inspect a RabbitMQ configuration file")
612 .long_about(format!(
613 "Inspect a RabbitMQ configuration file from the specified version.\n\n\
614 Available files: {}",
615 CONFIG_FILES.join(", ")
616 ))
617 .arg(
618 Arg::new("file")
619 .help("Configuration file to inspect")
620 .required(true)
621 .index(1),
622 )
623 .arg(version_arg())
624}
625
626fn version_arg() -> Arg {
627 Arg::new("version")
628 .long("version")
629 .short('V')
630 .help("RabbitMQ version to use")
631 .value_name("VERSION")
632}
633
634fn positional_version_arg(help: &'static str) -> Arg {
635 Arg::new("version").help(help).index(1).required(false)
636}
637
638fn version_opt_arg(help: &'static str) -> Arg {
639 Arg::new("version_opt")
640 .long("version")
641 .short('V')
642 .help(format!(
643 "{}; takes precedence over the positional argument",
644 help
645 ))
646 .value_name("VERSION")
647}
648
649pub fn get_version_arg(matches: &clap::ArgMatches) -> Option<&String> {
650 matches
651 .get_one::<String>("version_opt")
652 .or_else(|| matches.get_one::<String>("version"))
653}