Просмотр исходного кода

feat(mgmt): add ignore_readonly qeury-string to PUT /configs API

zmstone 1 год назад
Родитель
Сommit
07cbdc6e90

+ 13 - 9
apps/emqx_conf/src/emqx_conf_cli.erl

@@ -242,7 +242,7 @@ load_config(Bin, Opts) when is_binary(Bin) ->
 load_config_from_raw(RawConf0, Opts) ->
 load_config_from_raw(RawConf0, Opts) ->
     SchemaMod = emqx_conf:schema_module(),
     SchemaMod = emqx_conf:schema_module(),
     RawConf1 = emqx_config:upgrade_raw_conf(SchemaMod, RawConf0),
     RawConf1 = emqx_config:upgrade_raw_conf(SchemaMod, RawConf0),
-    case check_config(RawConf1) of
+    case check_config(RawConf1, Opts) of
         {ok, RawConf} ->
         {ok, RawConf} ->
             %% It has been ensured that the connector is always the first configuration to be updated.
             %% It has been ensured that the connector is always the first configuration to be updated.
             %% However, when deleting the connector, we need to clean up the dependent actions/sources first;
             %% However, when deleting the connector, we need to clean up the dependent actions/sources first;
@@ -395,24 +395,28 @@ suggest_msg(#{kind := validation_error, reason := unknown_fields}, Mode) ->
 suggest_msg(_, _) ->
 suggest_msg(_, _) ->
     <<"">>.
     <<"">>.
 
 
-check_config(Conf) ->
-    case check_keys_is_not_readonly(Conf) of
-        ok ->
-            Conf1 = emqx_config:fill_defaults(Conf),
-            case check_config_schema(Conf1) of
-                ok -> {ok, Conf1};
+check_config(Conf0, Opts) ->
+    case check_keys_is_not_readonly(Conf0, Opts) of
+        {ok, Conf1} ->
+            Conf = emqx_config:fill_defaults(Conf1),
+            case check_config_schema(Conf) of
+                ok -> {ok, Conf};
                 {error, Reason} -> {error, Reason}
                 {error, Reason} -> {error, Reason}
             end;
             end;
         Error ->
         Error ->
             Error
             Error
     end.
     end.
 
 
-check_keys_is_not_readonly(Conf) ->
+check_keys_is_not_readonly(Conf, Opts) ->
+    IgnoreReadonly = maps:get(ignore_readonly, Opts, false),
     Keys = maps:keys(Conf),
     Keys = maps:keys(Conf),
     ReadOnlyKeys = [atom_to_binary(K) || K <- ?READONLY_KEYS],
     ReadOnlyKeys = [atom_to_binary(K) || K <- ?READONLY_KEYS],
     case lists:filter(fun(K) -> lists:member(K, Keys) end, ReadOnlyKeys) of
     case lists:filter(fun(K) -> lists:member(K, Keys) end, ReadOnlyKeys) of
         [] ->
         [] ->
-            ok;
+            {ok, Conf};
+        BadKeys when IgnoreReadonly ->
+            ?SLOG(warning, #{msg => "readonly_root_keys_ignored", keys => BadKeys}),
+            {ok, maps:without(BadKeys, Conf)};
         BadKeys ->
         BadKeys ->
             BadKeysStr = lists:join(<<",">>, BadKeys),
             BadKeysStr = lists:join(<<",">>, BadKeys),
             {error, ?UPDATE_READONLY_KEYS_PROHIBITED, BadKeysStr}
             {error, ?UPDATE_READONLY_KEYS_PROHIBITED, BadKeysStr}

+ 10 - 3
apps/emqx_management/src/emqx_mgmt_api_configs.erl

@@ -147,7 +147,9 @@ schema("/configs") ->
                     hoconsc:mk(
                     hoconsc:mk(
                         hoconsc:enum([replace, merge]),
                         hoconsc:enum([replace, merge]),
                         #{in => query, default => merge, required => false}
                         #{in => query, default => merge, required => false}
-                    )}
+                    )},
+                {ignore_readonly,
+                    hoconsc:mk(boolean(), #{in => query, default => false, required => false})}
             ],
             ],
             'requestBody' => #{
             'requestBody' => #{
                 content =>
                 content =>
@@ -361,8 +363,13 @@ configs(get, #{query_string := QueryStr, headers := Headers}, _Req) ->
         {ok, <<"text/plain">>} -> get_configs_v2(QueryStr);
         {ok, <<"text/plain">>} -> get_configs_v2(QueryStr);
         {error, _} = Error -> {400, #{code => 'INVALID_ACCEPT', message => ?ERR_MSG(Error)}}
         {error, _} = Error -> {400, #{code => 'INVALID_ACCEPT', message => ?ERR_MSG(Error)}}
     end;
     end;
-configs(put, #{body := Conf, query_string := #{<<"mode">> := Mode}}, _Req) ->
-    case emqx_conf_cli:load_config(Conf, #{mode => Mode, log => none}) of
+configs(put, #{body := Conf, query_string := #{<<"mode">> := Mode} = QS}, _Req) ->
+    IngnoreReadonly = maps:get(<<"ignore_readonly">>, QS, false),
+    case
+        emqx_conf_cli:load_config(Conf, #{
+            mode => Mode, log => none, ignore_readonly => IngnoreReadonly
+        })
+    of
         ok ->
         ok ->
             {200};
             {200};
         %% bad hocon format
         %% bad hocon format

+ 16 - 4
apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl

@@ -331,7 +331,7 @@ t_configs_key(_Config) ->
         Log
         Log
     ),
     ),
     Log1 = emqx_utils_maps:deep_put([<<"log">>, <<"console">>, <<"level">>], Log, <<"error">>),
     Log1 = emqx_utils_maps:deep_put([<<"log">>, <<"console">>, <<"level">>], Log, <<"error">>),
-    ?assertEqual(<<>>, update_configs_with_binary(iolist_to_binary(hocon_pp:do(Log1, #{})))),
+    ?assertEqual({ok, <<>>}, update_configs_with_binary(iolist_to_binary(hocon_pp:do(Log1, #{})))),
     ?assertEqual(<<"error">>, read_conf([<<"log">>, <<"console">>, <<"level">>])),
     ?assertEqual(<<"error">>, read_conf([<<"log">>, <<"console">>, <<"level">>])),
     BadLog = emqx_utils_maps:deep_put([<<"log">>, <<"console">>, <<"level">>], Log, <<"erro1r">>),
     BadLog = emqx_utils_maps:deep_put([<<"log">>, <<"console">>, <<"level">>], Log, <<"erro1r">>),
     {error, Error} = update_configs_with_binary(iolist_to_binary(hocon_pp:do(BadLog, #{}))),
     {error, Error} = update_configs_with_binary(iolist_to_binary(hocon_pp:do(BadLog, #{}))),
@@ -358,6 +358,7 @@ t_configs_key(_Config) ->
     ReadOnlyBin = iolist_to_binary(hocon_pp:do(ReadOnlyConf, #{})),
     ReadOnlyBin = iolist_to_binary(hocon_pp:do(ReadOnlyConf, #{})),
     {error, ReadOnlyError} = update_configs_with_binary(ReadOnlyBin),
     {error, ReadOnlyError} = update_configs_with_binary(ReadOnlyBin),
     ?assertEqual(<<"{\"errors\":\"Cannot update read-only key 'cluster'.\"}">>, ReadOnlyError),
     ?assertEqual(<<"{\"errors\":\"Cannot update read-only key 'cluster'.\"}">>, ReadOnlyError),
+    ?assertMatch({ok, <<>>}, update_configs_with_binary(ReadOnlyBin, _InogreReadonly = true)),
     ok.
     ok.
 
 
 t_get_configs_in_different_accept(_Config) ->
 t_get_configs_in_different_accept(_Config) ->
@@ -407,7 +408,7 @@ t_create_webhook_v1_bridges_api(Config) ->
     WebHookFile = filename:join(?config(data_dir, Config), "webhook_v1.conf"),
     WebHookFile = filename:join(?config(data_dir, Config), "webhook_v1.conf"),
     ?assertMatch({ok, _}, hocon:files([WebHookFile])),
     ?assertMatch({ok, _}, hocon:files([WebHookFile])),
     {ok, WebHookBin} = file:read_file(WebHookFile),
     {ok, WebHookBin} = file:read_file(WebHookFile),
-    ?assertEqual(<<>>, update_configs_with_binary(WebHookBin)),
+    ?assertEqual({ok, <<>>}, update_configs_with_binary(WebHookBin)),
     Actions =
     Actions =
         #{
         #{
             <<"http">> =>
             <<"http">> =>
@@ -557,14 +558,25 @@ get_configs_with_binary(Key, Node) ->
     end.
     end.
 
 
 update_configs_with_binary(Bin) ->
 update_configs_with_binary(Bin) ->
-    Path = emqx_mgmt_api_test_util:api_path(["configs"]),
+    update_configs_with_binary(Bin, _InogreReadonly = undefined).
+
+update_configs_with_binary(Bin, IgnoreReadonly) ->
+    Path =
+        case IgnoreReadonly of
+            undefined ->
+                emqx_mgmt_api_test_util:api_path(["configs"]);
+            Boolean ->
+                emqx_mgmt_api_test_util:api_path([
+                    "configs?ignore_readonly=" ++ atom_to_list(Boolean)
+                ])
+        end,
     Auth = emqx_mgmt_api_test_util:auth_header_(),
     Auth = emqx_mgmt_api_test_util:auth_header_(),
     Headers = [{"accept", "text/plain"}, Auth],
     Headers = [{"accept", "text/plain"}, Auth],
     case httpc:request(put, {Path, Headers, "text/plain", Bin}, [], [{body_format, binary}]) of
     case httpc:request(put, {Path, Headers, "text/plain", Bin}, [], [{body_format, binary}]) of
         {ok, {{"HTTP/1.1", Code, _}, _Headers, Body}} when
         {ok, {{"HTTP/1.1", Code, _}, _Headers, Body}} when
             Code >= 200 andalso Code =< 299
             Code >= 200 andalso Code =< 299
         ->
         ->
-            Body;
+            {ok, Body};
         {ok, {{"HTTP/1.1", 400, _}, _Headers, Body}} ->
         {ok, {{"HTTP/1.1", 400, _}, _Headers, Body}} ->
             {error, Body};
             {error, Body};
         Error ->
         Error ->