Skip to content

Commit 3506ca4

Browse files
michaelklishinmergify-bot
authored andcommitted
Refactor definition import to allow for arbitrary sources
The classic local filesystem source is still supported using the same traditional configuration key, load_definitions. Configuration schema follows peer discovery in spirit: * definitions.import_backend configures the mechanism to use, which can be a module provided by a plugin * definitions.* keys can be defined by plugins and contain any keys a specific mechanism needs For example, the classic local filesystem source can now be configured like this: ``` ini definitions.import_backend = local_filesystem definitions.local.path = /path/to/definitions.d/definition.json ``` ``` ini definitions.import_backend = https definitions.https.url = https://hostname/path/to/definitions.json ``` HTTPS may require additional configuration keys related to TLS/x.509 peer verification. Such extra keys will be added as the need for them becomes evident. References #3249 (cherry picked from commit f3a5235)
1 parent 74f22ab commit 3506ca4

File tree

7 files changed

+391
-58
lines changed

7 files changed

+391
-58
lines changed

deps/rabbit/priv/schema/rabbit.schema

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,44 @@ end}.
121121
%% Definition import
122122
%%
123123

124-
%% Load definitions from a JSON file or directory of files. See
124+
%% Original key for definition loading from a JSON file or directory of files. See
125125
%% https://www.rabbitmq.com/management.html#load-definitions
126-
%%
127-
%% {load_definitions, "/path/to/schema.json"},
128-
%% {load_definitions, "/path/to/schemas"},
129126
{mapping, "load_definitions", "rabbit.load_definitions",
130127
[{datatype, string},
131128
{validators, ["file_accessible"]}]}.
132129

130+
%% Newer syntax for definition loading from a JSON file or directory of files. See
131+
%% https://www.rabbitmq.com/management.html#load-definitions
132+
{mapping, "definitions.local.path", "rabbit.definitions.local_path",
133+
[{datatype, string},
134+
{validators, ["file_accessible"]}]}.
135+
136+
%% Extensive mechanism for loading definitions from a remote source
137+
{mapping, "definitions.import_backend", "rabbit.definitions.import_backend", [
138+
{datatype, atom}
139+
]}.
140+
141+
{translation, "rabbit.definitions.import_backend",
142+
fun(Conf) ->
143+
case cuttlefish:conf_get("definitions.import_backend", Conf, rabbit_definitions_import_local_filesystem) of
144+
%% short aliases for known backends
145+
local_filesystem -> rabbit_definitions_import_local_filesystem;
146+
local -> rabbit_definitions_import_local_filesystem;
147+
https -> rabbit_definitions_import_https;
148+
http -> rabbit_definitions_import_https;
149+
%% accept both rabbitmq_ and rabbit_ (typical core module prefix)
150+
rabbitmq_definitions_import_local_filesystem -> rabbit_definitions_import_local_filesystem;
151+
rabbitmq_definitions_import_local_filesystem -> rabbit_definitions_import_https;
152+
%% any other value is used as is
153+
Module -> Module
154+
end
155+
end}.
156+
157+
%% Load definitions from a remote URL over HTTPS. See
158+
%% https://www.rabbitmq.com/management.html#load-definitions
159+
{mapping, "definitions.https.url", "rabbit.definitions.url",
160+
[{datatype, string}]}.
161+
133162
%%
134163
%% Security / AAA
135164
%% ==============

deps/rabbit/src/rabbit_definitions.erl

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010

1111
-export([boot/0]).
1212
%% automatic import on boot
13-
-export([maybe_load_definitions/0, maybe_load_definitions/2, maybe_load_definitions_from/2,
14-
has_configured_definitions_to_load/0]).
13+
-export([
14+
maybe_load_definitions/0,
15+
maybe_load_definitions/2,
16+
maybe_load_definitions_from/2,
17+
18+
has_configured_definitions_to_load/0
19+
]).
1520
%% import
1621
-export([import_raw/1, import_raw/2, import_parsed/1, import_parsed/2,
1722
apply_defs/2, apply_defs/3, apply_defs/4, apply_defs/5]).
@@ -25,7 +30,7 @@
2530
]).
2631
-export([decode/1, decode/2, args/1]).
2732

28-
-import(rabbit_misc, [pget/2]).
33+
-import(rabbit_misc, [pget/2, pget/3]).
2934
-import(rabbit_data_coercion, [to_binary/1]).
3035

3136
%%
@@ -59,9 +64,10 @@ boot() ->
5964
rabbit_sup:start_supervisor_child(definition_import_pool_sup, worker_pool_sup, [PoolSize, ?IMPORT_WORK_POOL]).
6065

6166
maybe_load_definitions() ->
62-
%% Note that management.load_definitions is handled in the plugin for backwards compatibility.
63-
%% This executes the "core" version of load_definitions.
64-
maybe_load_definitions(rabbit, load_definitions).
67+
%% Classic source: local file or data directory
68+
maybe_load_definitions_from_local_filesystem(rabbit, load_definitions),
69+
%% Extensible sources
70+
maybe_load_definitions_from_pluggable_source(rabbit, definitions).
6571

6672
-spec import_raw(Body :: binary() | iolist()) -> ok | {error, term()}.
6773
import_raw(Body) ->
@@ -126,63 +132,76 @@ all_definitions() ->
126132
exchanges => Xs
127133
}.
128134

135+
-spec has_configured_definitions_to_load() -> boolean().
136+
has_configured_definitions_to_load() ->
137+
has_configured_definitions_to_load_via_classic_option() or has_configured_definitions_to_load_via_modern_option().
138+
139+
%% Retained for backwards compatibility, implicitly assumes the local filesystem source
140+
maybe_load_definitions(App, Key) ->
141+
maybe_load_definitions_from_local_filesystem(App, Key).
142+
143+
maybe_load_definitions_from(IsDir, Path) ->
144+
rabbit_definitions_import_local_filesystem:load(IsDir, Path).
145+
129146
%%
130147
%% Implementation
131148
%%
132149

133-
-spec has_configured_definitions_to_load() -> boolean().
134-
has_configured_definitions_to_load() ->
150+
-spec has_configured_definitions_to_load_via_modern_option() -> boolean().
151+
has_configured_definitions_to_load_via_modern_option() ->
152+
case application:get_env(rabbit, definitions) of
153+
undefined -> false;
154+
{ok, none} -> false;
155+
{ok, []} -> false;
156+
{ok, _Options} -> true
157+
end.
158+
159+
has_configured_definitions_to_load_via_classic_option() ->
135160
case application:get_env(rabbit, load_definitions) of
136161
undefined -> false;
137162
{ok, none} -> false;
138163
{ok, _Path} -> true
139164
end.
140165

141-
maybe_load_definitions(App, Key) ->
166+
maybe_load_definitions_from_local_filesystem(App, Key) ->
142167
case application:get_env(App, Key) of
143-
undefined ->
144-
rabbit_log:debug("No definition file configured to import via load_definitions"),
145-
ok;
146-
{ok, none} ->
147-
rabbit_log:debug("No definition file configured to import via load_definitions"),
148-
ok;
149-
{ok, FileOrDir} ->
150-
rabbit_log:debug("Will import definitions file from load_definitions"),
151-
IsDir = filelib:is_dir(FileOrDir),
152-
maybe_load_definitions_from(IsDir, FileOrDir)
168+
undefined -> ok;
169+
{ok, none} -> ok;
170+
{ok, Path} ->
171+
IsDir = filelib:is_dir(Path),
172+
rabbit_definitions_import_local_filesystem:load(IsDir, Path)
153173
end.
154174

155-
maybe_load_definitions_from(true, Dir) ->
156-
rabbit_log:info("Applying definitions from directory ~s", [Dir]),
157-
load_definitions_from_files(file:list_dir(Dir), Dir);
158-
maybe_load_definitions_from(false, File) ->
159-
load_definitions_from_file(File).
160-
161-
load_definitions_from_files({ok, Filenames0}, Dir) ->
162-
Filenames1 = lists:sort(Filenames0),
163-
Filenames2 = [filename:join(Dir, F) || F <- Filenames1],
164-
load_definitions_from_filenames(Filenames2);
165-
load_definitions_from_files({error, E}, Dir) ->
166-
rabbit_log:error("Could not read definitions from directory ~s, Error: ~p", [Dir, E]),
167-
{error, {could_not_read_defs, E}}.
168-
169-
load_definitions_from_filenames([]) ->
170-
ok;
171-
load_definitions_from_filenames([File|Rest]) ->
172-
case load_definitions_from_file(File) of
173-
ok -> load_definitions_from_filenames(Rest);
174-
{error, E} -> {error, {failed_to_import_definitions, File, E}}
175+
maybe_load_definitions_from_pluggable_source(App, Key) ->
176+
case application:get_env(App, Key) of
177+
undefined -> ok;
178+
{ok, none} -> ok;
179+
{ok, []} -> ok;
180+
{ok, Proplist} ->
181+
case pget(import_backend, Proplist, undefined) of
182+
undefined ->
183+
{error, "definition import source is configured but definitions.import_backend is not set"};
184+
ModOrAlias ->
185+
Mod = normalize_backend_module(ModOrAlias),
186+
rabbit_log:debug("Will use module ~s to import definitions", [Mod]),
187+
Mod:load(Proplist)
188+
end
175189
end.
176190

177-
load_definitions_from_file(File) ->
178-
case file:read_file(File) of
179-
{ok, Body} ->
180-
rabbit_log:info("Applying definitions from file at '~s'", [File]),
181-
import_raw(Body);
182-
{error, E} ->
183-
rabbit_log:error("Could not read definitions from file at '~s', error: ~p", [File, E]),
184-
{error, {could_not_read_defs, {File, E}}}
185-
end.
191+
normalize_backend_module(local_filesystem) ->
192+
rabbit_definitions_import_local_filesystem;
193+
normalize_backend_module(local) ->
194+
rabbit_definitions_import_local_filesystem;
195+
normalize_backend_module(https) ->
196+
rabbit_definitions_import_https;
197+
normalize_backend_module(http) ->
198+
rabbit_definitions_import_https;
199+
normalize_backend_module(rabbitmq_definitions_import_local_filesystem) ->
200+
rabbit_definitions_import_local_filesystem;
201+
normalize_backend_module(rabbitmq_definitions_import_https) ->
202+
rabbit_definitions_import_https;
203+
normalize_backend_module(Other) ->
204+
Other.
186205

187206
decode(Keys, Body) ->
188207
case decode(Body) of
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
%% This Source Code Form is subject to the terms of the Mozilla Public
2+
%% License, v. 2.0. If a copy of the MPL was not distributed with this
3+
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
%%
5+
%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved.
6+
%%
7+
8+
-module(rabbit_definitions_import_https).
9+
-include_lib("rabbit_common/include/rabbit.hrl").
10+
11+
-export([
12+
is_enabled/0,
13+
load/1
14+
]).
15+
16+
17+
18+
-import(rabbit_misc, [pget/2]).
19+
-import(rabbit_data_coercion, [to_binary/1]).
20+
-import(rabbit_definitions, [import_raw/1]).
21+
22+
%%
23+
%% API
24+
%%
25+
26+
-spec is_enabled() -> boolean().
27+
is_enabled() ->
28+
case application:get_env(rabbit, definitions) of
29+
undefined -> false;
30+
{ok, none} -> false;
31+
{ok, []} -> false;
32+
{ok, Proplist} ->
33+
case proplists:get_value(import_backend, Proplist, undefined) of
34+
undefined -> false;
35+
?MODULE -> true;
36+
_ -> false
37+
end
38+
end.
39+
40+
load(Proplist) ->
41+
URL = pget(url, Proplist),
42+
%% TODO
43+
HTTPOptions = [],
44+
load_from_url(URL, HTTPOptions).
45+
46+
47+
%%
48+
%% Implementation
49+
%%
50+
51+
load_from_url(URL, HTTPOptions0) ->
52+
inets:start(),
53+
Options = [
54+
{body_format, binary}
55+
],
56+
HTTPOptions = HTTPOptions0 ++ [
57+
{autoredirect, true}
58+
],
59+
rabbit_log:info("Applying definitions from remote URL"),
60+
case httpc:request(get, {URL, []}, lists:usort(HTTPOptions), Options) of
61+
%% 2XX
62+
{ok, {{_, Code, _}, _Headers, Body}} when Code div 100 == 2 ->
63+
rabbit_log:debug("Requested definitions from remote URL '~s', response code: ~b", [URL, Code]),
64+
rabbit_log:debug("Requested definitions from remote URL '~s', body: ~p", [URL, Body]),
65+
import_raw(Body);
66+
{ok, {{_, Code, _}, _Headers, _Body}} when Code >= 400 ->
67+
rabbit_log:debug("Requested definitions from remote URL '~s', response code: ~b", [URL, Code]),
68+
{error, {could_not_read_defs, {URL, rabbit_misc:format("URL request failed with response code ~b", [Code])}}};
69+
{error, Reason} ->
70+
rabbit_log:error("Requested definitions from remote URL '~s', error: ~p", [URL, Reason]),
71+
{error, {could_not_read_defs, {URL, Reason}}}
72+
end.

0 commit comments

Comments
 (0)