|
| 1 | +-module(rabbit_cli). |
| 2 | + |
| 3 | +-include_lib("kernel/include/logger.hrl"). |
| 4 | + |
| 5 | +-export([main/1]). |
| 6 | + |
| 7 | +main(Args) -> |
| 8 | + case run_cli(Args) of |
| 9 | + ok -> |
| 10 | + erlang:halt(); |
| 11 | + {error, ErrorMsg} -> |
| 12 | + io:format(standard_error, "Error: ~ts~n", [ErrorMsg]), |
| 13 | + erlang:halt(1) |
| 14 | + end. |
| 15 | + |
| 16 | +run_cli(Args) -> |
| 17 | + maybe |
| 18 | + Progname = escript:script_name(), |
| 19 | + {ok, ArgMap, RemainingArgs} ?= parse_args(Progname, Args), |
| 20 | + Nodename = lookup_rabbitmq_nodename(ArgMap), |
| 21 | + {ok, _} ?= net_kernel:start(undefined, #{name_domain => shortnames}), |
| 22 | + true ?= net_kernel:connect_node(Nodename), |
| 23 | + |
| 24 | + {ok, IO} ?= rabbit_cli_io:start_link(), |
| 25 | + try |
| 26 | + Ret = run_command(Nodename, Progname, ArgMap, RemainingArgs, IO), |
| 27 | + io:format("Ret = ~p~n", [Ret]) |
| 28 | + after |
| 29 | + rabbit_cli_io:stop(IO) |
| 30 | + end, |
| 31 | + ok |
| 32 | + end. |
| 33 | + |
| 34 | +parse_args(Progname, Args) -> |
| 35 | + Definition = #{arguments => |
| 36 | + [#{name => verbose, |
| 37 | + long => "-verbose", |
| 38 | + short => $v, |
| 39 | + action => count, |
| 40 | + help => |
| 41 | + "Be verbose; can be specified multiple times to " |
| 42 | + "increase verbosity"}, |
| 43 | + #{name => node, |
| 44 | + long => "-node", |
| 45 | + short => $n, |
| 46 | + type => string, |
| 47 | + nargs => 1, |
| 48 | + help => "Name of the node to control"}]}, |
| 49 | + Options = #{progname => Progname}, |
| 50 | + case rabbit_cli_args:parse(Args, Definition, Options) of |
| 51 | + {ok, ArgMap, _CmdPath, _Command, RemainingArgs} -> |
| 52 | + {ok, ArgMap, RemainingArgs}; |
| 53 | + {error, _} = Error-> |
| 54 | + Error |
| 55 | + end. |
| 56 | + |
| 57 | +lookup_rabbitmq_nodename(#{node := Nodename}) -> |
| 58 | + Nodename1 = complete_nodename(Nodename), |
| 59 | + Nodename1; |
| 60 | +lookup_rabbitmq_nodename(_) -> |
| 61 | + GuessedNodename0 = guess_rabbitmq_nodename(), |
| 62 | + GuessedNodename1 = complete_nodename(GuessedNodename0), |
| 63 | + GuessedNodename1. |
| 64 | + |
| 65 | +guess_rabbitmq_nodename() -> |
| 66 | + case net_adm:names() of |
| 67 | + {ok, NamesAndPorts} -> |
| 68 | + Names0 = [Name || {Name, _Port} <- NamesAndPorts], |
| 69 | + Names1 = lists:sort(Names0), |
| 70 | + Names2 = lists:filter( |
| 71 | + fun |
| 72 | + ("rabbit" ++ _) -> true; |
| 73 | + (_) -> false |
| 74 | + end, Names1), |
| 75 | + case Names2 of |
| 76 | + [First | _] -> |
| 77 | + First; |
| 78 | + [] -> |
| 79 | + "rabbit" |
| 80 | + end; |
| 81 | + {error, address} -> |
| 82 | + "rabbit" |
| 83 | + end. |
| 84 | + |
| 85 | +complete_nodename(Nodename) -> |
| 86 | + case re:run(Nodename, "@", [{capture, none}]) of |
| 87 | + nomatch -> |
| 88 | + {ok, ThisHost} = inet:gethostname(), |
| 89 | + list_to_atom(Nodename ++ "@" ++ ThisHost); |
| 90 | + match -> |
| 91 | + list_to_atom(Nodename) |
| 92 | + end. |
| 93 | + |
| 94 | +%lookup_command_map(Nodename) -> |
| 95 | +% %% Order of operations: |
| 96 | +% %% 1. refresh the cached copy: |
| 97 | +% %% a. is the node running? |
| 98 | +% %% yes -> query its uptime |
| 99 | +% %% no -> local or remote? |
| 100 | +% %% local -> list beam files + file size + last modified date |
| 101 | +% %% remote -> no refresh possible |
| 102 | +% %% b. compare uptime to cache date |
| 103 | +% %% or |
| 104 | +% %% compare files list to cached files list |
| 105 | +% %% c. if refresh needed, query command map or (local only) extract it from files list |
| 106 | +% %% |
| 107 | +% %% 2. use cached command map; error out if none |
| 108 | +% %% |
| 109 | +% %% Or: |
| 110 | +% %% 0. node extracts the command map on startup and stores it persistent_term |
| 111 | +% %% 1. query node for command map with a very short timeout + store result locally |
| 112 | +% %% 2. read local copy |
| 113 | +% %% + we skip the query if `--help` or completion and if there is a local copy |
| 114 | +% case is_node_local(Nodename) of |
| 115 | +% true -> |
| 116 | +% %% Generated local command map. |
| 117 | +% lookup_local_command_map(), |
| 118 | +% {ok, #{}}; |
| 119 | +% false -> |
| 120 | +% %% Cache or RPC. |
| 121 | +% {ok, #{}} |
| 122 | +% end. |
| 123 | +% |
| 124 | +%is_node_local(Nodename) -> |
| 125 | +% case re:run(Nodename, "@(.+)", [{capture, all_but_first, list}]) of |
| 126 | +% {match, ["localhost"]} -> |
| 127 | +% true; |
| 128 | +% {match, [HostPart]} -> |
| 129 | +% ThisHost = inet:gethostname(), |
| 130 | +% HostPart =:= ThisHost; |
| 131 | +% nomatch -> |
| 132 | +% true |
| 133 | +% end. |
| 134 | +% |
| 135 | +%lookup_local_command_map() -> |
| 136 | +% ScriptDir = filename:dirname(escript:script_name()), |
| 137 | +% io:format("Script = ~p~n", [ScriptDir]), |
| 138 | +% ok. |
| 139 | + |
| 140 | +run_command(Nodename, Progname, ArgMap, RemainingArgs, IO) -> |
| 141 | + try |
| 142 | + erpc:call( |
| 143 | + Nodename, |
| 144 | + rabbit_cli_commands, run_command, |
| 145 | + [Progname, ArgMap, RemainingArgs, IO]) |
| 146 | + catch |
| 147 | + error:{erpc, Reason} -> |
| 148 | + {error, Reason} |
| 149 | + end. |
0 commit comments