Skip to content

Commit

Permalink
Add support for new relx directive that provides start/stop script hooks
Browse files Browse the repository at this point in the history
New 'extended_start_script_hooks' directive that allows the
developer to define six different hook scripts to be invoked
at pre/post start/stop/install upgrade phases.
Besides these custom defined scripts other types of builtin
scripts are also available, these offer pre-packaged functionality
that can be used directly, they are:
  pid - writes the beam pid to a configurable file location
          (/var/run/<rel_name>.pid by default).
  wait_for_vm_start - waits for the vm to start (ie. when it can be pinged)
  wait_for_process - waits for a configurable name
                                to appear in the erlang process registry
The hook scripts are invoked with the 'source' command, therefore
they have access to all the variables in the start script.
  • Loading branch information
lrascao committed Nov 10, 2016
1 parent 3b9239d commit 6082ee4
Show file tree
Hide file tree
Showing 7 changed files with 440 additions and 5 deletions.
12 changes: 12 additions & 0 deletions priv/templates/builtin_hook_pid
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

# loop until the VM starts responding to pings
while ! $(relx_nodetool "ping">/dev/null)
do
sleep 1
done

# get the beam pid and write it to the file passed as
# argument
PID="$(relx_get_pid)"
echo $PID > $1
17 changes: 17 additions & 0 deletions priv/templates/builtin_hook_wait_for_process
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# loop until the VM starts responding to pings
while ! $(relx_nodetool "ping">/dev/null)
do
sleep 1
done

# loop until the names provided as argument gets
# registered
while true
do
if [ "$(relx_nodetool eval "whereis($1).")" != "undefined" ]
then
exit 0
fi
done
7 changes: 7 additions & 0 deletions priv/templates/builtin_hook_wait_for_vm_start
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

# loop until the VM starts responding to pings
while ! $(relx_nodetool "ping">/dev/null)
do
sleep 1
done
33 changes: 33 additions & 0 deletions priv/templates/extended_bin
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ REL_DIR="$RELEASE_ROOT_DIR/releases/$REL_VSN"
ERL_OPTS="{{ erl_opts }}"
RUNNER_LOG_DIR="${RUNNER_LOG_DIR:-$RELEASE_ROOT_DIR/log}"

# start/stop/install/upgrade pre/post hooks
PRE_START_HOOKS="{{ pre_start_hooks }}"
POST_START_HOOKS="{{ post_start_hooks }}"
PRE_STOP_HOOKS="{{ pre_stop_hooks }}"
POST_STOP_HOOKS="{{ post_stop_hooks }}"
PRE_INSTALL_UPGRADE_HOOKS="{{ pre_install_upgrade_hooks }}"
POST_INSTALL_UPGRADE_HOOKS="{{ post_install_upgrade_hooks }}"

relx_usage() {
command="$1"

Expand Down Expand Up @@ -207,6 +215,23 @@ check_replace_os_vars() {
echo $OUT_FILE_PATH
}

relx_run_hooks() {
HOOKS=$1
for hook in $HOOKS
do
# the scripts arguments should be separated
# from each other by | , we now replace these
# by empty spaces and give them to the `set`
# command in order to be able to extract them
# separately
set `echo "$hook" | sed -e 's/|/ /g'`
SCRIPT=$1; shift
# all hook locations are expected to be
# relative to the start script location
[ "$SCRIPT_DIR/$SCRIPT" ] && . "$SCRIPT_DIR/$SCRIPT" $1 $2 $3
done
}

VMARGS_PATH=$(check_replace_os_vars vm.args $VMARGS_PATH)
RELX_CONFIG_PATH=$(check_replace_os_vars sys.config $RELX_CONFIG_PATH)

Expand Down Expand Up @@ -299,11 +324,14 @@ case "$1" in

mkdir -p "$PIPE_DIR"

relx_run_hooks "$PRE_START_HOOKS"
"$BINDIR/run_erl" -daemon "$PIPE_DIR" "$RUNNER_LOG_DIR" \
"$(relx_start_command)"
relx_run_hooks "$POST_START_HOOKS"
;;

stop)
relx_run_hooks "$PRE_STOP_HOOKS"
# Wait for the node to completely stop...
PID="$(relx_get_pid)"
if ! relx_nodetool "stop"; then
Expand All @@ -313,6 +341,7 @@ case "$1" in
do
sleep 1
done
relx_run_hooks "$POST_STOP_HOOKS"
;;

restart)
Expand Down Expand Up @@ -387,8 +416,12 @@ case "$1" in
exit 1
fi

relx_run_hooks "$PRE_INSTALL_UPGRADE_HOOKS"

exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
"$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"

relx_run_hooks "$POST_INSTALL_UPGRADE_HOOKS"
;;

versions)
Expand Down
110 changes: 107 additions & 3 deletions src/rlx_prv_assembler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,12 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
%% extended start script needs nodetool so it's
%% always included
include_nodetool(BinDir),
extended_bin_file_contents(OsFamily, RelName, RelVsn, rlx_release:erts(Release), ErlOpts)
Hooks = expand_hooks(BinDir,
rlx_state:get(State,
extended_start_script_hooks, [])),
extended_bin_file_contents(OsFamily, RelName, RelVsn,
rlx_release:erts(Release), ErlOpts,
Hooks)
end,
%% We generate the start script by default, unless the user
%% tells us not too
Expand Down Expand Up @@ -383,6 +388,90 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
E
end.

expand_hooks(_Bindir, []) -> [];
expand_hooks(BinDir, Hooks) ->
expand_hooks(BinDir, Hooks, []).

expand_hooks(_BinDir, [], Acc) -> Acc;
expand_hooks(BinDir, [{Phase, Hooks0} | Rest], Acc) ->
%% no filtermap on R15, so we must first filter and then map
Hooks1 =
lists:filter(
fun(Hook) ->
case validate_hook(Phase, Hook) of
true -> true;
false ->
rebar_api:info("~p hook is not allowed in the ~p phase, ignoring it",
[Hook, Phase]),
false
end
end, Hooks0),
Hooks =
lists:map(
fun(Hook) ->
%% all hooks are relative to the bin dir
HookScriptFilename = filename:join([BinDir,
hook_filename(Hook)]),
%% write the hook script file to it's proper location
ok = render_hook(hook_template(Hook), HookScriptFilename),
%% and return the invocation that's to be templated in the
%% extended script
hook_invocation(Hook)
end, Hooks1),
expand_hooks(BinDir, Rest, Acc ++ [{Phase, Hooks}]).

%% the pid script hook is only allowed in the
%% post_start phase
%% with args
validate_hook(post_start, {pid, _}) -> true;
%% and without args
validate_hook(post_start, pid) -> true;
%% same for wait_for_vm_start, wait_for_process script
validate_hook(post_start, wait_for_vm_start) -> true;
validate_hook(post_start, {wait_for_process, _}) -> true;
%% custom hooks are allowed in all phases
validate_hook(_Phase, {custom, _}) -> true;
%% deny all others
validate_hook(_, _) -> false.

hook_filename({custom, CustomScript}) -> CustomScript;
hook_filename(pid) -> "hooks/builtin/pid";
hook_filename({pid, _}) -> "hooks/builtin/pid";
hook_filename(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start";
hook_filename({wait_for_process, _}) -> "hooks/builtin/wait_for_process".

hook_invocation({custom, CustomScript}) -> CustomScript;
%% the pid builtin hook with no arguments writes to pid file
%% at /var/run/{{ rel_name }}.pid
hook_invocation(pid) -> string:join(["hooks/builtin/pid",
"/var/run/$REL_NAME.pid"], "|");
hook_invocation({pid, PidFile}) -> string:join(["hooks/builtin/pid",
PidFile], "|");
hook_invocation(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start";
hook_invocation({wait_for_process, Name}) ->
%% wait_for_process takes an atom as argument
%% which is the process name to wait for
string:join(["hooks/builtin/wait_for_process",
atom_to_list(Name)], "|").

hook_template({custom, _}) -> custom;
hook_template(pid) -> builtin_hook_pid;
hook_template({pid, _}) -> builtin_hook_pid;
hook_template(wait_for_vm_start) -> builtin_hook_wait_for_vm_start;
hook_template({wait_for_process, _}) -> builtin_hook_wait_for_process.

%% custom hooks are not rendered, they should
%% be copied by the release overlays
render_hook(custom, _) -> ok;
render_hook(TemplateName, Script) ->
rebar_api:debug("rendering ~p hook to ~p",
[TemplateName, Script]),
Template = render(TemplateName),
ok = filelib:ensure_dir(Script),
_ = ec_file:remove(Script),
ok = file:write_file(Script, Template),
ok = file:change_mode(Script, 8#755).

include_nodetool(BinDir) ->
NodeToolFile = nodetool_contents(),
InstallUpgradeFile = install_upgrade_escript_contents(),
Expand Down Expand Up @@ -644,13 +733,28 @@ bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).

extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks) ->
Template = case OsFamily of
unix -> extended_bin;
win32 -> extended_bin_windows
end,
%% turn all the hook lists into space separated strings
PreStartHooks = string:join(proplists:get_value(pre_start, Hooks, []), " "),
PostStartHooks = string:join(proplists:get_value(post_start, Hooks, []), " "),
PreStopHooks = string:join(proplists:get_value(pre_stop, Hooks, []), " "),
PostStopHooks = string:join(proplists:get_value(post_stop, Hooks, []), " "),
PreInstallUpgradeHooks = string:join(proplists:get_value(pre_install_upgrade,
Hooks, []), " "),
PostInstallUpgradeHooks = string:join(proplists:get_value(post_install_upgrade,
Hooks, []), " "),
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts},
{pre_start_hooks, PreStartHooks},
{post_start_hooks, PostStartHooks},
{pre_stop_hooks, PreStopHooks},
{post_stop_hooks, PostStopHooks},
{pre_install_upgrade_hooks, PreInstallUpgradeHooks},
{post_install_upgrade_hooks, PostInstallUpgradeHooks}]).

erl_ini(OutputDir, ErtsVsn) ->
ErtsDirName = string:concat("erts-", ErtsVsn),
Expand Down
Loading

0 comments on commit 6082ee4

Please sign in to comment.