1 |
|
-module(mongoose_config). |
2 |
|
|
3 |
|
%% API |
4 |
|
-export([start/0, |
5 |
|
stop/0, |
6 |
|
get_config_path/0, |
7 |
|
lookup_opt/1, |
8 |
|
get_opt/2, |
9 |
|
get_opt/1]). |
10 |
|
|
11 |
|
%% Test API, do not use outside of test suites, options set here are not cleaned up by stop/0 |
12 |
|
-export([set_opt/2, |
13 |
|
unset_opt/1]). |
14 |
|
|
15 |
|
%% Shell utilities intended for debugging and system inspection |
16 |
|
-export([config_state/0, |
17 |
|
config_states/0]). |
18 |
|
|
19 |
|
-ignore_xref([set_opt/2, unset_opt/1, config_state/0, config_states/0]). |
20 |
|
|
21 |
|
-include("mongoose.hrl"). |
22 |
|
|
23 |
|
-type key() :: atom() | host_type_key(). |
24 |
|
-type host_type_key() :: {atom(), mongooseim:host_type_or_global()}. |
25 |
|
|
26 |
|
%% Top-level key() followed by inner_key() for each of the nested maps |
27 |
|
-type key_path() :: [key() | inner_key()]. |
28 |
|
-type inner_key() :: atom() | binary() | integer() | string() | tuple(). |
29 |
|
|
30 |
|
-type value() :: atom() | binary() | integer() | string() | [value()] | tuple() | map(). |
31 |
|
|
32 |
|
-export_type([host_type_key/0, key/0, key_path/0, value/0]). |
33 |
|
|
34 |
|
-spec start() -> ok. |
35 |
|
start() -> |
36 |
80 |
Path = get_config_path(), |
37 |
80 |
State = mongoose_config_parser:parse_file(Path), |
38 |
80 |
persistent_term:put(mongoose_config_state, State), |
39 |
80 |
set_opts(State). |
40 |
|
|
41 |
|
-spec stop() -> ok | {error, not_started}. |
42 |
|
stop() -> |
43 |
80 |
try persistent_term:get(mongoose_config_state) of |
44 |
|
State -> |
45 |
80 |
unset_opts(State), |
46 |
80 |
persistent_term:erase(mongoose_config_state), |
47 |
80 |
ok |
48 |
|
catch |
49 |
|
_:_ -> |
50 |
:-( |
{error, not_started} |
51 |
|
end. |
52 |
|
|
53 |
|
%% @doc Get the filename of the ejabberd configuration file. |
54 |
|
%% The filename can be specified with: erl -config "/path/to/mongooseim.toml". |
55 |
|
%% It can also be specified with the environment variable EJABBERD_CONFIG_PATH. |
56 |
|
%% If not specified, the default value 'mongooseim.toml' is assumed. |
57 |
|
-spec get_config_path() -> string(). |
58 |
|
get_config_path() -> |
59 |
119 |
DefaultPath = case os:getenv("EJABBERD_CONFIG_PATH") of |
60 |
:-( |
false -> ?CONFIG_PATH; |
61 |
119 |
Path -> Path |
62 |
|
end, |
63 |
119 |
application:get_env(mongooseim, config, DefaultPath). |
64 |
|
|
65 |
|
-spec set_opts(mongoose_config_parser:state()) -> ok. |
66 |
|
set_opts(State) -> |
67 |
80 |
Opts = mongoose_config_parser:get_opts(State), |
68 |
80 |
lists:foreach(fun({Key, Value}) -> set_opt(Key, Value) end, Opts). |
69 |
|
|
70 |
|
-spec unset_opts(mongoose_config_parser:state()) -> ok. |
71 |
|
unset_opts(State) -> |
72 |
80 |
Opts = mongoose_config_parser:get_opts(State), |
73 |
80 |
lists:foreach(fun unset_opt/1, proplists:get_keys(Opts)). |
74 |
|
|
75 |
|
-spec set_opt(key() | key_path(), value()) -> ok. |
76 |
|
set_opt([Key], Value) -> |
77 |
:-( |
set_opt(Key, Value); |
78 |
|
set_opt([Key | Rest], Value) -> |
79 |
5 |
set_opt(Key, set_nested_opt(get_opt(Key), Rest, Value)); |
80 |
|
set_opt(Key, Value) -> |
81 |
5535 |
persistent_term:put({?MODULE, Key}, Value). |
82 |
|
|
83 |
|
-spec unset_opt(key() | key_path()) -> ok. |
84 |
|
unset_opt([Key]) -> |
85 |
:-( |
unset_opt(Key); |
86 |
|
unset_opt([Key | Rest]) -> |
87 |
1 |
set_opt(Key, unset_nested_opt(get_opt(Key), Rest)); |
88 |
|
unset_opt(Key) -> |
89 |
4373 |
persistent_term:erase({?MODULE, Key}), |
90 |
4373 |
ok. |
91 |
|
|
92 |
|
%% @doc Use instead of get_opt(Key, undefined) |
93 |
|
-spec lookup_opt(key() | key_path()) -> {ok, value()} | {error, not_found}. |
94 |
|
lookup_opt(Key) -> |
95 |
36874 |
try get_opt(Key) of |
96 |
28328 |
Value -> {ok, Value} |
97 |
|
catch |
98 |
3384 |
error:badarg -> {error, not_found}; % missing persistent term |
99 |
5162 |
error:{badkey, _} -> {error, not_found} % missing map key |
100 |
|
end. |
101 |
|
|
102 |
|
% @doc Returns Default if the option does not exist |
103 |
|
-spec get_opt(key() | key_path(), value()) -> value(). |
104 |
|
get_opt(Key, Default) -> |
105 |
34684 |
try |
106 |
34684 |
get_opt(Key) |
107 |
|
catch |
108 |
427 |
error:badarg -> Default; % missing persistent term |
109 |
10545 |
error:{badkey, _} -> Default % missing map key |
110 |
|
end. |
111 |
|
|
112 |
|
%% @doc Fails if the option does not exist |
113 |
|
-spec get_opt(key() | key_path()) -> value(). |
114 |
|
get_opt([Key | Rest]) -> |
115 |
203286 |
Config = persistent_term:get({?MODULE, Key}), |
116 |
202772 |
lists:foldl(fun maps:get/2, Config, Rest); |
117 |
|
get_opt(Key) -> |
118 |
397831 |
persistent_term:get({?MODULE, Key}). |
119 |
|
|
120 |
|
-spec config_state() -> mongoose_config_parser:state(). |
121 |
|
config_state() -> |
122 |
:-( |
persistent_term:get(mongoose_config_state). |
123 |
|
|
124 |
|
-spec config_states() -> [mongoose_config_parser:state()]. |
125 |
|
config_states() -> |
126 |
:-( |
config_states(mongoose_cluster:all_cluster_nodes()). |
127 |
|
|
128 |
|
-spec config_states([node()]) -> [mongoose_config_parser:state()]. |
129 |
|
%% @doc Returns config states from all nodes in cluster |
130 |
|
%% State from the local node comes as head of a list |
131 |
|
config_states(Nodes) -> |
132 |
:-( |
{States, FailedNodes} = rpc:multicall(Nodes, ?MODULE, config_state, [], 30000), |
133 |
:-( |
case FailedNodes of |
134 |
|
[] -> |
135 |
:-( |
States; |
136 |
|
[_|_] -> |
137 |
:-( |
erlang:error(#{issue => config_state_failed, |
138 |
|
cluster_nodes => Nodes, |
139 |
|
failed_nodes => FailedNodes}) |
140 |
|
end. |
141 |
|
|
142 |
|
%% Internal functions |
143 |
|
|
144 |
|
set_nested_opt(M, [Key], Value) -> |
145 |
5 |
M#{Key => Value}; |
146 |
|
set_nested_opt(M, [Key | Path], Value) -> |
147 |
:-( |
M#{Key => set_nested_opt(maps:get(Key, M), Path, Value)}. |
148 |
|
|
149 |
|
unset_nested_opt(M, [Key]) -> |
150 |
1 |
maps:remove(Key, M); |
151 |
|
unset_nested_opt(M, [Key | Path]) -> |
152 |
:-( |
M#{Key := unset_nested_opt(maps:get(Key, M), Path)}. |