|
|
@@ -25,30 +25,39 @@
|
|
|
-export([
|
|
|
running/0,
|
|
|
lookup_state/1,
|
|
|
+ get_backend_status/2,
|
|
|
make_resource_id/1,
|
|
|
create_resource/3,
|
|
|
- update_resource/3,
|
|
|
- call/1
|
|
|
+ update_resource/3
|
|
|
]).
|
|
|
|
|
|
-export([
|
|
|
update/2,
|
|
|
delete/1,
|
|
|
pre_config_update/3,
|
|
|
- post_config_update/5
|
|
|
+ post_config_update/5,
|
|
|
+ propagated_post_config_update/5
|
|
|
]).
|
|
|
|
|
|
--import(emqx_dashboard_sso, [provider/1]).
|
|
|
+-import(emqx_dashboard_sso, [provider/1, format/1]).
|
|
|
|
|
|
--define(MOD_KEY_PATH, [dashboard_sso]).
|
|
|
+-define(MOD_TAB, emqx_dashboard_sso).
|
|
|
+-define(MOD_KEY_PATH, [dashboard, sso]).
|
|
|
+-define(MOD_KEY_PATH(Sub), [dashboard, sso, Sub]).
|
|
|
-define(RESOURCE_GROUP, <<"emqx_dashboard_sso">>).
|
|
|
+-define(NO_ERROR, <<>>).
|
|
|
-define(DEFAULT_RESOURCE_OPTS, #{
|
|
|
start_after_created => false
|
|
|
}).
|
|
|
|
|
|
--record(dashboard_sso, {
|
|
|
+-define(DEFAULT_START_OPTS, #{
|
|
|
+ start_timeout => timer:seconds(30)
|
|
|
+}).
|
|
|
+
|
|
|
+-record(?MOD_TAB, {
|
|
|
backend :: atom(),
|
|
|
- state :: map()
|
|
|
+ state :: undefined | map(),
|
|
|
+ last_error = ?NO_ERROR :: term()
|
|
|
}).
|
|
|
|
|
|
%%------------------------------------------------------------------------------
|
|
|
@@ -58,26 +67,53 @@ start_link() ->
|
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
|
|
|
|
|
running() ->
|
|
|
- maps:fold(
|
|
|
+ SSO = emqx:get_config(?MOD_KEY_PATH, #{}),
|
|
|
+ lists:filtermap(
|
|
|
fun
|
|
|
- (Type, #{enable := true}, Acc) ->
|
|
|
- [Type | Acc];
|
|
|
- (_Type, _Cfg, Acc) ->
|
|
|
- Acc
|
|
|
+ (#{backend := Backend, enable := true}) ->
|
|
|
+ case lookup(Backend) of
|
|
|
+ undefined ->
|
|
|
+ false;
|
|
|
+ #?MOD_TAB{last_error = ?NO_ERROR} ->
|
|
|
+ {true, Backend};
|
|
|
+ _ ->
|
|
|
+ false
|
|
|
+ end;
|
|
|
+ (_) ->
|
|
|
+ false
|
|
|
end,
|
|
|
- [],
|
|
|
- emqx:get_config([emqx_dashboard_sso])
|
|
|
+ maps:values(SSO)
|
|
|
).
|
|
|
|
|
|
+get_backend_status(Backend, false) ->
|
|
|
+ #{
|
|
|
+ backend => Backend,
|
|
|
+ enable => false,
|
|
|
+ running => false,
|
|
|
+ last_error => ?NO_ERROR
|
|
|
+ };
|
|
|
+get_backend_status(Backend, _) ->
|
|
|
+ case lookup(Backend) of
|
|
|
+ undefined ->
|
|
|
+ #{
|
|
|
+ backend => Backend,
|
|
|
+ enable => true,
|
|
|
+ running => false,
|
|
|
+ last_error => <<"Resource not found">>
|
|
|
+ };
|
|
|
+ Data ->
|
|
|
+ maps:merge(#{backend => Backend, enable => true}, do_get_backend_status(Data))
|
|
|
+ end.
|
|
|
+
|
|
|
update(Backend, Config) ->
|
|
|
update_config(Backend, {?FUNCTION_NAME, Backend, Config}).
|
|
|
delete(Backend) ->
|
|
|
update_config(Backend, {?FUNCTION_NAME, Backend}).
|
|
|
|
|
|
lookup_state(Backend) ->
|
|
|
- case ets:lookup(dashboard_sso, Backend) of
|
|
|
+ case ets:lookup(?MOD_TAB, Backend) of
|
|
|
[Data] ->
|
|
|
- Data#dashboard_sso.state;
|
|
|
+ Data#?MOD_TAB.state;
|
|
|
[] ->
|
|
|
undefined
|
|
|
end.
|
|
|
@@ -102,31 +138,25 @@ update_resource(ResourceId, Module, Config) ->
|
|
|
),
|
|
|
start_resource_if_enabled(ResourceId, Result, Config).
|
|
|
|
|
|
-call(Req) ->
|
|
|
- gen_server:call(?MODULE, Req).
|
|
|
-
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% gen_server callbacks
|
|
|
%%------------------------------------------------------------------------------
|
|
|
init([]) ->
|
|
|
process_flag(trap_exit, true),
|
|
|
- emqx_conf:add_handler(?MOD_KEY_PATH, ?MODULE),
|
|
|
+ add_handler(),
|
|
|
emqx_utils_ets:new(
|
|
|
- dashboard_sso,
|
|
|
+ ?MOD_TAB,
|
|
|
[
|
|
|
- set,
|
|
|
+ ordered_set,
|
|
|
public,
|
|
|
named_table,
|
|
|
- {keypos, #dashboard_sso.backend},
|
|
|
+ {keypos, #?MOD_TAB.backend},
|
|
|
{read_concurrency, true}
|
|
|
]
|
|
|
),
|
|
|
start_backend_services(),
|
|
|
{ok, #{}}.
|
|
|
|
|
|
-handle_call({update_config, Req, NewConf}, _From, State) ->
|
|
|
- Result = on_config_update(Req, NewConf),
|
|
|
- {reply, Result, State};
|
|
|
handle_call(_Request, _From, State) ->
|
|
|
Reply = ok,
|
|
|
{reply, Reply, State}.
|
|
|
@@ -138,7 +168,7 @@ handle_info(_Info, State) ->
|
|
|
{noreply, State}.
|
|
|
|
|
|
terminate(_Reason, _State) ->
|
|
|
- emqx_conf:remove_handler(?MOD_KEY_PATH),
|
|
|
+ remove_handler(),
|
|
|
ok.
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
@@ -151,22 +181,24 @@ format_status(_Opt, Status) ->
|
|
|
%% Internal functions
|
|
|
%%------------------------------------------------------------------------------
|
|
|
start_backend_services() ->
|
|
|
- Backends = emqx_conf:get([dashboard_sso], #{}),
|
|
|
+ Backends = emqx_conf:get(?MOD_KEY_PATH, #{}),
|
|
|
lists:foreach(
|
|
|
fun({Backend, Config}) ->
|
|
|
Provider = provider(Backend),
|
|
|
case emqx_dashboard_sso:create(Provider, Config) of
|
|
|
{ok, State} ->
|
|
|
?SLOG(info, #{
|
|
|
- msg => "Start SSO backend successfully",
|
|
|
+ msg => "start_sso_backend_successfully",
|
|
|
backend => Backend
|
|
|
}),
|
|
|
- ets:insert(dashboard_sso, #dashboard_sso{backend = Backend, state = State});
|
|
|
+ update_state(Backend, State);
|
|
|
{error, Reason} ->
|
|
|
+ SafeReason = emqx_utils:redact(Reason),
|
|
|
+ update_last_error(Backend, SafeReason),
|
|
|
?SLOG(error, #{
|
|
|
- msg => "Start SSO backend failed",
|
|
|
+ msg => "start_sso_backend_failed",
|
|
|
backend => Backend,
|
|
|
- reason => Reason
|
|
|
+ reason => SafeReason
|
|
|
})
|
|
|
end
|
|
|
end,
|
|
|
@@ -174,96 +206,188 @@ start_backend_services() ->
|
|
|
).
|
|
|
|
|
|
update_config(Backend, UpdateReq) ->
|
|
|
- case emqx_conf:update([dashboard_sso], UpdateReq, #{override_to => cluster}) of
|
|
|
- {ok, UpdateResult} ->
|
|
|
- #{post_config_update := #{?MODULE := Result}} = UpdateResult,
|
|
|
- ?SLOG(info, #{
|
|
|
- msg => "Update SSO configuration successfully",
|
|
|
- backend => Backend,
|
|
|
- result => Result
|
|
|
- }),
|
|
|
- Result;
|
|
|
+ %% we always make sure the valid configuration will update successfully,
|
|
|
+ %% ignore the runtime error during its update
|
|
|
+ case emqx_conf:update(?MOD_KEY_PATH(Backend), UpdateReq, #{override_to => cluster}) of
|
|
|
+ {ok, _UpdateResult} ->
|
|
|
+ case lookup(Backend) of
|
|
|
+ undefined ->
|
|
|
+ ok;
|
|
|
+ #?MOD_TAB{state = State, last_error = ?NO_ERROR} ->
|
|
|
+ {ok, State};
|
|
|
+ Data ->
|
|
|
+ {error, Data#?MOD_TAB.last_error}
|
|
|
+ end;
|
|
|
{error, Reason} = Error ->
|
|
|
+ SafeReason = emqx_utils:redact(Reason),
|
|
|
?SLOG(error, #{
|
|
|
- msg => "Update SSO configuration failed",
|
|
|
+ msg => "update_sso_failed",
|
|
|
backend => Backend,
|
|
|
- reason => Reason
|
|
|
+ reason => SafeReason
|
|
|
}),
|
|
|
Error
|
|
|
end.
|
|
|
|
|
|
-pre_config_update(_Path, {update, Backend, Config}, OldConf) ->
|
|
|
- BackendBin = bin(Backend),
|
|
|
- {ok, OldConf#{BackendBin => Config}};
|
|
|
-pre_config_update(_Path, {delete, Backend}, OldConf) ->
|
|
|
- BackendBin = bin(Backend),
|
|
|
- case maps:find(BackendBin, OldConf) of
|
|
|
- error ->
|
|
|
- throw(not_exists);
|
|
|
- {ok, _} ->
|
|
|
- {ok, maps:remove(BackendBin, OldConf)}
|
|
|
- end.
|
|
|
+pre_config_update(_, {update, _Backend, Config}, _OldConf) ->
|
|
|
+ {ok, maybe_write_certs(Config)};
|
|
|
+pre_config_update(_, {delete, _Backend}, undefined) ->
|
|
|
+ throw(not_exists);
|
|
|
+pre_config_update(_, {delete, _Backend}, _OldConf) ->
|
|
|
+ {ok, null}.
|
|
|
|
|
|
-post_config_update(_Path, UpdateReq, NewConf, _OldConf, _AppEnvs) ->
|
|
|
- Result = call({update_config, UpdateReq, NewConf}),
|
|
|
- {ok, Result}.
|
|
|
+post_config_update(_, UpdateReq, NewConf, _OldConf, _AppEnvs) ->
|
|
|
+ _ = on_config_update(UpdateReq, NewConf),
|
|
|
+ ok.
|
|
|
|
|
|
-on_config_update({update, Backend, _Config}, NewConf) ->
|
|
|
+propagated_post_config_update(
|
|
|
+ ?MOD_KEY_PATH(BackendBin) = Path, _UpdateReq, undefined, OldConf, AppEnvs
|
|
|
+) ->
|
|
|
+ case atom(BackendBin) of
|
|
|
+ {ok, Backend} ->
|
|
|
+ post_config_update(Path, {delete, Backend}, undefined, OldConf, AppEnvs);
|
|
|
+ Error ->
|
|
|
+ Error
|
|
|
+ end;
|
|
|
+propagated_post_config_update(
|
|
|
+ ?MOD_KEY_PATH(BackendBin) = Path, _UpdateReq, NewConf, OldConf, AppEnvs
|
|
|
+) ->
|
|
|
+ case atom(BackendBin) of
|
|
|
+ {ok, Backend} ->
|
|
|
+ post_config_update(Path, {update, Backend, undefined}, NewConf, OldConf, AppEnvs);
|
|
|
+ Error ->
|
|
|
+ Error
|
|
|
+ end.
|
|
|
+
|
|
|
+on_config_update({update, Backend, _RawConfig}, Config) ->
|
|
|
Provider = provider(Backend),
|
|
|
- Config = maps:get(Backend, NewConf),
|
|
|
case lookup(Backend) of
|
|
|
undefined ->
|
|
|
on_backend_updated(
|
|
|
+ Backend,
|
|
|
emqx_dashboard_sso:create(Provider, Config),
|
|
|
fun(State) ->
|
|
|
- ets:insert(dashboard_sso, #dashboard_sso{backend = Backend, state = State})
|
|
|
+ update_state(Backend, State)
|
|
|
end
|
|
|
);
|
|
|
Data ->
|
|
|
+ update_last_error(Backend, ?NO_ERROR),
|
|
|
on_backend_updated(
|
|
|
- emqx_dashboard_sso:update(Provider, Config, Data#dashboard_sso.state),
|
|
|
+ Backend,
|
|
|
+ emqx_dashboard_sso:update(Provider, Config, Data#?MOD_TAB.state),
|
|
|
fun(State) ->
|
|
|
- ets:insert(dashboard_sso, Data#dashboard_sso{state = State})
|
|
|
+ update_state(Backend, State)
|
|
|
end
|
|
|
)
|
|
|
end;
|
|
|
on_config_update({delete, Backend}, _NewConf) ->
|
|
|
case lookup(Backend) of
|
|
|
undefined ->
|
|
|
- {error, not_exists};
|
|
|
+ on_backend_updated(Backend, {error, not_exists}, undefined);
|
|
|
Data ->
|
|
|
Provider = provider(Backend),
|
|
|
on_backend_updated(
|
|
|
- emqx_dashboard_sso:destroy(Provider, Data#dashboard_sso.state),
|
|
|
+ Backend,
|
|
|
+ emqx_dashboard_sso:destroy(Provider, Data#?MOD_TAB.state),
|
|
|
fun() ->
|
|
|
- ets:delete(dashboard_sso, Backend)
|
|
|
+ ets:delete(?MOD_TAB, Backend)
|
|
|
end
|
|
|
)
|
|
|
end.
|
|
|
|
|
|
lookup(Backend) ->
|
|
|
- case ets:lookup(dashboard_sso, Backend) of
|
|
|
+ case ets:lookup(?MOD_TAB, Backend) of
|
|
|
[Data] ->
|
|
|
Data;
|
|
|
[] ->
|
|
|
undefined
|
|
|
end.
|
|
|
|
|
|
-start_resource_if_enabled(ResourceId, {ok, _} = Result, #{enable := true}) ->
|
|
|
- _ = emqx_resource:start(ResourceId),
|
|
|
+%% to avoid resource leakage the resource start will never affect the update result,
|
|
|
+%% so the resource_id will always be recorded
|
|
|
+start_resource_if_enabled(ResourceId, {ok, _} = Result, #{enable := true, backend := Backend}) ->
|
|
|
+ case emqx_resource:start(ResourceId, ?DEFAULT_START_OPTS) of
|
|
|
+ ok ->
|
|
|
+ ok;
|
|
|
+ {error, Reason} ->
|
|
|
+ SafeReason = emqx_utils:redact(Reason),
|
|
|
+ ?SLOG(error, #{
|
|
|
+ msg => "start_backend_failed",
|
|
|
+ resource_id => ResourceId,
|
|
|
+ reason => SafeReason
|
|
|
+ }),
|
|
|
+ update_last_error(Backend, SafeReason),
|
|
|
+ ok
|
|
|
+ end,
|
|
|
Result;
|
|
|
start_resource_if_enabled(_ResourceId, Result, _Config) ->
|
|
|
Result.
|
|
|
|
|
|
-on_backend_updated({ok, State} = Ok, Fun) ->
|
|
|
+on_backend_updated(_Backend, {ok, State} = Ok, Fun) ->
|
|
|
Fun(State),
|
|
|
Ok;
|
|
|
-on_backend_updated(ok, Fun) ->
|
|
|
+on_backend_updated(_Backend, ok, Fun) ->
|
|
|
Fun(),
|
|
|
ok;
|
|
|
-on_backend_updated(Error, _) ->
|
|
|
+on_backend_updated(Backend, {error, Reason} = Error, _) ->
|
|
|
+ update_last_error(Backend, Reason),
|
|
|
Error.
|
|
|
|
|
|
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
|
|
|
bin(L) when is_list(L) -> list_to_binary(L);
|
|
|
bin(X) -> X.
|
|
|
+
|
|
|
+atom(B) ->
|
|
|
+ emqx_utils:safe_to_existing_atom(B).
|
|
|
+
|
|
|
+add_handler() ->
|
|
|
+ ok = emqx_conf:add_handler(?MOD_KEY_PATH('?'), ?MODULE).
|
|
|
+
|
|
|
+remove_handler() ->
|
|
|
+ ok = emqx_conf:remove_handler(?MOD_KEY_PATH('?')).
|
|
|
+
|
|
|
+maybe_write_certs(#{<<"backend">> := Backend} = Conf) ->
|
|
|
+ Dir = certs_path(Backend),
|
|
|
+ Provider = provider(Backend),
|
|
|
+ emqx_dashboard_sso:convert_certs(Provider, Dir, Conf).
|
|
|
+
|
|
|
+certs_path(Backend) ->
|
|
|
+ filename:join(["sso", Backend]).
|
|
|
+
|
|
|
+update_state(Backend, State) ->
|
|
|
+ Data = ensure_backend_data(Backend),
|
|
|
+ ets:insert(?MOD_TAB, Data#?MOD_TAB{state = State}).
|
|
|
+
|
|
|
+update_last_error(Backend, LastError) ->
|
|
|
+ Data = ensure_backend_data(Backend),
|
|
|
+ ets:insert(?MOD_TAB, Data#?MOD_TAB{last_error = LastError}).
|
|
|
+
|
|
|
+ensure_backend_data(Backend) ->
|
|
|
+ case ets:lookup(?MOD_TAB, Backend) of
|
|
|
+ [Data] ->
|
|
|
+ Data;
|
|
|
+ [] ->
|
|
|
+ #?MOD_TAB{backend = Backend}
|
|
|
+ end.
|
|
|
+
|
|
|
+do_get_backend_status(#?MOD_TAB{state = #{resource_id := ResourceId}}) ->
|
|
|
+ case emqx_resource_manager:lookup(ResourceId) of
|
|
|
+ {ok, _Group, #{status := connected}} ->
|
|
|
+ #{running => true, last_error => ?NO_ERROR};
|
|
|
+ {ok, _Group, #{status := Status}} ->
|
|
|
+ #{
|
|
|
+ running => false,
|
|
|
+ last_error => format([<<"Resource not valid, status: ">>, Status])
|
|
|
+ };
|
|
|
+ {error, not_found} ->
|
|
|
+ #{
|
|
|
+ running => false,
|
|
|
+ last_error => <<"Resource not found">>
|
|
|
+ }
|
|
|
+ end;
|
|
|
+do_get_backend_status(#?MOD_TAB{last_error = ?NO_ERROR}) ->
|
|
|
+ #{running => true, last_error => ?NO_ERROR};
|
|
|
+do_get_backend_status(#?MOD_TAB{last_error = LastError}) ->
|
|
|
+ #{
|
|
|
+ running => false,
|
|
|
+ last_error => format([LastError])
|
|
|
+ }.
|