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_local_filesystem).
9-include_lib("rabbit_common/include/rabbit.hrl").
10
11-export([
12    is_enabled/0,
13    %% definition source options
14    load/1,
15    %% classic arguments specific to this source
16    load/2,
17    location/0
18]).
19
20
21
22-import(rabbit_misc, [pget/2, pget/3]).
23-import(rabbit_data_coercion, [to_binary/1]).
24-import(rabbit_definitions, [import_raw/1]).
25
26%%
27%% API
28%%
29
30-spec is_enabled() -> boolean().
31is_enabled() ->
32    is_enabled_via_classic_option() or is_enabled_via_modern_option().
33
34load(Proplist) when is_list(Proplist) ->
35    case pget(local_path, Proplist, undefined) of
36        undefined -> {error, "local definition file path is not configured: local_path is not set"};
37        Path      ->
38            rabbit_log:debug("Asked to import definitions from a local file or directory at '~s'", [Path]),
39            case file:read_file_info(Path) of
40                {ok, FileInfo} ->
41                    %% same check is used by Cuttlefish validation, this is to be extra defensive
42                    IsReadable = (element(4, FileInfo) == read) or (element(4, FileInfo) == read_write),
43                    case IsReadable of
44                        true ->
45                            load_from_single_file(Path);
46                        false ->
47                            Msg = rabbit_misc:format("local definition file '~s' does not exist or cannot be read by the node", [Path]),
48                            {error, Msg}
49                    end;
50                _ ->
51                    Msg = rabbit_misc:format("local definition file '~s' does not exist or cannot be read by the node", [Path]),
52                    {error, {could_not_read_defs, Msg}}
53            end
54    end.
55
56load(IsDir, Path) ->
57    load_from_local_path(IsDir, Path).
58
59location() ->
60    case location_from_classic_option() of
61        undefined -> location_from_modern_option();
62        Value     -> Value
63    end.
64
65load_from_local_path(true, Dir) ->
66    rabbit_log:info("Applying definitions from directory ~s", [Dir]),
67    load_from_files(file:list_dir(Dir), Dir);
68load_from_local_path(false, File) ->
69    load_from_single_file(File).
70
71%%
72%% Implementation
73%%
74
75-spec is_enabled_via_classic_option() -> boolean().
76is_enabled_via_classic_option() ->
77    %% Classic way of defining a local filesystem definition source
78    case application:get_env(rabbit, load_definitions) of
79        undefined   -> false;
80        {ok, none}  -> false;
81        {ok, _Path} -> true
82    end.
83
84-spec is_enabled_via_modern_option() -> boolean().
85is_enabled_via_modern_option() ->
86    %% Modern way of defining a local filesystem definition source
87    case application:get_env(rabbit, definitions) of
88        undefined   -> false;
89        {ok, none}  -> false;
90        {ok, []}    -> false;
91        {ok, Proplist} ->
92            case pget(import_backend, Proplist, undefined) of
93                undefined -> false;
94                ?MODULE   -> true;
95                _         -> false
96            end
97    end.
98
99location_from_classic_option() ->
100    case application:get_env(rabbit, load_definitions) of
101        undefined  -> undefined;
102        {ok, none} -> undefined;
103        {ok, Path} -> Path
104    end.
105
106location_from_modern_option() ->
107    case application:get_env(rabbit, definitions) of
108        undefined  -> undefined;
109        {ok, none} -> undefined;
110        {ok, Proplist} ->
111            pget(local_path, Proplist)
112    end.
113
114
115load_from_files({ok, Filenames0}, Dir) ->
116    Filenames1 = lists:sort(Filenames0),
117    Filenames2 = [filename:join(Dir, F) || F <- Filenames1],
118    load_from_multiple_files(Filenames2);
119load_from_files({error, E}, Dir) ->
120    rabbit_log:error("Could not read definitions from directory ~s, Error: ~p", [Dir, E]),
121    {error, {could_not_read_defs, E}}.
122
123load_from_multiple_files([]) ->
124    ok;
125load_from_multiple_files([File|Rest]) ->
126    case load_from_single_file(File) of
127        ok         -> load_from_multiple_files(Rest);
128        {error, E} -> {error, {failed_to_import_definitions, File, E}}
129    end.
130
131load_from_single_file(Path) ->
132    rabbit_log:debug("Will try to load definitions from a local file or directory at '~s'", [Path]),
133    case file:read_file(Path) of
134        {ok, Body} ->
135            rabbit_log:info("Applying definitions from file at '~s'", [Path]),
136            import_raw(Body);
137        {error, E} ->
138            rabbit_log:error("Could not read definitions from file at '~s', error: ~p", [Path, E]),
139            {error, {could_not_read_defs, {Path, E}}}
140    end.
141