diff --git a/priv/templates/extended_bin b/priv/templates/extended_bin index 7c4acdcdb..68e67475f 100644 --- a/priv/templates/extended_bin +++ b/priv/templates/extended_bin @@ -69,6 +69,7 @@ PRE_INSTALL_UPGRADE_HOOKS="{{{ pre_install_upgrade_hooks }}}" POST_INSTALL_UPGRADE_HOOKS="{{{ post_install_upgrade_hooks }}}" STATUS_HOOK="{{{ status_hook }}}" EXTENSIONS="{{{ extensions }}}" +EXTENSION_DESCRIPTIONS="{{{ extension_descriptions }}}" relx_usage() { command="$1" @@ -188,7 +189,7 @@ Commands: versions Print versions of the release available escript Run an escript in the same environment as the release status Verify node is running and then run status hook scripts - $EXTENSIONS" +$(relx_extension_descriptions)" fi ;; esac @@ -496,9 +497,9 @@ relx_run_hooks() { set $(echo "$hook" | sed -e 's/|/ /g') HOOK_SCRIPT=$1; shift # all hook locations are expected to be - # relative to the start script location + # relative to the release root # shellcheck disable=SC1090,SC2240 - [ -f "$SCRIPT_DIR/$HOOK_SCRIPT" ] && . "$SCRIPT_DIR/$HOOK_SCRIPT" "$@" + [ -f "$RELEASE_ROOT_DIR/$HOOK_SCRIPT" ] && . "$RELEASE_ROOT_DIR/$HOOK_SCRIPT" "$@" done } @@ -542,9 +543,23 @@ relx_run_extension() { EXTENSION_SCRIPT=$1 shift # all extension script locations are expected to be - # relative to the start script location + # relative to the release root # shellcheck disable=SC1090,SC2240 - [ -f "$SCRIPT_DIR/$EXTENSION_SCRIPT" ] && . "$SCRIPT_DIR/$EXTENSION_SCRIPT" "$@" + [ -f "$RELEASE_ROOT_DIR/$EXTENSION_SCRIPT" ] && . "$RELEASE_ROOT_DIR/$EXTENSION_SCRIPT" "$@" +} + +relx_extension_descriptions() { + IFS0=$IFS + IFS='|' + for description in $EXTENSION_DESCRIPTIONS + do + cmd=$(echo "$description" |cut -d '=' -f1) + descr=$(echo "$description" |cut -d '=' -f2) + # 23 is the magic number of spaces that take up + # the space between a command and it's description + printf ' %-23s %s\n' "$cmd" "$descr" + done + IFS=$IFS0 } # given a list of arguments, identify the internal ones @@ -979,7 +994,7 @@ case "$1" in ping_or_exit # shellcheck disable=SC1090,SC2240 - [ -n "${STATUS_HOOK}" ] && [ -f "$SCRIPT_DIR/$STATUS_HOOK" ] && . "$SCRIPT_DIR/$STATUS_HOOK" "$@" + [ -n "${STATUS_HOOK}" ] && [ -f "$RELEASE_ROOT_DIR/$STATUS_HOOK" ] && . "$RELEASE_ROOT_DIR/$STATUS_HOOK" "$@" ;; help) if [ -z "$2" ]; then diff --git a/shelltests/extension_tests/rebar.config b/shelltests/extension_tests/rebar.config index 7780bc9e8..20db1543c 100644 --- a/shelltests/extension_tests/rebar.config +++ b/shelltests/extension_tests/rebar.config @@ -3,10 +3,12 @@ {relx, [{release, {extension_tests, "0.1.0"}, [extension_tests]}, - {extended_start_script_extensions, [{bar, "extensions/bar"}, - {foo, "extensions/foo"}, - {baz, "extensions/baz"}]}, - {overlay, [{copy, "./bar", "bin/extensions/bar"}, - {copy, "./foo", "bin/extensions/foo"}, - {copy, "./baz", "bin/extensions/baz"}]} + {extended_start_script_extensions, [ + % auto copied over to `bin/extensions/bar` with empty description + {bar, "./bar"}, + % auto copied over to `bin/extensions/foo` with description `"foo description"` + {foo, "./foo", "foo description"}, + % auto copied over to `bin/extensions/baz` with description `"baz description"` + {baz, "./baz", "bin/extensions/baz", "baz description"} + ]} ]}. diff --git a/shelltests/hooks_tests/hooks/post_start2 b/shelltests/hooks_tests/hooks/post_start2 new file mode 100644 index 000000000..82cfa8d38 --- /dev/null +++ b/shelltests/hooks_tests/hooks/post_start2 @@ -0,0 +1,3 @@ +#!/bin/bash +# $* +echo \{post_start2, $REL_NAME, \'$NAME\', $COOKIE\}. >> test diff --git a/shelltests/hooks_tests/hooks_tests.test b/shelltests/hooks_tests/hooks_tests.test index d3cc94838..fbc13e125 100644 --- a/shelltests/hooks_tests/hooks_tests.test +++ b/shelltests/hooks_tests/hooks_tests.test @@ -29,6 +29,7 @@ $ cat ./_build/default/rel/hooks_tests/test > {pre_start, hooks_tests, 'hooks_tests@localhost', hooks_tests}. {post_start, hooks_tests, 'hooks_tests@localhost', hooks_tests}. +{post_start2, hooks_tests, 'hooks_tests@localhost', hooks_tests}. {pre_stop, hooks_tests, 'hooks_tests@localhost', hooks_tests}. {post_stop, hooks_tests, 'hooks_tests@localhost', hooks_tests}. >= 0 diff --git a/shelltests/hooks_tests/rebar.config b/shelltests/hooks_tests/rebar.config index ba03a831d..da4f76159 100644 --- a/shelltests/hooks_tests/rebar.config +++ b/shelltests/hooks_tests/rebar.config @@ -5,25 +5,24 @@ [hooks_tests]}, {vm_args, "config/vm.args"}, {extended_start_script_hooks, [ - {pre_start, [ - {custom, "hooks/pre_start"} - ]}, - {post_start, [ - wait_for_vm_start, - {pid, "foo.pid"}, - wait_for_vm_start, - {custom, "hooks/post_start"} - ]}, - {pre_stop, [ - {custom, "hooks/pre_stop"} - ]}, - {post_stop, [ - {custom, "hooks/post_stop"} - ]}, - {status, [ - {custom, "hooks/status"} - ]} - ]}, - - {overlay, [{copy, "./hooks/status", "bin/hooks/status"}, - {copy, "./hooks/{pre,post}_{start,stop}", "bin/hooks/"}]}]}. + {pre_start, [ + {custom, "hooks/pre_start"} + ]}, + {post_start, [ + wait_for_vm_start, + {pid, "foo.pid"}, + wait_for_vm_start, + {custom, "hooks/post_start"}, + {custom, "hooks/post_start2", "bin/hooks/extra/post_start"} + ]}, + {pre_stop, [ + {custom, "hooks/pre_stop"} + ]}, + {post_stop, [ + {custom, "hooks/post_stop"} + ]}, + {status, [ + {custom, "hooks/status"} + ]} + ]} +]}. diff --git a/src/rlx_assemble.erl b/src/rlx_assemble.erl index 04d1a727f..7240759ab 100644 --- a/src/rlx_assemble.erl +++ b/src/rlx_assemble.erl @@ -273,7 +273,25 @@ create_release(State0, Release0, OutputDir) -> ok = rlx_file_utils:write_term(StartCleanFile, StartCleanMeta), ok = rlx_file_utils:write_term(NoDotErlFile, NoDotErlMeta), write_bin_file(State1, Release1, OutputDir, ReleaseDir), - {ok, State1}. + % check for existing start script extensions and append + % the overlays necessary for them to be copied over to the + % release + State2 = apply_extension_overlays( + rlx_state:extended_start_script_extensions(State1), State1), + {ok, State2}. + +apply_extension_overlays([], State) -> State; +apply_extension_overlays([{Name, ExtensionSrc} | Rest], State) -> + ExtensionTarget = extension_default_target(ExtensionSrc), + apply_extension_overlays([{Name, ExtensionSrc, ExtensionTarget} | Rest], State); +apply_extension_overlays([{Name, ExtensionSrc, _ExtensionDescription} | Rest], State) -> + ExtensionTarget = extension_default_target(ExtensionSrc), + apply_extension_overlays([{Name, ExtensionSrc, ExtensionTarget, _ExtensionDescription} | Rest], State); +apply_extension_overlays([{_, ExtensionSrc, ExtensionTarget, _} | Rest], State0) -> + State1 = rlx_state:overlay(State0, + [{copy, ExtensionSrc, ExtensionTarget} | rlx_state:overlay(State0)]), + % create the overlay instruction that will copy the extension script to it's release location + apply_extension_overlays(Rest, State1). write_bin_file(State, Release, OutputDir, RelDir) -> BinDir = filename:join([OutputDir, "bin"]), @@ -317,7 +335,7 @@ write_start_scripts_for(Type, Release, OutputDir, State) -> %% extended start script needs nodetool so it's %% always included include_nodetool(BinDir), - Hooks = expand_hooks(BinDir, + Hooks = expand_hooks(OutputDir, rlx_state:extended_start_script_hooks(State), State), Extensions = rlx_state:extended_start_script_extensions(State), @@ -361,20 +379,20 @@ write_start_script(BaseName, Type, StartFile) -> ok = file:write_file(RelStartFile, StartFile), ok = file:change_mode(RelStartFile, 8#755). -expand_hooks(_Bindir, [], _State) -> []; -expand_hooks(BinDir, Hooks, _State) -> - expand_hooks(BinDir, Hooks, [], _State). +expand_hooks(_OutputDir, [], _State) -> []; +expand_hooks(OutputDir, Hooks, _State) -> + expand_hooks(OutputDir, Hooks, [], _State). -expand_hooks(_BinDir, [], Acc, _State) -> Acc; -expand_hooks(BinDir, [{Phase, Hooks0} | Rest], Acc, State) -> +expand_hooks(_OutputDir, [], Acc, _State) -> Acc; +expand_hooks(OutputDir, [{Phase, Hooks0} | Rest], Acc, State) -> %% filter and expand hooks to their respective shell scripts Hooks = lists:foldl( fun(Hook, Acc0) -> case validate_hook(Phase, Hook) of true -> - %% all hooks are relative to the bin dir - HookScriptFilename = filename:join([BinDir, + %% all hooks are relative to the release root dir + HookScriptFilename = filename:join([OutputDir, hook_filename(Hook)]), %% write the hook script file to it's proper location ok = render_hook(hook_template(Hook), HookScriptFilename, State), @@ -386,7 +404,7 @@ expand_hooks(BinDir, [{Phase, Hooks0} | Rest], Acc, State) -> Acc0 end end, [], Hooks0), - expand_hooks(BinDir, Rest, Acc ++ [{Phase, Hooks}], State). + expand_hooks(OutputDir, Rest, Acc ++ [{Phase, Hooks}], State). %% the pid script hook is only allowed in the %% post_start phase @@ -399,43 +417,48 @@ 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; +validate_hook(_Phase, {custom, _, _}) -> true; %% as well as status hooks validate_hook(status, _) -> 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_filename(builtin_status) -> "hooks/builtin/status". - -hook_invocation({custom, CustomScript}) -> CustomScript; +% custom hook target location defaults to `bin/hooks` +hook_filename({custom, Src}) -> + filename:join(["bin/hooks", filename:basename(Src)]); +hook_filename({custom, _, Target}) -> Target; +hook_filename(pid) -> "bin/hooks/builtin/pid"; +hook_filename({pid, _}) -> "bin/hooks/builtin/pid"; +hook_filename(wait_for_vm_start) -> "bin/hooks/builtin/wait_for_vm_start"; +hook_filename({wait_for_process, _}) -> "bin/hooks/builtin/wait_for_process"; +hook_filename(builtin_status) -> "bin/hooks/builtin/status". + +hook_invocation({custom, Src}) -> + filename:join(["bin/hooks", filename:basename(Src)]); +hook_invocation({custom, _, Target}) -> + Target; %% the pid builtin hook with no arguments writes to pid file %% at /var/run/{{ rel_name }}.pid -hook_invocation(pid) -> rlx_string:join(["hooks/builtin/pid", +hook_invocation(pid) -> rlx_string:join(["bin/hooks/builtin/pid", "/var/run/$REL_NAME.pid"], "|"); -hook_invocation({pid, PidFile}) -> rlx_string:join(["hooks/builtin/pid", +hook_invocation({pid, PidFile}) -> rlx_string:join(["bin/hooks/builtin/pid", PidFile], "|"); -hook_invocation(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start"; +hook_invocation(wait_for_vm_start) -> "bin/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 - rlx_string:join(["hooks/builtin/wait_for_process", + rlx_string:join(["bin/hooks/builtin/wait_for_process", atom_to_list(Name)], "|"); -hook_invocation(builtin_status) -> "hooks/builtin/status". +hook_invocation(builtin_status) -> "bin/hooks/builtin/status". -hook_template({custom, _}) -> custom; +hook_template({custom, Src}) -> {file, Src}; +hook_template({custom, Src, _}) -> {file, Src}; 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; hook_template(builtin_status) -> builtin_hook_status. -%% custom hooks are not rendered, they should -%% be copied by the release overlays -render_hook(custom, _, _) -> ok; render_hook(TemplateName, Script, _State) -> ?log_info("rendering ~p hook to ~s", [TemplateName, rlx_file_utils:print_path(Script)]), Template = render(TemplateName), @@ -966,6 +989,38 @@ bin_file_contents(Type, RelName, RelVsn, ErtsVsn) -> render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn}, {erts_vsn, ErtsVsn}]). +extension_default_target(Src) -> + filename:join("bin/extensions", filename:basename(Src)). + +extension_default_description(_) -> + "". + +extensions(Extensions) -> + extensions(Extensions, {[], [], []}). + +extensions([], {ExtensionsList0, ExtensionDeclarations0, ExtensionDescriptions0}) -> + % pipe separated string of extensions + % (eg. foo|bar|baz|undefined) + ExtensionsList = rlx_string:join(ExtensionsList0 ++ ["undefined"], "|"), + % command separated string of extension script declarations + % (eg. foo_extension="path/to/foo_script:bar_extension="path/to/bar_script") + ExtensionDeclarations = rlx_string:join(ExtensionDeclarations0, ";"), + % (eg. baz=baz description|foo=foo description|bar=) + ExtensionDescriptions = rlx_string:join(ExtensionDescriptions0, "|"), + {ExtensionsList, ExtensionDeclarations, ExtensionDescriptions}; +extensions([{Name, Src} | Rest], Acc) -> + extensions([{Name, Src, extension_default_target(Src), extension_default_description(Name)} | Rest], Acc); +extensions([{Name, Src, Description} | Rest], Acc) -> + extensions([{Name, Src, extension_default_target(Src), Description} | Rest], Acc); +extensions([{Name, _Src, Target, Description} | Rest], + {Acc0, Acc1, Acc2}) -> + NameStr = atom_to_list(Name), + % eg. bar_extension=bin/extensions/bar + ExtensionDeclaration = NameStr ++ "_extension=\"" ++ Target ++ "\"", + % eg. bar=bar description + ExtensionDescription = NameStr ++ "=" ++ Description, + extensions(Rest, {[atom_to_list(Name) | Acc0], [ExtensionDeclaration | Acc1], [ExtensionDescription | Acc2]}). + extended_bin_file_contents(Type, RelName, RelVsn, ErtsVsn, Hooks, Extensions) -> Template = case Type of unix -> extended_bin; @@ -982,21 +1037,7 @@ extended_bin_file_contents(Type, RelName, RelVsn, ErtsVsn, Hooks, Extensions) -> PostInstallUpgradeHooks = rlx_string:join(proplists:get_value(post_install_upgrade, Hooks, []), " "), StatusHook = rlx_string:join(proplists:get_value(status, Hooks, []), " "), - {ExtensionsList1, ExtensionDeclarations1} = - lists:foldl(fun({Name, Script}, - {ExtensionsList0, ExtensionDeclarations0}) -> - ExtensionDeclaration = atom_to_list(Name) ++ - "_extension=\"" ++ - Script ++ "\"", - {ExtensionsList0 ++ [atom_to_list(Name)], - ExtensionDeclarations0 ++ [ExtensionDeclaration]} - end, {[], []}, Extensions), - % pipe separated string of extensions, to show on the start script usage - % (eg. foo|bar) - ExtensionsList = rlx_string:join(ExtensionsList1 ++ ["undefined"], "|"), - % command separated string of extension script declarations - % (eg. foo_extension="path/to/foo_script") - ExtensionDeclarations = rlx_string:join(ExtensionDeclarations1, ";"), + {ExtensionsList, ExtensionDeclarations, ExtensionDescriptions} = extensions(Extensions), render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn}, {erts_vsn, ErtsVsn}, {pre_start_hooks, PreStartHooks}, @@ -1007,7 +1048,8 @@ extended_bin_file_contents(Type, RelName, RelVsn, ErtsVsn, Hooks, Extensions) -> {post_install_upgrade_hooks, PostInstallUpgradeHooks}, {status_hook, StatusHook}, {extensions, ExtensionsList}, - {extension_declarations, ExtensionDeclarations}]). + {extension_declarations, ExtensionDeclarations}, + {extension_descriptions, ExtensionDescriptions}]). install_upgrade_escript_contents() -> render(install_upgrade_escript). @@ -1027,6 +1069,10 @@ vm_args_file(RelName) -> render(Template) -> render(Template, []). +render({file, Name}, Data) -> + {ok, Tpl} = file:read_file(Name), + {ok, Content} = rlx_util:render(Tpl, Data), + Content; render(Template, Data) -> Files = rlx_util:template_files(), Tpl = rlx_util:load_file(Files, escript, atom_to_list(Template)),