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
|
set -euo pipefail
|
||||||
|
|
||||||
DEBUG="${DEBUG:-0}"
|
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
|
# We need to find real directory with emqx files on all platforms
|
||||||
# even when bin/emqx is symlinked on several levels
|
# even when bin/emqx is symlinked on several levels
|
||||||
|
@ -36,6 +42,7 @@ export RUNNER_ROOT_DIR
|
||||||
export EMQX_ETC_DIR
|
export EMQX_ETC_DIR
|
||||||
export REL_VSN
|
export REL_VSN
|
||||||
export SCHEMA_MOD
|
export SCHEMA_MOD
|
||||||
|
export IS_ENTERPRISE
|
||||||
|
|
||||||
RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
|
RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
|
||||||
CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
|
CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
|
||||||
|
@ -540,6 +547,121 @@ check_license() {
|
||||||
fi
|
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
|
# Run an escript in the node's environment
|
||||||
relx_escript() {
|
relx_escript() {
|
||||||
shift; scriptpath="$1"; shift
|
shift; scriptpath="$1"; shift
|
||||||
|
@ -922,8 +1044,21 @@ case "${COMMAND}" in
|
||||||
|
|
||||||
assert_node_alive
|
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" \
|
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'}" "$@"
|
"$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
|
|
@ -18,22 +18,31 @@ main([Command0, DistInfoStr | CommandArgs]) ->
|
||||||
Opts = parse_arguments(CommandArgs),
|
Opts = parse_arguments(CommandArgs),
|
||||||
%% invoke the command passed as argument
|
%% invoke the command passed as argument
|
||||||
F = case Command0 of
|
F = case Command0 of
|
||||||
"install" -> fun(A, B) -> install(A, B) end;
|
%% "install" -> fun(A, B) -> install(A, B) end;
|
||||||
"unpack" -> fun(A, B) -> unpack(A, B) end;
|
%% "unpack" -> fun(A, B) -> unpack(A, B) end;
|
||||||
"upgrade" -> fun(A, B) -> upgrade(A, B) end;
|
%% "upgrade" -> fun(A, B) -> upgrade(A, B) end;
|
||||||
"downgrade" -> fun(A, B) -> downgrade(A, B) end;
|
%% "downgrade" -> fun(A, B) -> downgrade(A, B) end;
|
||||||
"uninstall" -> fun(A, B) -> uninstall(A, B) end;
|
%% "uninstall" -> fun(A, B) -> uninstall(A, B) end;
|
||||||
"versions" -> fun(A, B) -> versions(A, B) end
|
"versions" -> fun(A, B) -> versions(A, B) end;
|
||||||
|
_ -> fun fail_upgrade/2
|
||||||
end,
|
end,
|
||||||
F(DistInfo, Opts);
|
F(DistInfo, Opts);
|
||||||
main(Args) ->
|
main(Args) ->
|
||||||
?INFO("unknown args: ~p", [Args]),
|
?INFO("unknown args: ~p", [Args]),
|
||||||
erlang:halt(1).
|
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) ->
|
unpack({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||||||
Version = proplists:get_value(version, Opts),
|
Version = proplists:get_value(version, Opts),
|
||||||
case unpack_release(RelName, TargetNode, Version) of
|
case unpack_release(RelName, TargetNode, Version, Opts) of
|
||||||
{ok, Vsn} ->
|
{ok, Vsn} ->
|
||||||
?INFO("Unpacked successfully: ~p", [Vsn]);
|
?INFO("Unpacked successfully: ~p", [Vsn]);
|
||||||
old ->
|
old ->
|
||||||
|
@ -57,7 +66,7 @@ install({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||||||
Version = proplists:get_value(version, Opts),
|
Version = proplists:get_value(version, Opts),
|
||||||
validate_target_version(Version, TargetNode),
|
validate_target_version(Version, TargetNode),
|
||||||
case unpack_release(RelName, TargetNode, Version) of
|
case unpack_release(RelName, TargetNode, Version, Opts) of
|
||||||
{ok, Vsn} ->
|
{ok, Vsn} ->
|
||||||
?INFO("Unpacked successfully: ~p.", [Vsn]),
|
?INFO("Unpacked successfully: ~p.", [Vsn]),
|
||||||
check_and_install(TargetNode, Vsn),
|
check_and_install(TargetNode, Vsn),
|
||||||
|
@ -132,12 +141,13 @@ uninstall({_RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||||||
uninstall(_, Args) ->
|
uninstall(_, Args) ->
|
||||||
?INFO("uninstall: unknown args ~p", [Args]).
|
?INFO("uninstall: unknown args ~p", [Args]).
|
||||||
|
|
||||||
versions({_RelName, NameTypeArg, NodeName, Cookie}, []) ->
|
versions({_RelName, NameTypeArg, NodeName, Cookie}, _Opts) ->
|
||||||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||||||
print_existing_versions(TargetNode).
|
print_existing_versions(TargetNode).
|
||||||
|
|
||||||
parse_arguments(Args) ->
|
parse_arguments(Args) ->
|
||||||
parse_arguments(Args, []).
|
IsEnterprise = os:getenv("IS_ENTERPRISE") == "yes",
|
||||||
|
parse_arguments(Args, [{is_enterprise, IsEnterprise}]).
|
||||||
|
|
||||||
parse_arguments([], Acc) -> Acc;
|
parse_arguments([], Acc) -> Acc;
|
||||||
parse_arguments(["--no-permanent"|Rest], Acc) ->
|
parse_arguments(["--no-permanent"|Rest], Acc) ->
|
||||||
|
@ -146,9 +156,10 @@ parse_arguments([VersionStr|Rest], Acc) ->
|
||||||
Version = parse_version(VersionStr),
|
Version = parse_version(VersionStr),
|
||||||
parse_arguments(Rest, [{version, Version}] ++ Acc).
|
parse_arguments(Rest, [{version, Version}] ++ Acc).
|
||||||
|
|
||||||
unpack_release(RelName, TargetNode, Version) ->
|
unpack_release(RelName, TargetNode, Version, Opts) ->
|
||||||
StartScriptExists = filelib:is_dir(filename:join(["releases", Version, "start.boot"])),
|
StartScriptExists = filelib:is_regular(filename:join(["releases", Version, "start.boot"])),
|
||||||
WhichReleases = which_releases(TargetNode),
|
WhichReleases = which_releases(TargetNode),
|
||||||
|
IsEnterprise = proplists:get_value(is_enterprise, Opts),
|
||||||
case proplists:get_value(Version, WhichReleases) of
|
case proplists:get_value(Version, WhichReleases) of
|
||||||
Res when Res =:= undefined; (Res =:= unpacked andalso not StartScriptExists) ->
|
Res when Res =:= undefined; (Res =:= unpacked andalso not StartScriptExists) ->
|
||||||
%% not installed, so unpack tarball:
|
%% not installed, so unpack tarball:
|
||||||
|
@ -156,7 +167,7 @@ unpack_release(RelName, TargetNode, Version) ->
|
||||||
%% releases/<relname>-<version>.tar.gz
|
%% releases/<relname>-<version>.tar.gz
|
||||||
%% releases/<version>/<relname>-<version>.tar.gz
|
%% releases/<version>/<relname>-<version>.tar.gz
|
||||||
%% releases/<version>/<relname>.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} ->
|
{_, undefined} ->
|
||||||
{error, release_package_not_found};
|
{error, release_package_not_found};
|
||||||
{ReleasePackage, ReleasePackageLink} ->
|
{ReleasePackage, ReleasePackageLink} ->
|
||||||
|
@ -206,7 +217,7 @@ extract_tar(Cwd, Tar) ->
|
||||||
%% to the release package tarball found in 1.
|
%% to the release package tarball found in 1.
|
||||||
%% 3. return a tuple with the paths to the release package and
|
%% 3. return a tuple with the paths to the release package and
|
||||||
%% to the symlink that is to be provided to release handler
|
%% 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),
|
RelNameStr = atom_to_list(RelName),
|
||||||
%% regardless of the location of the release package, we'll
|
%% regardless of the location of the release package, we'll
|
||||||
%% always give release handler the same path which is the symlink
|
%% 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
|
%% we've found where the actual release package is located
|
||||||
ReleaseLink = filename:join(["releases", Version,
|
ReleaseLink = filename:join(["releases", Version,
|
||||||
RelNameStr ++ ".tar.gz"]),
|
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
|
case filelib:wildcard(TarBalls) of
|
||||||
[] ->
|
[] ->
|
||||||
{undefined, undefined};
|
{undefined, undefined};
|
||||||
|
|
Loading…
Reference in New Issue