%%% Copyright (c) 2019, Sunet. %%% See LICENSE for licensing information. -module(p11p_config). -behaviour(gen_server). %% API -export([start_link/0]). %%-export([config/0]). -export([nameof/1]). -export([tokens/0]). -export([proxyapp_bin_path/0, modules_for_token/1, module_path/1, module_env/1, token_mode/1]). -export_type([token_mode_t/0]). %% Genserver callbacks. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% Records and types. -record(p11module, { name :: string(), path :: string(), env :: [{string(), string()}] %FIXME: maches [] too? }). -type p11module() :: #p11module{}. -type token_mode_t() :: {failover, [timeout]} | {balance, [non_neg_integer()]}. -record(token, { name :: string(), mode :: token_mode_t(), modules = #{} :: #{string() => p11module()} }). -type token() :: #token{}. %% Genserver state. -record(state, { proxyapp_bin_path :: string(), tokens :: #{string() => token()} }). %%%%%%%%%%%%%%%%%%%% %% API. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %% config() -> %% gen_server:call(?MODULE, config). proxyapp_bin_path() -> gen_server:call(?MODULE, proxyapp_bin_path). -spec tokens() -> [token()]. tokens() -> gen_server:call(?MODULE, tokens). -spec token_mode(string()) -> token_mode_t(). token_mode(TokName) -> gen_server:call(?MODULE, {token_mode, TokName}). -spec modules_for_token(string()) -> [p11module()]. modules_for_token(TokName) -> gen_server:call(?MODULE, {modules_for_token, TokName}). -spec module_path(p11module()) -> string(). module_path(Module) -> Module#p11module.path. -spec module_env(p11module()) -> []. module_env(Module) -> Module#p11module.env. nameof(#token{name = Name}) -> Name; nameof(#p11module{name = Name}) -> Name; nameof(List) -> [nameof(E) || E <- List]. %%%%%%%%%%%%%%%%%%%% %% Genserver callbacks. init(_Args) -> State = init_state(), {ok, State}. %% handle_call(config, _From, State) -> %% {reply, State, State}; handle_call(proxyapp_bin_path, _From, #state{proxyapp_bin_path = Path} = State) -> {reply, Path, State}; handle_call(tokens, _From, #state{tokens = Tokens} = State) -> {reply, maps:values(Tokens), State}; handle_call({modules_for_token, TokName}, _, #state{tokens = Tokens} = S) -> #{TokName := Token} = Tokens, {reply, maps:values(Token#token.modules), S}; handle_call({token_mode, TokName}, _, #state{tokens = Tokens} = State) -> #{TokName := Token} = Tokens, {reply, Token#token.mode, State}; handle_call(Request, _From, State) -> lager:warning("Unhandled call: ~p", [Request]), {reply, unhandled, State}. handle_cast(Message, State) -> lager:warning("Unhandled cast: ~p", [Message]), {noreply, State}. handle_info(Info, State) -> lager:warning("Unhandled info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVersion, State, _Extra) -> {ok, State}. %%%%%%%%%%%%%%%%%%%% %% Private. init_state() -> #state { proxyapp_bin_path = application:get_env(p11p, proxyapp_bin_path, "/usr/local/libexec/p11-kit/p11-kit-remote"), tokens = conf_tokens(application:get_env(p11p, groups, [])) }. conf_tokens(L) -> conf_tokens(L, #{}). conf_tokens([], Acc) -> Acc; conf_tokens([H = {Name, _}|T], Acc) -> conf_tokens(T, Acc#{Name => new_token(H)}). -spec new_token({string(), [tuple()]}) -> token(). new_token({Name, Settings}) -> Modules = conf_modules(proplists:get_value(modules, Settings)), Mode = mode(proplists:get_value(mode, Settings, {failover, [timeout]}), %FIXME: s/[timeout]/[10]/g or some other sane default? maps:size(Modules)), #token{ name = Name, mode = Mode, modules = Modules }. conf_modules(L) -> conf_modules(L, #{}). conf_modules([], Acc) -> Acc; conf_modules([{Name, Path}|T], Acc) -> conf_modules(T, Acc#{Name => new_module(Name, Path, [])}); conf_modules([{Name, Path, Env}|T], Acc) -> conf_modules(T, Acc#{Name => new_module(Name, Path, Env)}). new_module(Name, Path, Env) -> #p11module{ name = Name, path = Path, env = Env }. -spec mode(p11p_config:token_mode_t(), non_neg_integer()) -> p11p_config:token_mode_t(). mode({balance, Args}, NModules) -> {balance, Args ++ [1 || _ <- lists:seq(1, NModules - length(Args))]}; mode(Conf, _) -> Conf. %%%%%%%%%%%%%% %% Unit tests. -include_lib("eunit/include/eunit.hrl"). tokens_init_test_() -> {setup, fun() -> conf_tokens( [ {"vtoken0", [{mode, {balance, [3]}}, {modules, [{"bogusmod0_0", "/path/to/bogusmod0_0"}, {"bogusmod0_1", "/path/to/bogusmod0_1"} ]}]}, {"vtoken1", [{modules, [{"bogusmod1_0", "/path/to/bogusmod1_0"}, {"bogusmod1_1", "/path/to/bogusmod1_1", [{"MYENV", "myenv"}]} ]}]} ]) end, fun(_) -> ok end, fun(Conf) -> [?_assertEqual( #{"vtoken0" => {token,"vtoken0", {balance,[3,1]}, #{"bogusmod0_0" => {p11module,"bogusmod0_0", "/path/to/bogusmod0_0", []}, "bogusmod0_1" => {p11module,"bogusmod0_1", "/path/to/bogusmod0_1", []}}}, "vtoken1" => {token,"vtoken1", {failover,[timeout]}, #{"bogusmod1_0" => {p11module,"bogusmod1_0", "/path/to/bogusmod1_0", []}, "bogusmod1_1" => {p11module,"bogusmod1_1", "/path/to/bogusmod1_1", [{"MYENV", "myenv"}]}}} }, Conf)] end}. %% modules_for_token_test_() -> %% {setup, %% fun() ->