diff options
Diffstat (limited to 'priv/templates/install_upgrade_escript')
-rw-r--r-- | priv/templates/install_upgrade_escript | 298 |
1 files changed, 238 insertions, 60 deletions
diff --git a/priv/templates/install_upgrade_escript b/priv/templates/install_upgrade_escript index 0910c38..47521c6 100644 --- a/priv/templates/install_upgrade_escript +++ b/priv/templates/install_upgrade_escript @@ -6,85 +6,236 @@ -define(TIMEOUT, 300000). -define(INFO(Fmt,Args), io:format(Fmt,Args)). -%% Unpack or upgrade to a new tar.gz release -main(["unpack", RelName, NameTypeArg, NodeName, Cookie, VersionArg]) -> +main([Command0, DistInfoStr | CommandArgs]) -> + %% convert the distribution info arguments string to an erlang term + {ok, Tokens, _} = erl_scan:string(DistInfoStr ++ "."), + {ok, DistInfo} = erl_parse:parse_term(Tokens), + %% convert arguments into a proplist + Opts = parse_arguments(CommandArgs), + %% invoke the command passed as argument + F = case Command0 of + "install" -> fun(A, B) -> install(A, B) end; + "unpack" -> fun(A, B) -> unpack(A, B) end; + "upgrade" -> fun(A, B) -> upgrade(A, B) end; + "downgrade" -> fun(A, B) -> downgrade(A, B) end; + "uninstall" -> fun(A, B) -> uninstall(A, B) end; + "versions" -> fun(A, B) -> versions(A, B) end + end, + F(DistInfo, Opts); +main(Args) -> + ?INFO("unknown args: ~p\n", [Args]), + erlang:halt(1). + +unpack({RelName, NameTypeArg, NodeName, Cookie}, Opts) -> TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), - WhichReleases = which_releases(TargetNode), - Version = parse_version(VersionArg), - case proplists:get_value(Version, WhichReleases) of - undefined -> - %% not installed, so unpack tarball: - ?INFO("Release ~s not found, attempting to unpack releases/~s/~s.tar.gz~n",[Version,Version,RelName]), - ReleasePackage = Version ++ "/" ++ RelName, - case rpc:call(TargetNode, release_handler, unpack_release, - [ReleasePackage], ?TIMEOUT) of - {ok, Vsn} -> - ?INFO("Unpacked successfully: ~p~n", [Vsn]); - {error, UnpackReason} -> - print_existing_versions(TargetNode), - ?INFO("Unpack failed: ~p~n",[UnpackReason]), - erlang:halt(2) - end; + Version = proplists:get_value(version, Opts), + case unpack_release(RelName, TargetNode, Version) of + {ok, Vsn} -> + ?INFO("Unpacked successfully: ~p~n", [Vsn]); old -> %% no need to unpack, has been installed previously - ?INFO("Release ~s is marked old, switching to it.~n",[Version]); + ?INFO("Release ~s is marked old.~n",[Version]); unpacked -> - ?INFO("Release ~s is already unpacked, now installing.~n",[Version]); + ?INFO("Release ~s is already unpacked.~n",[Version]); current -> - ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]); + ?INFO("Release ~s is already installed and current.~n",[Version]); permanent -> - ?INFO("Release ~s is already installed, and set permanent.~n",[Version]) + ?INFO("Release ~s is already installed and set permanent.~n",[Version]); + {error, Reason} -> + ?INFO("Unpack failed: ~p~n",[Reason]), + print_existing_versions(TargetNode), + erlang:halt(2) end; -main(["install", RelName, NameTypeArg, NodeName, Cookie, VersionArg]) -> +unpack(_, Args) -> + ?INFO("unpack: unknown args ~p\n", [Args]). + +install({RelName, NameTypeArg, NodeName, Cookie}, Opts) -> TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), - WhichReleases = which_releases(TargetNode), - Version = parse_version(VersionArg), - case proplists:get_value(Version, WhichReleases) of - undefined -> - %% not installed, so unpack tarball: - ?INFO("Release ~s not found, attempting to unpack releases/~s/~s.tar.gz~n",[Version,Version,RelName]), - ReleasePackage = Version ++ "/" ++ RelName, - case rpc:call(TargetNode, release_handler, unpack_release, - [ReleasePackage], ?TIMEOUT) of - {ok, Vsn} -> - ?INFO("Unpacked successfully: ~p~n", [Vsn]), - install_and_permafy(TargetNode, RelName, Vsn); - {error, UnpackReason} -> - print_existing_versions(TargetNode), - ?INFO("Unpack failed: ~p~n",[UnpackReason]), - erlang:halt(2) - end; + Version = proplists:get_value(version, Opts), + case unpack_release(RelName, TargetNode, Version) of + {ok, Vsn} -> + ?INFO("Unpacked successfully: ~p~n", [Vsn]), + check_and_install(TargetNode, Vsn), + maybe_permafy(TargetNode, RelName, Vsn, Opts); old -> %% no need to unpack, has been installed previously ?INFO("Release ~s is marked old, switching to it.~n",[Version]), - install_and_permafy(TargetNode, RelName, Version); + check_and_install(TargetNode, Version), + maybe_permafy(TargetNode, RelName, Version, Opts); unpacked -> ?INFO("Release ~s is already unpacked, now installing.~n",[Version]), - install_and_permafy(TargetNode, RelName, Version); - current -> %% installed and in-use, just needs to be permanent - ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]), - permafy(TargetNode, RelName, Version); + check_and_install(TargetNode, Version), + maybe_permafy(TargetNode, RelName, Version, Opts); + current -> + case proplists:get_value(permanent, Opts, true) of + true -> + ?INFO("Release ~s is already installed and current, making permanent.~n", + [Version]), + permafy(TargetNode, RelName, Version); + false -> + ?INFO("Release ~s is already installed and current.~n", + [Version]) + end; permanent -> - ?INFO("Release ~s is already installed, and set permanent.~n",[Version]) + %% this release is marked permanent, however it might not the + %% one currently running + case current_release_version(TargetNode) of + Version -> + ?INFO("Release ~s is already installed, running and set permanent.~n", + [Version]); + CurrentVersion -> + ?INFO("Release ~s is the currently running version.~n", + [CurrentVersion]), + check_and_install(TargetNode, Version), + maybe_permafy(TargetNode, RelName, Version, Opts) + end; + {error, Reason} -> + ?INFO("Unpack failed: ~p~n",[Reason]), + print_existing_versions(TargetNode), + erlang:halt(2) end; -main(_) -> - erlang:halt(1). +install(_, Args) -> + ?INFO("install: unknown args ~p\n", [Args]). + +upgrade(DistInfo, Args) -> + install(DistInfo, Args). + +downgrade(DistInfo, Args) -> + install(DistInfo, Args). + +uninstall({_RelName, NameTypeArg, NodeName, Cookie}, Opts) -> + TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), + WhichReleases = which_releases(TargetNode), + Version = proplists:get_value(version, Opts), + case proplists:get_value(Version, WhichReleases) of + undefined -> + ?INFO("Release ~s is already uninstalled.~n", [Version]); + old -> + ?INFO("Release ~s is marked old, uninstalling it.~n", [Version]), + remove_release(TargetNode, Version); + unpacked -> + ?INFO("Release ~s is marked unpacked, uninstalling it~n", + [Version]), + remove_release(TargetNode, Version); + current -> + ?INFO("Uninstall failed: Release ~s is marked current.~n", [Version]), + erlang:halt(2); + permanent -> + ?INFO("Uninstall failed: Release ~s is running.~n", [Version]), + erlang:halt(2) + end; +uninstall(_, Args) -> + ?INFO("uninstall: unknown args ~p\n", [Args]). + +versions({_RelName, NameTypeArg, NodeName, Cookie}, []) -> + TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), + print_existing_versions(TargetNode). + +parse_arguments(Args) -> + parse_arguments(Args, []). + +parse_arguments([], Acc) -> Acc; +parse_arguments(["--no-permanent"|Rest], Acc) -> + parse_arguments(Rest, [{permanent, false}] ++ Acc); +parse_arguments([VersionStr|Rest], Acc) -> + Version = parse_version(VersionStr), + parse_arguments(Rest, [{version, Version}] ++ Acc). + +unpack_release(RelName, TargetNode, Version) -> + WhichReleases = which_releases(TargetNode), + case proplists:get_value(Version, WhichReleases) of + undefined -> + %% not installed, so unpack tarball: + %% look for a release package with the intended version in the following order: + %% releases/<relname>-<version>.tar.gz + %% releases/<version>/<relname>-<version>.tar.gz + %% releases/<version>/<relname>.tar.gz + case find_and_link_release_package(Version, RelName) of + {_, undefined} -> + {error, release_package_not_found}; + {ReleasePackage, ReleasePackageLink} -> + ?INFO("Release ~s not found, attempting to unpack ~s~n", + [Version, ReleasePackage]), + case rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackageLink], ?TIMEOUT) of + {ok, Vsn} -> {ok, Vsn}; + {error, _} = Error -> Error + end + end; + Other -> Other + end. + +%% 1. look for a release package tarball with the provided version in the following order: +%% releases/<relname>-<version>.tar.gz +%% releases/<version>/<relname>-<version>.tar.gz +%% releases/<version>/<relname>.tar.gz +%% 2. create a symlink from a fixed location (ie. releases/<version>/<relname>.tar.gz) +%% to the release package tarball found in 1. +%% 3. return a tuple with the paths to the release package and +%% to the symlink that is to be provided to release handler +find_and_link_release_package(Version, RelName) -> + RelNameStr = atom_to_list(RelName), + %% regardless of the location of the release package, we'll + %% always give release handler the same path which is the symlink + %% the path to the package link is relative to "releases/" because + %% that's what release handler is expecting + ReleaseHandlerPackageLink = filename:join(Version, RelNameStr), + %% this is the symlink name we'll create once + %% we've found where the actual release package is located + ReleaseLink = filename:join(["releases", Version, + RelNameStr ++ ".tar.gz"]), + case first_value(fun filelib:is_file/1, + [filename:join(["releases", + RelNameStr ++ "-" ++ Version ++ ".tar.gz"]), + filename:join(["releases", Version, + RelNameStr ++ "-" ++ Version ++ ".tar.gz"]), + filename:join(["releases", Version, + RelNameStr ++ ".tar.gz"])]) of + no_value -> + {undefined, undefined}; + %% no need to create the link since the release package we + %% found is located in the same place as the link would be + {ok, Filename} when is_list(Filename) andalso + Filename =:= ReleaseLink -> + {Filename, ReleaseHandlerPackageLink}; + {ok, Filename} when is_list(Filename) -> + %% we now have the location of the release package, however + %% release handler expects a fixed nomenclature (<relname>.tar.gz) + %% so give it just that by creating a symlink to the tarball + %% we found. + %% make sure that the dir where we're creating the link in exists + ok = filelib:ensure_dir(filename:join([filename:dirname(ReleaseLink), "dummy"])), + %% create the symlink pointing to the full path name of the + %% release package we found + ok = file:make_symlink(filename:absname(Filename), ReleaseLink), + {Filename, ReleaseHandlerPackageLink} + end. + +first_value(_Fun, []) -> no_value; +first_value(Fun, [Value | Rest]) -> + case Fun(Value) of + false -> + first_value(Fun, Rest); + true -> + {ok, Value} + end. parse_version(V) when is_list(V) -> hd(string:tokens(V,"/")). -install_and_permafy(TargetNode, RelName, Vsn) -> - case rpc:call(TargetNode, release_handler, check_install_release, [Vsn], ?TIMEOUT) of +check_and_install(TargetNode, Vsn) -> + case rpc:call(TargetNode, release_handler, + check_install_release, [Vsn], ?TIMEOUT) of {ok, _OtherVsn, _Desc} -> ok; {error, Reason} -> ?INFO("ERROR: release_handler:check_install_release failed: ~p~n",[Reason]), erlang:halt(3) end, - case rpc:call(TargetNode, release_handler, install_release, [Vsn], ?TIMEOUT) of + case rpc:call(TargetNode, release_handler, install_release, + [Vsn, [{update_paths, true}]], ?TIMEOUT) of {ok, _, _} -> ?INFO("Installed Release: ~s~n", [Vsn]), - permafy(TargetNode, RelName, Vsn), ok; {error, {no_such_release, Vsn}} -> VerList = @@ -106,28 +257,55 @@ install_and_permafy(TargetNode, RelName, Vsn) -> erlang:halt(4) end. +maybe_permafy(TargetNode, RelName, Vsn, Opts) -> + case proplists:get_value(permanent, Opts, true) of + true -> + permafy(TargetNode, RelName, Vsn); + false -> ok + end. + permafy(TargetNode, RelName, Vsn) -> - ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), - file:copy(filename:join(["bin", RelName++"-"++Vsn]), - filename:join(["bin", RelName])), + ok = rpc:call(TargetNode, release_handler, + make_permanent, [Vsn], ?TIMEOUT), + file:copy(filename:join(["bin", atom_to_list(RelName)++"-"++Vsn]), + filename:join(["bin", atom_to_list(RelName)])), ?INFO("Made release permanent: ~p~n", [Vsn]), ok. +remove_release(TargetNode, Vsn) -> + case rpc:call(TargetNode, release_handler, remove_release, [Vsn], ?TIMEOUT) of + ok -> + ?INFO("Uninstalled Release: ~s~n", [Vsn]), + ok; + {error, Reason} -> + ?INFO("ERROR: release_handler:remove_release failed: ~p~n", [Reason]), + erlang:halt(3) + end. + which_releases(TargetNode) -> R = rpc:call(TargetNode, release_handler, which_releases, [], ?TIMEOUT), [ {V, S} || {_,V,_, S} <- R ]. +%% the running release version is either the only one marked `current´ +%% or, if none exists, the one marked `permanent` +current_release_version(TargetNode) -> + R = rpc:call(TargetNode, release_handler, which_releases, + [], ?TIMEOUT), + Versions = [ {S, V} || {_,V,_, S} <- R ], + %% current version takes priority over the permanent + proplists:get_value(current, Versions, + proplists:get_value(permanent, Versions)). + print_existing_versions(TargetNode) -> VerList = iolist_to_binary([ io_lib:format("* ~s\t~s~n",[V,S]) || {V,S} <- which_releases(TargetNode) ]), ?INFO("Installed versions:~n~s", [VerList]). -start_distribution(NodeName, NameTypeArg, Cookie) -> - MyNode = make_script_node(NodeName), +start_distribution(TargetNode, NameTypeArg, Cookie) -> + MyNode = make_script_node(TargetNode), {ok, _Pid} = net_kernel:start([MyNode, get_name_type(NameTypeArg)]), - erlang:set_cookie(node(), list_to_atom(Cookie)), - TargetNode = list_to_atom(NodeName), + erlang:set_cookie(node(), Cookie), case {net_kernel:connect_node(TargetNode), net_adm:ping(TargetNode)} of {true, pong} -> @@ -141,7 +319,7 @@ start_distribution(NodeName, NameTypeArg, Cookie) -> TargetNode. make_script_node(Node) -> - [Name, Host] = string:tokens(Node, "@"), + [Name, Host] = string:tokens(atom_to_list(Node), "@"), list_to_atom(lists:concat([Name, "_upgrader_", os:getpid(), "@", Host])). %% get name type from arg |