Skip to content

Commit 6ea25e2

Browse files
authored
Merge pull request #4350 from badlop/define_keyword
New define_keyword option
2 parents ad8e325 + 888c335 commit 6ea25e2

16 files changed

+678
-52
lines changed

src/econf.erl

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,10 @@ sip_uri() ->
546546
-spec host() -> yconf:validator(binary()).
547547
host() ->
548548
fun(Domain) ->
549-
Host = ejabberd_config:get_myname(),
550549
Hosts = ejabberd_config:get_option(hosts),
551-
Domain1 = (binary())(Domain),
552-
Domain2 = misc:expand_keyword(<<"@HOST@">>, Domain1, Host),
553-
Domain3 = (domain())(Domain2),
554-
case lists:member(Domain3, Hosts) of
555-
true -> fail({route_conflict, Domain3});
556-
false -> Domain3
550+
case lists:member(Domain, Hosts) of
551+
true -> fail({route_conflict, Domain});
552+
false -> Domain
557553
end
558554
end.
559555

src/ejabberd_config.erl

Lines changed: 122 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
-export([dump/0, dump/1, convert_to_yaml/1, convert_to_yaml/2]).
3939
-export([callback_modules/1]).
4040
-export([set_option/2]).
41+
-export([get_defined_keywords/1, get_predefined_keywords/1, replace_keywords/2, replace_keywords/3]).
4142

4243
%% Deprecated functions
4344
-export([get_option/2]).
@@ -342,12 +343,20 @@ env_binary_to_list(Application, Parameter) ->
342343
Other
343344
end.
344345

346+
%% ejabberd_options calls this function when parsing options inside host_config
345347
-spec validators([atom()]) -> {econf:validators(), [atom()]}.
346348
validators(Disallowed) ->
349+
Host = global,
350+
DefinedKeywords = get_defined_keywords(Host),
351+
validators(Disallowed, DefinedKeywords).
352+
353+
%% validate/1 calls this function when parsing toplevel options
354+
-spec validators([atom()], [any()]) -> {econf:validators(), [atom()]}.
355+
validators(Disallowed, DK) ->
347356
Modules = callback_modules(all),
348357
Validators = lists:foldl(
349358
fun(M, Vs) ->
350-
maps:merge(Vs, validators(M, Disallowed))
359+
maps:merge(Vs, validators(M, Disallowed, DK))
351360
end, #{}, Modules),
352361
Required = lists:flatmap(
353362
fun(M) ->
@@ -404,6 +413,93 @@ format_error({error, {exception, Class, Reason, St}}) ->
404413
"file attached and the following stacktrace included:~n** ~ts",
405414
[misc:format_exception(2, Class, Reason, St)])).
406415

416+
%% @format-begin
417+
418+
replace_keywords(Host, Value) ->
419+
Keywords = get_defined_keywords(Host) ++ get_predefined_keywords(Host),
420+
replace_keywords(Host, Value, Keywords).
421+
422+
replace_keywords(Host, List, Keywords) when is_list(List) ->
423+
[replace_keywords(Host, Element, Keywords) || Element <- List];
424+
replace_keywords(_Host, Atom, Keywords) when is_atom(Atom) ->
425+
Str = atom_to_list(Atom),
426+
case Str == string:uppercase(Str) of
427+
false ->
428+
Atom;
429+
true ->
430+
MacroName = iolist_to_binary(Str),
431+
case proplists:get_value(MacroName, Keywords) of
432+
undefined ->
433+
Atom;
434+
Replacement ->
435+
Replacement
436+
end
437+
end;
438+
replace_keywords(_Host, Binary, Keywords) when is_binary(Binary) ->
439+
lists:foldl(fun ({Key, Replacement}, V) when is_binary(Replacement) ->
440+
misc:expand_keyword(<<"@", Key/binary, "@">>, V, Replacement);
441+
({_, _}, V) ->
442+
V
443+
end,
444+
Binary,
445+
Keywords);
446+
replace_keywords(Host, {Element1, Element2}, Keywords) ->
447+
{Element1, replace_keywords(Host, Element2, Keywords)};
448+
replace_keywords(_Host, Value, _DK) ->
449+
Value.
450+
451+
get_defined_keywords(Host) ->
452+
Tab = case get_tmp_config() of
453+
undefined ->
454+
ejabberd_options;
455+
T ->
456+
T
457+
end,
458+
get_defined_keywords(Tab, Host).
459+
460+
get_defined_keywords(Tab, Host) ->
461+
KeysHost =
462+
case ets:lookup(Tab, {define_keyword, Host}) of
463+
[{_, List}] ->
464+
List;
465+
_ ->
466+
[]
467+
end,
468+
KeysGlobal =
469+
case Host /= global andalso ets:lookup(Tab, {define_keyword, global}) of
470+
[{_, ListG}] ->
471+
ListG;
472+
_ ->
473+
[]
474+
end,
475+
%% Trying to get defined keywords in host_config when starting ejabberd,
476+
%% the options are not yet stored in ets
477+
KeysTemp = case not is_atom(Tab) andalso KeysHost == [] andalso KeysGlobal == [] of
478+
true ->
479+
get_defined_keywords_yaml_config(ets:lookup_element(Tab, {yaml_config, global}, 2));
480+
false ->
481+
[]
482+
end,
483+
lists:reverse(KeysTemp ++ KeysGlobal ++ KeysHost).
484+
485+
get_defined_keywords_yaml_config(Y) ->
486+
[{erlang:atom_to_binary(KwAtom, latin1), KwValue}
487+
|| {KwAtom, KwValue} <- proplists:get_value(define_keyword, Y, [])].
488+
489+
get_predefined_keywords(Host) ->
490+
HostList = case Host of
491+
global -> [];
492+
_ -> [{<<"HOST">>, Host}]
493+
end,
494+
{ok, [[Home]]} = init:get_argument(home),
495+
HostList ++
496+
[{<<"HOME">>, list_to_binary(Home)},
497+
{<<"SEMVER">>, ejabberd_option:version()},
498+
{<<"VERSION">>,
499+
misc:semver_to_xxyy(
500+
ejabberd_option:version())}].
501+
%% @format-end
502+
407503
%%%===================================================================
408504
%%% Internal functions
409505
%%%===================================================================
@@ -470,21 +566,37 @@ callback_modules(external) ->
470566
end
471567
end, beams(external));
472568
callback_modules(all) ->
473-
callback_modules(local) ++ callback_modules(external).
569+
lists_uniq(callback_modules(local) ++ callback_modules(external)).
570+
571+
-ifdef(OTP_BELOW_25).
572+
lists_uniq(List) ->
573+
lists:usort(List).
574+
-else.
575+
lists_uniq(List) ->
576+
lists:uniq(List).
577+
-endif.
474578

475-
-spec validators(module(), [atom()]) -> econf:validators().
476-
validators(Mod, Disallowed) ->
579+
-spec validators(module(), [atom()], [any()]) -> econf:validators().
580+
validators(Mod, Disallowed, DK) ->
581+
Keywords = DK ++ get_predefined_keywords(global),
477582
maps:from_list(
478583
lists:filtermap(
479584
fun(O) ->
480585
case lists:member(O, Disallowed) of
481586
true -> false;
482587
false ->
483-
{true,
484-
try {O, Mod:opt_type(O)}
588+
Type =
589+
try Mod:opt_type(O)
485590
catch _:_ ->
486-
{O, ejabberd_options:opt_type(O)}
487-
end}
591+
ejabberd_options:opt_type(O)
592+
end,
593+
TypeProcessed =
594+
econf:and_then(
595+
fun(B) ->
596+
replace_keywords(global, B, Keywords)
597+
end,
598+
Type),
599+
{true, {O, TypeProcessed}}
488600
end
489601
end, proplists:get_keys(Mod:options()))).
490602

@@ -577,12 +689,13 @@ validate(Y1) ->
577689
{ok, Y3} ->
578690
Hosts = proplists:get_value(hosts, Y3),
579691
Version = proplists:get_value(version, Y3, version()),
692+
DK = get_defined_keywords_yaml_config(Y3),
580693
create_tmp_config(),
581694
set_option(hosts, Hosts),
582695
set_option(host, hd(Hosts)),
583696
set_option(version, Version),
584697
set_option(yaml_config, Y3),
585-
{Validators, Required} = validators([]),
698+
{Validators, Required} = validators([], DK),
586699
Validator = econf:options(Validators,
587700
[{required, Required},
588701
unique]),

src/ejabberd_config_transformer.erl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ transform(Host, s2s_use_starttls, required_trusted, Acc) ->
128128
Hosts = maps:get(remove_s2s_dialback, Acc, []),
129129
Acc1 = maps:put(remove_s2s_dialback, [Host|Hosts], Acc),
130130
{{true, {s2s_use_starttls, required}}, Acc1};
131+
transform(Host, define_macro, Macro, Acc) when is_binary(Host) ->
132+
?WARNING_MSG("The option 'define_macro' is not supported inside 'host_config'. "
133+
"Consequently those macro definitions for host '~ts' are unused: ~ts",
134+
[Host, io_lib:format("~p", [Macro])]),
135+
{true, Acc};
131136
transform(_Host, _Opt, _Val, Acc) ->
132137
{true, Acc}.
133138

src/ejabberd_http.erl

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -932,12 +932,7 @@ listen_opt_type(default_host) ->
932932
listen_opt_type(custom_headers) ->
933933
econf:map(
934934
econf:binary(),
935-
econf:and_then(
936-
econf:binary(),
937-
fun(V) ->
938-
misc:expand_keyword(<<"@VERSION@">>, V,
939-
ejabberd_option:version())
940-
end)).
935+
econf:binary()).
941936

942937
listen_options() ->
943938
[{ciphers, undefined},

src/ejabberd_listener.erl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -677,13 +677,21 @@ validator(M, T) ->
677677
true ->
678678
[]
679679
end,
680+
Keywords = ejabberd_config:get_defined_keywords(global) ++ ejabberd_config:get_predefined_keywords(global),
680681
Validator = maps:from_list(
681682
lists:map(
682683
fun(Opt) ->
683-
try {Opt, M:listen_opt_type(Opt)}
684+
Type = try M:listen_opt_type(Opt)
684685
catch _:_ when M /= ?MODULE ->
685-
{Opt, listen_opt_type(Opt)}
686-
end
686+
listen_opt_type(Opt)
687+
end,
688+
TypeProcessed =
689+
econf:and_then(
690+
fun(B) ->
691+
ejabberd_config:replace_keywords(global, B, Keywords)
692+
end,
693+
Type),
694+
{Opt, TypeProcessed}
687695
end, proplists:get_keys(Options))),
688696
econf:options(
689697
Validator,

src/ejabberd_option.erl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
-export([cluster_nodes/0]).
3939
-export([default_db/0, default_db/1]).
4040
-export([default_ram_db/0, default_ram_db/1]).
41+
-export([define_keyword/0, define_keyword/1]).
4142
-export([define_macro/0]).
4243
-export([disable_sasl_mechanisms/0, disable_sasl_mechanisms/1]).
4344
-export([disable_sasl_scram_downgrade_protection/0, disable_sasl_scram_downgrade_protection/1]).
@@ -372,6 +373,13 @@ default_ram_db() ->
372373
default_ram_db(Host) ->
373374
ejabberd_config:get_option({default_ram_db, Host}).
374375

376+
-spec define_keyword() -> any().
377+
define_keyword() ->
378+
define_keyword(global).
379+
-spec define_keyword(global | binary()) -> any().
380+
define_keyword(Host) ->
381+
ejabberd_config:get_option({define_keyword, Host}).
382+
375383
-spec define_macro() -> any().
376384
define_macro() ->
377385
ejabberd_config:get_option({define_macro, global}).

src/ejabberd_options.erl

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,7 @@ opt_type(cache_missed) ->
112112
opt_type(cache_size) ->
113113
econf:pos_int(infinity);
114114
opt_type(captcha_cmd) ->
115-
econf:and_then(
116-
econf:binary(),
117-
fun(V) ->
118-
V2 = misc:expand_keyword(<<"@SEMVER@">>, V,
119-
ejabberd_option:version()),
120-
misc:expand_keyword(<<"@VERSION@">>, V2,
121-
misc:semver_to_xxyy(ejabberd_option:version()))
122-
end);
115+
econf:binary();
123116
opt_type(captcha_host) ->
124117
econf:binary();
125118
opt_type(captcha_limit) ->
@@ -138,6 +131,8 @@ opt_type(default_db) ->
138131
econf:enum([mnesia, sql]);
139132
opt_type(default_ram_db) ->
140133
econf:enum([mnesia, sql, redis]);
134+
opt_type(define_keyword) ->
135+
econf:map(econf:binary(), econf:any(), [unique]);
141136
opt_type(define_macro) ->
142137
econf:map(econf:binary(), econf:any(), [unique]);
143138
opt_type(disable_sasl_scram_downgrade_protection) ->
@@ -491,7 +486,6 @@ opt_type(jwt_auth_only_rule) ->
491486
{c2s_protocol_options, undefined | binary()} |
492487
{s2s_ciphers, undefined | binary()} |
493488
{c2s_ciphers, undefined | binary()} |
494-
{captcha_cmd, undefined | binary()} |
495489
{websocket_origin, [binary()]} |
496490
{disable_sasl_mechanisms, [binary()]} |
497491
{s2s_zlib, boolean()} |
@@ -510,6 +504,7 @@ opt_type(jwt_auth_only_rule) ->
510504
{jwt_key, jose_jwk:key() | undefined} |
511505
{append_host_config, [{binary(), any()}]} |
512506
{host_config, [{binary(), any()}]} |
507+
{define_keyword, any()} |
513508
{define_macro, any()} |
514509
{include_config_file, any()} |
515510
{atom(), any()}].
@@ -567,6 +562,7 @@ options() ->
567562
{certfiles, undefined},
568563
{cluster_backend, mnesia},
569564
{cluster_nodes, []},
565+
{define_keyword, []},
570566
{define_macro, []},
571567
{disable_sasl_scram_downgrade_protection, false},
572568
{disable_sasl_mechanisms, []},

src/ejabberd_options_doc.erl

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -534,18 +534,26 @@ doc() ->
534534
?T("A list of Erlang nodes to connect on ejabberd startup. "
535535
"This option is mostly intended for ejabberd customization "
536536
"and sophisticated setups. The default value is an empty list.")}},
537+
{define_keyword,
538+
#{value => "{NAME: Value}",
539+
desc =>
540+
?T("Allows to define configuration "
541+
"_`../configuration/file-format.md#keywords|keywords`_. "),
542+
example =>
543+
["define_keyword:",
544+
" SQL_USERNAME: \"eja.global\"",
545+
"",
546+
"host_config:",
547+
" localhost:",
548+
" define_keyword:",
549+
" SQL_USERNAME: \"eja.localhost\"",
550+
"",
551+
"sql_username: \"prefix.@SQL_USERNAME@\""]}},
537552
{define_macro,
538-
#{value => "{MacroName: MacroValue}",
539-
desc =>
540-
?T("Defines a "
541-
"_`../configuration/file-format.md#macros-in-configuration-file|macro`_. "
542-
"The value can be any valid arbitrary "
543-
"YAML value. For convenience, it's recommended to define "
544-
"a 'MacroName' in capital letters. Duplicated macros are not allowed. "
545-
"Macros are processed after additional configuration files have "
546-
"been included, so it is possible to use macros that are defined "
547-
"in configuration files included before the usage. "
548-
"It is possible to use a 'MacroValue' in the definition of another macro."),
553+
#{value => "{NAME: Value}",
554+
desc =>
555+
?T("Allows to define configuration "
556+
"_`../configuration/file-format.md#macros|macros`_. "),
549557
example =>
550558
["define_macro:",
551559
" DEBUG: debug",

src/gen_mod.erl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -551,10 +551,10 @@ validator(Host, Module, Opts) ->
551551
lists:mapfoldl(
552552
fun({Opt, Def}, {DAcc1, VAcc1}) ->
553553
{[], {DAcc1#{Opt => Def},
554-
VAcc1#{Opt => get_opt_type(Module, M, Opt)}}};
554+
VAcc1#{Opt => get_opt_type(Host, Module, M, Opt)}}};
555555
(Opt, {DAcc1, VAcc1}) ->
556556
{[Opt], {DAcc1,
557-
VAcc1#{Opt => get_opt_type(Module, M, Opt)}}}
557+
VAcc1#{Opt => get_opt_type(Host, Module, M, Opt)}}}
558558
end, {DAcc, VAcc}, DefOpts)
559559
end, {#{}, #{}}, get_defaults(Host, Module, Opts)),
560560
econf:and_then(
@@ -604,11 +604,16 @@ get_defaults(Host, Module, Opts) ->
604604
false
605605
end, DefaultOpts)].
606606

607-
-spec get_opt_type(module(), module(), atom()) -> econf:validator().
608-
get_opt_type(Mod, SubMod, Opt) ->
609-
try SubMod:mod_opt_type(Opt)
607+
-spec get_opt_type(binary(), module(), module(), atom()) -> econf:validator().
608+
get_opt_type(Host, Mod, SubMod, Opt) ->
609+
Type = try SubMod:mod_opt_type(Opt)
610610
catch _:_ -> Mod:mod_opt_type(Opt)
611-
end.
611+
end,
612+
econf:and_then(
613+
fun(B) ->
614+
ejabberd_config:replace_keywords(Host, B)
615+
end,
616+
Type).
612617

613618
-spec sort_modules(binary(), [{module(), opts()}]) -> {ok, [{module(), opts(), integer()}]}.
614619
sort_modules(Host, ModOpts) ->

0 commit comments

Comments
 (0)