From 9855022433c9e3e08850c13b925b94f3b87df6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Rasc=C3=A3o?= Date: Thu, 19 Oct 2017 05:23:47 +0100 Subject: Start script extensions (#613) * Extended start script command extensions Provide a mechanism that allows for the application to extend the list of commands available to be invoked from the start script. An application may be able to define a 'foo' extension that is associated with a 'foo_script' written and maintained by the applicationr, (this association is kept in rebar.config), upon invocation of bin/ foo the 'foo_script' will then be invoked. * Add test coverage for extension script * Ensure extended script usage argument --- priv/templates/extended_bin | 55 +++++++++++++++++++++++++++++++++++++++-- src/rlx_prv_assembler.erl | 26 ++++++++++++++++--- test/rlx_extended_bin_SUITE.erl | 46 ++++++++++++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 7 deletions(-) diff --git a/priv/templates/extended_bin b/priv/templates/extended_bin index 0abf38b..6742cf7 100755 --- a/priv/templates/extended_bin +++ b/priv/templates/extended_bin @@ -47,6 +47,7 @@ POST_STOP_HOOKS="{{{ post_stop_hooks }}}" PRE_INSTALL_UPGRADE_HOOKS="{{{ pre_install_upgrade_hooks }}}" POST_INSTALL_UPGRADE_HOOKS="{{{ post_install_upgrade_hooks }}}" STATUS_HOOK="{{{ status_hook }}}" +EXTENSIONS="{{{ extensions }}}" relx_usage() { command="$1" @@ -107,7 +108,15 @@ relx_usage() { echo "Obtains node status information." ;; *) - echo "Usage: $REL_NAME {start|start_boot |foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot |attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|rpc|rpcterms|eval|status}" + # check for extension + IS_EXTENSION=$(relx_is_extension $command) + if [ "$IS_EXTENSION" = "1" ]; then + EXTENSION_SCRIPT=$(relx_get_extension_script $command) + relx_run_extension $EXTENSION_SCRIPT help + else + EXTENSIONS=`echo $EXTENSIONS | sed -e 's/|undefined//g'` + echo "Usage: $REL_NAME {start|start_boot |foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot |attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|rpc|rpcterms|eval|status|$EXTENSIONS}" + fi ;; esac } @@ -285,6 +294,40 @@ relx_run_hooks() { done } +relx_is_extension() { + EXTENSION=$1 + case "$EXTENSION" in + {{{ extensions }}}) + echo "1" + ;; + *) + echo "0" + ;; + esac +} + +relx_get_extension_script() { + EXTENSION=$1 + # below are the extensions declarations + # of the form: + # foo_extension="path/to/foo_script";bar_extension="path/to/bar_script" + {{{extension_declarations}}} + # get the command extension (eg. foo) and + # obtain the actual script filename that it + # refers to (eg. "path/to/foo_script" + eval echo "$"${EXTENSION}_extension"" +} + +relx_run_extension() { + # drop the first argument which is the name of the + # extension script + EXTENSION_SCRIPT=$1 + shift + # all extension script locations are expected to be + # relative to the start script location + [ "$SCRIPT_DIR/$EXTENSION_SCRIPT" ] && . "$SCRIPT_DIR/$EXTENSION_SCRIPT" $@ +} + find_erts_dir export ROOTDIR="$RELEASE_ROOT_DIR" export BINDIR="$ERTS_DIR/bin" @@ -671,7 +714,15 @@ case "$1" in relx_usage $TOPIC ;; *) - relx_usage + # check for extension + IS_EXTENSION=$(relx_is_extension $1) + if [ "$IS_EXTENSION" = "1" ]; then + EXTENSION_SCRIPT=$(relx_get_extension_script $1) + shift + relx_run_extension $EXTENSION_SCRIPT $@ + else + relx_usage $1 + fi exit 1 ;; esac diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl index 2088de6..a6bf5f8 100644 --- a/src/rlx_prv_assembler.erl +++ b/src/rlx_prv_assembler.erl @@ -401,9 +401,12 @@ write_bin_file(State, Release, OutputDir, RelDir) -> extended_start_script_hooks, []), State), + Extensions = rlx_state:get(State, + extended_start_script_extensions, + []), extended_bin_file_contents(OsFamily, RelName, RelVsn, rlx_release:erts(Release), ErlOpts, - Hooks) + Hooks, Extensions) end, %% We generate the start script by default, unless the user %% tells us not too @@ -787,7 +790,7 @@ 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, Hooks) -> +extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks, Extensions) -> Template = case OsFamily of unix -> extended_bin; win32 -> extended_bin_windows @@ -802,6 +805,21 @@ extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks) - PostInstallUpgradeHooks = string:join(proplists:get_value(post_install_upgrade, Hooks, []), " "), StatusHook = 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 = string:join(ExtensionsList1 ++ ["undefined"], "|"), + % command separated string of extension script declarations + % (eg. foo_extension="path/to/foo_script") + ExtensionDeclarations = string:join(ExtensionDeclarations1, ";"), render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn}, {erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}, {pre_start_hooks, PreStartHooks}, @@ -810,7 +828,9 @@ extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks) - {post_stop_hooks, PostStopHooks}, {pre_install_upgrade_hooks, PreInstallUpgradeHooks}, {post_install_upgrade_hooks, PostInstallUpgradeHooks}, - {status_hook, StatusHook}]). + {status_hook, StatusHook}, + {extensions, ExtensionsList}, + {extension_declarations, ExtensionDeclarations}]). erl_ini(OutputDir, ErtsVsn) -> ErtsDirName = string:concat("erts-", ErtsVsn), diff --git a/test/rlx_extended_bin_SUITE.erl b/test/rlx_extended_bin_SUITE.erl index c2e6bc2..4407cfc 100644 --- a/test/rlx_extended_bin_SUITE.erl +++ b/test/rlx_extended_bin_SUITE.erl @@ -49,7 +49,8 @@ builtin_pid_start_script_hook/1, builtin_wait_for_process_start_script_hook/1, mixed_custom_and_builtin_start_script_hooks/1, - builtin_status_script/1, custom_status_script/1]). + builtin_status_script/1, custom_status_script/1, + extension_script/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -82,7 +83,7 @@ all() -> replace_os_vars_custom_location, replace_os_vars_dev_mode, replace_os_vars_twice, custom_start_script_hooks, builtin_wait_for_vm_start_script_hook, builtin_pid_start_script_hook, builtin_wait_for_process_start_script_hook, mixed_custom_and_builtin_start_script_hooks, - builtin_status_script, custom_status_script]. + builtin_status_script, custom_status_script, extension_script]. ping(Config) -> LibDir1 = proplists:get_value(lib1, Config), @@ -1492,6 +1493,47 @@ start_fail_when_circular_argsfiles(Config) -> ec_file:write(VmArgs3, "-args_file " ++ VmArgs2 ++ "\n"), start_fail_with_vmargs(Config, VmArgs, 5). +extension_script(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + + rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1", + [stdlib,kernel], []), + + OutputDir = filename:join([proplists:get_value(priv_dir, Config), + rlx_test_utils:create_random_name("relx-output")]), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + rlx_test_utils:write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app]}, + {lib_dirs, [filename:join(LibDir1, "*")]}, + {generate_start_script, true}, + {extended_start_script, true}, + {extended_start_script_extensions, [ + {bar, "extensions/bar"} + ]}, + {overlay, [ + {copy, "./bar", "bin/extensions/bar"}]} + ]), + + %% write the extension script + ok = file:write_file(filename:join([LibDir1, "./bar"]), + "#!/bin/bash\n" + "echo \\{bar, $REL_NAME, \\'$NAME\\', $COOKIE\\}.\n" + "exit 0"), + + {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3, + OutputDir, ConfigFile), + os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])), + timer:sleep(2000), + {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])), + %% write the extension script output to a file + {ok, Str} = sh(filename:join([OutputDir, "foo", "bin", "foo bar"])), + ec_file:write(filename:join([OutputDir, "bar.txt"]), Str), + os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])), + {ok, [Term]} = file:consult(filename:join([OutputDir, "bar.txt"])), + {ok, {bar, foo, _, foo} = Term}. + %%------------------------------------------------------------------- %% Helper Function for start_fail_when_* tests %%------------------------------------------------------------------- -- cgit v1.2.3