feat(emqx_config): add emqx_config:fill_defaults/1
This commit is contained in:
parent
a4d29ec8de
commit
1c86bd6199
|
@ -15,7 +15,7 @@
|
||||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
||||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.4"}}}
|
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.4"}}}
|
||||||
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
||||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.11.0"}}}
|
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.11.1"}}}
|
||||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
||||||
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
||||||
|
|
|
@ -20,14 +20,20 @@
|
||||||
-export([ init_load/2
|
-export([ init_load/2
|
||||||
, read_override_conf/0
|
, read_override_conf/0
|
||||||
, check_config/2
|
, check_config/2
|
||||||
|
, fill_defaults/1
|
||||||
|
, fill_defaults/2
|
||||||
, save_configs/4
|
, save_configs/4
|
||||||
, save_to_app_env/1
|
, save_to_app_env/1
|
||||||
, save_to_config_map/2
|
, save_to_config_map/2
|
||||||
, save_to_override_conf/1
|
, save_to_override_conf/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([get_root/1,
|
-export([ get_root/1
|
||||||
get_root_raw/1]).
|
, get_root_raw/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([ get_default_value/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export([ get/1
|
-export([ get/1
|
||||||
, get/2
|
, get/2
|
||||||
|
@ -37,6 +43,12 @@
|
||||||
, put/2
|
, put/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([ save_schema_mod/1
|
||||||
|
, get_schema_mod/0
|
||||||
|
, get_schema_mod/1
|
||||||
|
, get_root_names/0
|
||||||
|
]).
|
||||||
|
|
||||||
-export([ get_zone_conf/2
|
-export([ get_zone_conf/2
|
||||||
, get_zone_conf/3
|
, get_zone_conf/3
|
||||||
, put_zone_conf/3
|
, put_zone_conf/3
|
||||||
|
@ -53,6 +65,7 @@
|
||||||
, update/3
|
, update/3
|
||||||
, remove/1
|
, remove/1
|
||||||
, remove/2
|
, remove/2
|
||||||
|
, reset/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ get_raw/1
|
-export([ get_raw/1
|
||||||
|
@ -63,6 +76,7 @@
|
||||||
|
|
||||||
-define(CONF, conf).
|
-define(CONF, conf).
|
||||||
-define(RAW_CONF, raw_conf).
|
-define(RAW_CONF, raw_conf).
|
||||||
|
-define(PERSIS_MOD_ROOTNAMES, {?MODULE, default_conf}).
|
||||||
-define(PERSIS_KEY(TYPE, ROOT), {?MODULE, TYPE, ROOT}).
|
-define(PERSIS_KEY(TYPE, ROOT), {?MODULE, TYPE, ROOT}).
|
||||||
-define(ZONE_CONF_PATH(ZONE, PATH), [zones, ZONE | PATH]).
|
-define(ZONE_CONF_PATH(ZONE, PATH), [zones, ZONE | PATH]).
|
||||||
-define(LISTENER_CONF_PATH(ZONE, LISTENER, PATH), [zones, ZONE, listeners, LISTENER | PATH]).
|
-define(LISTENER_CONF_PATH(ZONE, LISTENER, PATH), [zones, ZONE, listeners, LISTENER | PATH]).
|
||||||
|
@ -170,20 +184,49 @@ put(KeyPath, Config) -> do_put(?CONF, KeyPath, Config).
|
||||||
|
|
||||||
-spec update(emqx_map_lib:config_key_path(), update_request()) ->
|
-spec update(emqx_map_lib:config_key_path(), update_request()) ->
|
||||||
ok | {error, term()}.
|
ok | {error, term()}.
|
||||||
update(KeyPath, UpdateReq) ->
|
update([RootName | _] = KeyPath, UpdateReq) ->
|
||||||
update(emqx_schema, KeyPath, UpdateReq).
|
update(get_schema_mod(RootName), KeyPath, UpdateReq).
|
||||||
|
|
||||||
-spec update(module(), emqx_map_lib:config_key_path(), update_request()) ->
|
-spec update(module(), emqx_map_lib:config_key_path(), update_request()) ->
|
||||||
ok | {error, term()}.
|
ok | {error, term()}.
|
||||||
update(SchemaModule, KeyPath, UpdateReq) ->
|
update(SchemaMod, KeyPath, UpdateReq) ->
|
||||||
emqx_config_handler:update_config(SchemaModule, KeyPath, {update, UpdateReq}).
|
emqx_config_handler:update_config(SchemaMod, KeyPath, {update, UpdateReq}).
|
||||||
|
|
||||||
-spec remove(emqx_map_lib:config_key_path()) -> ok | {error, term()}.
|
-spec remove(emqx_map_lib:config_key_path()) -> ok | {error, term()}.
|
||||||
remove(KeyPath) ->
|
remove([RootName | _] = KeyPath) ->
|
||||||
remove(emqx_schema, KeyPath).
|
remove(get_schema_mod(RootName), KeyPath).
|
||||||
|
|
||||||
remove(SchemaModule, KeyPath) ->
|
remove(SchemaMod, KeyPath) ->
|
||||||
emqx_config_handler:update_config(SchemaModule, KeyPath, remove).
|
emqx_config_handler:update_config(SchemaMod, KeyPath, remove).
|
||||||
|
|
||||||
|
-spec reset(emqx_map_lib:config_key_path()) -> ok | {error, term()}.
|
||||||
|
reset([RootName | _] = KeyPath) ->
|
||||||
|
case get_default_value(KeyPath) of
|
||||||
|
{ok, Default} ->
|
||||||
|
emqx_config_handler:update_config(get_schema_mod(RootName), KeyPath,
|
||||||
|
{update, Default});
|
||||||
|
{error, _} = Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec get_default_value(emqx_map_lib:config_key_path()) -> ok | {error, term()}.
|
||||||
|
get_default_value([RootName | _] = KeyPath) ->
|
||||||
|
BinKeyPath = [bin(Key) || Key <- KeyPath],
|
||||||
|
case find_raw([RootName]) of
|
||||||
|
{ok, RawConf} ->
|
||||||
|
RawConf1 = emqx_map_lib:deep_remove(BinKeyPath, RawConf),
|
||||||
|
SchemaMod = get_schema_mod(RootName),
|
||||||
|
try fill_defaults(SchemaMod, RawConf1) of FullConf ->
|
||||||
|
case emqx_map_lib:deep_find(BinKeyPath, FullConf) of
|
||||||
|
{not_found, _, _} -> {error, no_default_value};
|
||||||
|
{ok, Val} -> {ok, Val}
|
||||||
|
end
|
||||||
|
catch error:_ ->
|
||||||
|
{error, required_conf}
|
||||||
|
end;
|
||||||
|
{not_found, _, _} ->
|
||||||
|
{error, {rootname_not_found, RootName}}
|
||||||
|
end.
|
||||||
|
|
||||||
-spec get_raw(emqx_map_lib:config_key_path()) -> term().
|
-spec get_raw(emqx_map_lib:config_key_path()) -> term().
|
||||||
get_raw(KeyPath) -> do_get(?RAW_CONF, KeyPath).
|
get_raw(KeyPath) -> do_get(?RAW_CONF, KeyPath).
|
||||||
|
@ -208,7 +251,7 @@ put_raw(KeyPath, Config) -> do_put(?RAW_CONF, KeyPath, Config).
|
||||||
%% NOTE: The order of the files is significant, configs from files orderd
|
%% NOTE: The order of the files is significant, configs from files orderd
|
||||||
%% in the rear of the list overrides prior values.
|
%% in the rear of the list overrides prior values.
|
||||||
-spec init_load(module(), [string()] | binary() | hocon:config()) -> ok.
|
-spec init_load(module(), [string()] | binary() | hocon:config()) -> ok.
|
||||||
init_load(SchemaModule, Conf) when is_list(Conf) orelse is_binary(Conf) ->
|
init_load(SchemaMod, Conf) when is_list(Conf) orelse is_binary(Conf) ->
|
||||||
ParseOptions = #{format => richmap},
|
ParseOptions = #{format => richmap},
|
||||||
Parser = case is_binary(Conf) of
|
Parser = case is_binary(Conf) of
|
||||||
true -> fun hocon:binary/2;
|
true -> fun hocon:binary/2;
|
||||||
|
@ -216,39 +259,78 @@ init_load(SchemaModule, Conf) when is_list(Conf) orelse is_binary(Conf) ->
|
||||||
end,
|
end,
|
||||||
case Parser(Conf, ParseOptions) of
|
case Parser(Conf, ParseOptions) of
|
||||||
{ok, RawRichConf} ->
|
{ok, RawRichConf} ->
|
||||||
init_load(SchemaModule, RawRichConf);
|
init_load(SchemaMod, RawRichConf);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
logger:error(#{msg => failed_to_load_hocon_conf,
|
logger:error(#{msg => failed_to_load_hocon_conf,
|
||||||
reason => Reason
|
reason => Reason
|
||||||
}),
|
}),
|
||||||
error(failed_to_load_hocon_conf)
|
error(failed_to_load_hocon_conf)
|
||||||
end;
|
end;
|
||||||
init_load(SchemaModule, RawRichConf) when is_map(RawRichConf) ->
|
init_load(SchemaMod, RawRichConf) when is_map(RawRichConf) ->
|
||||||
%% check with richmap for line numbers in error reports (future enhancement)
|
%% check with richmap for line numbers in error reports (future enhancement)
|
||||||
Opts = #{return_plain => true,
|
Opts = #{return_plain => true,
|
||||||
nullable => true
|
nullable => true
|
||||||
},
|
},
|
||||||
%% this call throws exception in case of check failure
|
%% this call throws exception in case of check failure
|
||||||
{_AppEnvs, CheckedConf} = hocon_schema:map_translate(SchemaModule, RawRichConf, Opts),
|
{_AppEnvs, CheckedConf} = hocon_schema:map_translate(SchemaMod, RawRichConf, Opts),
|
||||||
ok = save_to_config_map(emqx_map_lib:unsafe_atom_key_map(CheckedConf),
|
ok = save_schema_mod(SchemaMod),
|
||||||
hocon_schema:richmap_to_map(RawRichConf)).
|
ok = save_to_config_map(emqx_map_lib:unsafe_atom_key_map(normalize_conf(CheckedConf)),
|
||||||
|
normalize_conf(hocon_schema:richmap_to_map(RawRichConf))).
|
||||||
|
|
||||||
|
normalize_conf(Conf) ->
|
||||||
|
maps:with(get_root_names(), Conf).
|
||||||
|
|
||||||
-spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf}
|
-spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf}
|
||||||
when AppEnvs :: app_envs(), CheckedConf :: config().
|
when AppEnvs :: app_envs(), CheckedConf :: config().
|
||||||
check_config(SchemaModule, RawConf) ->
|
check_config(SchemaMod, RawConf) ->
|
||||||
Opts = #{return_plain => true,
|
Opts = #{return_plain => true,
|
||||||
nullable => true,
|
nullable => true,
|
||||||
format => map
|
format => map
|
||||||
},
|
},
|
||||||
{AppEnvs, CheckedConf} =
|
{AppEnvs, CheckedConf} =
|
||||||
hocon_schema:map_translate(SchemaModule, RawConf, Opts),
|
hocon_schema:map_translate(SchemaMod, RawConf, Opts),
|
||||||
Conf = maps:with(maps:keys(RawConf), CheckedConf),
|
Conf = maps:with(maps:keys(RawConf), CheckedConf),
|
||||||
{AppEnvs, emqx_map_lib:unsafe_atom_key_map(Conf)}.
|
{AppEnvs, emqx_map_lib:unsafe_atom_key_map(Conf)}.
|
||||||
|
|
||||||
|
-spec fill_defaults(raw_config()) -> map().
|
||||||
|
fill_defaults(RawConf) ->
|
||||||
|
RootNames = get_root_names(),
|
||||||
|
maps:fold(fun(Key, Conf, Acc) ->
|
||||||
|
SubMap = #{Key => Conf},
|
||||||
|
WithDefaults = case lists:member(Key, RootNames) of
|
||||||
|
true -> fill_defaults(get_schema_mod(Key), SubMap);
|
||||||
|
false -> SubMap
|
||||||
|
end,
|
||||||
|
maps:merge(Acc, WithDefaults)
|
||||||
|
end, #{}, RawConf).
|
||||||
|
|
||||||
|
-spec fill_defaults(module(), raw_config()) -> map().
|
||||||
|
fill_defaults(SchemaMod, RawConf) ->
|
||||||
|
hocon_schema:check_plain(SchemaMod, RawConf,
|
||||||
|
#{nullable => true, no_conversion => true}, [str(K) || K <- maps:keys(RawConf)]).
|
||||||
|
|
||||||
-spec read_override_conf() -> raw_config().
|
-spec read_override_conf() -> raw_config().
|
||||||
read_override_conf() ->
|
read_override_conf() ->
|
||||||
load_hocon_file(emqx_override_conf_name(), map).
|
load_hocon_file(emqx_override_conf_name(), map).
|
||||||
|
|
||||||
|
-spec save_schema_mod(module()) -> ok.
|
||||||
|
save_schema_mod(SchemaMod) ->
|
||||||
|
OldMods = get_schema_mod(),
|
||||||
|
NewMods = maps:from_list([{bin(RootName), SchemaMod} || RootName <- SchemaMod:structs()]),
|
||||||
|
persistent_term:put(?PERSIS_MOD_ROOTNAMES, maps:merge(OldMods, NewMods)).
|
||||||
|
|
||||||
|
-spec get_schema_mod() -> #{binary() => atom()}.
|
||||||
|
get_schema_mod() ->
|
||||||
|
persistent_term:get(?PERSIS_MOD_ROOTNAMES, #{}).
|
||||||
|
|
||||||
|
-spec get_schema_mod(atom() | binary()) -> [module()].
|
||||||
|
get_schema_mod(RootName) ->
|
||||||
|
maps:get(bin(RootName), get_schema_mod()).
|
||||||
|
|
||||||
|
-spec get_root_names() -> [binary()].
|
||||||
|
get_root_names() ->
|
||||||
|
maps:keys(get_schema_mod()).
|
||||||
|
|
||||||
-spec save_configs(app_envs(), config(), raw_config(), raw_config()) -> ok | {error, term()}.
|
-spec save_configs(app_envs(), config(), raw_config(), raw_config()) -> ok | {error, term()}.
|
||||||
save_configs(_AppEnvs, Conf, RawConf, OverrideConf) ->
|
save_configs(_AppEnvs, Conf, RawConf, OverrideConf) ->
|
||||||
%% We may need also support hot config update for the apps that use application envs.
|
%% We may need also support hot config update for the apps that use application envs.
|
||||||
|
@ -338,10 +420,20 @@ do_deep_put(?RAW_CONF, KeyPath, Map, Value) ->
|
||||||
|
|
||||||
atom(Bin) when is_binary(Bin) ->
|
atom(Bin) when is_binary(Bin) ->
|
||||||
binary_to_existing_atom(Bin, latin1);
|
binary_to_existing_atom(Bin, latin1);
|
||||||
|
atom(Str) when is_list(Str) ->
|
||||||
|
list_to_existing_atom(Str);
|
||||||
atom(Atom) when is_atom(Atom) ->
|
atom(Atom) when is_atom(Atom) ->
|
||||||
Atom.
|
Atom.
|
||||||
|
|
||||||
|
str(Bin) when is_binary(Bin) ->
|
||||||
|
binary_to_list(Bin);
|
||||||
|
str(Str) when is_list(Str) ->
|
||||||
|
Str;
|
||||||
|
str(Atom) when is_atom(Atom) ->
|
||||||
|
atom_to_list(Atom).
|
||||||
|
|
||||||
bin(Bin) when is_binary(Bin) -> Bin;
|
bin(Bin) when is_binary(Bin) -> Bin;
|
||||||
|
bin(Str) when is_list(Str) -> list_to_binary(Str);
|
||||||
bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||||
|
|
||||||
conf_key(?CONF, RootName) ->
|
conf_key(?CONF, RootName) ->
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
, deep_merge/2
|
, deep_merge/2
|
||||||
, safe_atom_key_map/1
|
, safe_atom_key_map/1
|
||||||
, unsafe_atom_key_map/1
|
, unsafe_atom_key_map/1
|
||||||
|
, jsonable_map/1
|
||||||
|
, deep_convert/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export_type([config_key/0, config_key_path/0]).
|
-export_type([config_key/0, config_key_path/0]).
|
||||||
|
@ -97,21 +99,42 @@ deep_merge(BaseMap, NewMap) ->
|
||||||
end, #{}, BaseMap),
|
end, #{}, BaseMap),
|
||||||
maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
|
maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
|
||||||
|
|
||||||
|
-spec deep_convert(map(), fun((K::any(), V::any()) -> {K1::any(), V1::any()})) -> map().
|
||||||
|
deep_convert(Map, ConvFun) when is_map(Map) ->
|
||||||
|
maps:fold(fun(K, V, Acc) ->
|
||||||
|
{K1, V1} = ConvFun(K, deep_convert(V, ConvFun)),
|
||||||
|
Acc#{K1 => V1}
|
||||||
|
end, #{}, Map);
|
||||||
|
deep_convert(ListV, ConvFun) when is_list(ListV) ->
|
||||||
|
[deep_convert(V, ConvFun) || V <- ListV];
|
||||||
|
deep_convert(Val, _) -> Val.
|
||||||
|
|
||||||
|
-spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
|
||||||
unsafe_atom_key_map(Map) ->
|
unsafe_atom_key_map(Map) ->
|
||||||
covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
|
covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
|
||||||
|
|
||||||
|
-spec safe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
|
||||||
safe_atom_key_map(Map) ->
|
safe_atom_key_map(Map) ->
|
||||||
covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
|
covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
|
||||||
|
|
||||||
|
-spec jsonable_map(map()) -> map().
|
||||||
|
jsonable_map(Map) ->
|
||||||
|
deep_convert(Map, fun(K, V) ->
|
||||||
|
{jsonable_value(K), jsonable_value(V)}
|
||||||
|
end).
|
||||||
|
|
||||||
|
jsonable_value([]) -> [];
|
||||||
|
jsonable_value(Val) when is_list(Val) ->
|
||||||
|
case io_lib:printable_unicode_list(Val) of
|
||||||
|
true -> unicode:characters_to_binary(Val);
|
||||||
|
false -> Val
|
||||||
|
end;
|
||||||
|
jsonable_value(Val) ->
|
||||||
|
Val.
|
||||||
|
|
||||||
%%---------------------------------------------------------------------------
|
%%---------------------------------------------------------------------------
|
||||||
covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) ->
|
covert_keys_to_atom(BinKeyMap, Conv) ->
|
||||||
maps:fold(
|
deep_convert(BinKeyMap, fun
|
||||||
fun(K, V, Acc) when is_binary(K) ->
|
(K, V) when is_atom(K) -> {K, V};
|
||||||
Acc#{Conv(K) => covert_keys_to_atom(V, Conv)};
|
(K, V) when is_binary(K) -> {Conv(K), V}
|
||||||
(K, V, Acc) when is_atom(K) ->
|
end).
|
||||||
%% richmap keys
|
|
||||||
Acc#{K => covert_keys_to_atom(V, Conv)}
|
|
||||||
end, #{}, BinKeyMap);
|
|
||||||
covert_keys_to_atom(ListV, Conv) when is_list(ListV) ->
|
|
||||||
[covert_keys_to_atom(V, Conv) || V <- ListV];
|
|
||||||
covert_keys_to_atom(Val, _) -> Val.
|
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
, emqx_dashboard_schema
|
, emqx_dashboard_schema
|
||||||
, emqx_gateway_schema
|
, emqx_gateway_schema
|
||||||
, emqx_prometheus_schema
|
, emqx_prometheus_schema
|
||||||
|
, emqx_rule_engine_schema
|
||||||
, emqx_exhook_schema
|
, emqx_exhook_schema
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
, {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1
|
, {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1
|
||||||
, {getopt, "1.0.2"}
|
, {getopt, "1.0.2"}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
||||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.11.0"}}}
|
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.11.1"}}}
|
||||||
, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.4.0"}}}
|
, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.4.0"}}}
|
||||||
, {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.1.0"}}}
|
, {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.1.0"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
Loading…
Reference in New Issue