feat: refactor call_hocon to use escript

This commit is contained in:
x1001100011 2021-09-19 19:15:05 -07:00
parent 13e146d1ee
commit 188f44ad50
3 changed files with 333 additions and 36 deletions

View File

@ -14,8 +14,8 @@ defmodule EmqxReleaseHelper.Overlay do
copy "bin/install_upgrade.escript", "bin/install_upgrade.escript" copy "bin/install_upgrade.escript", "bin/install_upgrade.escript"
copy "bin/node_dump", "bin/node_dump" copy "bin/node_dump", "bin/node_dump"
copy "bin/nodetool", "bin/nodetool" # copy "bin/nodetool", "bin/nodetool"
copy "bin/nodetool", "bin/nodetool-#{release_version}" # copy "bin/nodetool", "bin/nodetool-#{release_version}"
# copy "bin/emqx", "bin/emqx" # copy "bin/emqx", "bin/emqx"
# copy "bin/emqx_ctl", "bin/emqx_ctl" # copy "bin/emqx_ctl", "bin/emqx_ctl"

View File

@ -20,7 +20,6 @@ export RELEASE_NODE="${RELEASE_NODE:-"$RELEASE_NAME"}"
export RELEASE_TMP="${RELEASE_TMP:-"$RELEASE_ROOT/tmp"}" export RELEASE_TMP="${RELEASE_TMP:-"$RELEASE_ROOT/tmp"}"
export RELEASE_VM_ARGS="${RELEASE_VM_ARGS:-"$REL_VSN_DIR/vm.args"}" export RELEASE_VM_ARGS="${RELEASE_VM_ARGS:-"$REL_VSN_DIR/vm.args"}"
export RELEASE_REMOTE_VM_ARGS="${RELEASE_REMOTE_VM_ARGS:-"$REL_VSN_DIR/remote.vm.args"}" export RELEASE_REMOTE_VM_ARGS="${RELEASE_REMOTE_VM_ARGS:-"$REL_VSN_DIR/remote.vm.args"}"
export RELEASE_DISTRIBUTION="${RELEASE_DISTRIBUTION:-"sname"}"
export RELEASE_BOOT_SCRIPT="${RELEASE_BOOT_SCRIPT:-"start"}" export RELEASE_BOOT_SCRIPT="${RELEASE_BOOT_SCRIPT:-"start"}"
export RELEASE_BOOT_SCRIPT_CLEAN="${RELEASE_BOOT_SCRIPT_CLEAN:-"start_clean"}" export RELEASE_BOOT_SCRIPT_CLEAN="${RELEASE_BOOT_SCRIPT_CLEAN:-"start_clean"}"
export RELEASE_SYS_CONFIG="${RELEASE_SYS_CONFIG:-"$REL_VSN_DIR/sys"}" export RELEASE_SYS_CONFIG="${RELEASE_SYS_CONFIG:-"$REL_VSN_DIR/sys"}"
@ -28,6 +27,7 @@ export RELEASE_SYS_CONFIG="${RELEASE_SYS_CONFIG:-"$REL_VSN_DIR/sys"}"
# defined in emqx_vars # defined in emqx_vars
export RUNNER_ROOT_DIR export RUNNER_ROOT_DIR
export RUNNER_ETC_DIR export RUNNER_ETC_DIR
export ERTS_VSN
export SCHEMA_MOD=emqx_machine_schema export SCHEMA_MOD=emqx_machine_schema
export CONFIGS_DIR="$RUNNER_DATA_DIR/configs" export CONFIGS_DIR="$RUNNER_DATA_DIR/configs"
@ -50,7 +50,7 @@ set_name () {
if [ -z "$NAME" ]; then if [ -z "$NAME" ]; then
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
# for boot commands, inspect emqx.conf for node name # for boot commands, inspect emqx.conf for node name
NAME="$(call_hocon -s $SCHEMA_MOD -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")" NAME="$(call_hocon -s $SCHEMA_MOD -c $RUNNER_ETC_DIR/emqx.conf get node.name | tr -d \")"
else else
# for non-boot commands, inspect vm.<time>.args for node name # for non-boot commands, inspect vm.<time>.args for node name
# shellcheck disable=SC2012,SC2086 # shellcheck disable=SC2012,SC2086
@ -86,10 +86,6 @@ rand () {
dd count=1 bs=2 if=/dev/urandom 2> /dev/null | od -x | awk 'NR==1{print $2}' dd count=1 bs=2 if=/dev/urandom 2> /dev/null | od -x | awk 'NR==1{print $2}'
} }
call_hocon () {
eval_elixir "EmqxConfigHelper.hocon_cli(\"$1 $2 $3 $4 $5 $6 $7 $8 $9\")"
}
generate_config () { generate_config () {
local name_type="$NAME_TYPE" local name_type="$NAME_TYPE"
local node_name="$NAME" local node_name="$NAME"
@ -110,7 +106,7 @@ generate_config () {
## ths command populates two files: app.<time>.config and vm.<time>.args ## ths command populates two files: app.<time>.config and vm.<time>.args
## disable SC2086 to allow EMQX_LICENSE_CONF_OPTION to split ## disable SC2086 to allow EMQX_LICENSE_CONF_OPTION to split
# shellcheck disable=SC2086 # shellcheck disable=SC2086
call_hocon -v -t "$NOW_TIME" -s $SCHEMA_MOD -c "$RUNNER_ETC_DIR"/emqx.conf $EMQX_LICENSE_CONF_OPTION -d "$RUNNER_DATA_DIR"/configs generate call_hocon -v -t $NOW_TIME -s $SCHEMA_MOD -c $RUNNER_ETC_DIR/emqx.conf $EMQX_LICENSE_CONF_OPTION -d $RUNNER_DATA_DIR/configs generate
## filenames are per-hocon convention ## filenames are per-hocon convention
RELEASE_SYS_CONFIG="$CONFIGS_DIR/app.$NOW_TIME.config" RELEASE_SYS_CONFIG="$CONFIGS_DIR/app.$NOW_TIME.config"
@ -152,27 +148,10 @@ generate_config () {
# fi # fi
} }
release_distribution () {
case $RELEASE_DISTRIBUTION in
none)
;;
name | sname)
echo "--$RELEASE_DISTRIBUTION $1"
;;
*)
echo "ERROR: Expected sname, name, or none in RELEASE_DISTRIBUTION, got: $RELEASE_DISTRIBUTION" >&2
exit 1
;;
esac
}
rpc () { rpc () {
# TODO: remote vm args # TODO: remote vm args
exec "$REL_VSN_DIR/elixir" \ exec "$REL_VSN_DIR/elixir" \
--hidden --cookie "$RELEASE_COOKIE" \ --hidden --cookie "$RELEASE_COOKIE" \
$(release_distribution "rpc-$(rand)-$RELEASE_NODE") \
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \ --boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \ --boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \
--vm-args "$RELEASE_REMOTE_VM_ARGS" \ --vm-args "$RELEASE_REMOTE_VM_ARGS" \
@ -185,7 +164,6 @@ start () {
shift shift
exec "$REL_VSN_DIR/$REL_EXEC" \ exec "$REL_VSN_DIR/$REL_EXEC" \
--cookie "$RELEASE_COOKIE" \ --cookie "$RELEASE_COOKIE" \
$(release_distribution "$RELEASE_NODE") \
--erl "-mode $RELEASE_MODE -mnesia dir \"${MNESIA_DATA_DIR}\"" \ --erl "-mode $RELEASE_MODE -mnesia dir \"${MNESIA_DATA_DIR}\"" \
--erl-config "$RELEASE_SYS_CONFIG" \ --erl-config "$RELEASE_SYS_CONFIG" \
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT" \ --boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT" \
@ -193,13 +171,19 @@ start () {
--vm-args "$RELEASE_VM_ARGS" "$@" --vm-args "$RELEASE_VM_ARGS" "$@"
} }
eval_elixir () { call_hocon() {
"$REL_VSN_DIR/elixir" \ nodetool hocon "$@"
--cookie "$RELEASE_COOKIE" \ }
--erl-config "$RELEASE_SYS_CONFIG" \
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \ nodetool () {
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \ command="$1"; shift
--vm-args "$RELEASE_VM_ARGS" --eval "$1" "$RUNNER_ROOT_DIR/erts-$ERTS_VSN/bin/erl" \
+B -noshell \
-boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \
-boot_var RELEASE_LIB "$RELEASE_ROOT/lib" \
-run escript start \
-extra "$RUNNER_ROOT_DIR/bin/nodetool" \
"$command" "$@"
} }
set_name "$1" set_name "$1"
@ -226,14 +210,18 @@ case $1 in
echo "ERROR: EVAL expects an expression as argument" >&2 echo "ERROR: EVAL expects an expression as argument" >&2
exit 1 exit 1
fi fi
eval_elixir "$2" exec "$REL_VSN_DIR/elixir" \
--cookie "$RELEASE_COOKIE" \
--erl-config "$RELEASE_SYS_CONFIG" \
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \
--vm-args "$RELEASE_VM_ARGS" --eval "$2"
;; ;;
remote) remote)
generate_config generate_config
exec "$REL_VSN_DIR/iex" \ exec "$REL_VSN_DIR/iex" \
--werl --hidden --cookie "$RELEASE_COOKIE" \ --werl --hidden --cookie "$RELEASE_COOKIE" \
$(release_distribution "rem-$(rand)-$RELEASE_NODE") \
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \ --boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \ --boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \
--vm-args "$RELEASE_REMOTE_VM_ARGS" \ --vm-args "$RELEASE_REMOTE_VM_ARGS" \

309
rel/overlays/bin/nodetool Executable file
View File

@ -0,0 +1,309 @@
#!/usr/bin/env escript
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ft=erlang ts=4 sw=4 et
%% -------------------------------------------------------------------
%%
%% nodetool: Helper Script for interacting with live nodes
%%
%% -------------------------------------------------------------------
-mode(compile).
main(Args) ->
case os:type() of
{win32, nt} -> ok;
_nix ->
case init:get_argument(start_epmd) of
{ok, [["true"]]} ->
ok = start_epmd();
_ ->
ok
end
end,
case Args of
["hocon" | Rest] ->
%% forward the call to hocon_cli
hocon_cli:main(Rest);
_ ->
do(Args)
end.
do(Args) ->
ok = do_with_halt(Args, "mnesia_dir", fun create_mnesia_dir/2),
ok = do_with_halt(Args, "chkconfig", fun("-config", X) -> chkconfig(X) end),
ok = do_with_halt(Args, "chkconfig", fun chkconfig/1),
Args1 = do_with_ret(Args, "-name",
fun(TargetName) ->
ThisNode = this_node_name(longnames, TargetName),
{ok, _} = net_kernel:start([ThisNode, longnames]),
put(target_node, nodename(TargetName))
end),
Args2 = do_with_ret(Args1, "-sname",
fun(TargetName) ->
ThisNode = this_node_name(shortnames, TargetName),
{ok, _} = net_kernel:start([ThisNode, shortnames]),
put(target_node, nodename(TargetName))
end),
RestArgs = do_with_ret(Args2, "-setcookie",
fun(Cookie) ->
erlang:set_cookie(node(), list_to_atom(Cookie))
end),
[application:start(App) || App <- [crypto, public_key, ssl]],
TargetNode = get(target_node),
%% See if the node is currently running -- if it's not, we'll bail
case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of
{true, pong} ->
ok;
{false, pong} ->
io:format(standard_error, "Failed to connect to node ~p\n", [TargetNode]),
halt(1);
{_, pang} ->
io:format(standard_error, "Node ~p not responding to pings.\n", [TargetNode]),
halt(1)
end,
case RestArgs of
["getpid"] ->
io:format("~p\n", [list_to_integer(rpc:call(TargetNode, os, getpid, []))]);
["ping"] ->
%% If we got this far, the node already responsed to a ping, so just dump
%% a "pong"
io:format("pong\n");
["stop"] ->
case rpc:call(TargetNode, emqx_machine, graceful_shutdown, [], 60000) of
ok ->
ok;
{badrpc, nodedown} ->
%% nodetool commands are always executed after a ping
%% which if the code gets here, it's because the target node
%% has shutdown before RPC returns.
ok
end;
["rpc", Module, Function | RpcArgs] ->
case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
[RpcArgs], 60000) of
ok ->
ok;
{error, cmd_not_found} ->
halt(1);
{error, Reason} ->
io:format("RPC to ~s error: ~p\n", [TargetNode, Reason]),
halt(1);
{badrpc, Reason} ->
io:format("RPC to ~s failed: ~p\n", [TargetNode, Reason]),
halt(1);
_ ->
halt(1)
end;
["rpc_infinity", Module, Function | RpcArgs] ->
case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], infinity) of
ok ->
ok;
{badrpc, Reason} ->
io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
halt(1);
_ ->
halt(1)
end;
["rpcterms", Module, Function | ArgsAsString] ->
case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
consult(lists:flatten(ArgsAsString)), 60000) of
{badrpc, Reason} ->
io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
halt(1);
Other ->
io:format("~p\n", [Other])
end;
["eval" | ListOfArgs] ->
Parsed = parse_eval_args(ListOfArgs),
% and evaluate it on the remote node
case rpc:call(TargetNode, erl_eval, exprs, [Parsed, [] ]) of
{value, Value, _} ->
io:format ("~p~n",[Value]);
{badrpc, Reason} ->
io:format("RPC to ~p failed: ~p~n", [TargetNode, Reason]),
halt(1)
end;
Other ->
io:format("Other: ~p~n", [Other]),
io:format("Usage: nodetool chkconfig|getpid|ping|stop|rpc|rpc_infinity|rpcterms|eval|cold_eval [Terms] [RPC]\n")
end,
net_kernel:stop().
parse_eval_args(Args) ->
% shells may process args into more than one, and end up stripping
% spaces, so this converts all of that to a single string to parse
String = binary_to_list(
list_to_binary(
join(Args," ")
)
),
% then just as a convenience to users, if they forgot a trailing
% '.' add it for them.
Normalized =
case lists:reverse(String) of
[$. | _] -> String;
R -> lists:reverse([$. | R])
end,
% then scan and parse the string
{ok, Scanned, _} = erl_scan:string(Normalized),
{ok, Parsed } = erl_parse:parse_exprs(Scanned),
Parsed.
do_with_ret(Args, Name, Handler) ->
{arity, Arity} = erlang:fun_info(Handler, arity),
case take_args(Args, Name, Arity) of
false ->
Args;
{Args1, Rest} ->
_ = erlang:apply(Handler, Args1),
Rest
end.
do_with_halt(Args, Name, Handler) ->
{arity, Arity} = erlang:fun_info(Handler, arity),
case take_args(Args, Name, Arity) of
false ->
ok;
{Args1, _Rest} ->
erlang:apply(Handler, Args1), %% should halt
io:format(standard_error, "~s handler did not halt", [Name]),
halt(?LINE)
end.
%% Return option args list if found, otherwise 'false'.
take_args(Args, OptName, 0) ->
lists:member(OptName, Args) andalso [];
take_args(Args, OptName, OptArity) ->
take_args(Args, OptName, OptArity, _Scanned = []).
take_args([], _, _, _) -> false; %% no such option
take_args([Name | Rest], Name, Arity, Scanned) ->
length(Rest) >= Arity orelse error({not_enough_args_for, Name}),
{Result, Tail} = lists:split(Arity, Rest),
{Result, lists:reverse(Scanned) ++ Tail};
take_args([Other | Rest], Name, Arity, Scanned) ->
take_args(Rest, Name, Arity, [Other | Scanned]).
start_epmd() ->
[] = os:cmd("\"" ++ epmd_path() ++ "\" -daemon"),
ok.
epmd_path() ->
ErtsBinDir = filename:dirname(escript:script_name()),
Name = "epmd",
case os:find_executable(Name, ErtsBinDir) of
false ->
case os:find_executable(Name) of
false ->
io:format("Could not find epmd.~n"),
halt(1);
GlobalEpmd ->
GlobalEpmd
end;
Epmd ->
Epmd
end.
nodename(Name) ->
case re:split(Name, "@", [{return, list}, unicode]) of
[_Node, _Host] ->
list_to_atom(Name);
[Node] ->
[_, Host] = re:split(atom_to_list(node()), "@", [{return, list}, unicode]),
list_to_atom(lists:concat([Node, "@", Host]))
end.
this_node_name(longnames, Name) ->
[Node, Host] = re:split(Name, "@", [{return, list}, unicode]),
list_to_atom(lists:concat(["remsh_maint_", Node, os:getpid(), "@", Host]));
this_node_name(shortnames, Name) ->
list_to_atom(lists:concat(["remsh_maint_", Name, os:getpid()])).
%% For windows???
create_mnesia_dir(DataDir, NodeName) ->
MnesiaDir = filename:join(DataDir, NodeName),
file:make_dir(MnesiaDir),
io:format("~s", [MnesiaDir]),
halt(0).
chkconfig(File) ->
case file:consult(File) of
{ok, Terms} ->
case validate(Terms) of
ok ->
halt(0);
{error, Problems} ->
lists:foreach(fun print_issue/1, Problems),
%% halt(1) if any problems were errors
halt(case [x || {error, _} <- Problems] of
[] -> 0;
_ -> 1
end)
end;
{error, {Line, Mod, Term}} ->
io:format(standard_error, ["Error on line ", file:format_error({Line, Mod, Term}), "\n"], []),
halt(1);
{error, Error} ->
io:format(standard_error, ["Error reading config file: ", File, " ", file:format_error(Error), "\n"], []),
halt(1)
end.
%%
%% Given a string or binary, parse it into a list of terms, ala file:consult/0
%%
consult(Str) when is_list(Str) ->
consult([], Str, []);
consult(Bin) when is_binary(Bin)->
consult([], binary_to_list(Bin), []).
consult(Cont, Str, Acc) ->
case erl_scan:tokens(Cont, Str, 0) of
{done, Result, Remaining} ->
case Result of
{ok, Tokens, _} ->
{ok, Term} = erl_parse:parse_term(Tokens),
consult([], Remaining, [Term | Acc]);
{eof, _Other} ->
lists:reverse(Acc);
{error, Info, _} ->
{error, Info}
end;
{more, Cont1} ->
consult(Cont1, eof, Acc)
end.
%%
%% Validation functions for checking the app.config
%%
validate([Terms]) ->
Results = [ValidateFun(Terms) || ValidateFun <- get_validation_funs()],
Failures = [Res || Res <- Results, Res /= true],
case Failures of
[] ->
ok;
_ ->
{error, Failures}
end.
%% Some initial and basic checks for the app.config file
get_validation_funs() ->
[ ].
print_issue({warning, Warning}) ->
io:format(standard_error, "Warning in app.config: ~s~n", [Warning]);
print_issue({error, Error}) ->
io:format(standard_error, "Error in app.config: ~s~n", [Error]).
%% string:join/2 copy; string:join/2 is getting obsoleted
%% and replaced by lists:join/2, but lists:join/2 is too new
%% for version support (only appeared in 19.0) so it cannot be
%% used. Instead we just adopt join/2 locally and hope it works
%% for most unicode use cases anyway.
join([], Sep) when is_list(Sep) ->
[];
join([H|T], Sep) ->
H ++ lists:append([Sep ++ X || X <- T]).