Merge pull request #9612 from thalesmg/upgrade-script-fwd-check-e501
feat(upgrade): add forward version check for upgrade script
This commit is contained in:
commit
62d3943fc1
139
bin/emqx
139
bin/emqx
|
@ -5,7 +5,13 @@
|
|||
set -euo pipefail
|
||||
|
||||
DEBUG="${DEBUG:-0}"
|
||||
[ "$DEBUG" -eq 1 ] && set -x
|
||||
if [ "$DEBUG" -eq 1 ]; then
|
||||
set -x
|
||||
fi
|
||||
if [ "$DEBUG" -eq 2 ]; then
|
||||
set -x
|
||||
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||
fi
|
||||
|
||||
# We need to find real directory with emqx files on all platforms
|
||||
# even when bin/emqx is symlinked on several levels
|
||||
|
@ -36,6 +42,7 @@ export RUNNER_ROOT_DIR
|
|||
export EMQX_ETC_DIR
|
||||
export REL_VSN
|
||||
export SCHEMA_MOD
|
||||
export IS_ENTERPRISE
|
||||
|
||||
RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
|
||||
CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
|
||||
|
@ -540,6 +547,121 @@ check_license() {
|
|||
fi
|
||||
}
|
||||
|
||||
# When deciding which install upgrade script to run, we have to check
|
||||
# our own version so we may avoid infinite loops and call the correct
|
||||
# version.
|
||||
current_script_version() {
|
||||
curr_script=$(basename "${BASH_SOURCE[0]}")
|
||||
suffix=${curr_script#*-}
|
||||
if [[ "${suffix}" == "${curr_script}" ]]; then
|
||||
# there's no suffix, so we're running the default `emqx` script;
|
||||
# we'll have to trust the REL_VSN variable
|
||||
echo "$REL_VSN"
|
||||
else
|
||||
echo "${suffix}"
|
||||
fi
|
||||
}
|
||||
|
||||
parse_semver() {
|
||||
echo "$1" | tr '.|-' ' '
|
||||
}
|
||||
|
||||
max_version_of() {
|
||||
local vsn1="$1"
|
||||
local vsn2="$2"
|
||||
|
||||
echo "${vsn1}" "${vsn2}" | tr " " "\n" | sort -rV | head -n1
|
||||
}
|
||||
|
||||
versioned_script_path() {
|
||||
local script_name="$1"
|
||||
local vsn="$2"
|
||||
|
||||
echo "$RUNNER_ROOT_DIR/bin/$script_name-$vsn"
|
||||
}
|
||||
|
||||
does_script_version_exist() {
|
||||
local script_name="$1"
|
||||
local vsn="$2"
|
||||
|
||||
if [[ -f "$(versioned_script_path "$script_name" "$vsn")" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# extract_from_package packege_path destination file1 file2
|
||||
extract_from_package() {
|
||||
local package="$1"
|
||||
local dest_dir="$2"
|
||||
shift 2
|
||||
|
||||
tar -C "$dest_dir" -xf "$package" "$@"
|
||||
}
|
||||
|
||||
am_i_the_newest_script() {
|
||||
local curr_vsn other_vsn
|
||||
curr_vsn="$(current_script_version)"
|
||||
other_vsn="$1"
|
||||
max_vsn="$(max_version_of "$other_vsn" "$curr_vsn")"
|
||||
|
||||
if [[ "$max_vsn" == "$curr_vsn" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
locate_package() {
|
||||
local package_path candidates vsn
|
||||
vsn="$1"
|
||||
|
||||
if [[ "${IS_ENTERPRISE}" == "yes" ]]; then
|
||||
package_pattern="$RUNNER_ROOT_DIR/releases/emqx-enterprise-$vsn-*.tar.gz"
|
||||
else
|
||||
package_pattern="$RUNNER_ROOT_DIR/releases/emqx-$vsn-*.tar.gz"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2207,SC2086
|
||||
candidates=($(ls $package_pattern))
|
||||
|
||||
if [[ "${#candidates[@]}" == 0 ]]; then
|
||||
logerr "No package matching $package_pattern found."
|
||||
exit 1
|
||||
elif [[ "${#candidates[@]}" -gt 1 ]]; then
|
||||
logerr "Multiple packages matching $package_pattern found. Ensure only one exists."
|
||||
exit 1
|
||||
else
|
||||
echo "${candidates[0]}"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_newest_script_is_extracted() {
|
||||
local newest_vsn="$1"
|
||||
local package_path tmpdir
|
||||
|
||||
if does_script_version_exist "emqx" "$newest_vsn" \
|
||||
&& does_script_version_exist "install_upgrade.escript" "$newest_vsn"; then
|
||||
return
|
||||
else
|
||||
package_path="$(locate_package "$newest_vsn")"
|
||||
tmpdir="$(mktemp -dp /tmp emqx.XXXXXXXXXXX)"
|
||||
|
||||
extract_from_package \
|
||||
"$package_path" \
|
||||
"$tmpdir" \
|
||||
"bin/emqx-$newest_vsn" \
|
||||
"bin/install_upgrade.escript-$newest_vsn"
|
||||
|
||||
cp "$tmpdir/bin/emqx-$newest_vsn" \
|
||||
"$tmpdir/bin/install_upgrade.escript-$newest_vsn" \
|
||||
"$RUNNER_ROOT_DIR/bin/"
|
||||
|
||||
rm -rf "$tmpdir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run an escript in the node's environment
|
||||
relx_escript() {
|
||||
shift; scriptpath="$1"; shift
|
||||
|
@ -922,8 +1044,21 @@ case "${COMMAND}" in
|
|||
|
||||
assert_node_alive
|
||||
|
||||
curr_vsn="$(current_script_version)"
|
||||
target_vsn="$1"
|
||||
newest_vsn="$(max_version_of "$target_vsn" "$curr_vsn")"
|
||||
ensure_newest_script_is_extracted "$newest_vsn"
|
||||
# if we are not the newest script, run the same command from it
|
||||
if ! am_i_the_newest_script "$newest_vsn"; then
|
||||
script_path="$(versioned_script_path emqx "$newest_vsn")"
|
||||
exec "$script_path" "$COMMAND" "$@"
|
||||
fi
|
||||
|
||||
upgrade_script_path="$(versioned_script_path install_upgrade.escript "$newest_vsn")"
|
||||
echo "using ${upgrade_script_path} to run ${COMMAND} $*"
|
||||
|
||||
ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
|
||||
exec "$BINDIR/escript" "$RUNNER_ROOT_DIR/bin/install_upgrade.escript" \
|
||||
exec "$BINDIR/escript" "$upgrade_script_path" \
|
||||
"$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
|
||||
;;
|
||||
|
||||
|
|
|
@ -18,22 +18,31 @@ main([Command0, DistInfoStr | CommandArgs]) ->
|
|||
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
|
||||
%% "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;
|
||||
_ -> fun fail_upgrade/2
|
||||
end,
|
||||
F(DistInfo, Opts);
|
||||
main(Args) ->
|
||||
?INFO("unknown args: ~p", [Args]),
|
||||
erlang:halt(1).
|
||||
|
||||
%% temporary block for hot-upgrades; next release will just remove
|
||||
%% this and the new script version shall be used instead of this
|
||||
%% current version.
|
||||
%% TODO: always deny relup for macos (unsupported)
|
||||
fail_upgrade(_DistInfo, _Opts) ->
|
||||
?ERROR("Unsupported upgrade path", []),
|
||||
erlang:halt(1).
|
||||
|
||||
unpack({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||||
Version = proplists:get_value(version, Opts),
|
||||
case unpack_release(RelName, TargetNode, Version) of
|
||||
case unpack_release(RelName, TargetNode, Version, Opts) of
|
||||
{ok, Vsn} ->
|
||||
?INFO("Unpacked successfully: ~p", [Vsn]);
|
||||
old ->
|
||||
|
@ -57,7 +66,7 @@ install({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
|||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||||
Version = proplists:get_value(version, Opts),
|
||||
validate_target_version(Version, TargetNode),
|
||||
case unpack_release(RelName, TargetNode, Version) of
|
||||
case unpack_release(RelName, TargetNode, Version, Opts) of
|
||||
{ok, Vsn} ->
|
||||
?INFO("Unpacked successfully: ~p.", [Vsn]),
|
||||
check_and_install(TargetNode, Vsn),
|
||||
|
@ -132,12 +141,13 @@ uninstall({_RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
|||
uninstall(_, Args) ->
|
||||
?INFO("uninstall: unknown args ~p", [Args]).
|
||||
|
||||
versions({_RelName, NameTypeArg, NodeName, Cookie}, []) ->
|
||||
versions({_RelName, NameTypeArg, NodeName, Cookie}, _Opts) ->
|
||||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||||
print_existing_versions(TargetNode).
|
||||
|
||||
parse_arguments(Args) ->
|
||||
parse_arguments(Args, []).
|
||||
IsEnterprise = os:getenv("IS_ENTERPRISE") == "yes",
|
||||
parse_arguments(Args, [{is_enterprise, IsEnterprise}]).
|
||||
|
||||
parse_arguments([], Acc) -> Acc;
|
||||
parse_arguments(["--no-permanent"|Rest], Acc) ->
|
||||
|
@ -146,9 +156,10 @@ parse_arguments([VersionStr|Rest], Acc) ->
|
|||
Version = parse_version(VersionStr),
|
||||
parse_arguments(Rest, [{version, Version}] ++ Acc).
|
||||
|
||||
unpack_release(RelName, TargetNode, Version) ->
|
||||
StartScriptExists = filelib:is_dir(filename:join(["releases", Version, "start.boot"])),
|
||||
unpack_release(RelName, TargetNode, Version, Opts) ->
|
||||
StartScriptExists = filelib:is_regular(filename:join(["releases", Version, "start.boot"])),
|
||||
WhichReleases = which_releases(TargetNode),
|
||||
IsEnterprise = proplists:get_value(is_enterprise, Opts),
|
||||
case proplists:get_value(Version, WhichReleases) of
|
||||
Res when Res =:= undefined; (Res =:= unpacked andalso not StartScriptExists) ->
|
||||
%% not installed, so unpack tarball:
|
||||
|
@ -156,7 +167,7 @@ unpack_release(RelName, TargetNode, Version) ->
|
|||
%% 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
|
||||
case find_and_link_release_package(Version, RelName, IsEnterprise) of
|
||||
{_, undefined} ->
|
||||
{error, release_package_not_found};
|
||||
{ReleasePackage, ReleasePackageLink} ->
|
||||
|
@ -206,7 +217,7 @@ extract_tar(Cwd, Tar) ->
|
|||
%% 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) ->
|
||||
find_and_link_release_package(Version, RelName, IsEnterprise) ->
|
||||
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
|
||||
|
@ -217,7 +228,13 @@ find_and_link_release_package(Version, RelName) ->
|
|||
%% we've found where the actual release package is located
|
||||
ReleaseLink = filename:join(["releases", Version,
|
||||
RelNameStr ++ ".tar.gz"]),
|
||||
TarBalls = filename:join(["releases", RelNameStr ++ "-*" ++ Version ++ "*.tar.gz"]),
|
||||
ReleaseNamePattern =
|
||||
case IsEnterprise of
|
||||
false -> RelNameStr;
|
||||
true -> RelNameStr ++ "-enterprise"
|
||||
end,
|
||||
FilePattern = lists:flatten([ReleaseNamePattern, "-", Version, "*.tar.gz"]),
|
||||
TarBalls = filename:join(["releases", FilePattern]),
|
||||
case filelib:wildcard(TarBalls) of
|
||||
[] ->
|
||||
{undefined, undefined};
|
||||
|
|
Loading…
Reference in New Issue