feat: refactor call_hocon to use escript
This commit is contained in:
parent
13e146d1ee
commit
188f44ad50
|
@ -14,8 +14,8 @@ defmodule EmqxReleaseHelper.Overlay do
|
|||
copy "bin/install_upgrade.escript", "bin/install_upgrade.escript"
|
||||
|
||||
copy "bin/node_dump", "bin/node_dump"
|
||||
copy "bin/nodetool", "bin/nodetool"
|
||||
copy "bin/nodetool", "bin/nodetool-#{release_version}"
|
||||
# copy "bin/nodetool", "bin/nodetool"
|
||||
# copy "bin/nodetool", "bin/nodetool-#{release_version}"
|
||||
|
||||
# copy "bin/emqx", "bin/emqx"
|
||||
# copy "bin/emqx_ctl", "bin/emqx_ctl"
|
||||
|
|
|
@ -20,7 +20,6 @@ export RELEASE_NODE="${RELEASE_NODE:-"$RELEASE_NAME"}"
|
|||
export RELEASE_TMP="${RELEASE_TMP:-"$RELEASE_ROOT/tmp"}"
|
||||
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_DISTRIBUTION="${RELEASE_DISTRIBUTION:-"sname"}"
|
||||
export RELEASE_BOOT_SCRIPT="${RELEASE_BOOT_SCRIPT:-"start"}"
|
||||
export RELEASE_BOOT_SCRIPT_CLEAN="${RELEASE_BOOT_SCRIPT_CLEAN:-"start_clean"}"
|
||||
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
|
||||
export RUNNER_ROOT_DIR
|
||||
export RUNNER_ETC_DIR
|
||||
export ERTS_VSN
|
||||
|
||||
export SCHEMA_MOD=emqx_machine_schema
|
||||
export CONFIGS_DIR="$RUNNER_DATA_DIR/configs"
|
||||
|
@ -50,7 +50,7 @@ set_name () {
|
|||
if [ -z "$NAME" ]; then
|
||||
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
|
||||
# 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
|
||||
# for non-boot commands, inspect vm.<time>.args for node name
|
||||
# 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}'
|
||||
}
|
||||
|
||||
call_hocon () {
|
||||
eval_elixir "EmqxConfigHelper.hocon_cli(\"$1 $2 $3 $4 $5 $6 $7 $8 $9\")"
|
||||
}
|
||||
|
||||
generate_config () {
|
||||
local name_type="$NAME_TYPE"
|
||||
local node_name="$NAME"
|
||||
|
@ -110,7 +106,7 @@ generate_config () {
|
|||
## ths command populates two files: app.<time>.config and vm.<time>.args
|
||||
## disable SC2086 to allow EMQX_LICENSE_CONF_OPTION to split
|
||||
# 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
|
||||
RELEASE_SYS_CONFIG="$CONFIGS_DIR/app.$NOW_TIME.config"
|
||||
|
@ -152,27 +148,10 @@ generate_config () {
|
|||
# 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 () {
|
||||
# TODO: remote vm args
|
||||
exec "$REL_VSN_DIR/elixir" \
|
||||
--hidden --cookie "$RELEASE_COOKIE" \
|
||||
$(release_distribution "rpc-$(rand)-$RELEASE_NODE") \
|
||||
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \
|
||||
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \
|
||||
--vm-args "$RELEASE_REMOTE_VM_ARGS" \
|
||||
|
@ -185,7 +164,6 @@ start () {
|
|||
shift
|
||||
exec "$REL_VSN_DIR/$REL_EXEC" \
|
||||
--cookie "$RELEASE_COOKIE" \
|
||||
$(release_distribution "$RELEASE_NODE") \
|
||||
--erl "-mode $RELEASE_MODE -mnesia dir \"${MNESIA_DATA_DIR}\"" \
|
||||
--erl-config "$RELEASE_SYS_CONFIG" \
|
||||
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT" \
|
||||
|
@ -193,13 +171,19 @@ start () {
|
|||
--vm-args "$RELEASE_VM_ARGS" "$@"
|
||||
}
|
||||
|
||||
eval_elixir () {
|
||||
"$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 "$1"
|
||||
call_hocon() {
|
||||
nodetool hocon "$@"
|
||||
}
|
||||
|
||||
nodetool () {
|
||||
command="$1"; shift
|
||||
"$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"
|
||||
|
@ -226,14 +210,18 @@ case $1 in
|
|||
echo "ERROR: EVAL expects an expression as argument" >&2
|
||||
exit 1
|
||||
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)
|
||||
generate_config
|
||||
exec "$REL_VSN_DIR/iex" \
|
||||
--werl --hidden --cookie "$RELEASE_COOKIE" \
|
||||
$(release_distribution "rem-$(rand)-$RELEASE_NODE") \
|
||||
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT_CLEAN" \
|
||||
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \
|
||||
--vm-args "$RELEASE_REMOTE_VM_ARGS" \
|
|
@ -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]).
|
Loading…
Reference in New Issue