Skip to main content

frm/
cli.rs

1// Copyright (c) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use clap::{Arg, ArgAction, Command};
10use clap_complete::Shell as CompletionShell;
11
12use crate::commands::{CONFIG_FILES, RABBITMQ_TOOLS};
13use crate::shell::Shell;
14
15pub fn build_cli() -> Command {
16    Command::new("frm")
17        .version(env!("CARGO_PKG_VERSION"))
18        .author("Michael S. Klishin")
19        .about("Frakking RabbitMQ version Manager")
20        .arg_required_else_help(true)
21        .subcommand(releases_command())
22        .subcommand(alphas_command())
23        .subcommand(tanzu_command())
24        .subcommand(conf_command())
25        .subcommand(use_command())
26        .subcommand(default_command())
27        .subcommand(cli_command())
28        .subcommand(fg_command())
29        .subcommand(inspect_command())
30        .subcommand(env_command())
31        .subcommand(completions_command())
32}
33
34fn releases_command() -> Command {
35    Command::new("releases")
36        .about("Install or manage RabbitMQ releases (GA, RCs, betas); for alphas, see the 'alphas' command group")
37        .arg_required_else_help(true)
38        .subcommand(releases_list_command())
39        .subcommand(releases_path_command())
40        .subcommand(releases_logs_command())
41        .subcommand(releases_install_command())
42        .subcommand(releases_reinstall_command())
43        .subcommand(releases_uninstall_command())
44}
45
46fn releases_list_command() -> Command {
47    Command::new("list")
48        .visible_alias("ls")
49        .about("List installed stable RabbitMQ releases")
50}
51
52fn releases_path_command() -> Command {
53    Command::new("path")
54        .about("Show the local path of an installed release")
55        .long_about(
56            "Show the local path of an installed release.\n\n\
57            If no version is specified, tries to use the local .tool-versions file.",
58        )
59        .arg(version_arg())
60}
61
62fn releases_logs_command() -> Command {
63    Command::new("logs")
64        .about("Show RabbitMQ log file information for a release")
65        .arg_required_else_help(true)
66        .subcommand(
67            Command::new("path")
68                .about("Show the path to the log file")
69                .arg(version_arg()),
70        )
71        .subcommand(
72            Command::new("tail")
73                .about("Show the last lines of the log file")
74                .arg(version_arg())
75                .arg(
76                    Arg::new("lines")
77                        .long("lines")
78                        .short('n')
79                        .help("Number of lines to show")
80                        .default_value("10")
81                        .value_parser(clap::value_parser!(usize)),
82                ),
83        )
84}
85
86fn releases_install_command() -> Command {
87    Command::new("install")
88        .visible_alias("i")
89        .about("Install a stable RabbitMQ release")
90        .long_about(
91            "Install a stable RabbitMQ release (beta, rc, or GA).\n\n\
92            If no version is specified, tries to use the local .tool-versions file.\n\n\
93            Alpha versions are not allowed; use 'frm alphas install' instead.",
94        )
95        .arg(
96            Arg::new("version")
97                .help("Version to install (e.g., 4.2.3 or 4.2.0-rc.1)")
98                .index(1),
99        )
100        .arg(
101            Arg::new("force")
102                .long("force")
103                .short('f')
104                .help("Force reinstallation if version exists")
105                .action(ArgAction::SetTrue),
106        )
107}
108
109fn releases_reinstall_command() -> Command {
110    Command::new("reinstall")
111        .about("Reinstall a stable RabbitMQ release")
112        .long_about(
113            "Reinstall a stable RabbitMQ release.\n\n\
114            Removes the existing installation and downloads a fresh copy.\n\n\
115            If no version is specified, tries to use the local .tool-versions file.",
116        )
117        .arg(
118            Arg::new("version")
119                .help("Version to reinstall (e.g., 4.2.3)")
120                .index(1),
121        )
122}
123
124fn releases_uninstall_command() -> Command {
125    Command::new("uninstall")
126        .visible_alias("rm")
127        .about("Uninstall a stable RabbitMQ release")
128        .arg(
129            Arg::new("version")
130                .help("Version to uninstall (e.g., 4.2.3)")
131                .required(true)
132                .index(1),
133        )
134}
135
136fn alphas_command() -> Command {
137    Command::new("alphas")
138        .about("Install, manage, rotate alpha RabbitMQ releases")
139        .arg_required_else_help(true)
140        .subcommand(alphas_list_command())
141        .subcommand(alphas_path_command())
142        .subcommand(alphas_logs_command())
143        .subcommand(alphas_install_command())
144        .subcommand(alphas_reinstall_command())
145        .subcommand(alphas_uninstall_command())
146        .subcommand(alphas_prune_command())
147        .subcommand(alphas_clean_command())
148}
149
150fn alphas_list_command() -> Command {
151    Command::new("list")
152        .visible_alias("ls")
153        .about("List installed alpha RabbitMQ releases")
154}
155
156fn alphas_path_command() -> Command {
157    Command::new("path")
158        .about("Show the local path of an installed alpha release")
159        .long_about(
160            "Show the local path of an installed alpha release.\n\n\
161            If no version is specified, tries to use the local .tool-versions file.",
162        )
163        .arg(version_arg())
164}
165
166fn alphas_logs_command() -> Command {
167    Command::new("logs")
168        .about("Show RabbitMQ log file information for an alpha release")
169        .arg_required_else_help(true)
170        .subcommand(
171            Command::new("path")
172                .about("Show the path to the log file")
173                .arg(version_arg()),
174        )
175        .subcommand(
176            Command::new("tail")
177                .about("Show the last lines of the log file")
178                .arg(version_arg())
179                .arg(
180                    Arg::new("lines")
181                        .long("lines")
182                        .short('n')
183                        .help("Number of lines to show")
184                        .default_value("10")
185                        .value_parser(clap::value_parser!(usize)),
186                ),
187        )
188}
189
190fn alphas_install_command() -> Command {
191    Command::new("install")
192        .visible_alias("i")
193        .about("Install an alpha RabbitMQ release")
194        .long_about(
195            "Install an alpha RabbitMQ release from rabbitmq/server-packages.\n\n\
196            Use --latest to automatically install the most recent alpha release.",
197        )
198        .arg(
199            Arg::new("version")
200                .help("Alpha version to install (e.g., 4.3.0-alpha.132057c7)")
201                .index(1)
202                .conflicts_with("latest"),
203        )
204        .arg(
205            Arg::new("latest")
206                .long("latest")
207                .short('l')
208                .help("Install the most recent alpha release")
209                .action(ArgAction::SetTrue),
210        )
211        .arg(
212            Arg::new("force")
213                .long("force")
214                .short('f')
215                .help("Force reinstallation if version exists")
216                .action(ArgAction::SetTrue),
217        )
218}
219
220fn alphas_reinstall_command() -> Command {
221    Command::new("reinstall")
222        .about("Reinstall an alpha RabbitMQ release")
223        .long_about(
224            "Reinstall an alpha RabbitMQ release.\n\n\
225            Removes the existing installation and downloads a fresh copy.",
226        )
227        .arg(
228            Arg::new("version")
229                .help("Alpha version to reinstall (e.g., 4.3.0-alpha.132057c7)")
230                .required(true)
231                .index(1),
232        )
233}
234
235fn alphas_uninstall_command() -> Command {
236    Command::new("uninstall")
237        .visible_alias("rm")
238        .about("Uninstall an alpha RabbitMQ release")
239        .arg(
240            Arg::new("version")
241                .help("Alpha version to uninstall (e.g., 4.3.0-alpha.132057c7)")
242                .required(true)
243                .index(1),
244        )
245}
246
247fn alphas_prune_command() -> Command {
248    Command::new("prune")
249        .about("Remove all installed alpha releases")
250        .long_about("Remove all installed alpha releases to free up disk space.")
251}
252
253fn alphas_clean_command() -> Command {
254    Command::new("clean")
255        .about("Remove alpha releases older than a specified time")
256        .long_about(
257            "Remove alpha releases older than a specified time.\n\n\
258            The --older-than flag accepts human-readable time strings like:\n\
259            - \"2 weeks ago\"\n\
260            - \"1 month ago\"\n\
261            - \"yesterday\"\n\
262            - \"2025-01-01\" (absolute date)",
263        )
264        .arg(
265            Arg::new("older_than")
266                .long("older-than")
267                .help("Remove alphas installed before this time (e.g., \"2 weeks ago\")")
268                .required(true)
269                .value_name("TIME"),
270        )
271}
272
273fn tanzu_command() -> Command {
274    Command::new("tanzu")
275        .about("Install Tanzu RabbitMQ from local tarballs")
276        .arg_required_else_help(true)
277        .subcommand(tanzu_install_command())
278}
279
280fn tanzu_install_command() -> Command {
281    Command::new("install")
282        .visible_alias("i")
283        .about("Install Tanzu RabbitMQ from a local tarball")
284        .long_about(
285            "Install Tanzu RabbitMQ from a local tarball.\n\n\
286            Requires both the tarball path and the expected version.\n\
287            The version in the tarball filename must match the specified version.\n\n\
288            Supported formats: .tar.xz, .tar.gz, .tgz",
289        )
290        .arg(
291            Arg::new("tarball_path")
292                .long("local-tanzu-rabbitmq-tarball-path")
293                .help("Path to the local Tanzu RabbitMQ tarball")
294                .required(true)
295                .value_name("PATH"),
296        )
297        .arg(
298            Arg::new("version")
299                .long("version")
300                .short('V')
301                .help("Expected RabbitMQ version (e.g., 4.2.3 or 4.2.3-rc.1)")
302                .required(true)
303                .value_name("VERSION"),
304        )
305        .arg(
306            Arg::new("force")
307                .long("force")
308                .short('f')
309                .help("Force reinstallation if version exists")
310                .action(ArgAction::SetTrue),
311        )
312}
313
314fn conf_command() -> Command {
315    Command::new("conf")
316        .about("Manage RabbitMQ configuration files")
317        .arg_required_else_help(true)
318        .subcommand(conf_get_key_command())
319        .subcommand(conf_set_key_command())
320}
321
322fn conf_get_key_command() -> Command {
323    Command::new("get-key")
324        .about("Get a configuration key value from rabbitmq.conf")
325        .long_about(
326            "Get a configuration key value from rabbitmq.conf.\n\n\
327            Supports pattern matching with * as a wildcard for a single segment:\n\n \
328            * `listeners.tcp.*` matches `listeners.tcp.default`, `listeners.tcp.amqp`, etc.\n \
329            * `log.*.level` matches `log.console.level`, `log.file.level`, etc.\n\n\
330            If no version is specified, tries to use the local .tool-versions file.",
331        )
332        .arg(
333            Arg::new("key")
334                .help("Configuration key or pattern (e.g., listeners.tcp.* or heartbeat)")
335                .required(true)
336                .index(1),
337        )
338        .arg(version_arg())
339}
340
341fn conf_set_key_command() -> Command {
342    Command::new("set-key")
343        .about("Set a configuration key value in rabbitmq.conf")
344        .long_about(
345            "Set a configuration key value in rabbitmq.conf.\n\n\
346            If no version is specified, tries to use the local .tool-versions file.\n\n\
347            Keys are validated against the known RabbitMQ configuration schema.\n\
348            Use --force to set unknown keys.",
349        )
350        .arg(
351            Arg::new("key")
352                .help("Configuration key (e.g., listeners.tcp.default)")
353                .required(true)
354                .index(1),
355        )
356        .arg(
357            Arg::new("value")
358                .help("Value to set")
359                .required(true)
360                .index(2),
361        )
362        .arg(version_arg())
363        .arg(
364            Arg::new("force")
365                .long("force")
366                .short('f')
367                .help("Set the key even if it's not recognized")
368                .action(ArgAction::SetTrue),
369        )
370}
371
372fn use_command() -> Command {
373    Command::new("use")
374        .about("Output shell commands to use a specific version")
375        .long_about(
376            "Output shell commands to use a specific version.\n\n\
377            If no version is specified, tries to use the local .tool-versions file.\n\n\
378            bash/zsh: eval \"$(frm use [version])\"\n\
379            nushell:  Use 'frm env nu' init script, then call 'frm-use [version]'",
380        )
381        .arg(
382            Arg::new("version")
383                .help("Version to use (e.g., 4.2.3), or uses .tool-versions")
384                .index(1),
385        )
386        .arg(
387            Arg::new("shell")
388                .long("shell")
389                .short('s')
390                .help("Shell type (bash, zsh, nu)")
391                .value_parser(clap::value_parser!(Shell)),
392        )
393}
394
395fn default_command() -> Command {
396    Command::new("default")
397        .about("Set the default RabbitMQ version")
398        .arg(
399            Arg::new("version")
400                .help("Version to set as default (e.g., 4.2.3)")
401                .required(true)
402                .index(1),
403        )
404}
405
406fn env_command() -> Command {
407    Command::new("env")
408        .about("Output shell initialization script")
409        .long_about(
410            "Output shell initialization script.\n\n\
411            Add to your shell profile:\n\
412            - bash: eval \"$(frm env bash)\" in ~/.bashrc\n\
413            - zsh: eval \"$(frm env zsh)\" in ~/.zshrc\n\
414            - nu: frm env nu | save -f ~/.local/frm/env.nu, then source in config.nu\n\n\
415            After setup, use 'frm-use <version>' to switch versions.",
416        )
417        .arg(
418            Arg::new("shell")
419                .help("Shell type (bash, zsh, nu)")
420                .required(true)
421                .index(1)
422                .value_parser(clap::value_parser!(Shell)),
423        )
424}
425
426fn cli_command() -> Command {
427    Command::new("cli")
428        .about("Run a RabbitMQ CLI tool")
429        .long_about(format!(
430            "Run a RabbitMQ CLI tool from the specified version.\n\n\
431            Available tools: {}\n\n\
432            If no version is specified, tries to use the local .tool-versions file.\n\n\
433            Use -- to separate tool arguments from frm options:\n\
434            frm cli rabbitmqctl -V 4.2.3 -- status",
435            RABBITMQ_TOOLS.join(", ")
436        ))
437        .trailing_var_arg(true)
438        .arg(Arg::new("tool").help("Tool to run").required(true).index(1))
439        .arg(version_arg())
440        .arg(
441            Arg::new("args")
442                .help("Arguments to pass to the tool (after --)")
443                .num_args(1..)
444                .index(2),
445        )
446}
447
448fn fg_command() -> Command {
449    Command::new("fg")
450        .about("Run RabbitMQ nodes in foreground")
451        .arg_required_else_help(true)
452        .subcommand(
453            Command::new("node")
454                .about("Start RabbitMQ server in foreground")
455                .long_about(
456                    "Start RabbitMQ server in foreground.\n\n\
457                    If no version is specified, tries to use the local .tool-versions file.",
458                )
459                .arg(version_arg()),
460        )
461}
462
463fn inspect_command() -> Command {
464    Command::new("inspect")
465        .about("Inspect a RabbitMQ configuration file")
466        .long_about(format!(
467            "Inspect a RabbitMQ configuration file from the specified version.\n\n\
468            Available files: {}\n\n\
469            If no version is specified, tries to use the local .tool-versions file.",
470            CONFIG_FILES.join(", ")
471        ))
472        .arg(
473            Arg::new("file")
474                .help("Configuration file to inspect")
475                .required(true)
476                .index(1),
477        )
478        .arg(version_arg())
479}
480
481fn version_arg() -> Arg {
482    Arg::new("version")
483        .long("version")
484        .short('V')
485        .help("RabbitMQ version to use, or uses .tool-versions")
486        .value_name("VERSION")
487}
488
489fn completions_command() -> Command {
490    Command::new("completions")
491        .about("Generate shell completions")
492        .arg(
493            Arg::new("shell")
494                .help("Target shell")
495                .required(true)
496                .index(1)
497                .value_parser(clap::value_parser!(CompletionShell)),
498        )
499}