diff --git a/apps/emqx/include/emqx_authentication.hrl b/apps/emqx/include/emqx_authentication.hrl index 2b1f4d33f..20ae2bf1e 100644 --- a/apps/emqx/include/emqx_authentication.hrl +++ b/apps/emqx/include/emqx_authentication.hrl @@ -20,6 +20,7 @@ -include_lib("emqx/include/logger.hrl"). -define(AUTHN_TRACE_TAG, "AUTHN"). +-define(GLOBAL, 'mqtt:global'). -define(TRACE_AUTHN_PROVIDER(Msg), ?TRACE_AUTHN_PROVIDER(Msg, #{})). -define(TRACE_AUTHN_PROVIDER(Msg, Meta), ?TRACE_AUTHN_PROVIDER(debug, Msg, Meta)). diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index fe93bed68..cce789f24 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -60,7 +60,8 @@ update_authenticator/3, lookup_authenticator/2, list_authenticators/1, - move_authenticator/3 + move_authenticator/3, + reorder_authenticator/2 ]). %% APIs for observer built_in_database @@ -86,12 +87,6 @@ %% utility functions -export([authenticator_id/1, metrics_id/2]). -%% proxy callback --export([ - pre_config_update/3, - post_config_update/5 -]). - -export_type([ authenticator_id/0, position/0, @@ -275,12 +270,6 @@ get_enabled(Authenticators) -> %% APIs %%------------------------------------------------------------------------------ -pre_config_update(Path, UpdateReq, OldConfig) -> - emqx_authentication_config:pre_config_update(Path, UpdateReq, OldConfig). - -post_config_update(Path, UpdateReq, NewConfig, OldConfig, AppEnvs) -> - emqx_authentication_config:post_config_update(Path, UpdateReq, NewConfig, OldConfig, AppEnvs). - %% @doc Get all registered authentication providers. get_providers() -> call(get_providers). @@ -413,6 +402,12 @@ list_authenticators(ChainName) -> move_authenticator(ChainName, AuthenticatorID, Position) -> call({move_authenticator, ChainName, AuthenticatorID, Position}). +-spec reorder_authenticator(chain_name(), [authenticator_id()]) -> ok. +reorder_authenticator(_ChainName, []) -> + ok; +reorder_authenticator(ChainName, AuthenticatorIDs) -> + call({reorder_authenticator, ChainName, AuthenticatorIDs}). + -spec import_users(chain_name(), authenticator_id(), {binary(), binary()}) -> ok | {error, term()}. import_users(ChainName, AuthenticatorID, Filename) -> @@ -447,8 +442,9 @@ list_users(ChainName, AuthenticatorID, FuzzyParams) -> init(_Opts) -> process_flag(trap_exit, true), - ok = emqx_config_handler:add_handler([?CONF_ROOT], ?MODULE), - ok = emqx_config_handler:add_handler([listeners, '?', '?', ?CONF_ROOT], ?MODULE), + Module = emqx_authentication_config, + ok = emqx_config_handler:add_handler([?CONF_ROOT], Module), + ok = emqx_config_handler:add_handler([listeners, '?', '?', ?CONF_ROOT], Module), {ok, #{hooked => false, providers => #{}}}. handle_call(get_providers, _From, #{providers := Providers} = State) -> @@ -504,6 +500,12 @@ handle_call({move_authenticator, ChainName, AuthenticatorID, Position}, _From, S end, Reply = with_chain(ChainName, UpdateFun), reply(Reply, State); +handle_call({reorder_authenticator, ChainName, AuthenticatorIDs}, _From, State) -> + UpdateFun = fun(Chain) -> + handle_reorder_authenticator(Chain, AuthenticatorIDs) + end, + Reply = with_chain(ChainName, UpdateFun), + reply(Reply, State); handle_call({import_users, ChainName, AuthenticatorID, Filename}, _From, State) -> Reply = call_authenticator(ChainName, AuthenticatorID, import_users, [Filename]), reply(Reply, State); @@ -609,6 +611,24 @@ handle_move_authenticator(Chain, AuthenticatorID, Position) -> {error, Reason} end. +handle_reorder_authenticator(Chain, AuthenticatorIDs) -> + #chain{authenticators = Authenticators} = Chain, + NAuthenticators = + lists:filtermap( + fun(ID) -> + case lists:keyfind(ID, #authenticator.id, Authenticators) of + false -> + ?SLOG(error, #{msg => "authenticator_not_found", id => ID}), + false; + Authenticator -> + {true, Authenticator} + end + end, + AuthenticatorIDs + ), + NewChain = Chain#chain{authenticators = NAuthenticators}, + {ok, ok, NewChain}. + handle_create_authenticator(Chain, Config, Providers) -> #chain{name = Name, authenticators = Authenticators} = Chain, AuthenticatorID = authenticator_id(Config), diff --git a/apps/emqx/src/emqx_authentication_config.erl b/apps/emqx/src/emqx_authentication_config.erl index 98c0a19f8..a1b55ea43 100644 --- a/apps/emqx/src/emqx_authentication_config.erl +++ b/apps/emqx/src/emqx_authentication_config.erl @@ -65,8 +65,8 @@ -spec pre_config_update(list(atom()), update_request(), emqx_config:raw_config()) -> {ok, map() | list()} | {error, term()}. -pre_config_update(_, UpdateReq, OldConfig) -> - try do_pre_config_update(UpdateReq, to_list(OldConfig)) of +pre_config_update(Paths, UpdateReq, OldConfig) -> + try do_pre_config_update(Paths, UpdateReq, to_list(OldConfig)) of {error, Reason} -> {error, Reason}; {ok, NewConfig} -> {ok, NewConfig} catch @@ -74,9 +74,9 @@ pre_config_update(_, UpdateReq, OldConfig) -> {error, Reason} end. -do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) -> +do_pre_config_update(_, {create_authenticator, ChainName, Config}, OldConfig) -> NewId = authenticator_id(Config), - case lists:filter(fun(OldConfig0) -> authenticator_id(OldConfig0) =:= NewId end, OldConfig) of + case filter_authenticator(NewId, OldConfig) of [] -> CertsDir = certs_dir(ChainName, Config), NConfig = convert_certs(CertsDir, Config), @@ -84,7 +84,7 @@ do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) -> [_] -> {error, {already_exists, {authenticator, NewId}}} end; -do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) -> +do_pre_config_update(_, {delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) -> NewConfig = lists:filter( fun(OldConfig0) -> AuthenticatorID =/= authenticator_id(OldConfig0) @@ -92,7 +92,7 @@ do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldCon OldConfig ), {ok, NewConfig}; -do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) -> +do_pre_config_update(_, {update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) -> CertsDir = certs_dir(ChainName, AuthenticatorID), NewConfig = lists:map( fun(OldConfig0) -> @@ -104,7 +104,7 @@ do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig ), {ok, NewConfig}; -do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) -> +do_pre_config_update(_, {move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) -> case split_by_id(AuthenticatorID, OldConfig) of {error, Reason} -> {error, Reason}; @@ -129,7 +129,18 @@ do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position} {ok, BeforeNFound ++ [FoundRelated, Found | AfterNFound]} end end - end. + end; +do_pre_config_update(_, OldConfig, OldConfig) -> + {ok, OldConfig}; +do_pre_config_update(Paths, NewConfig, _OldConfig) -> + ChainName = chain_name(Paths), + {ok, [ + begin + CertsDir = certs_dir(ChainName, New), + convert_certs(CertsDir, New) + end + || New <- to_list(NewConfig) + ]}. -spec post_config_update( list(atom()), @@ -139,13 +150,16 @@ do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position} emqx_config:app_envs() ) -> ok | {ok, map()} | {error, term()}. -post_config_update(_, UpdateReq, NewConfig, OldConfig, AppEnvs) -> - do_post_config_update(UpdateReq, to_list(NewConfig), OldConfig, AppEnvs). +post_config_update(Paths, UpdateReq, NewConfig, OldConfig, AppEnvs) -> + do_post_config_update(Paths, UpdateReq, to_list(NewConfig), OldConfig, AppEnvs). -do_post_config_update({create_authenticator, ChainName, Config}, NewConfig, _OldConfig, _AppEnvs) -> +do_post_config_update( + _, {create_authenticator, ChainName, Config}, NewConfig, _OldConfig, _AppEnvs +) -> NConfig = get_authenticator_config(authenticator_id(Config), NewConfig), emqx_authentication:create_authenticator(ChainName, NConfig); do_post_config_update( + _, {delete_authenticator, ChainName, AuthenticatorID}, _NewConfig, OldConfig, @@ -160,6 +174,7 @@ do_post_config_update( {error, Reason} end; do_post_config_update( + _, {update_authenticator, ChainName, AuthenticatorID, Config}, NewConfig, _OldConfig, @@ -172,12 +187,57 @@ do_post_config_update( emqx_authentication:update_authenticator(ChainName, AuthenticatorID, NConfig) end; do_post_config_update( + _, {move_authenticator, ChainName, AuthenticatorID, Position}, _NewConfig, _OldConfig, _AppEnvs ) -> - emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position). + emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position); +do_post_config_update(_, _UpdateReq, OldConfig, OldConfig, _AppEnvs) -> + ok; +do_post_config_update(Paths, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> + ChainName = chain_name(Paths), + OldConfig = to_list(OldConfig0), + NewConfig = to_list(NewConfig0), + OldIds = lists:map(fun authenticator_id/1, OldConfig), + NewIds = lists:map(fun authenticator_id/1, NewConfig), + ok = delete_authenticators(NewIds, ChainName, OldConfig), + ok = create_or_update_authenticators(OldIds, ChainName, NewConfig), + ok = emqx_authentication:reorder_authenticator(ChainName, NewIds), + ok. + +%% create new authenticators and update existing ones +create_or_update_authenticators(OldIds, ChainName, NewConfig) -> + lists:foreach( + fun(Conf) -> + Id = authenticator_id(Conf), + case lists:member(Id, OldIds) of + true -> + emqx_authentication:update_authenticator(ChainName, Id, Conf); + false -> + emqx_authentication:create_authenticator(ChainName, Conf) + end + end, + NewConfig + ). + +%% delete authenticators that are not in the new config +delete_authenticators(NewIds, ChainName, OldConfig) -> + lists:foreach( + fun(Conf) -> + Id = authenticator_id(Conf), + case lists:member(Id, NewIds) of + true -> + ok; + false -> + _ = emqx_authentication:delete_authenticator(ChainName, Id), + CertsDir = certs_dir(ChainName, Conf), + ok = clear_certs(CertsDir, Conf) + end + end, + OldConfig + ). to_list(undefined) -> []; to_list(M) when M =:= #{} -> []; @@ -213,14 +273,15 @@ clear_certs(CertsDir, Config) -> ok = emqx_tls_lib:delete_ssl_files(CertsDir, undefined, OldSSL). get_authenticator_config(AuthenticatorID, AuthenticatorsConfig) -> - case - lists:filter(fun(C) -> AuthenticatorID =:= authenticator_id(C) end, AuthenticatorsConfig) - of + case filter_authenticator(AuthenticatorID, AuthenticatorsConfig) of [C] -> C; [] -> {error, not_found}; _ -> error({duplicated_authenticator_id, AuthenticatorsConfig}) end. +filter_authenticator(ID, Authenticators) -> + lists:filter(fun(A) -> ID =:= authenticator_id(A) end, Authenticators). + split_by_id(ID, AuthenticatorsConfig) -> case lists:foldl( @@ -287,3 +348,8 @@ dir(ChainName, ID) when is_binary(ID) -> emqx_utils:safe_filename(iolist_to_binary([to_bin(ChainName), "-", ID])); dir(ChainName, Config) when is_map(Config) -> dir(ChainName, authenticator_id(Config)). + +chain_name([authentication]) -> + ?GLOBAL; +chain_name([listeners, Type, Name, authentication]) -> + binary_to_existing_atom(<<(atom_to_binary(Type))/binary, ":", (atom_to_binary(Name))/binary>>). diff --git a/apps/emqx/src/emqx_config.erl b/apps/emqx/src/emqx_config.erl index 839d51533..e271aeece 100644 --- a/apps/emqx/src/emqx_config.erl +++ b/apps/emqx/src/emqx_config.erl @@ -288,12 +288,14 @@ get_default_value([RootName | _] = KeyPath) -> end. -spec get_raw(emqx_utils_maps:config_key_path()) -> term(). -get_raw([Root | T]) when is_atom(Root) -> get_raw([bin(Root) | T]); -get_raw(KeyPath) -> do_get_raw(KeyPath). +get_raw([Root | _] = KeyPath) when is_binary(Root) -> do_get_raw(KeyPath); +get_raw([Root | T]) -> get_raw([bin(Root) | T]); +get_raw([]) -> do_get_raw([]). -spec get_raw(emqx_utils_maps:config_key_path(), term()) -> term(). -get_raw([Root | T], Default) when is_atom(Root) -> get_raw([bin(Root) | T], Default); -get_raw(KeyPath, Default) -> do_get_raw(KeyPath, Default). +get_raw([Root | _] = KeyPath, Default) when is_binary(Root) -> do_get_raw(KeyPath, Default); +get_raw([Root | T], Default) -> get_raw([bin(Root) | T], Default); +get_raw([], Default) -> do_get_raw([], Default). -spec put_raw(map()) -> ok. put_raw(Config) -> diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index d51057dcd..5be3521d9 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -174,7 +174,7 @@ t_authenticator(Config) when is_list(Config) -> register_provider(AuthNType1, ?MODULE), ID1 = <<"password_based:built_in_database">>, - % CRUD of authencaticator + % CRUD of authenticator ?assertMatch( {ok, #{id := ID1, state := #{mark := 1}}}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig1) @@ -296,8 +296,10 @@ t_update_config({init, Config}) -> | Config ]; t_update_config(Config) when is_list(Config) -> - emqx_config_handler:add_handler([?CONF_ROOT], emqx_authentication), - ok = emqx_config_handler:add_handler([listeners, '?', '?', ?CONF_ROOT], emqx_authentication), + emqx_config_handler:add_handler([?CONF_ROOT], emqx_authentication_config), + ok = emqx_config_handler:add_handler( + [listeners, '?', '?', ?CONF_ROOT], emqx_authentication_config + ), ok = register_provider(?config("auth1"), ?MODULE), ok = register_provider(?config("auth2"), ?MODULE), Global = ?config(global), @@ -356,6 +358,10 @@ t_update_config(Config) when is_list(Config) -> ?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(Global)), + [Raw2, Raw1] = emqx:get_raw_config([?CONF_ROOT]), + ?assertMatch({ok, _}, update_config([?CONF_ROOT], [Raw1, Raw2])), + ?assertMatch({ok, [#{id := ID1}, #{id := ID2}]}, ?AUTHN:list_authenticators(Global)), + ?assertMatch({ok, _}, update_config([?CONF_ROOT], {delete_authenticator, Global, ID1})), ?assertEqual( {error, {not_found, {authenticator, ID1}}}, @@ -418,11 +424,16 @@ t_update_config(Config) when is_list(Config) -> {ok, _}, update_config(ConfKeyPath, {move_authenticator, ListenerID, ID2, ?CMD_MOVE_FRONT}) ), - ?assertMatch( {ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(ListenerID) ), + [LRaw2, LRaw1] = emqx:get_raw_config(ConfKeyPath), + ?assertMatch({ok, _}, update_config(ConfKeyPath, [LRaw1, LRaw2])), + ?assertMatch( + {ok, [#{id := ID1}, #{id := ID2}]}, + ?AUTHN:list_authenticators(ListenerID) + ), ?assertMatch( {ok, _}, diff --git a/apps/emqx_authn/include/emqx_authn.hrl b/apps/emqx_authn/include/emqx_authn.hrl index 30482553c..601b161d5 100644 --- a/apps/emqx_authn/include/emqx_authn.hrl +++ b/apps/emqx_authn/include/emqx_authn.hrl @@ -23,8 +23,6 @@ -define(AUTHN, emqx_authentication). --define(GLOBAL, 'mqtt:global'). - -define(RE_PLACEHOLDER, "\\$\\{[a-z0-9\\-]+\\}"). -define(AUTH_SHARD, emqx_authn_shard). diff --git a/apps/emqx_authn/src/emqx_authn.app.src b/apps/emqx_authn/src/emqx_authn.app.src index a291f2f61..8a5b29642 100644 --- a/apps/emqx_authn/src/emqx_authn.app.src +++ b/apps/emqx_authn/src/emqx_authn.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_authn, [ {description, "EMQX Authentication"}, - {vsn, "0.1.20"}, + {vsn, "0.1.21"}, {modules, []}, {registered, [emqx_authn_sup, emqx_authn_registry]}, {applications, [kernel, stdlib, emqx_resource, emqx_connector, ehttpc, epgsql, mysql, jose]}, diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index f00ca8ed1..65ce0cc32 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -31,6 +31,7 @@ -define(NOT_FOUND, 'NOT_FOUND'). -define(ALREADY_EXISTS, 'ALREADY_EXISTS'). -define(INTERNAL_ERROR, 'INTERNAL_ERROR'). +-define(CONFIG, emqx_authentication_config). % Swagger @@ -833,12 +834,12 @@ with_chain(ListenerID, Fun) -> create_authenticator(ConfKeyPath, ChainName, Config) -> case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of {ok, #{ - post_config_update := #{emqx_authentication := #{id := ID}}, + post_config_update := #{?CONFIG := #{id := ID}}, raw_config := AuthenticatorsConfig }} -> {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; - {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> + {error, {_PrePostConfigUpdate, ?CONFIG, Reason}} -> serialize_error(Reason); {error, Reason} -> serialize_error(Reason) @@ -1017,7 +1018,7 @@ update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) -> of {ok, _} -> {204}; - {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> + {error, {_PrePostConfigUpdate, ?CONFIG, Reason}} -> serialize_error(Reason); {error, Reason} -> serialize_error(Reason) @@ -1027,7 +1028,7 @@ delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) -> case update_config(ConfKeyPath, {delete_authenticator, ChainName, AuthenticatorID}) of {ok, _} -> {204}; - {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> + {error, {_PrePostConfigUpdate, ?CONFIG, Reason}} -> serialize_error(Reason); {error, Reason} -> serialize_error(Reason) @@ -1044,7 +1045,7 @@ move_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> of {ok, _} -> {204}; - {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> + {error, {_PrePostConfigUpdate, ?CONFIG, Reason}} -> serialize_error(Reason); {error, Reason} -> serialize_error(Reason) diff --git a/apps/emqx_authn/test/emqx_authn_SUITE.erl b/apps/emqx_authn/test/emqx_authn_SUITE.erl index 4f96ca2dd..d5df4add3 100644 --- a/apps/emqx_authn/test/emqx_authn_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_SUITE.erl @@ -200,6 +200,127 @@ t_union_selector_errors(Config) when is_list(Config) -> ), ok. +t_update_conf({init, Config}) -> + emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]), + {ok, _} = emqx:update_config([authentication], []), + Config; +t_update_conf({'end', _Config}) -> + {ok, _} = emqx:update_config([authentication], []), + emqx_common_test_helpers:stop_apps([emqx_authn, emqx_conf]), + ok; +t_update_conf(Config) when is_list(Config) -> + Authn1 = #{ + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"built_in_database">>, + <<"user_id_type">> => <<"clientid">>, + <<"enable">> => true + }, + Authn2 = #{ + <<"mechanism">> => <<"password_based">>, + <<"backend">> => <<"http">>, + <<"method">> => <<"post">>, + <<"url">> => <<"http://127.0.0.1:18083">>, + <<"headers">> => #{ + <<"content-type">> => <<"application/json">> + }, + <<"enable">> => true + }, + Authn3 = #{ + <<"mechanism">> => <<"jwt">>, + <<"use_jwks">> => false, + <<"algorithm">> => <<"hmac-based">>, + <<"secret">> => <<"mysecret">>, + <<"secret_base64_encoded">> => false, + <<"verify_claims">> => #{<<"username">> => <<"${username}">>}, + <<"enable">> => true + }, + Chain = 'mqtt:global', + {ok, _} = emqx:update_config([authentication], [Authn1]), + ?assertMatch( + {ok, #{ + authenticators := [ + #{ + enable := true, + id := <<"password_based:built_in_database">>, + provider := emqx_authn_mnesia + } + ] + }}, + emqx_authentication:lookup_chain(Chain) + ), + + {ok, _} = emqx:update_config([authentication], [Authn1, Authn2, Authn3]), + ?assertMatch( + {ok, #{ + authenticators := [ + #{ + enable := true, + id := <<"password_based:built_in_database">>, + provider := emqx_authn_mnesia + }, + #{ + enable := true, + id := <<"password_based:http">>, + provider := emqx_authn_http + }, + #{ + enable := true, + id := <<"jwt">>, + provider := emqx_authn_jwt + } + ] + }}, + emqx_authentication:lookup_chain(Chain) + ), + {ok, _} = emqx:update_config([authentication], [Authn2, Authn1]), + ?assertMatch( + {ok, #{ + authenticators := [ + #{ + enable := true, + id := <<"password_based:http">>, + provider := emqx_authn_http + }, + #{ + enable := true, + id := <<"password_based:built_in_database">>, + provider := emqx_authn_mnesia + } + ] + }}, + emqx_authentication:lookup_chain(Chain) + ), + + {ok, _} = emqx:update_config([authentication], [Authn3, Authn2, Authn1]), + ?assertMatch( + {ok, #{ + authenticators := [ + #{ + enable := true, + id := <<"jwt">>, + provider := emqx_authn_jwt + }, + #{ + enable := true, + id := <<"password_based:http">>, + provider := emqx_authn_http + }, + #{ + enable := true, + id := <<"password_based:built_in_database">>, + provider := emqx_authn_mnesia + } + ] + }}, + emqx_authentication:lookup_chain(Chain) + ), + {ok, _} = emqx:update_config([authentication], []), + ?assertMatch( + {error, {not_found, {chain, Chain}}}, + emqx_authentication:lookup_chain(Chain) + ), + ok. + parse(Bytes) -> {ok, Frame, <<>>, {none, _}} = emqx_frame:parse(Bytes), Frame. diff --git a/apps/emqx_conf/src/emqx_conf_cli.erl b/apps/emqx_conf/src/emqx_conf_cli.erl index 8e109a1e6..9240d2116 100644 --- a/apps/emqx_conf/src/emqx_conf_cli.erl +++ b/apps/emqx_conf/src/emqx_conf_cli.erl @@ -18,16 +18,40 @@ -export([ load/0, admins/1, + conf/1, unload/0 ]). --define(CMD, cluster_call). +-define(CLUSTER_CALL, cluster_call). +-define(CONF, conf). load() -> - emqx_ctl:register_command(?CMD, {?MODULE, admins}, []). + emqx_ctl:register_command(?CLUSTER_CALL, {?MODULE, admins}, []), + emqx_ctl:register_command(?CONF, {?MODULE, conf}, []). unload() -> - emqx_ctl:unregister_command(?CMD). + emqx_ctl:unregister_command(?CLUSTER_CALL), + emqx_ctl:unregister_command(?CONF). + +conf(["show", "--keys-only"]) -> + print(emqx_config:get_root_names()); +conf(["show"]) -> + print_hocon(get_config()); +conf(["show", Key]) -> + print_hocon(get_config(Key)); +conf(["load", Path]) -> + load_config(Path); +conf(_) -> + emqx_ctl:usage( + [ + %% TODO add reload + %{"conf reload", "reload etc/emqx.conf on local node"}, + {"conf show --keys-only", "print all keys"}, + {"conf show", "print all running configures"}, + {"conf show ", "print a specific configuration"}, + {"conf load ", "load a hocon file to all nodes"} + ] + ). admins(["status"]) -> status(); @@ -43,7 +67,7 @@ admins(["skip", Node0]) -> status(); admins(["tnxid", TnxId0]) -> TnxId = list_to_integer(TnxId0), - emqx_ctl:print("~p~n", [emqx_cluster_rpc:query(TnxId)]); + print(emqx_cluster_rpc:query(TnxId)); admins(["fast_forward"]) -> status(), Nodes = mria:running_nodes(), @@ -91,3 +115,30 @@ status() -> Status ), emqx_ctl:print("-----------------------------------------------\n"). + +print(Json) -> + emqx_ctl:print("~ts~n", [emqx_logger_jsonfmt:best_effort_json(Json)]). + +print_hocon(Hocon) -> + emqx_ctl:print("~ts~n", [hocon_pp:do(Hocon, #{})]). + +get_config() -> emqx_config:fill_defaults(emqx:get_raw_config([])). +get_config(Key) -> emqx_config:fill_defaults(#{Key => emqx:get_raw_config([Key])}). + +-define(OPTIONS, #{rawconf_with_defaults => true, override_to => cluster}). +load_config(Path) -> + case hocon:files([Path]) of + {ok, Conf} -> + maps:foreach( + fun(Key, Value) -> + case emqx_conf:update([Key], Value, ?OPTIONS) of + {ok, _} -> emqx_ctl:print("load ~ts ok~n", [Key]); + {error, Reason} -> emqx_ctl:print("load ~ts failed: ~p~n", [Key, Reason]) + end + end, + Conf + ); + {error, Reason} -> + emqx_ctl:print("load ~ts failed~n~p~n", [Path, Reason]), + {error, bad_hocon_file} + end. diff --git a/apps/emqx_ctl/src/emqx_ctl.app.src b/apps/emqx_ctl/src/emqx_ctl.app.src index c3abade67..1196f17a5 100644 --- a/apps/emqx_ctl/src/emqx_ctl.app.src +++ b/apps/emqx_ctl/src/emqx_ctl.app.src @@ -1,6 +1,6 @@ {application, emqx_ctl, [ {description, "Backend for emqx_ctl script"}, - {vsn, "0.1.1"}, + {vsn, "0.1.2"}, {registered, []}, {mod, {emqx_ctl_app, []}}, {applications, [ diff --git a/apps/emqx_ctl/src/emqx_ctl.erl b/apps/emqx_ctl/src/emqx_ctl.erl index 6123056b9..d2ced7268 100644 --- a/apps/emqx_ctl/src/emqx_ctl.erl +++ b/apps/emqx_ctl/src/emqx_ctl.erl @@ -128,16 +128,21 @@ run_command(Cmd, Args) when is_atom(Cmd) -> }), {error, Reason} end; - [] -> + Error -> help(), - {error, cmd_not_found} + Error end. -spec lookup_command(cmd()) -> [{module(), atom()}]. lookup_command(Cmd) when is_atom(Cmd) -> - case ets:match(?CMD_TAB, {{'_', Cmd}, '$1', '_'}) of - [El] -> El; - [] -> [] + case is_initialized() of + true -> + case ets:match(?CMD_TAB, {{'_', Cmd}, '$1', '_'}) of + [El] -> El; + [] -> {error, cmd_not_found} + end; + false -> + {error, cmd_is_initializing} end. -spec get_commands() -> list({cmd(), module(), atom()}). @@ -145,18 +150,23 @@ get_commands() -> [{Cmd, M, F} || {{_Seq, Cmd}, {M, F}, _Opts} <- ets:tab2list(?CMD_TAB)]. help() -> - case ets:tab2list(?CMD_TAB) of - [] -> - print("No commands available.~n"); - Cmds -> - print("Usage: ~ts~n", ["emqx ctl"]), - lists:foreach( - fun({_, {Mod, Cmd}, _}) -> - print("~110..-s~n", [""]), - apply(Mod, Cmd, [usage]) - end, - Cmds - ) + case is_initialized() of + true -> + case ets:tab2list(?CMD_TAB) of + [] -> + print("No commands available.~n"); + Cmds -> + print("Usage: ~ts~n", ["emqx ctl"]), + lists:foreach( + fun({_, {Mod, Cmd}, _}) -> + print("~110..-s~n", [""]), + apply(Mod, Cmd, [usage]) + end, + Cmds + ) + end; + false -> + print("Command table is initializing.~n") end. -spec print(io:format()) -> ok. @@ -279,3 +289,6 @@ safe_to_existing_atom(Str) -> _:badarg -> undefined end. + +is_initialized() -> + ets:info(?CMD_TAB) =/= undefined. diff --git a/apps/emqx_ctl/test/emqx_ctl_SUITE.erl b/apps/emqx_ctl/test/emqx_ctl_SUITE.erl index 46d9008e8..c11a1d5cb 100644 --- a/apps/emqx_ctl/test/emqx_ctl_SUITE.erl +++ b/apps/emqx_ctl/test/emqx_ctl_SUITE.erl @@ -49,8 +49,8 @@ t_reg_unreg_command(_) -> emqx_ctl:unregister_command(cmd1), emqx_ctl:unregister_command(cmd2), ct:sleep(100), - ?assertEqual([], emqx_ctl:lookup_command(cmd1)), - ?assertEqual([], emqx_ctl:lookup_command(cmd2)), + ?assertEqual({error, cmd_not_found}, emqx_ctl:lookup_command(cmd1)), + ?assertEqual({error, cmd_not_found}, emqx_ctl:lookup_command(cmd2)), ?assertEqual([], emqx_ctl:get_commands()) end ).