diff --git a/priv/bin b/priv/bin new file mode 100755 index 0000000..5b43f49 --- /dev/null +++ b/priv/bin @@ -0,0 +1,96 @@ +#!/bin/sh + +set -e + +SCRIPT=$(readlink $0 || true) +if [ -z $SCRIPT ]; then + SCRIPT=$0 +fi; +SCRIPT_DIR="$(cd `dirname "$SCRIPT"` && pwd -P)" +RELEASE_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)" +REL_NAME="{{ release_name }}" +REL_VSN="{{ release_version }}" +ERTS_VSN="{{ release_erts_version }}" +CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}" +REL_DIR="$RELEASE_ROOT_DIR/releases/$REL_VSN" +ERL_OPTS="{{ erl_opts }}" +export ESCRIPT_NAME="${ESCRIPT_NAME-$SCRIPT}" + +PRE_START_HOOK_DIR="$SCRIPT_DIR/pre-start-hooks" + +erllambda_run_hooks() { + HOOKS_DIR=$1 + for HOOK in "$HOOKS_DIR"/* + do + if [ -x "$HOOK" ]; then + . "$HOOK" + fi + done +} + +find_erts_dir() { + __erts_dir="$RELEASE_ROOT_DIR/erts-$ERTS_VSN" + if [ -d "$__erts_dir" ]; then + ERTS_DIR="$__erts_dir"; + ROOTDIR="$RELEASE_ROOT_DIR" + else + __erl="$(which erl)" + code="io:format(\"~s\", [code:root_dir()]), halt()." + __erl_root="$("$__erl" -boot no_dot_erlang -noshell -eval "$code")" + ERTS_DIR="$__erl_root/erts-$ERTS_VSN" + ROOTDIR="$__erl_root" + fi +} + +find_sys_config() { + __possible_sys="$REL_DIR/sys.config" + if [ -f "$__possible_sys" ]; then + SYS_CONFIG="$__possible_sys" + else + if [ -L "$__possible_sys".orig ]; then + mv "$__possible_sys".orig "$__possible_sys" + SYS_CONFIG="$__possible_sys" + fi + fi +} + +find_vm_args() { + __possible_vm_args="$REL_DIR/vm.args" + if [ -f "$__possible_vm_args" ]; then + VM_ARGS="$__possible_vm_args" + else + if [ -L "$__possible_vm_args".orig ]; then + mv "$__possible_vm_args".orig "$__possible_vm_args" + VM_ARGS="$__possible_vm_args" + fi + fi +} + +find_erts_dir +find_sys_config +find_vm_args +export ROOTDIR="$RELEASE_ROOT_DIR" +export HOME="$ROOTDIR" +export BINDIR="$ERTS_DIR/bin" +export EMU="beam" +export PROGNAME="erl" +export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH" +ERTS_LIB_DIR="$ERTS_DIR/../lib" +[ -f "$REL_DIR/$REL_NAME.boot" ] && BOOTFILE="$REL_NAME" || BOOTFILE=start +cd "$ROOTDIR" + +# Save extra arguments +ARGS="$@" + +# Build arguments for erlexec +set -- "$ERL_OPTS" +[ "$SYS_CONFIG" ] && set -- "$@" -config "$SYS_CONFIG" +[ "$VM_ARGS" ] && set -- "$@" -args_file "$VM_ARGS" +set -- "$@" -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" -boot "$REL_DIR/$BOOTFILE" \ + -mode "$CODE_LOADING_MODE" -noshell -noinput +Bd \ + "$ARGS" + +erllambda_run_hooks "$PRE_START_HOOK_DIR" + +# Boot the release +exec $BINDIR/erlexec $@ diff --git a/priv/bootstrap b/priv/bootstrap new file mode 100755 index 0000000..e65cc0a --- /dev/null +++ b/priv/bootstrap @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +SCRIPT=$(readlink $0 || true) +if [ -z $SCRIPT ]; then + SCRIPT=$0 +fi; +SCRIPT_DIR="$(cd `dirname "$SCRIPT"` && pwd -P)" + +# temporary migration folders +# specific to some of Alert Logic application +# harmless to other +# to be removed later +echo "creating necessary erllambda run dirs" +export RUN_DIR="/tmp/erllambda_rundir" +export VAR_DIR=${RUN_DIR} +$(mkdir -p "${RUN_DIR}/cachefs") +$(mkdir -p "${RUN_DIR}/checkpointfs") +$(mkdir -p "${RUN_DIR}/tmpfs") +$(mkdir -p "${RUN_DIR}/ramfs") + +# we no need coredumps in Lambda +export ERL_CRASH_DUMP="/dev/null" + +exec $SCRIPT_DIR/bin/{{ release_name }} diff --git a/priv/make_zip b/priv/make_zip new file mode 100755 index 0000000..81ce1b2 --- /dev/null +++ b/priv/make_zip @@ -0,0 +1,13 @@ +#!/bin/sh + +zippath=$1 +shift + +srcdir=$1 +shift + +rm -rf ${zippath} + +(cd ${srcdir} && + zip -q -r ${zippath} "$@" \ + -x \*.a \*.c \*.h \*.o \*.erl \*.hrl \*.xrl \*.yrl \*.asn1) diff --git a/src/rebar3_erllambda.erl b/src/rebar3_erllambda.erl index 646781e..fecb820 100644 --- a/src/rebar3_erllambda.erl +++ b/src/rebar3_erllambda.erl @@ -5,15 +5,12 @@ %% %% %% @copyright 2017 Alert Logic, Inc +%% Licensed under the MIT License. See LICENSE file in the project +%% root for full license information. %%%--------------------------------------------------------------------------- -module(rebar3_erllambda). --author('Paul Fisher '). --export([init/1]). - --export([format_error/1, - release_name/1, target_dir/1, erllambda_dir/1, zip_path/1, - list/1, os_cmd/1]). +-export([init/1, format_error/1, add_property/4]). %%============================================================================ %% Callback Functions @@ -39,54 +36,25 @@ format_error( Error ) -> io_lib:format( "~s: ~s", [?MODULE, Error] ). -release_name( State ) -> - case lists:keyfind( release, 1, rebar_state:get(State, relx, []) ) of - false -> throw( {relx_release_undefined, undefined} ); - {release, {Name, _Vsn}, _} -> atom_to_list(Name) - end. - - -target_dir( State ) -> - ReleaseDir = filename:join( rebar_dir:base_dir(State), "rel" ), - ReleaseName = release_name( State ), - TargetDir = filename:join( [ReleaseDir, ReleaseName] ), - case filelib:is_dir( TargetDir ) of - true -> TargetDir; - false -> throw( {target_director_does_not_exist, TargetDir, undefined} ) - end. - - -zip_path( State ) -> - ProfileDir = rebar_dir:base_dir(State), - Version = git_version(), - ReleaseName = [release_name( State ), $-, Version, ".zip"], - filename:join( [ProfileDir, ReleaseName] ). - - -git_version() -> - string:strip( os:cmd( "git describe" ), right, $\n ). - - -erllambda_dir( State ) -> - ChkDir = filename:join( ["_checkouts", "erllambda"] ), - ProfDir = filename:join( [rebar_dir:base_dir(State), "lib", "erllambda"] ), - case {filelib:is_dir( ChkDir ), filelib:is_dir( ProfDir )} of - {true, _} -> filename:absname(ChkDir); - {_, true} -> ProfDir; - _Otherwise -> throw( erllambda_dep_missing ) - end. - - -list( V ) when is_atom(V) -> atom_to_list(V); -list( V ) -> V. - - -os_cmd( Command ) -> - Port = open_port( {spawn, Command}, [exit_status, in, stderr_to_stdout] ), - os_cmd_receive( Port ). +%%%--------------------------------------------------------------------------- +-spec add_property( + State :: rebar_state:t(), + ConfigKey :: atom(), + Key :: atom(), + Value :: term()) -> rebar_state:t(). +%%%--------------------------------------------------------------------------- +%% @doc add at the beginning of existing list of properties or add a new one +%% +add_property(State, ConfigKey, Key, Value) -> + Config = rebar_state:get(State, ConfigKey, []), + Properties = proplists:get_value(Key, Config, []), + Config1 = property_store(Key, Value, Properties, Config), + rebar_state:set(State, ConfigKey, Config1). -os_cmd_receive( Port ) -> - receive - {Port, {data, _Output}} -> os_cmd_receive( Port ); - {Port, {exit_status, Status}} -> Status - end. +%%============================================================================ +%% Internal functions +%%============================================================================ +property_store(Key, Value, Properties, Config) when is_list(Value) -> + lists:keystore(Key, 1, Config, {Key, Value ++ Properties}); +property_store(Key, Value, Properties, Config) -> + lists:keystore(Key, 1, Config, {Key, [Value | Properties]}). diff --git a/src/rebar3_erllambda_release.erl b/src/rebar3_erllambda_release.erl index 5992c3b..e31b2f0 100644 --- a/src/rebar3_erllambda_release.erl +++ b/src/rebar3_erllambda_release.erl @@ -6,9 +6,10 @@ %% %% %% @copyright 2017 Alert Logic, Inc +%% Licensed under the MIT License. See LICENSE file in the project +%% root for full license information. %%%--------------------------------------------------------------------------- -module(rebar3_erllambda_release). --author('Paul Fisher '). -behaviour(provider). -export([init/1, do/1, format_error/1]). @@ -39,7 +40,7 @@ init( State ) -> {deps, ?DEPS}, {example, "rebar3 erllambda release"}, {opts, relx:opt_spec_list()}, - {short_desc, "Rebar3 erllambda release provider"}, + {short_desc, "Build lambda release of project"}, {desc, "Performs erllamba specific release generation on top of the " "standard rebar3 release generation."} @@ -55,18 +56,10 @@ init( State ) -> %% @doc Initialize the release provider %% do( State ) -> - try - rebar_api:info("running erllambda release generator", []), - ErllambdaDir = rebar3_erllambda:erllambda_dir( State ), - StartScript = start_script( ErllambdaDir ), - {Command, _} = HandlerInfo = handler_info( State ), - TargetDir = rebar3_erllambda:target_dir( State ), - generate_start_script( TargetDir, Command, StartScript ), - {ok, State} - catch - throw:Error -> - {error, format_error(Error)} - end. + rebar_api:info("running erllambda release generator", []), + Overlay = overlay(State), + State1 = rebar3_erllambda:add_property(State, relx, overlay, Overlay), + rebar_relx:do(rlx_prv_release, "release", ?PROVIDER, State1). %%%--------------------------------------------------------------------------- @@ -81,42 +74,28 @@ format_error( Error ) -> %%============================================================================ %% Internal Functions %%============================================================================ -generate_start_script( Dir, Command, Script ) -> - rebar_api:info( "generating start script bin/~s", [Command] ), - Filename = filename:join( [Dir, rebar3_erllambda:list(Command)] ), - BootFilename = filename:join( [Dir, "bootstrap"]), - case file:write_file( Filename, Script ) of - ok -> - ok = make_executable(Filename ), - % it can already exist from previous run. remove it - % technically we can just keep it - % TODO rework for NO symlinks - file:delete(BootFilename), - %% create necessary symlink - ok = file:make_symlink( "/var/task/" ++ rebar3_erllambda:list(Command), BootFilename); - {error, Reason} -> - throw( {generate_start_script_failed, Reason} ) - end. - -make_executable(Filename ) -> - Mode = 8#00755, - case file:change_mode( Filename, Mode ) of - ok -> ok; - {error, Reason} -> throw( {generate_start_script_failed, Reason} ) - end. - - -handler_info( State ) -> - DefaultName = rebar3_erllambda:release_name( State ), - Config = rebar_state:get(State, erllambda, []), - Module = proplists:get_value( module, Config, DefaultName ), - {["bin/", DefaultName], Module}. +overlay(State) -> + Config = rebar_state:get(State, rebar3_erllambda, []), + boot_overlay() ++ custom_hooks(Config). + +custom_hooks(Config) -> + Hooks = proplists:get_value(hooks, Config, []), + PreStartHooks = proplists:get_value(pre_start, Hooks, []), + HookDir = "bin/pre-start-hooks", + HooksOverlay = hooks_overlay(PreStartHooks, HookDir), + case HooksOverlay of + [] -> + []; + _ -> + [{mkdir, HookDir} | HooksOverlay] + end. +hooks_overlay(Hooks, Directory) -> + [{template, Hook, filename:join(Directory, filename:basename(Hook))} + || Hook <- Hooks]. -start_script( ErllambdaDir ) -> - ScriptFile = filename:join( [ErllambdaDir, "priv", "erlang-start"] ), - case file:read_file( ScriptFile ) of - {ok, Script} -> Script; - {error, Reason} -> - throw( {erllambda_script_missing, Reason} ) - end. +boot_overlay() -> + PrivDir = code:priv_dir(rebar3_erllambda), + %% relx can't symlink + [{template, filename:join(PrivDir, "bootstrap"), "bootstrap"}, + {template, filename:join(PrivDir, "bin"), "bin/{{ release_name }}"}]. diff --git a/src/rebar3_erllambda_rlx_zip_prv.erl b/src/rebar3_erllambda_rlx_zip_prv.erl new file mode 100644 index 0000000..02a3b88 --- /dev/null +++ b/src/rebar3_erllambda_rlx_zip_prv.erl @@ -0,0 +1,78 @@ +%%%--------------------------------------------------------------------------- +%% @doc rebar3_erllambda_rlx_zip_prv - Relx provider for zip packaging +%% +%% This module will package an erllambda function into a .zip file, suitable +%% for deployment to AWS lambda. +%% +%% +%% @copyright 2018 Alert Logic, Inc +%% Licensed under the MIT License. See LICENSE file in the project +%% root for full license information. +%%%--------------------------------------------------------------------------- +-module(rebar3_erllambda_rlx_zip_prv). + +-behaviour(provider). + +-export([init/1, do/1, format_error/1]). + +-define(PROVIDER, erllambda_zip). +-define(DEPS, [resolve_release]). + +%%============================================================================ +%% API +%%============================================================================ + +-spec init(rlx_state:t()) -> {ok, rlx_state:t()}. +init(State) -> + State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, + {module, ?MODULE}, + {deps, ?DEPS}])), + {ok, State1}. + +-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). +do(State) -> + {RelName, RelVsn} = rlx_state:default_configured_release(State), + Release = rlx_state:get_realized_release(State, RelName, RelVsn), + ReleaseDir = rlx_state:output_dir(State), + Targets = archive_targets(Release), + ArchivePath = archive_path(State, Release), + make_zip(ArchivePath, ReleaseDir, Targets), + {ok, State}. + +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error(ErrorDetail) -> + rebar3_erllambda:format_error("~p", [ErrorDetail]). + +%%============================================================================ +%% Internal Functions +%%============================================================================ +archive_path(State, Release) -> + Dir = rlx_state:base_output_dir(State), + FileName = archive_name(Release), + filename:join(Dir, FileName). + +archive_name(Release) -> + Name = atom_to_list(rlx_release:name(Release)), + Vsn = rlx_release:vsn(Release), + Name ++ "-" ++ Vsn ++ ".zip". + +%% Erlang zip library does not preserve execution flags, which is +%% important for bootstrap file +make_zip(ArchiveFile, SrcDir, Targets) -> + Command = zip_cmd(ArchiveFile, SrcDir, Targets), + rebar_api:info("Executing: ~s", [Command]), + {ok, []} = rebar_utils:sh(Command, [{use_stdout, false}, abort_on_error]). + +zip_cmd(ArchiveFile, SrcDir, Targets) -> + Script = zip_script(), + lists:join( + " ", [Script, ArchiveFile, SrcDir | Targets]). + +zip_script() -> + PrivDir = code:priv_dir(rebar3_erllambda), + filename:join(PrivDir, "make_zip"). + +archive_targets(Release) -> + ErtsVersion = rlx_release:erts(Release), + ErtsDir = "erts-" ++ ErtsVersion, + [ErtsDir, "bin", "lib", "releases", "bootstrap"]. diff --git a/src/rebar3_erllambda_zip.erl b/src/rebar3_erllambda_zip.erl index 4a53990..d992efd 100644 --- a/src/rebar3_erllambda_zip.erl +++ b/src/rebar3_erllambda_zip.erl @@ -1,14 +1,14 @@ %%%--------------------------------------------------------------------------- %% @doc rebar3_erllambda_zip - Package an erlang lambda function %% -%% This module will package an erllambda function into a .zip file, suitable -%% for deployment to AWS lambda. +%% This module will call relx provider for packaging lambda function. %% %% %% @copyright 2017 Alert Logic, Inc +%% Licensed under the MIT License. See LICENSE file in the project +%% root for full license information. %%%--------------------------------------------------------------------------- -module(rebar3_erllambda_zip). --author('Paul Fisher '). -export([init/1, do/1, format_error/1]). @@ -35,11 +35,10 @@ init( State ) -> {module, ?MODULE}, {namespace, ?NAMESPACE}, {bare, true}, - {deps, []}, + {deps, ?DEPS}, {example, "rebar3 erllambda zip"}, {opts, relx:opt_spec_list()}, - {short_desc, "Rebar3 erllambda zip provider"}, - {desc, "Generates a deployable AWS lambda zip file."} + {short_desc, "Generates a deployable AWS lambda zip file."} ], Provider = providers:create( Options ), {ok, rebar_state:add_provider(State, Provider)}. @@ -52,22 +51,10 @@ init( State ) -> %% @doc Initialize the release provider %% do( State ) -> - try - rebar_api:info( "generating erllambda zip package", [] ), - ErllambdaDir = rebar3_erllambda:erllambda_dir( State ), - Zippath = rebar3_erllambda:zip_path( State ), - TargetDir = rebar3_erllambda:target_dir( State ), - Command = [ErllambdaDir, "/priv/lambda-zip ", Zippath, $ , TargetDir], - rebar_api:info( "executing: ~s", [Command] ), - case rebar3_erllambda:os_cmd( Command ) of - 0 -> {ok, State}; - Status -> throw( {zip_generate_failed, Status} ) - end - catch - throw:Error -> - {error, format_error(Error)} - end. - + rebar_api:info( "generating erllambda zip package", [] ), + State1 = rebar3_erllambda:add_property( + State, relx, add_providers, rebar3_erllambda_rlx_zip_prv), + rebar_relx:do(rebar3_erllambda_rlx_prv, "erllambda_zip", ?PROVIDER, State1). %%%--------------------------------------------------------------------------- -spec format_error( Error :: any() ) -> iolist(). @@ -81,4 +68,3 @@ format_error( Error ) -> %%============================================================================ %% Internal Functions %%============================================================================ -