From c0c5bcc6985f2d7f08d7627ea2505b9e153c12d0 Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Mon, 26 Jul 2021 17:33:02 +0800 Subject: [PATCH] feat(config): support deleting a config entry --- apps/emqx/src/emqx_config.erl | 38 ++++++++--------- apps/emqx/src/emqx_config_handler.erl | 59 +++++++++++++++++++-------- apps/emqx/src/emqx_map_lib.erl | 14 +++++++ 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/apps/emqx/src/emqx_config.erl b/apps/emqx/src/emqx_config.erl index fb9d82fc1..f7d97eb2c 100644 --- a/apps/emqx/src/emqx_config.erl +++ b/apps/emqx/src/emqx_config.erl @@ -18,6 +18,7 @@ -compile({no_auto_import, [get/0, get/1]}). -export([ load/0 + , read_override_conf/0 , save_configs/2 , save_to_app_env/1 , save_to_emqx_config/2 @@ -47,6 +48,7 @@ ]). -export([ update/2 + , remove/1 ]). %% raw configs is the config that is now parsed and tranlated by hocon schema @@ -129,7 +131,11 @@ put(KeyPath, Config) -> -spec update(emqx_map_lib:config_key_path(), update_request()) -> ok | {error, term()}. update(ConfKeyPath, UpdateReq) -> - emqx_config_handler:update_config(ConfKeyPath, UpdateReq, get_raw()). + emqx_config_handler:update_config(ConfKeyPath, UpdateReq). + +-spec remove(emqx_map_lib:config_key_path()) -> ok | {error, term()}. +remove(ConfKeyPath) -> + emqx_config_handler:remove_config(ConfKeyPath). -spec get_raw() -> map(). get_raw() -> @@ -164,22 +170,18 @@ load() -> {_MappedEnvs, RichConf} = hocon_schema:map_translate(emqx_schema, RawRichConf, #{}), ok = save_to_emqx_config(to_plainmap(RichConf), to_plainmap(RawRichConf)). --spec save_configs(raw_config(), Opts) -> ok | {error, term()} - when Opts :: #{overridden_keys => all | [binary()]}. -save_configs(RawConf, Opts) -> - {_MappedEnvs, RichConf} = hocon_schema:map_translate(emqx_schema, to_richmap(RawConf), #{}), - save_to_emqx_config(to_plainmap(RichConf), RawConf), +-spec read_override_conf() -> raw_config(). +read_override_conf() -> + load_hocon_file(emqx_override_conf_name(), map). +-spec save_configs(raw_config(), raw_config()) -> ok | {error, term()}. +save_configs(RawConf, OverrideConf) -> + {_MappedEnvs, RichConf} = hocon_schema:map_translate(emqx_schema, to_richmap(RawConf), #{}), %% We may need also support hot config update for the apps that use application envs. %% If that is the case uncomment the following line to update the configs to application env %save_to_app_env(_MappedEnvs), - - %% We don't save the entire config to emqx_override.conf, but only the sub configs - %% specified by RootKeys - case maps:get(overridden_keys, Opts, all) of - all -> save_to_override_conf(RawConf); - RootKeys -> save_to_override_conf(maps:with(RootKeys, RawConf)) - end. + save_to_emqx_config(to_plainmap(RichConf), RawConf), + save_to_override_conf(OverrideConf). -spec save_to_app_env([tuple()]) -> ok. save_to_app_env(AppEnvs) -> @@ -192,13 +194,11 @@ save_to_emqx_config(Conf, RawConf) -> emqx_config:put(emqx_map_lib:unsafe_atom_key_map(Conf)), emqx_config:put_raw(RawConf). --spec save_to_override_conf(config()) -> ok | {error, term()}. -save_to_override_conf(Conf) -> +-spec save_to_override_conf(raw_config()) -> ok | {error, term()}. +save_to_override_conf(RawConf) -> FileName = emqx_override_conf_name(), - OldConf = load_hocon_file(FileName, map), - MergedConf = maps:merge(OldConf, Conf), ok = filelib:ensure_dir(FileName), - case file:write_file(FileName, jsx:prettify(jsx:encode(MergedConf))) of + case file:write_file(FileName, jsx:prettify(jsx:encode(RawConf))) of ok -> ok; {error, Reason} -> logger:error("write to ~s failed, ~p", [FileName, Reason]), @@ -221,4 +221,4 @@ to_richmap(Map) -> RichMap. to_plainmap(RichMap) -> - hocon_schema:richmap_to_map(RichMap). \ No newline at end of file + hocon_schema:richmap_to_map(RichMap). diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index e051b27eb..7b70b5382 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -24,14 +24,11 @@ %% API functions -export([ start_link/0 , add_handler/2 - , update_config/3 + , update_config/2 + , remove_config/1 , merge_to_old_config/2 ]). -%% emqx_config_handler callbacks --export([ pre_config_update/2 - ]). - %% gen_server callbacks -export([init/1, handle_call/3, @@ -62,11 +59,15 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, {}, []). --spec update_config(emqx_config:config_key_path(), emqx_config:update_request(), - emqx_config:raw_config()) -> +-spec update_config(emqx_config:config_key_path(), emqx_config:update_request()) -> ok | {error, term()}. -update_config(ConfKeyPath, UpdateReq, RawConfig) -> - gen_server:call(?MODULE, {update_config, ConfKeyPath, UpdateReq, RawConfig}). +update_config(ConfKeyPath, UpdateReq) -> + gen_server:call(?MODULE, {update_config, ConfKeyPath, UpdateReq}). + +-spec remove_config(emqx_config:config_key_path()) -> + ok | {error, term()}. +remove_config(ConfKeyPath) -> + gen_server:call(?MODULE, {remove_config, ConfKeyPath}). -spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok. add_handler(ConfKeyPath, HandlerName) -> @@ -83,11 +84,28 @@ handle_call({add_child, ConfKeyPath, HandlerName}, _From, {reply, ok, State#{handlers => emqx_map_lib:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}}; -handle_call({update_config, ConfKeyPath, UpdateReq, RawConf}, _From, +handle_call({update_config, ConfKeyPath, UpdateReq}, _From, #{handlers := Handlers} = State) -> OldConf = emqx_config:get(), - try {RootKeys, Conf} = do_update_config(ConfKeyPath, Handlers, RawConf, UpdateReq), - Result = emqx_config:save_configs(Conf, #{overridden_keys => RootKeys}), + OldRawConf = emqx_config:get_raw(), + try NewRawConf = do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq), + OverrideConf = update_override_config(ConfKeyPath, NewRawConf), + Result = emqx_config:save_configs(NewRawConf, OverrideConf), + do_post_config_update(ConfKeyPath, Handlers, OldConf, emqx_config:get()), + {reply, Result, State} + catch + Error : Reason : ST -> + ?LOG(error, "update config failed: ~p", [{Error, Reason, ST}]), + {reply, {error, Reason}, State} + end; + +handle_call({remove_config, ConfKeyPath}, _From, #{handlers := Handlers} = State) -> + OldConf = emqx_config:get(), + OldRawConf = emqx_config:get_raw(), + BinKeyPath = bin_path(ConfKeyPath), + try NewRawConf = emqx_map_lib:deep_remove(BinKeyPath, OldRawConf), + OverrideConf = emqx_map_lib:deep_remove(BinKeyPath, emqx_config:read_override_conf()), + Result = emqx_config:save_configs(NewRawConf, OverrideConf), do_post_config_update(ConfKeyPath, Handlers, OldConf, emqx_config:get()), {reply, Result, State} catch @@ -148,11 +166,6 @@ call_post_config_update(Handlers, OldConf, NewConf) -> false -> ok end. -%% callbacks for the top-level handler -pre_config_update(UpdateReq, OldConf) -> - FullRawConf = merge_to_old_config(UpdateReq, OldConf), - {maps:keys(UpdateReq), FullRawConf}. - %% The default callback of config handlers %% the behaviour is overwriting the old config if: %% 1. the old config is undefined @@ -163,6 +176,18 @@ merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) merge_to_old_config(UpdateReq, _RawConf) -> UpdateReq. +update_override_config(ConfKeyPath, RawConf) -> + %% We don't save the entire config to emqx_override.conf, but only the part + %% specified by the ConfKeyPath + PartialConf = maps:with(root_keys(ConfKeyPath), RawConf), + OldConf = emqx_config:read_override_conf(), + maps:merge(OldConf, PartialConf). + +root_keys([]) -> []; +root_keys([RootKey | _]) -> [bin(RootKey)]. + +bin_path(ConfKeyPath) -> [bin(Key) || Key <- ConfKeyPath]. + bin(A) when is_atom(A) -> list_to_binary(atom_to_list(A)); bin(B) when is_binary(B) -> B; bin(S) when is_list(S) -> list_to_binary(S). diff --git a/apps/emqx/src/emqx_map_lib.erl b/apps/emqx/src/emqx_map_lib.erl index c0d922de6..03ea620f4 100644 --- a/apps/emqx/src/emqx_map_lib.erl +++ b/apps/emqx/src/emqx_map_lib.erl @@ -19,6 +19,7 @@ , deep_get/3 , deep_find/2 , deep_put/3 + , deep_remove/2 , deep_merge/2 , safe_atom_key_map/1 , unsafe_atom_key_map/1 @@ -64,6 +65,19 @@ deep_put([Key | KeyPath], Map, Config) -> SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config), Map#{Key => SubMap}. +-spec deep_remove(config_key_path(), map()) -> map(). +deep_remove([], Map) -> + Map; +deep_remove([Key], Map) -> + maps:remove(Key, Map); +deep_remove([Key | KeyPath], Map) -> + case maps:find(Key, Map) of + {ok, SubMap} when is_map(SubMap) -> + Map#{Key => deep_remove(KeyPath, SubMap)}; + {ok, _Val} -> Map; + error -> Map + end. + %% #{a => #{b => 3, c => 2}, d => 4} %% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}). -spec deep_merge(map(), map()) -> map().