Skip to content

Commit 8694388

Browse files
committed
New prototype with commands defined by broker, not cli
1 parent f75e0fc commit 8694388

File tree

5 files changed

+436
-36
lines changed

5 files changed

+436
-36
lines changed

deps/rabbit/Makefile

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,24 @@ DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
158158
include ../../rabbitmq-components.mk
159159
include ../../erlang.mk
160160

161-
CLI_SCRIPTS := scripts/rmq
161+
ESCRIPT_NAME := rabbit_cli
162+
ESCRIPT_FILE := scripts/rmq
162163

163-
ebin/$(PROJECT).app:: $(CLI_SCRIPTS)
164+
ebin/$(PROJECT).app:: $(ESCRIPT_FILE)
164165

165-
$(CLI_SCRIPTS): ebin/rabbit_cli_escript.beam
166-
$(gen_verbose) echo '#!/usr/bin/env escript' > "$@"
167-
$(verbose) echo '%%! -start_epmd false' >> "$@"
168-
$(verbose) cat $^ >> "$@"
169-
$(verbose) chmod a+x "$@"
166+
$(ESCRIPT_FILE): ebin/rabbit_cli.beam ebin/rabbit_cli_args.beam ebin/rabbit_cli_io.beam
167+
$(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) $(patsubst %,$(PROJECT)/%,$^)
168+
$(gen_verbose) printf "%s\n" \
169+
"#!$(ESCRIPT_SHEBANG)" \
170+
"%% $(ESCRIPT_COMMENT)" \
171+
"%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE)
172+
$(verbose) cat $(abspath $(ESCRIPT_ZIP_FILE)) >> $(ESCRIPT_FILE)
173+
$(verbose) chmod a+x $(ESCRIPT_FILE)
170174

171175
clean:: clean-cli
172176

173177
clean-cli:
174-
$(gen_verbose) rm -f $(CLI_SCRIPTS)
178+
$(gen_verbose) rm -f $(ESCRIPT_FILE)
175179

176180
ifeq ($(strip $(BATS)),)
177181
BATS := $(ERLANG_MK_TMP)/bats/bin/bats

deps/rabbit/src/rabbit_cli.erl

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-module(rabbit_cli_args).
2+
3+
-include_lib("kernel/include/logger.hrl").
4+
5+
-export([parse/3]).
6+
7+
parse(Args, Definition, Options) ->
8+
parse(Args, Definition, Options, []).
9+
10+
parse(Args, Definition, Options, RemainingArgs) ->
11+
case argparse:parse(Args, Definition, Options) of
12+
{ok, ArgMap, CmdPath, Command} ->
13+
RemainingArgs1 = lists:reverse(RemainingArgs),
14+
{ok, ArgMap, CmdPath, Command, RemainingArgs1};
15+
{error, {_CmdPath, undefined, Arg, <<>>}} ->
16+
Args1 = Args -- [Arg],
17+
RemainingArgs1 = [Arg | RemainingArgs],
18+
parse(Args1, Definition, Options, RemainingArgs1);
19+
{error, _} = Error ->
20+
Error
21+
end.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
-module(rabbit_cli_commands).
2+
3+
-include_lib("kernel/include/logger.hrl").
4+
5+
-export([commands/0, run_command/4]).
6+
-export([list_exchanges/4]).
7+
8+
commands() ->
9+
%% Extract the commands from module attributes like feature flags and boot
10+
%% steps.
11+
#{
12+
commands =>
13+
#{
14+
"list" =>
15+
#{
16+
commands =>
17+
#{"exchanges" =>
18+
#{
19+
handler => {?MODULE, list_exchanges}
20+
}
21+
}
22+
}
23+
}
24+
}.
25+
26+
run_command(Progname, ArgMap, Args, IO) ->
27+
Definition = commands(),
28+
Options = #{progname => Progname},
29+
case rabbit_cli_args:parse(Args, Definition, Options) of
30+
{ok, NewArgMap, _CmdPath, Command, RemainingArgs} ->
31+
ArgMap1 = maps:merge(ArgMap, NewArgMap),
32+
%% TODO: Put both processes under the rabbit supervision tree.
33+
RunnerPid = command_runner(
34+
Progname, Command, ArgMap1, RemainingArgs, IO),
35+
RunnerMRef = erlang:monitor(process, RunnerPid),
36+
receive
37+
{'DOWN', RunnerMRef, _, _, Reason} ->
38+
{ok, Reason}
39+
end;
40+
{error, Reason} = Error ->
41+
?LOG_ALERT("Error: ~s", [argparse:format_error(Reason)]),
42+
Error
43+
end.
44+
45+
command_runner(
46+
Progname, #{handler := {Mod, Fun}} = _Command, ArgMap, RemainingArgs, IO) ->
47+
spawn_link(Mod, Fun, [Progname, ArgMap, RemainingArgs, IO]).
48+
49+
list_exchanges(Progname, ArgMap, RemainingArgs, IO) ->
50+
InfoKeys = rabbit_exchange:info_keys(),
51+
Fields = lists:map(
52+
fun
53+
(name = Key) ->
54+
#{name => Key, type => resource};
55+
(type = Key) ->
56+
#{name => Key, type => string};
57+
(durable = Key) ->
58+
#{name => Key, type => boolean};
59+
(auto_delete = Key) ->
60+
#{name => Key, type => boolean};
61+
(internal = Key) ->
62+
#{name => Key, type => boolean};
63+
(arguments = Key) ->
64+
#{name => Key, type => term};
65+
(policy = Key) ->
66+
#{name => Key, type => string};
67+
(user_who_performed_action = Key) ->
68+
#{name => Key, type => string};
69+
(Key) ->
70+
#{name => Key, type => term}
71+
end, InfoKeys),
72+
case rabbit_cli_io:start_record_stream(IO, exchanges, Fields, {Progname, ArgMap, RemainingArgs}) of
73+
{ok, Stream} ->
74+
Exchanges = rabbit_exchange:list(),
75+
lists:foreach(
76+
fun(Exchange) ->
77+
Record0 = rabbit_exchange:info(Exchange),
78+
Record1 = lists:sublist(Record0, length(Fields)),
79+
Record2 = [Value || {_Key, Value} <- Record1],
80+
rabbit_cli_io:push_new_record(IO, Stream, Record2)
81+
end, Exchanges),
82+
rabbit_cli_io:end_record_stream(IO, Stream);
83+
{error, _} = Error ->
84+
Error
85+
end.

0 commit comments

Comments
 (0)