emqx_plugins.erl 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2017-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_plugins).
  17. -include_lib("emqx/include/emqx.hrl").
  18. -include_lib("emqx/include/logger.hrl").
  19. -export([
  20. ensure_installed/1,
  21. ensure_uninstalled/1,
  22. ensure_enabled/1,
  23. ensure_enabled/2,
  24. ensure_disabled/1,
  25. purge/1,
  26. delete_package/1
  27. ]).
  28. -export([
  29. ensure_started/0,
  30. ensure_started/1,
  31. ensure_stopped/0,
  32. ensure_stopped/1,
  33. restart/1,
  34. list/0,
  35. describe/1,
  36. parse_name_vsn/1
  37. ]).
  38. -export([
  39. get_config/2,
  40. put_config/2
  41. ]).
  42. %% internal
  43. -export([do_ensure_started/1]).
  44. -export([
  45. install_dir/0
  46. ]).
  47. -ifdef(TEST).
  48. -compile(export_all).
  49. -compile(nowarn_export_all).
  50. -endif.
  51. -include_lib("emqx/include/emqx.hrl").
  52. -include_lib("emqx/include/logger.hrl").
  53. -include("emqx_plugins.hrl").
  54. %% "my_plugin-0.1.0"
  55. -type name_vsn() :: binary() | string().
  56. %% the parse result of the JSON info file
  57. -type plugin() :: map().
  58. -type position() :: no_move | front | rear | {before, name_vsn()} | {behind, name_vsn()}.
  59. %%--------------------------------------------------------------------
  60. %% APIs
  61. %%--------------------------------------------------------------------
  62. %% @doc Describe a plugin.
  63. -spec describe(name_vsn()) -> {ok, plugin()} | {error, any()}.
  64. describe(NameVsn) -> read_plugin(NameVsn, #{fill_readme => true}).
  65. %% @doc Install a .tar.gz package placed in install_dir.
  66. -spec ensure_installed(name_vsn()) -> ok | {error, any()}.
  67. ensure_installed(NameVsn) ->
  68. case read_plugin(NameVsn, #{}) of
  69. {ok, _} ->
  70. ok;
  71. {error, _} ->
  72. ok = purge(NameVsn),
  73. do_ensure_installed(NameVsn)
  74. end.
  75. do_ensure_installed(NameVsn) ->
  76. TarGz = pkg_file(NameVsn),
  77. case erl_tar:extract(TarGz, [{cwd, install_dir()}, compressed]) of
  78. ok ->
  79. case read_plugin(NameVsn, #{}) of
  80. {ok, _} ->
  81. ok;
  82. {error, Reason} ->
  83. ?SLOG(warning, Reason#{msg => "failed_to_read_after_install"}),
  84. _ = ensure_uninstalled(NameVsn),
  85. {error, Reason}
  86. end;
  87. {error, {_, enoent}} ->
  88. {error, #{
  89. reason => "failed_to_extract_plugin_package",
  90. path => TarGz,
  91. return => not_found
  92. }};
  93. {error, Reason} ->
  94. {error, #{
  95. reason => "bad_plugin_package",
  96. path => TarGz,
  97. return => Reason
  98. }}
  99. end.
  100. %% @doc Ensure files and directories for the given plugin are delete.
  101. %% If a plugin is running, or enabled, error is returned.
  102. -spec ensure_uninstalled(name_vsn()) -> ok | {error, any()}.
  103. ensure_uninstalled(NameVsn) ->
  104. case read_plugin(NameVsn, #{}) of
  105. {ok, #{running_status := RunningSt}} when RunningSt =/= stopped ->
  106. {error, #{
  107. reason => "bad_plugin_running_status",
  108. hint => "stop_the_plugin_first"
  109. }};
  110. {ok, #{config_status := enabled}} ->
  111. {error, #{
  112. reason => "bad_plugin_config_status",
  113. hint => "disable_the_plugin_first"
  114. }};
  115. _ ->
  116. purge(NameVsn),
  117. ensure_delete(NameVsn)
  118. end.
  119. ensure_delete(NameVsn0) ->
  120. NameVsn = bin(NameVsn0),
  121. List = configured(),
  122. put_configured(lists:filter(fun(#{name_vsn := N1}) -> bin(N1) =/= NameVsn end, List)),
  123. ok.
  124. %% @doc Ensure a plugin is enabled to the end of the plugins list.
  125. -spec ensure_enabled(name_vsn()) -> ok | {error, any()}.
  126. ensure_enabled(NameVsn) ->
  127. ensure_enabled(NameVsn, no_move).
  128. %% @doc Ensure a plugin is enabled at the given position of the plugin list.
  129. -spec ensure_enabled(name_vsn(), position()) -> ok | {error, any()}.
  130. ensure_enabled(NameVsn, Position) ->
  131. ensure_state(NameVsn, Position, true).
  132. %% @doc Ensure a plugin is disabled.
  133. -spec ensure_disabled(name_vsn()) -> ok | {error, any()}.
  134. ensure_disabled(NameVsn) ->
  135. ensure_state(NameVsn, no_move, false).
  136. ensure_state(NameVsn, Position, State) when is_binary(NameVsn) ->
  137. ensure_state(binary_to_list(NameVsn), Position, State);
  138. ensure_state(NameVsn, Position, State) ->
  139. case read_plugin(NameVsn, #{}) of
  140. {ok, _} ->
  141. Item = #{
  142. name_vsn => NameVsn,
  143. enable => State
  144. },
  145. tryit("ensure_state", fun() -> ensure_configured(Item, Position) end);
  146. {error, Reason} ->
  147. {error, Reason}
  148. end.
  149. ensure_configured(#{name_vsn := NameVsn} = Item, Position) ->
  150. Configured = configured(),
  151. SplitFun = fun(#{name_vsn := Nv}) -> bin(Nv) =/= bin(NameVsn) end,
  152. {Front, Rear} = lists:splitwith(SplitFun, Configured),
  153. NewConfigured =
  154. case Rear of
  155. [_ | More] when Position =:= no_move ->
  156. Front ++ [Item | More];
  157. [_ | More] ->
  158. add_new_configured(Front ++ More, Position, Item);
  159. [] ->
  160. add_new_configured(Configured, Position, Item)
  161. end,
  162. ok = put_configured(NewConfigured).
  163. add_new_configured(Configured, no_move, Item) ->
  164. %% default to rear
  165. add_new_configured(Configured, rear, Item);
  166. add_new_configured(Configured, front, Item) ->
  167. [Item | Configured];
  168. add_new_configured(Configured, rear, Item) ->
  169. Configured ++ [Item];
  170. add_new_configured(Configured, {Action, NameVsn}, Item) ->
  171. SplitFun = fun(#{name_vsn := Nv}) -> bin(Nv) =/= bin(NameVsn) end,
  172. {Front, Rear} = lists:splitwith(SplitFun, Configured),
  173. Rear =:= [] andalso
  174. throw(#{
  175. error => "position_anchor_plugin_not_configured",
  176. hint => "maybe_install_and_configure",
  177. name_vsn => NameVsn
  178. }),
  179. case Action of
  180. before ->
  181. Front ++ [Item | Rear];
  182. behind ->
  183. [Anchor | Rear0] = Rear,
  184. Front ++ [Anchor, Item | Rear0]
  185. end.
  186. %% @doc Delete the package file.
  187. -spec delete_package(name_vsn()) -> ok.
  188. delete_package(NameVsn) ->
  189. File = pkg_file(NameVsn),
  190. case file:delete(File) of
  191. ok ->
  192. ?SLOG(info, #{msg => "purged_plugin_dir", path => File}),
  193. ok;
  194. {error, enoent} ->
  195. ok;
  196. {error, Reason} ->
  197. ?SLOG(error, #{
  198. msg => "failed_to_delete_package_file",
  199. path => File,
  200. reason => Reason
  201. }),
  202. {error, Reason}
  203. end.
  204. %% @doc Delete extracted dir
  205. %% In case one lib is shared by multiple plugins.
  206. %% it might be the case that purging one plugin's install dir
  207. %% will cause deletion of loaded beams.
  208. %% It should not be a problem, because shared lib should
  209. %% reside in all the plugin install dirs.
  210. -spec purge(name_vsn()) -> ok.
  211. purge(NameVsn) ->
  212. Dir = dir(NameVsn),
  213. case file:del_dir_r(Dir) of
  214. ok ->
  215. ?SLOG(info, #{msg => "purged_plugin_dir", dir => Dir});
  216. {error, enoent} ->
  217. ok;
  218. {error, Reason} ->
  219. ?SLOG(error, #{
  220. msg => "failed_to_purge_plugin_dir",
  221. dir => Dir,
  222. reason => Reason
  223. }),
  224. {error, Reason}
  225. end.
  226. %% @doc Start all configured plugins are started.
  227. -spec ensure_started() -> ok.
  228. ensure_started() ->
  229. ok = for_plugins(fun ?MODULE:do_ensure_started/1).
  230. %% @doc Start a plugin from Management API or CLI.
  231. %% the input is a <name>-<vsn> string.
  232. -spec ensure_started(name_vsn()) -> ok | {error, term()}.
  233. ensure_started(NameVsn) ->
  234. case do_ensure_started(NameVsn) of
  235. ok ->
  236. ok;
  237. {error, Reason} ->
  238. ?SLOG(alert, #{
  239. msg => "failed_to_start_plugin",
  240. reason => Reason
  241. }),
  242. {error, Reason}
  243. end.
  244. %% @doc Stop all plugins before broker stops.
  245. -spec ensure_stopped() -> ok.
  246. ensure_stopped() ->
  247. for_plugins(fun ?MODULE:ensure_stopped/1).
  248. %% @doc Stop a plugin from Management API or CLI.
  249. -spec ensure_stopped(name_vsn()) -> ok | {error, term()}.
  250. ensure_stopped(NameVsn) ->
  251. tryit(
  252. "stop_plugin",
  253. fun() ->
  254. Plugin = do_read_plugin(NameVsn),
  255. ensure_apps_stopped(Plugin)
  256. end
  257. ).
  258. %% @doc Stop and then start the plugin.
  259. restart(NameVsn) ->
  260. case ensure_stopped(NameVsn) of
  261. ok -> ensure_started(NameVsn);
  262. {error, Reason} -> {error, Reason}
  263. end.
  264. %% @doc List all installed plugins.
  265. %% Including the ones that are installed, but not enabled in config.
  266. -spec list() -> [plugin()].
  267. list() ->
  268. Pattern = filename:join([install_dir(), "*", "release.json"]),
  269. All = lists:filtermap(
  270. fun(JsonFile) ->
  271. case read_plugin({file, JsonFile}, #{}) of
  272. {ok, Info} ->
  273. {true, Info};
  274. {error, Reason} ->
  275. ?SLOG(warning, Reason),
  276. false
  277. end
  278. end,
  279. filelib:wildcard(Pattern)
  280. ),
  281. list(configured(), All).
  282. %% Make sure configured ones are ordered in front.
  283. list([], All) ->
  284. All;
  285. list([#{name_vsn := NameVsn} | Rest], All) ->
  286. SplitF = fun(#{<<"name">> := Name, <<"rel_vsn">> := Vsn}) ->
  287. bin([Name, "-", Vsn]) =/= bin(NameVsn)
  288. end,
  289. case lists:splitwith(SplitF, All) of
  290. {_, []} ->
  291. ?SLOG(warning, #{
  292. msg => "configured_plugin_not_installed",
  293. name_vsn => NameVsn
  294. }),
  295. list(Rest, All);
  296. {Front, [I | Rear]} ->
  297. [I | list(Rest, Front ++ Rear)]
  298. end.
  299. do_ensure_started(NameVsn) ->
  300. tryit(
  301. "start_plugins",
  302. fun() ->
  303. Plugin = do_read_plugin(NameVsn),
  304. ok = load_code_start_apps(NameVsn, Plugin)
  305. end
  306. ).
  307. %% try the function, catch 'throw' exceptions as normal 'error' return
  308. %% other exceptions with stacktrace returned.
  309. tryit(WhichOp, F) ->
  310. try
  311. F()
  312. catch
  313. throw:Reason ->
  314. %% thrown exceptions are known errors
  315. %% translate to a return value without stacktrace
  316. {error, Reason};
  317. error:Reason:Stacktrace ->
  318. %% unexpected errors, log stacktrace
  319. ?SLOG(warning, #{
  320. msg => "plugin_op_failed",
  321. which_op => WhichOp,
  322. exception => Reason,
  323. stacktrace => Stacktrace
  324. }),
  325. {error, {failed, WhichOp}}
  326. end.
  327. %% read plugin info from the JSON file
  328. %% returns {ok, Info} or {error, Reason}
  329. read_plugin(NameVsn, Options) ->
  330. tryit(
  331. "read_plugin_info",
  332. fun() -> {ok, do_read_plugin(NameVsn, Options)} end
  333. ).
  334. do_read_plugin(Plugin) -> do_read_plugin(Plugin, #{}).
  335. do_read_plugin({file, InfoFile}, Options) ->
  336. [_, NameVsn | _] = lists:reverse(filename:split(InfoFile)),
  337. case hocon:load(InfoFile, #{format => richmap}) of
  338. {ok, RichMap} ->
  339. Info0 = check_plugin(hocon_maps:ensure_plain(RichMap), NameVsn, InfoFile),
  340. Info1 = plugins_readme(NameVsn, Options, Info0),
  341. plugin_status(NameVsn, Info1);
  342. {error, Reason} ->
  343. throw(#{
  344. error => "bad_info_file",
  345. path => InfoFile,
  346. return => Reason
  347. })
  348. end;
  349. do_read_plugin(NameVsn, Options) ->
  350. do_read_plugin({file, info_file(NameVsn)}, Options).
  351. plugins_readme(NameVsn, #{fill_readme := true}, Info) ->
  352. case file:read_file(readme_file(NameVsn)) of
  353. {ok, Bin} -> Info#{readme => Bin};
  354. _ -> Info#{readme => <<>>}
  355. end;
  356. plugins_readme(_NameVsn, _Options, Info) ->
  357. Info.
  358. plugin_status(NameVsn, Info) ->
  359. {ok, AppName, _AppVsn} = parse_name_vsn(NameVsn),
  360. RunningSt =
  361. case application:get_key(AppName, vsn) of
  362. {ok, _} ->
  363. case lists:keyfind(AppName, 1, running_apps()) of
  364. {AppName, _} -> running;
  365. _ -> loaded
  366. end;
  367. undefined ->
  368. stopped
  369. end,
  370. Configured = lists:filtermap(
  371. fun(#{name_vsn := Nv, enable := St}) ->
  372. case bin(Nv) =:= bin(NameVsn) of
  373. true -> {true, St};
  374. false -> false
  375. end
  376. end,
  377. configured()
  378. ),
  379. ConfSt =
  380. case Configured of
  381. [] -> not_configured;
  382. [true] -> enabled;
  383. [false] -> disabled
  384. end,
  385. Info#{
  386. running_status => RunningSt,
  387. config_status => ConfSt
  388. }.
  389. bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
  390. bin(L) when is_list(L) -> unicode:characters_to_binary(L, utf8);
  391. bin(B) when is_binary(B) -> B.
  392. check_plugin(
  393. #{
  394. <<"name">> := Name,
  395. <<"rel_vsn">> := Vsn,
  396. <<"rel_apps">> := Apps,
  397. <<"description">> := _
  398. } = Info,
  399. NameVsn,
  400. File
  401. ) ->
  402. case bin(NameVsn) =:= bin([Name, "-", Vsn]) of
  403. true ->
  404. try
  405. %% assert
  406. [_ | _] = Apps,
  407. %% validate if the list is all <app>-<vsn> strings
  408. lists:foreach(fun(App) -> {ok, _, _} = parse_name_vsn(App) end, Apps)
  409. catch
  410. _:_ ->
  411. throw(#{
  412. error => "bad_rel_apps",
  413. rel_apps => Apps,
  414. hint => "A non-empty string list of app_name-app_vsn format"
  415. })
  416. end,
  417. Info;
  418. false ->
  419. throw(#{
  420. error => "name_vsn_mismatch",
  421. name_vsn => NameVsn,
  422. path => File,
  423. name => Name,
  424. rel_vsn => Vsn
  425. })
  426. end;
  427. check_plugin(_What, NameVsn, File) ->
  428. throw(#{
  429. error => "bad_info_file_content",
  430. mandatory_fields => [rel_vsn, name, rel_apps, description],
  431. name_vsn => NameVsn,
  432. path => File
  433. }).
  434. load_code_start_apps(RelNameVsn, #{<<"rel_apps">> := Apps}) ->
  435. LibDir = filename:join([install_dir(), RelNameVsn]),
  436. RunningApps = running_apps(),
  437. %% load plugin apps and beam code
  438. AppNames =
  439. lists:map(
  440. fun(AppNameVsn) ->
  441. {ok, AppName, AppVsn} = parse_name_vsn(AppNameVsn),
  442. EbinDir = filename:join([LibDir, AppNameVsn, "ebin"]),
  443. ok = load_plugin_app(AppName, AppVsn, EbinDir, RunningApps),
  444. AppName
  445. end,
  446. Apps
  447. ),
  448. lists:foreach(fun start_app/1, AppNames).
  449. load_plugin_app(AppName, AppVsn, Ebin, RunningApps) ->
  450. case lists:keyfind(AppName, 1, RunningApps) of
  451. false ->
  452. do_load_plugin_app(AppName, Ebin);
  453. {_, Vsn} ->
  454. case bin(Vsn) =:= bin(AppVsn) of
  455. true ->
  456. %% already started on the exact version
  457. ok;
  458. false ->
  459. %% running but a different version
  460. ?SLOG(warning, #{
  461. msg => "plugin_app_already_running",
  462. name => AppName,
  463. running_vsn => Vsn,
  464. loading_vsn => AppVsn
  465. })
  466. end
  467. end.
  468. do_load_plugin_app(AppName, Ebin) when is_binary(Ebin) ->
  469. do_load_plugin_app(AppName, binary_to_list(Ebin));
  470. do_load_plugin_app(AppName, Ebin) ->
  471. _ = code:add_patha(Ebin),
  472. Modules = filelib:wildcard(filename:join([Ebin, "*.beam"])),
  473. lists:foreach(
  474. fun(BeamFile) ->
  475. Module = list_to_atom(filename:basename(BeamFile, ".beam")),
  476. case code:load_file(Module) of
  477. {module, _} ->
  478. ok;
  479. {error, Reason} ->
  480. throw(#{
  481. error => "failed_to_load_plugin_beam",
  482. path => BeamFile,
  483. reason => Reason
  484. })
  485. end
  486. end,
  487. Modules
  488. ),
  489. case application:load(AppName) of
  490. ok ->
  491. ok;
  492. {error, {already_loaded, _}} ->
  493. ok;
  494. {error, Reason} ->
  495. throw(#{
  496. error => "failed_to_load_plugin_app",
  497. name => AppName,
  498. reason => Reason
  499. })
  500. end.
  501. start_app(App) ->
  502. case application:ensure_all_started(App) of
  503. {ok, Started} ->
  504. case Started =/= [] of
  505. true -> ?SLOG(debug, #{msg => "started_plugin_apps", apps => Started});
  506. false -> ok
  507. end,
  508. ?SLOG(debug, #{msg => "started_plugin_app", app => App}),
  509. ok;
  510. {error, {ErrApp, Reason}} ->
  511. throw(#{
  512. error => "failed_to_start_plugin_app",
  513. app => App,
  514. err_app => ErrApp,
  515. reason => Reason
  516. })
  517. end.
  518. %% Stop all apps installed by the plugin package,
  519. %% but not the ones shared with others.
  520. ensure_apps_stopped(#{<<"rel_apps">> := Apps}) ->
  521. %% load plugin apps and beam code
  522. AppsToStop =
  523. lists:map(
  524. fun(NameVsn) ->
  525. {ok, AppName, _AppVsn} = parse_name_vsn(NameVsn),
  526. AppName
  527. end,
  528. Apps
  529. ),
  530. case tryit("stop_apps", fun() -> stop_apps(AppsToStop) end) of
  531. {ok, []} ->
  532. %% all apps stopped
  533. ok;
  534. {ok, Left} ->
  535. ?SLOG(warning, #{
  536. msg => "unabled_to_stop_plugin_apps",
  537. apps => Left,
  538. reason => "running_apps_still_depends_on_this_apps"
  539. }),
  540. ok;
  541. {error, Reason} ->
  542. {error, Reason}
  543. end.
  544. stop_apps(Apps) ->
  545. RunningApps = running_apps(),
  546. case do_stop_apps(Apps, [], RunningApps) of
  547. %% all stopped
  548. {ok, []} -> {ok, []};
  549. %% no progress
  550. {ok, Remain} when Remain =:= Apps -> {ok, Apps};
  551. %% try again
  552. {ok, Remain} -> stop_apps(Remain)
  553. end.
  554. do_stop_apps([], Remain, _AllApps) ->
  555. {ok, lists:reverse(Remain)};
  556. do_stop_apps([App | Apps], Remain, RunningApps) ->
  557. case is_needed_by_any(App, RunningApps) of
  558. true ->
  559. do_stop_apps(Apps, [App | Remain], RunningApps);
  560. false ->
  561. ok = stop_app(App),
  562. do_stop_apps(Apps, Remain, RunningApps)
  563. end.
  564. stop_app(App) ->
  565. case application:stop(App) of
  566. ok ->
  567. ?SLOG(debug, #{msg => "stop_plugin_successfully", app => App}),
  568. ok = unload_moudle_and_app(App);
  569. {error, {not_started, App}} ->
  570. ?SLOG(debug, #{msg => "plugin_not_started", app => App}),
  571. ok = unload_moudle_and_app(App);
  572. {error, Reason} ->
  573. throw(#{error => "failed_to_stop_app", app => App, reason => Reason})
  574. end.
  575. unload_moudle_and_app(App) ->
  576. case application:get_key(App, modules) of
  577. {ok, Modules} -> lists:foreach(fun code:soft_purge/1, Modules);
  578. _ -> ok
  579. end,
  580. _ = application:unload(App),
  581. ok.
  582. is_needed_by_any(AppToStop, RunningApps) ->
  583. lists:any(
  584. fun({RunningApp, _RunningAppVsn}) ->
  585. is_needed_by(AppToStop, RunningApp)
  586. end,
  587. RunningApps
  588. ).
  589. is_needed_by(AppToStop, AppToStop) ->
  590. false;
  591. is_needed_by(AppToStop, RunningApp) ->
  592. case application:get_key(RunningApp, applications) of
  593. {ok, Deps} -> lists:member(AppToStop, Deps);
  594. undefined -> false
  595. end.
  596. put_config(Key, Value) when is_atom(Key) ->
  597. put_config([Key], Value);
  598. put_config(Path, Values) when is_list(Path) ->
  599. Opts = #{rawconf_with_defaults => true, override_to => cluster},
  600. case emqx:update_config([?CONF_ROOT | Path], bin_key(Values), Opts) of
  601. {ok, _} -> ok;
  602. Error -> Error
  603. end.
  604. bin_key(Map) when is_map(Map) ->
  605. maps:fold(fun(K, V, Acc) -> Acc#{bin(K) => V} end, #{}, Map);
  606. bin_key(List = [#{} | _]) ->
  607. lists:map(fun(M) -> bin_key(M) end, List);
  608. bin_key(Term) ->
  609. Term.
  610. get_config(Key, Default) when is_atom(Key) ->
  611. get_config([Key], Default);
  612. get_config(Path, Default) ->
  613. emqx_conf:get([?CONF_ROOT | Path], Default).
  614. install_dir() -> get_config(install_dir, "").
  615. put_configured(Configured) ->
  616. ok = put_config(states, bin_key(Configured)).
  617. configured() ->
  618. get_config(states, []).
  619. for_plugins(ActionFun) ->
  620. case lists:flatmap(fun(I) -> for_plugin(I, ActionFun) end, configured()) of
  621. [] -> ok;
  622. Errors -> erlang:error(#{function => ActionFun, errors => Errors})
  623. end.
  624. for_plugin(#{name_vsn := NameVsn, enable := true}, Fun) ->
  625. case Fun(NameVsn) of
  626. ok -> [];
  627. {error, Reason} -> [{NameVsn, Reason}]
  628. end;
  629. for_plugin(#{name_vsn := NameVsn, enable := false}, _Fun) ->
  630. ?SLOG(debug, #{
  631. msg => "plugin_disabled",
  632. name_vsn => NameVsn
  633. }),
  634. [].
  635. parse_name_vsn(NameVsn) when is_binary(NameVsn) ->
  636. parse_name_vsn(binary_to_list(NameVsn));
  637. parse_name_vsn(NameVsn) when is_list(NameVsn) ->
  638. case lists:splitwith(fun(X) -> X =/= $- end, NameVsn) of
  639. {AppName, [$- | Vsn]} -> {ok, list_to_atom(AppName), Vsn};
  640. _ -> {error, "bad_name_vsn"}
  641. end.
  642. pkg_file(NameVsn) ->
  643. filename:join([install_dir(), bin([NameVsn, ".tar.gz"])]).
  644. dir(NameVsn) ->
  645. filename:join([install_dir(), NameVsn]).
  646. info_file(NameVsn) ->
  647. filename:join([dir(NameVsn), "release.json"]).
  648. readme_file(NameVsn) ->
  649. filename:join([dir(NameVsn), "README.md"]).
  650. running_apps() ->
  651. lists:map(
  652. fun({N, _, V}) ->
  653. {N, V}
  654. end,
  655. application:which_applications(infinity)
  656. ).