mirror of
https://gitee.com/dgiiot/dgiot.git
synced 2024-11-30 03:07:40 +08:00
297 lines
9.8 KiB
Erlang
Executable File
297 lines
9.8 KiB
Erlang
Executable File
#!/usr/bin/env escript
|
|
%% -*- mode: erlang; -*-
|
|
|
|
-mode(compile).
|
|
|
|
-define(RED, "\e[31m").
|
|
-define(RESET, "\e[39m").
|
|
|
|
usage() ->
|
|
"A script to manage the released versions of EMQX for relup and hot
|
|
upgrade/downgrade.
|
|
|
|
We store a \"database\" of released versions as an `eterm' file, which
|
|
is a mapping from a given version `Vsn' to its OTP version and a list
|
|
of previous versions from which one can upgrade to `Vsn' (the
|
|
\"from_versions\" list). That allow us to more easily/explicitly keep
|
|
track of allowed version upgrades/downgrades, as well as OTP changes
|
|
between releases
|
|
|
|
In the examples below, `VERSION_DB_PATH' represents the path to the
|
|
`eterm' file containing the version database to be used.
|
|
|
|
Usage:
|
|
|
|
* List the previous base versions from which `TO_VSN' may be
|
|
upgraded to. Used to list versions for which relup files are to
|
|
be made.
|
|
|
|
relup-base-vsns.escript base-vsns TO_VSN VERSION_DB_PATH
|
|
|
|
|
|
* Show the OTP version with which `Vsn' was built.
|
|
|
|
relup-base-vsns.escript otp-vsn-for VSN VERSION_DB_PATH
|
|
|
|
|
|
* Automatically inserts a new version into the database. Previous
|
|
versions with the same Major and Minor numbers as `Vsn' are
|
|
considered to be upgradeable from, and versions with higher Major
|
|
and Minor numbers will automatically include `Vsn' in their
|
|
\"from_versions\" list.
|
|
|
|
For example, if inserting 4.4.8 when 4.5.0 and 4.5.1 exists,
|
|
versions `BASE_FROM_VSN'...4.4.7 will be considered 4.4.8's
|
|
\"from_versions\", and 4.4.8 will be included into 4.5.0 and
|
|
4.5.1's from versions.
|
|
|
|
relup-base-vsns.escript insert-new-vsn NEW_VSN BASE_FROM_VSN OTP_VSN VERSION_DB_PATH
|
|
|
|
* Check if the version database is consistent considering `VSN'.
|
|
|
|
relup-base-vsns.escript check-vsn-db VSN VERSION_DB_PATH
|
|
".
|
|
|
|
main(["base-vsns", To0, VsnDB]) ->
|
|
VsnMap = read_db(VsnDB),
|
|
To = strip_pre_release(To0),
|
|
#{from_versions := Froms} = fetch_version(To, VsnMap),
|
|
AvailableVersionsIndex = available_versions_index(),
|
|
lists:foreach(
|
|
fun(From) ->
|
|
io:format(user, "~s~n", [From])
|
|
end,
|
|
filter_froms(Froms, AvailableVersionsIndex)),
|
|
halt(0);
|
|
main(["otp-vsn-for", Vsn0, VsnDB]) ->
|
|
VsnMap = read_db(VsnDB),
|
|
Vsn = strip_pre_release(Vsn0),
|
|
#{otp := OtpVsn} = fetch_version(Vsn, VsnMap),
|
|
io:format(user, "~s~n", [OtpVsn]),
|
|
halt(0);
|
|
main(["insert-new-vsn", NewVsn0, BaseFromVsn0, OtpVsn0, VsnDB]) ->
|
|
VsnMap = read_db(VsnDB),
|
|
NewVsn = strip_pre_release(NewVsn0),
|
|
validate_version(NewVsn),
|
|
BaseFromVsn = strip_pre_release(BaseFromVsn0),
|
|
validate_version(BaseFromVsn),
|
|
OtpVsn = list_to_binary(OtpVsn0),
|
|
case VsnMap of
|
|
#{NewVsn := _} ->
|
|
print_warning("Version ~s already in DB!~n", [NewVsn]),
|
|
halt(1);
|
|
#{BaseFromVsn := _} ->
|
|
ok;
|
|
_ ->
|
|
print_warning("Version ~s not found in DB!~n", [BaseFromVsn]),
|
|
halt(1)
|
|
end,
|
|
NewVsnMap = insert_new_vsn(VsnMap, NewVsn, OtpVsn, BaseFromVsn),
|
|
NewVsnList =
|
|
lists:sort(
|
|
fun({Vsn1, _}, {Vsn2, _}) ->
|
|
parse_vsn(Vsn1) < parse_vsn(Vsn2)
|
|
end, maps:to_list(NewVsnMap)),
|
|
{ok, FD} = file:open(VsnDB, [write]),
|
|
io:format(FD, "%% -*- mode: erlang; -*-\n\n", []),
|
|
lists:foreach(
|
|
fun(Entry) ->
|
|
io:format(FD, "~p.~n", [Entry])
|
|
end,
|
|
NewVsnList),
|
|
file:close(FD),
|
|
halt(0);
|
|
main(["check-vsn-db", NewVsn0, VsnDB]) ->
|
|
VsnMap = read_db(VsnDB),
|
|
NewVsn = strip_pre_release(NewVsn0),
|
|
case check_all_vsns_schema(VsnMap) of
|
|
[] -> ok;
|
|
Problems ->
|
|
print_warning("Invalid Version DB ~s!~n", [VsnDB]),
|
|
print_warning("Problems found:~n"),
|
|
lists:foreach(
|
|
fun(Problem) ->
|
|
print_warning(" ~p~n", [Problem])
|
|
end, Problems),
|
|
halt(1)
|
|
end,
|
|
case VsnMap of
|
|
#{NewVsn := _} ->
|
|
io:format(user, "ok~n", []),
|
|
halt(0);
|
|
_ ->
|
|
Candidates = find_insertion_candidates(NewVsn, VsnMap),
|
|
print_warning("Version ~s not found in the version DB!~n", [NewVsn]),
|
|
[] =/= Candidates
|
|
andalso print_warning("Candidates for to insert this version into:~n"),
|
|
lists:foreach(
|
|
fun(Vsn) ->
|
|
io:format(user, " ~s~n", [Vsn])
|
|
end, Candidates),
|
|
print_warning(
|
|
"To insert this version automatically, run:~n"
|
|
"./scripts/relup-base-vsns insert-new-vsn NEW-VSN BASE-FROM-VSN NEW-OTP-VSN ~s~n"
|
|
"And commit the results. Be sure to revise the changes.~n"
|
|
"Otherwise, edit the file manually~n",
|
|
[VsnDB]),
|
|
halt(1)
|
|
end;
|
|
main(_) ->
|
|
io:format(user, usage(), []),
|
|
halt(1).
|
|
|
|
strip_pre_release(Vsn0) ->
|
|
case re:run(Vsn0, "[0-9]+\\.[0-9]+\\.[0-9]+", [{capture, all, binary}]) of
|
|
{match, [Vsn]} ->
|
|
Vsn;
|
|
_ ->
|
|
print_warning("Invalid Version: ~s ~n", [Vsn0]),
|
|
halt(1)
|
|
end.
|
|
|
|
fetch_version(Vsn, VsnMap) ->
|
|
case VsnMap of
|
|
#{Vsn := VsnData} ->
|
|
VsnData;
|
|
_ ->
|
|
print_warning("Version not found in releases: ~s ~n", [Vsn]),
|
|
halt(1)
|
|
end.
|
|
|
|
filter_froms(Froms0, AvailableVersionsIndex) ->
|
|
Froms1 =
|
|
case get_system() of
|
|
%% we do not support relup for windows
|
|
"windows" ->
|
|
[];
|
|
%% debian11 is introduced since v4.4.2 and e4.4.2
|
|
%% exclude tags before them
|
|
"debian11" ->
|
|
lists:filter(
|
|
fun(Vsn) ->
|
|
not lists:member(Vsn, [<<"4.4.0">>, <<"4.4.1">>])
|
|
end, Froms0);
|
|
_ ->
|
|
Froms0
|
|
end,
|
|
lists:filter(
|
|
fun(V) -> maps:get(V, AvailableVersionsIndex, false) end,
|
|
Froms1).
|
|
|
|
get_system() ->
|
|
case os:getenv("SYSTEM") of
|
|
false ->
|
|
string:trim(os:cmd("./scripts/get-distro.sh"));
|
|
System ->
|
|
System
|
|
end.
|
|
|
|
%% assumes that's X.Y.Z, without pre-releases
|
|
parse_vsn(VsnBin) ->
|
|
{match, [Major0, Minor0, Patch0]} = re:run(VsnBin, "([0-9]+)\\.([0-9]+)\\.([0-9]+)",
|
|
[{capture, all_but_first, binary}]),
|
|
[Major, Minor, Patch] = lists:map(fun binary_to_integer/1, [Major0, Minor0, Patch0]),
|
|
{Major, Minor, Patch}.
|
|
|
|
parsed_vsn_to_bin({Major, Minor, Patch}) ->
|
|
iolist_to_binary(io_lib:format("~b.~b.~b", [Major, Minor, Patch])).
|
|
|
|
find_insertion_candidates(NewVsn, VsnMap) ->
|
|
ParsedNewVsn = parse_vsn(NewVsn),
|
|
[Vsn
|
|
|| Vsn <- maps:keys(VsnMap),
|
|
ParsedVsn <- [parse_vsn(Vsn)],
|
|
ParsedVsn > ParsedNewVsn].
|
|
|
|
check_all_vsns_schema(VsnMap) ->
|
|
maps:fold(
|
|
fun(Vsn, Val, Acc) ->
|
|
Problems =
|
|
[{Vsn, should_be_binary} || not is_binary(Vsn)] ++
|
|
[{Vsn, must_have_map_value} || not is_map(Val)] ++
|
|
[{Vsn, {must_contain_keys, [otp, from_versions]}}
|
|
|| case Val of
|
|
#{otp := _, from_versions := _} ->
|
|
false;
|
|
_ ->
|
|
true
|
|
end] ++
|
|
[{Vsn, otp_version_must_be_binary}
|
|
|| case Val of
|
|
#{otp := Otp} when is_binary(Otp) ->
|
|
false;
|
|
_ ->
|
|
true
|
|
end] ++
|
|
[{Vsn, versions_must_be_list_of_binaries}
|
|
|| case Val of
|
|
#{from_versions := Froms} when is_list(Froms) ->
|
|
not lists:all(fun is_binary/1, Froms);
|
|
_ ->
|
|
true
|
|
end],
|
|
Problems ++ Acc
|
|
end,
|
|
[],
|
|
VsnMap).
|
|
|
|
insert_new_vsn(VsnMap0, NewVsn, OtpVsn, BaseFromVsn) ->
|
|
ParsedNewVsn = parse_vsn(NewVsn),
|
|
ParsedBaseFromVsn = parse_vsn(BaseFromVsn),
|
|
%% candidates to insert this version into (they are "future" versions)
|
|
Candidates = find_insertion_candidates(NewVsn, VsnMap0),
|
|
%% Past versions we can upgrade from
|
|
Froms = [Vsn || Vsn <- maps:keys(VsnMap0),
|
|
ParsedVsn <- [parse_vsn(Vsn)],
|
|
ParsedVsn >= ParsedBaseFromVsn,
|
|
ParsedVsn < ParsedNewVsn],
|
|
VsnMap1 =
|
|
lists:foldl(
|
|
fun(FutureVsn, Acc) ->
|
|
FutureData0 = #{from_versions := Froms0} = maps:get(FutureVsn, Acc),
|
|
FutureData = FutureData0#{from_versions => lists:usort(Froms0 ++ [NewVsn])},
|
|
Acc#{FutureVsn => FutureData}
|
|
end,
|
|
VsnMap0,
|
|
Candidates),
|
|
VsnMap1#{NewVsn => #{otp => OtpVsn, from_versions => Froms}}.
|
|
|
|
validate_version(Vsn) ->
|
|
ParsedVsn = parse_vsn(Vsn),
|
|
VsnBack = parsed_vsn_to_bin(ParsedVsn),
|
|
case VsnBack =:= Vsn of
|
|
true -> ok;
|
|
false ->
|
|
print_warning("Invalid version ~p !~n", [Vsn]),
|
|
print_warning("Versions MUST be of the form X.Y.Z "
|
|
"and not prefixed by `e` or `v`~n"),
|
|
halt(1)
|
|
end.
|
|
|
|
available_versions_index() ->
|
|
Output = os:cmd("git tag -l"),
|
|
AllVersions =
|
|
lists:filtermap(
|
|
fun(Line) ->
|
|
case re:run(Line, "^[ve]([0-9]+)\\.([0-9]+)\\.([0-9]+)$",
|
|
[{capture, all_but_first, binary}]) of
|
|
{match, [Major, Minor, Patch]} ->
|
|
Vsn = iolist_to_binary(io_lib:format("~s.~s.~s", [Major, Minor, Patch])),
|
|
{true, Vsn};
|
|
_ -> false
|
|
end
|
|
end, string:split(Output, "\n", all)),
|
|
%% FIXME: `maps:from_keys' is available only in OTP 24, but we
|
|
%% still build with 23. Switch to that once we drop OTP 23.
|
|
maps:from_list([{Vsn, true} || Vsn <- AllVersions]).
|
|
|
|
read_db(VsnDB) ->
|
|
{ok, VsnList} = file:consult(VsnDB),
|
|
maps:from_list(VsnList).
|
|
|
|
print_warning(Msg) ->
|
|
print_warning(Msg, []).
|
|
|
|
print_warning(Msg, Args) ->
|
|
io:format(standard_error, ?RED ++ Msg ++ ?RESET, Args).
|