From 2008130071b2d3cc97c23e89ab9ce0fc494d209d Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Tue, 9 Jul 2024 16:05:33 +0800 Subject: [PATCH] feat: add HTTP APIs for relup --- .../src/emqx_mgmt_api_plugins.erl | 3 + .../src/emqx_mgmt_api_relup.erl | 643 ++++++++++++++++++ .../proto/emqx_mgmt_api_relup_proto_v1.erl | 43 ++ apps/emqx_plugins/src/emqx_plugins.erl | 14 +- build | 2 +- 5 files changed, 703 insertions(+), 2 deletions(-) create mode 100644 apps/emqx_management/src/emqx_mgmt_api_relup.erl create mode 100644 apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl diff --git a/apps/emqx_management/src/emqx_mgmt_api_plugins.erl b/apps/emqx_management/src/emqx_mgmt_api_plugins.erl index 5e6d07a49..97b2b562e 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_plugins.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_plugins.erl @@ -566,6 +566,8 @@ install_package(FileName, Bin) -> ok = filelib:ensure_dir(File), ok = file:write_file(File, Bin), PackageName = string:trim(FileName, trailing, ".tar.gz"), + MD5 = emqx_utils:bin_to_hexstr(crypto:hash(md5, Bin), lower), + ok = file:write_file(emqx_plugins:md5sum_file(PackageName), MD5), case emqx_plugins:ensure_installed(PackageName, ?fresh_install) of {error, #{reason := plugin_not_found}} = NotFound -> NotFound; @@ -596,6 +598,7 @@ delete_package(Name, _Opts) -> _ = emqx_plugins:ensure_disabled(Name), _ = emqx_plugins:ensure_uninstalled(Name), _ = emqx_plugins:delete_package(Name), + _ = file:delete(emqx_plugins:md5sum_file(Name)), ok; Error -> Error diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl new file mode 100644 index 000000000..5704de8f4 --- /dev/null +++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl @@ -0,0 +1,643 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_mgmt_api_relup). + +-behaviour(minirest_api). + +-include_lib("typerefl/include/types.hrl"). +-include_lib("emqx/include/logger.hrl"). + +-export([get_upgrade_status/0]). + +-export([ + api_spec/0, + fields/1, + paths/0, + schema/1, + namespace/0, + validate_name/1 +]). + +-export([ + '/relup/package/upload'/2, + '/relup/package'/2, + '/relup/status'/2, + '/relup/status/:node'/2, + '/relup/upgrade'/2, + '/relup/upgrade/:node'/2 +]). + +-define(TAGS, [<<"Relup">>]). +-define(NAME_RE, "^[A-Za-z]+[A-Za-z0-9-_.]*$"). +-define(CONTENT_PACKAGE, plugin). +-define(PLUGIN_NAME, <<"emqx_relup">>). + +-define(EXAM_VSN1, <<"5.8.0">>). +-define(EXAM_VSN2, <<"5.8.1">>). +-define(EXAM_VSN3, <<"5.8.2">>). +-define(EXAM_PACKAGE_NAME_2, <<"emqx_relup-5.8.1.tar.gz">>). +-define(EXAM_PACKAGE_NAME_3, <<"emqx_relup-5.8.2.tar.gz">>). + +-define(ASSERT_PKG_READY(EXPR), + case code:is_loaded(emqx_relup_main) of + false -> return_bad_request(<<"No relup package is installed">>); + {file, _} -> EXPR + end +). + +%%============================================================================== +%% API Spec +namespace() -> + "relup". + +api_spec() -> + emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). + +paths() -> + [ + "/relup/package", + "/relup/package/upload", + "/relup/status", + "/relup/status/:node", + "/relup/upgrade", + "/relup/upgrade/:node" + ]. + +schema("/relup/package/upload") -> + #{ + 'operationId' => '/relup/package/upload', + post => #{ + summary => <<"Upload a hot upgrade package">>, + description => << + "Upload a hot upgrade package (emqx_relup-vsn.tar.gz).
" + "Note that only one package is alllowed to be installed at a time." + >>, + tags => ?TAGS, + 'requestBody' => #{ + content => #{ + 'multipart/form-data' => #{ + schema => #{ + type => object, + properties => #{ + ?CONTENT_PACKAGE => #{type => string, format => binary} + } + }, + encoding => #{?CONTENT_PACKAGE => #{'contentType' => 'application/gzip'}} + } + } + }, + responses => #{ + 204 => <<"Package is uploaded successfully">>, + 400 => emqx_dashboard_swagger:error_codes( + ['UNEXPECTED_ERROR', 'BAD_PLUGIN_INFO'] + ) + } + } + }; +schema("/relup/package") -> + #{ + 'operationId' => '/relup/package', + get => #{ + summary => <<"Get the installed hot upgrade package">>, + description => + <<"Get information of the installed hot upgrade package.
">>, + tags => ?TAGS, + responses => #{ + 200 => hoconsc:ref(package), + 404 => emqx_dashboard_swagger:error_codes( + ['NOT_FOUND'], + <<"No relup package is installed">> + ) + } + }, + delete => #{ + summary => <<"Delete the installed hot upgrade package">>, + description => + <<"Delete the installed hot upgrade package.
">>, + tags => ?TAGS, + responses => #{ + 204 => <<"Packages are deleted successfully">> + } + } + }; +schema("/relup/status") -> + #{ + 'operationId' => '/relup/status', + get => #{ + summary => <<"Get the hot upgrade status of all nodes">>, + description => <<"Get the hot upgrade status of all nodes">>, + tags => ?TAGS, + responses => #{ + 200 => hoconsc:array(hoconsc:ref(running_status)) + } + } + }; +schema("/relup/status/:node") -> + #{ + 'operationId' => '/relup/status/:node', + get => #{ + summary => <<"Get the hot upgrade status of a specified node">>, + description => <<"Get the hot upgrade status of a specified node">>, + tags => ?TAGS, + parameters => [hoconsc:ref(node_name)], + responses => #{ + 200 => hoconsc:ref(running_status) + } + } + }; +schema("/relup/upgrade") -> + #{ + 'operationId' => '/relup/upgrade', + post => #{ + summary => <<"Upgrade all nodes">>, + description => << + "Upgrade all nodes to the target version with the installed package." + >>, + tags => ?TAGS, + responses => #{ + 204 => <<"Upgrade is started successfully">>, + 400 => emqx_dashboard_swagger:error_codes( + ['UNEXPECTED_ERROR'], + <<"Upgrade failed because of invalid input or environment">> + ), + 500 => emqx_dashboard_swagger:error_codes( + ['INTERNAL_ERROR'], <<"Upgrade failed because of internal errors">> + ) + } + } + }; +schema("/relup/upgrade/:node") -> + #{ + 'operationId' => '/relup/upgrade/:node', + post => #{ + summary => <<"Upgrade a specified node">>, + description => << + "Upgrade a specified node to the target version with the installed package." + >>, + tags => ?TAGS, + parameters => [hoconsc:ref(node_name)], + responses => #{ + 204 => <<"Upgrade is started successfully">>, + 400 => emqx_dashboard_swagger:error_codes( + ['UNEXPECTED_ERROR'], + <<"Upgrade failed because of invalid input or environment">> + ), + 404 => emqx_dashboard_swagger:error_codes( + ['NOT_FOUND'], + <<"Node not found">> + ), + 500 => emqx_dashboard_swagger:error_codes( + ['INTERNAL_ERROR'], <<"Upgrade failed because of internal errors">> + ) + } + } + }. + +%%============================================================================== +%% Field definitions +fields(package) -> + [ + {name, + hoconsc:mk( + binary(), + #{ + desc => <<"File name of the package">>, + validator => fun ?MODULE:validate_name/1, + example => ?EXAM_PACKAGE_NAME_3 + } + )}, + {target_vsn, + hoconsc:mk( + binary(), + #{ + desc => <<"Target emqx version for this package">>, + example => ?EXAM_VSN3 + } + )}, + {built_on_otp_release, hoconsc:mk(binary(), #{example => <<"24">>})}, + {applicable_vsns, + hoconsc:mk(hoconsc:array(binary()), #{ + example => [?EXAM_VSN1, ?EXAM_VSN2], + desc => <<"The emqx versions that this package can be applied to.">> + })}, + {build_date, + hoconsc:mk(binary(), #{ + example => <<"2021-12-25">>, + desc => <<"The date when the package was built.">> + })}, + {change_logs, + hoconsc:mk( + hoconsc:array(binary()), + #{ + desc => <<"Changes that this package brings">>, + example => [ + << + "1. Fix a bug foo in the plugin." + "2. Add a new bar feature." + >> + ] + } + )}, + {md5_sum, hoconsc:mk(binary(), #{example => <<"d41d8cd98f00b204e9800998ecf8427e">>})} + ]; +fields(upgrade_history) -> + [ + {started_at, + hoconsc:mk( + binary(), + #{ + desc => <<"The timestamp (in format of RFC3339) when the upgrade started">>, + example => <<"2024-07-15T13:48:02.648559+08:00">> + } + )}, + {finished_at, + hoconsc:mk( + binary(), + #{ + desc => <<"The timestamp (in format of RFC3339) when the upgrade finished">>, + example => <<"2024-07-16T11:00:01.875627+08:00">> + } + )}, + {from_vsn, + hoconsc:mk( + binary(), + #{ + desc => <<"The version before the upgrade">>, + example => ?EXAM_VSN1 + } + )}, + {target_vsn, + hoconsc:mk( + binary(), + #{ + desc => <<"The target version of the upgrade">>, + example => ?EXAM_VSN3 + } + )}, + {upgrade_opts, + hoconsc:mk( + map(), + #{ + desc => <<"The options used for the upgrade">>, + example => #{deploy_inplace => false} + } + )}, + {status, + hoconsc:mk( + hoconsc:enum(['in-progress', finished]), + #{ + desc => <<"The upgrade status of the node">>, + example => 'in-progress' + } + )}, + {result, + hoconsc:mk( + hoconsc:union([success, hoconsc:ref(?MODULE, upgrade_error)]), + #{ + desc => <<"The upgrade result">>, + example => success + } + )} + ]; +fields(running_status) -> + [ + {node, hoconsc:mk(binary(), #{example => <<"emqx@127.0.0.1">>})}, + {status, + hoconsc:mk(hoconsc:enum(['in-progress', idle]), #{ + desc => << + "The upgrade status of a node:
" + "1. in-progress: hot upgrade is in progress.
" + "2. idle: hot upgrade is not started.
" + >> + })}, + {role, + hoconsc:mk(hoconsc:enum([core, replicant]), #{ + desc => <<"The role of the node">>, + example => core + })}, + {live_connections, + hoconsc:mk(integer(), #{ + desc => <<"The number of live connections">>, + example => 100 + })}, + {current_vsn, + hoconsc:mk(binary(), #{ + desc => <<"The current version of the node">>, + example => ?EXAM_VSN1 + })}, + {upgrade_history, + hoconsc:mk( + hoconsc:array(hoconsc:ref(upgrade_history)), + #{ + desc => <<"The upgrade history of the node">>, + example => [ + #{ + started_at => <<"2024-07-15T13:48:02.648559+08:00">>, + finished_at => <<"2024-07-16T11:00:01.875627+08:00">>, + from_vsn => ?EXAM_VSN1, + target_vsn => ?EXAM_VSN2, + upgrade_opts => #{deploy_inplace => false}, + status => finished, + result => success + } + ] + } + )} + ]; +fields(upgrade_error) -> + [ + {err_type, + hoconsc:mk( + binary(), + #{ + desc => <<"The type of the error">>, + example => <<"no_write_permission">> + } + )}, + {details, + hoconsc:mk( + map(), + #{ + desc => <<"The details of the error">>, + example => #{ + dir => <<"emqx/relup">>, + msg => <<"no write permission in dir 'emqx/relup'">> + } + } + )} + ]; +fields(node_name) -> + [ + {node, + hoconsc:mk( + binary(), + #{ + default => all, + in => path, + desc => <<"The node to be upgraded">>, + example => <<"emqx@127.0.0.1">> + } + )} + ]. + +validate_name(Name) -> + NameLen = byte_size(Name), + case NameLen > 0 andalso NameLen =< 256 of + true -> + case re:run(Name, ?NAME_RE) of + nomatch -> {error, <<"Name should be " ?NAME_RE>>}; + _ -> ok + end; + false -> + {error, <<"Name Length must =< 256">>} + end. + +%%============================================================================== +%% HTTP API CallBacks + +'/relup/package/upload'(post, #{body := #{<<"plugin">> := Plugin}} = Params) -> + case emqx_plugins:list() of + [] -> + [{FileName, _Bin}] = maps:to_list(maps:without([type], Plugin)), + NameVsn = string:trim(FileName, trailing, ".tar.gz"), + %% we install a relup package as a "hidden" plugin + case emqx_mgmt_api_plugins:upload_install(post, Params) of + {204} -> + case emqx_mgmt_api_plugins_proto_v3:ensure_action(NameVsn, start) of + ok -> + {204}; + {error, Reason} -> + %% try our best to clean up if start failed + _ = emqx_mgmt_api_plugins_proto_v3:delete_package(NameVsn), + return_internal_error(Reason) + end; + ErrResp -> + ErrResp + end; + _ -> + {400, #{ + code => 'BAD_REQUEST', + message => << + "Only one relup package can be installed at a time." + "Please delete the existing package first." + >> + }} + end. + +'/relup/package'(get, _) -> + case get_installed_packages() of + [PluginInfo] -> + {200, format_package_info(PluginInfo)}; + [] -> + return_not_found(<<"No relup package is installed">>) + end; +'/relup/package'(delete, _) -> + delete_installed_packages(), + {204}. + +'/relup/status'(get, _) -> + ?ASSERT_PKG_READY(begin + {[_ | _] = Res, []} = emqx_mgmt_api_relup_proto_v1:get_upgrade_status_from_all_nodes(), + case + lists:filter( + fun + (R) when is_map(R) -> false; + (_) -> true + end, + Res + ) + of + [] -> + {200, Res}; + Filtered -> + return_internal_error( + case hd(Filtered) of + {badrpc, Reason} -> Reason; + Reason -> Reason + end + ) + end + end). + +'/relup/status/:node'(get, #{bindings := #{node := NodeNameStr}}) -> + ?ASSERT_PKG_READY( + emqx_utils_api:with_node( + NodeNameStr, + fun + (Node) when node() =:= Node -> + {200, get_upgrade_status()}; + (Node) when is_atom(Node) -> + {200, emqx_mgmt_api_relup_proto_v1:get_upgrade_status(Node)} + end + ) + ). + +'/relup/upgrade'(post, _) -> + ?ASSERT_PKG_READY( + upgrade_with_targe_vsn(fun(TargetVsn) -> + run_upgrade_on_nodes(emqx:running_nodes(), TargetVsn) + end) + ). + +'/relup/upgrade/:node'(post, #{bindings := #{node := NodeNameStr}}) -> + ?ASSERT_PKG_READY( + upgrade_with_targe_vsn( + fun(TargetVsn) -> + emqx_utils_api:with_node( + NodeNameStr, + fun + (Node) when node() =:= Node -> + run_upgrade(TargetVsn); + (Node) when is_atom(Node) -> + run_upgrade_on_nodes([Node], TargetVsn) + end + ) + end + ) + ). +%%============================================================================== +%% Helper functions + +get_upgrade_status() -> + #{ + node => node(), + role => mria_rlog:role(), + live_connections => emqx_cm:get_connected_client_count(), + current_vsn => list_to_binary(emqx_release:version()), + status => emqx_relup_main:get_latest_upgrade_status(), + upgrade_history => emqx_relup_main:get_all_upgrade_logs() + }. + +upgrade_with_targe_vsn(Fun) -> + case get_target_vsn() of + {ok, TargetVsn} -> + Fun(TargetVsn); + {error, no_relup_package_installed} -> + return_bad_request(<<"No relup package is installed">>); + {error, multiple_relup_packages_installed} -> + return_internal_error(<<"Multiple relup package installed">>) + end. + +run_upgrade_on_nodes(Nodes, TargetVsn) -> + {[_ | _] = Res, []} = emqx_mgmt_api_relup_proto_v1:run_upgrade(Nodes, TargetVsn), + case lists:filter(fun(R) -> R =/= ok end, Res) of + [] -> + {204}; + Filtered -> + upgrade_return( + case hd(Filtered) of + {badrpc, Reason} -> Reason; + {error, Reason} -> Reason; + Reason -> Reason + end + ) + end. + +run_upgrade(TargetVsn) -> + case emqx_relup_main:upgrade(TargetVsn) of + ok -> {204}; + {error, Reason} -> upgrade_return(Reason) + end. + +get_target_vsn() -> + case get_installed_packages() of + [PackageInfo] -> {ok, target_vsn_from_rel_vsn(maps_get(rel_vsn, PackageInfo))}; + [] -> {error, no_relup_package_installed}; + _ -> {error, multiple_relup_packages_installed} + end. + +get_installed_packages() -> + lists:filtermap( + fun(PackageInfo) -> + case maps_get(name, PackageInfo) of + ?PLUGIN_NAME -> true; + _ -> false + end + end, + emqx_plugins:list() + ). + +target_vsn_from_rel_vsn(Vsn) -> + case string:split(binary_to_list(Vsn), "-") of + [VsnStr | _] -> VsnStr; + _ -> throw({invalid_vsn, Vsn}) + end. + +delete_installed_packages() -> + lists:foreach( + fun(PackageInfo) -> + ok = emqx_mgmt_api_plugins_proto_v3:delete_package( + name_vsn(?PLUGIN_NAME, maps_get(rel_vsn, PackageInfo)) + ) + end, + get_installed_packages() + ). + +format_package_info(PluginInfo) when is_map(PluginInfo) -> + Vsn = maps_get(rel_vsn, PluginInfo), + case emqx_relup_main:get_package_info(target_vsn_from_rel_vsn(Vsn)) of + {error, Reason} -> + throw({get_pkg_info_failed, Reason}); + {ok, #{base_vsns := BaseVsns, change_logs := ChangeLogs}} -> + #{ + name => name_vsn(?PLUGIN_NAME, Vsn), + target_vsn => Vsn, + built_on_otp_release => maps_get(built_on_otp_release, PluginInfo), + applicable_vsns => BaseVsns, + build_date => maps_get(git_commit_or_build_date, PluginInfo), + change_logs => ChangeLogs, + md5_sum => maps_get(md5sum, PluginInfo) + } + end. + +maps_get(Key, Map) when is_atom(Key) -> + maps_get(Key, Map, unknown). + +maps_get(Key, Map, Def) when is_atom(Key) -> + case maps:find(Key, Map) of + {ok, Value} -> Value; + error -> maps:get(atom_to_binary(Key, utf8), Map, Def) + end. + +upgrade_return(#{stage := check_and_unpack} = Reason) -> + return_bad_request(Reason); +upgrade_return(Reason) -> + return_internal_error(Reason). + +return_not_found(Reason) -> + {404, #{ + code => 'NOT_FOUND', + message => emqx_utils:readable_error_msg(Reason) + }}. + +return_bad_request(Reason) -> + {400, #{ + code => 'BAD_REQUEST', + message => emqx_utils:readable_error_msg(Reason) + }}. + +return_internal_error(Reason) -> + {500, #{ + code => 'INTERNAL_ERROR', + message => emqx_utils:readable_error_msg(Reason) + }}. + +name_vsn(Name, Vsn) -> + bin([Name, "-", Vsn]). + +bin(A) when is_atom(A) -> atom_to_binary(A, utf8); +bin(L) when is_list(L) -> unicode:characters_to_binary(L, utf8); +bin(B) when is_binary(B) -> B. diff --git a/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl new file mode 100644 index 000000000..8f389f003 --- /dev/null +++ b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl @@ -0,0 +1,43 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_mgmt_api_relup_proto_v1). + +-behaviour(emqx_bpapi). + +-include_lib("emqx/include/bpapi.hrl"). + +-export([ + introduced_in/0, + run_upgrade/2, + get_upgrade_status_from_all_nodes/0, + get_upgrade_status/1 +]). + +-define(RPC_TIMEOUT_OP, 180_000). +-define(RPC_TIMEOUT_INFO, 15_000). + +introduced_in() -> + "5.8.0". + +-spec run_upgrade([node()], string()) -> emqx_rpc:multicall_result(). +run_upgrade(Nodes, TargetVsn) -> + rpc:multicall(Nodes, emqx_relup_main, upgrade, [TargetVsn], ?RPC_TIMEOUT_OP). + +get_upgrade_status_from_all_nodes() -> + rpc:multicall(emqx_mgmt_api_relup, get_upgrade_status, [], ?RPC_TIMEOUT_INFO). + +get_upgrade_status(Node) -> + rpc:call(Node, emqx_mgmt_api_relup, get_upgrade_status, [], ?RPC_TIMEOUT_INFO). diff --git a/apps/emqx_plugins/src/emqx_plugins.erl b/apps/emqx_plugins/src/emqx_plugins.erl index 253d1b4ef..c7ae9fd2c 100644 --- a/apps/emqx_plugins/src/emqx_plugins.erl +++ b/apps/emqx_plugins/src/emqx_plugins.erl @@ -73,6 +73,7 @@ decode_plugin_config_map/2, install_dir/0, avsc_file_path/1, + md5sum_file/1, with_plugin_avsc/1 ]). @@ -736,7 +737,8 @@ do_read_plugin(NameVsn, InfoFilePath, Options) -> {ok, PlainMap} = (read_file_fun(InfoFilePath, "bad_info_file", #{read_mode => ?JSON_MAP}))(), Info0 = check_plugin(PlainMap, NameVsn, InfoFilePath), Info1 = plugins_readme(NameVsn, Options, Info0), - plugin_status(NameVsn, Info1). + Info2 = plugins_package_info(NameVsn, Info1), + plugin_status(NameVsn, Info2). read_plugin_avsc(NameVsn) -> read_plugin_avsc(NameVsn, #{read_mode => ?JSON_MAP}). @@ -837,6 +839,12 @@ get_plugin_config_from_any_node([Node | T], NameVsn, Errors) -> get_plugin_config_from_any_node(T, NameVsn, [{Node, Err} | Errors]) end. +plugins_package_info(NameVsn, Info) -> + case file:read_file(md5sum_file(NameVsn)) of + {ok, MD5} -> Info#{md5sum => MD5}; + _ -> Info#{md5sum => <<>>} + end. + plugins_readme(NameVsn, #{fill_readme := true}, Info) -> case file:read_file(readme_file(NameVsn)) of {ok, Bin} -> Info#{readme => Bin}; @@ -1489,6 +1497,10 @@ default_plugin_config_file(NameVsn) -> i18n_file_path(NameVsn) -> wrap_to_list(filename:join([plugin_priv_dir(NameVsn), "config_i18n.json"])). +-spec md5sum_file(name_vsn()) -> string(). +md5sum_file(NameVsn) -> + plugin_dir(NameVsn) ++ ".tar.gz.md5sum". + -spec readme_file(name_vsn()) -> string(). readme_file(NameVsn) -> wrap_to_list(filename:join([plugin_dir(NameVsn), "README.md"])). diff --git a/build b/build index bc2fa4b52..8a8fd7776 100755 --- a/build +++ b/build @@ -211,8 +211,8 @@ make_elixir_rel() { assert_no_excluded_deps emqx-enterprise emqx_telemetry } -## extract previous version .tar.gz files to _build/$PROFILE/rel/emqx before making relup make_relup() { + export RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)" ./rebar3 emqx relup_gen --relup-dir=./relup make rel -C _build/default/plugins/emqx_relup }