Enforce unified formatting w/ erlfmt (#18)

* Bump to rbkmoney/build_utils@e131872
This commit is contained in:
Andrew Mayorov 2021-02-04 17:23:35 +03:00 committed by GitHub
parent db9721bf71
commit 6a7ca8f2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 563 additions and 623 deletions

View File

@ -19,7 +19,7 @@ BASE_IMAGE_TAG := 688cee70c0eb6540709fe35b816c81a90dc542ea
BUILD_IMAGE_TAG := 917afcdd0c0a07bf4155d597bbba72e962e1a34a BUILD_IMAGE_TAG := 917afcdd0c0a07bf4155d597bbba72e962e1a34a
CALL_ANYWHERE := \ CALL_ANYWHERE := \
submodules \ submodules \
all compile xref lint dialyze cover release clean distclean all compile xref lint format check_format dialyze cover release clean distclean
CALL_W_CONTAINER := $(CALL_ANYWHERE) test CALL_W_CONTAINER := $(CALL_ANYWHERE) test
@ -45,6 +45,12 @@ xref:
lint: lint:
$(REBAR) lint $(REBAR) lint
check_format:
$(REBAR) fmt -c
format:
$(REBAR) fmt -w
dialyze: dialyze:
$(REBAR) dialyzer $(REBAR) dialyzer

@ -1 +1 @@
Subproject commit 2c4c2289ad7919ef953603f70d5cc967419ec2dd Subproject commit e1318727d4d0c3e48f5122bf3197158b6695f50e

View File

@ -1,6 +1,5 @@
%% Common project erlang options. %% Common project erlang options.
{erl_opts, [ {erl_opts, [
% mandatory % mandatory
debug_info, debug_info,
warnings_as_errors, warnings_as_errors,
@ -27,74 +26,38 @@
%% Common project dependencies. %% Common project dependencies.
{deps, [ {deps, [
{cowboy, "2.8.0"}, {cowboy, "2.8.0"},
{jsx, "3.0.0"}, {jsx, "3.0.0"},
{jesse, "1.5.5"}, {jesse, "1.5.5"},
{gun, {gun, {git, "https://github.com/ninenines/gun.git", {branch, "master"}}},
{git, "https://github.com/ninenines/gun.git", {genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}},
{branch, "master"} {thrift, {git, "https://github.com/rbkmoney/thrift_erlang.git", {branch, "master"}}},
}}, {woody, {git, "https://github.com/rbkmoney/woody_erlang.git", {branch, "master"}}},
{genlib,
{git, "https://github.com/rbkmoney/genlib.git",
{branch, "master"}
}
},
{thrift,
{git, "https://github.com/rbkmoney/thrift_erlang.git",
{branch, "master"}
}
},
{woody,
{git, "https://github.com/rbkmoney/woody_erlang.git",
{branch, "master"}
}
},
{woody_user_identity, {woody_user_identity,
{git, "https://github.com/rbkmoney/woody_erlang_user_identity.git", {git, "https://github.com/rbkmoney/woody_erlang_user_identity.git", {branch, "master"}}},
{branch, "master"} {bouncer_proto, {git, "git@github.com:rbkmoney/bouncer-proto.git", {branch, "master"}}},
}
},
{bouncer_proto,
{git, "git@github.com:rbkmoney/bouncer-proto.git",
{branch, "master"}
}
},
{org_management_proto, {org_management_proto,
{git, "git@github.com:rbkmoney/org-management-proto.git", {git, "git@github.com:rbkmoney/org-management-proto.git", {branch, "master"}}},
{branch, "master"} {scoper, {git, "https://github.com/rbkmoney/scoper.git", {branch, "master"}}},
} {erl_health, {git, "https://github.com/rbkmoney/erlang-health.git", {branch, "master"}}},
},
{scoper,
{git, "https://github.com/rbkmoney/scoper.git",
{branch, "master"}
}
},
{erl_health,
{git, "https://github.com/rbkmoney/erlang-health.git",
{branch, "master"}
}
},
% Production-only deps. % Production-only deps.
% Defined here for the sake of rebar-locking. % Defined here for the sake of rebar-locking.
{recon, "2.5.1"}, {recon, "2.5.1"},
{logger_logstash_formatter, {logger_logstash_formatter,
{git, "https://github.com/rbkmoney/logger_logstash_formatter.git", {git, "https://github.com/rbkmoney/logger_logstash_formatter.git", {branch, "master"}}},
{branch, "master"} {how_are_you, {git, "https://github.com/rbkmoney/how_are_you.git", {branch, "master"}}}
}
},
{how_are_you,
{git, "https://github.com/rbkmoney/how_are_you.git",
{branch, "master"}
}
}
]}. ]}.
%% Helpful plugins. %% Helpful plugins.
{plugins, [ {plugins, [
rebar3_lint rebar3_lint,
{erlfmt, "0.10.0"}
]}.
{erlfmt, [
{print_width, 100},
{files, ["{src,include,test}/*.{hrl,erl}", "rebar.config"]}
]}. ]}.
%% Linter config. %% Linter config.
@ -104,10 +67,12 @@
filter => "*.erl", filter => "*.erl",
ruleset => erl_files, ruleset => erl_files,
rules => [ rules => [
{elvis_style, invalid_dynamic_call, #{ignore => [ {elvis_style, invalid_dynamic_call, #{
% Uses thrift reflection through `struct_info/1`. ignore => [
bouncer_thrift % Uses thrift reflection through `struct_info/1`.
]}} bouncer_thrift
]
}}
] ]
}, },
#{ #{
@ -161,6 +126,7 @@
deprecated_functions_calls, deprecated_functions_calls,
deprecated_functions deprecated_functions
]}. ]}.
% at will % at will
% {xref_warnings, true}. % {xref_warnings, true}.
@ -169,11 +135,15 @@
%% Relx configuration %% Relx configuration
{relx, [ {relx, [
{release, {bouncer , "0.1.0"}, [ {release, {bouncer, "0.1.0"}, [
{recon , load}, % tools for introspection % tools for introspection
{runtime_tools, load}, % debugger {recon, load},
{tools , load}, % profiler % debugger
{logger_logstash_formatter, load}, % logger formatter {runtime_tools, load},
% profiler
{tools, load},
% logger formatter
{logger_logstash_formatter, load},
how_are_you, how_are_you,
bouncer bouncer
]}, ]},
@ -197,7 +167,6 @@
]}. ]}.
{profiles, [ {profiles, [
{prod, [ {prod, [
{relx, [ {relx, [
{dev_mode, false}, {dev_mode, false},
@ -210,5 +179,4 @@
{cover_enabled, true}, {cover_enabled, true},
{deps, []} {deps, []}
]} ]}
]}. ]}.

View File

@ -3,6 +3,7 @@
%% Application callbacks %% Application callbacks
-behaviour(application). -behaviour(application).
-export([start/2]). -export([start/2]).
-export([prep_stop/1]). -export([prep_stop/1]).
-export([stop/1]). -export([stop/1]).
@ -10,18 +11,16 @@
%% Supervisor callbacks %% Supervisor callbacks
-behaviour(supervisor). -behaviour(supervisor).
-export([init/1]). -export([init/1]).
%% %%
-spec start(normal, any()) -> -spec start(normal, any()) -> {ok, pid()} | {error, any()}.
{ok, pid()} | {error, any()}.
start(_StartType, _StartArgs) -> start(_StartType, _StartArgs) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec prep_stop(State) -> -spec prep_stop(State) -> State.
State.
prep_stop(State) -> prep_stop(State) ->
% NOTE % NOTE
% We have to do it in this magic `prep_stop/1` here because for some inexplicable reason the % We have to do it in this magic `prep_stop/1` here because for some inexplicable reason the
@ -29,17 +28,13 @@ prep_stop(State) ->
ok = bouncer_audit_log:stop(genlib_app:env(?MODULE, audit, #{})), ok = bouncer_audit_log:stop(genlib_app:env(?MODULE, audit, #{})),
State. State.
-spec stop(any()) -> -spec stop(any()) -> ok.
ok.
stop(_State) -> stop(_State) ->
ok. ok.
%% %%
-spec init([]) -> -spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) -> init([]) ->
AuditPulse = bouncer_audit_log:init(genlib_app:env(?MODULE, audit, #{})), AuditPulse = bouncer_audit_log:init(genlib_app:env(?MODULE, audit, #{})),
ServiceOpts = genlib_app:env(?MODULE, services, #{}), ServiceOpts = genlib_app:env(?MODULE, services, #{}),
@ -48,56 +43,46 @@ init([]) ->
ChildSpec = woody_server:child_spec( ChildSpec = woody_server:child_spec(
?MODULE, ?MODULE,
#{ #{
ip => get_ip_address(), ip => get_ip_address(),
port => get_port(), port => get_port(),
protocol_opts => get_protocol_opts(), protocol_opts => get_protocol_opts(),
transport_opts => get_transport_opts(), transport_opts => get_transport_opts(),
shutdown_timeout => get_shutdown_timeout(), shutdown_timeout => get_shutdown_timeout(),
event_handler => EventHandlers, event_handler => EventHandlers,
handlers => handlers =>
get_handler_specs(ServiceOpts, AuditPulse) ++ get_stub_handler_specs(ServiceOpts), get_handler_specs(ServiceOpts, AuditPulse) ++ get_stub_handler_specs(ServiceOpts),
additional_routes => [erl_health_handle:get_route(Healthcheck)] additional_routes => [erl_health_handle:get_route(Healthcheck)]
} }
), ),
{ok, { {ok,
#{strategy => one_for_all, intensity => 6, period => 30}, {
[ChildSpec] #{strategy => one_for_all, intensity => 6, period => 30},
}}. [ChildSpec]
}}.
-spec get_ip_address() ->
inet:ip_address().
-spec get_ip_address() -> inet:ip_address().
get_ip_address() -> get_ip_address() ->
{ok, Address} = inet:parse_address(genlib_app:env(?MODULE, ip, "::")), {ok, Address} = inet:parse_address(genlib_app:env(?MODULE, ip, "::")),
Address. Address.
-spec get_port() -> -spec get_port() -> inet:port_number().
inet:port_number().
get_port() -> get_port() ->
genlib_app:env(?MODULE, port, 8022). genlib_app:env(?MODULE, port, 8022).
-spec get_protocol_opts() -> -spec get_protocol_opts() -> woody_server_thrift_http_handler:protocol_opts().
woody_server_thrift_http_handler:protocol_opts().
get_protocol_opts() -> get_protocol_opts() ->
genlib_app:env(?MODULE, protocol_opts, #{}). genlib_app:env(?MODULE, protocol_opts, #{}).
-spec get_transport_opts() -> -spec get_transport_opts() -> woody_server_thrift_http_handler:transport_opts().
woody_server_thrift_http_handler:transport_opts().
get_transport_opts() -> get_transport_opts() ->
genlib_app:env(?MODULE, transport_opts, #{}). genlib_app:env(?MODULE, transport_opts, #{}).
-spec get_shutdown_timeout() -> -spec get_shutdown_timeout() -> timeout().
timeout().
get_shutdown_timeout() -> get_shutdown_timeout() ->
genlib_app:env(?MODULE, shutdown_timeout, 0). genlib_app:env(?MODULE, shutdown_timeout, 0).
-spec get_handler_specs(map(), bouncer_arbiter_pulse:handlers()) -> -spec get_handler_specs(map(), bouncer_arbiter_pulse:handlers()) ->
[woody:http_handler(woody:th_handler())]. [woody:http_handler(woody:th_handler())].
get_handler_specs(ServiceOpts, AuditPulse) -> get_handler_specs(ServiceOpts, AuditPulse) ->
ArbiterService = maps:get(arbiter, ServiceOpts, #{}), ArbiterService = maps:get(arbiter, ServiceOpts, #{}),
ArbiterPulse = maps:get(pulse, ArbiterService, []), ArbiterPulse = maps:get(pulse, ArbiterService, []),
@ -122,12 +107,10 @@ get_stub_handler_specs(ServiceOpts) ->
%% %%
-spec enable_health_logging(erl_health:check()) -> -spec enable_health_logging(erl_health:check()) -> erl_health:check().
erl_health:check().
enable_health_logging(Check) -> enable_health_logging(Check) ->
EvHandler = {erl_health_event_handler, []}, EvHandler = {erl_health_event_handler, []},
maps:map( maps:map(
fun (_, Runner) -> #{runner => Runner, event_handler => EvHandler} end, fun(_, Runner) -> #{runner => Runner, event_handler => EvHandler} end,
Check Check
). ).

View File

@ -14,9 +14,9 @@
%% ``` %% ```
-type ruleset_id() :: iodata(). -type ruleset_id() :: iodata().
-type judgement() :: {resolution(), [assertion()]}. -type judgement() :: {resolution(), [assertion()]}.
-type resolution() :: allowed | forbidden | {restricted, map()}. -type resolution() :: allowed | forbidden | {restricted, map()}.
-type assertion() :: {_Code :: binary(), _Details :: #{binary() => _}}. -type assertion() :: {_Code :: binary(), _Details :: #{binary() => _}}.
-export_type([judgement/0]). -export_type([judgement/0]).
-export_type([resolution/0]). -export_type([resolution/0]).
@ -26,12 +26,11 @@
%% %%
-spec judge(ruleset_id(), bouncer_context:ctx()) -> -spec judge(ruleset_id(), bouncer_context:ctx()) ->
{ok, judgement()} | {ok, judgement()}
{error, | {error,
ruleset_notfound | ruleset_notfound
{ruleset_invalid, _Details} | | {ruleset_invalid, _Details}
{unavailable | unknown, _Reason} | {unavailable | unknown, _Reason}}.
}.
judge(RulesetID, Context) -> judge(RulesetID, Context) ->
case mk_opa_client() of case mk_opa_client() of
{ok, Client} -> {ok, Client} ->
@ -48,8 +47,7 @@ judge(RulesetID, Context) ->
Error Error
end. end.
-spec infer_judgement(document()) -> -spec infer_judgement(document()) -> {ok, judgement()} | {error, {ruleset_invalid, _Details}}.
{ok, judgement()} | {error, {ruleset_invalid, _Details}}.
infer_judgement(Document) -> infer_judgement(Document) ->
case jesse:validate_with_schema(get_judgement_schema(), Document) of case jesse:validate_with_schema(get_judgement_schema(), Document) of
{ok, _} -> {ok, _} ->
@ -74,8 +72,7 @@ extract_assertions(Assertions) ->
extract_assertion(Assertion = #{<<"code">> := Code}) -> extract_assertion(Assertion = #{<<"code">> := Code}) ->
{Code, maps:without([<<"code">>], Assertion)}. {Code, maps:without([<<"code">>], Assertion)}.
-spec get_judgement_schema() -> -spec get_judgement_schema() -> jesse:schema().
jesse:schema().
get_judgement_schema() -> get_judgement_schema() ->
% TODO % TODO
% Worth declaring in a separate file? Should be helpful w/ CI-like activities. % Worth declaring in a separate file? Should be helpful w/ CI-like activities.
@ -121,37 +118,36 @@ get_judgement_schema() ->
-type endpoint() :: {inet:hostname() | inet:ip_address(), inet:port_number()}. -type endpoint() :: {inet:hostname() | inet:ip_address(), inet:port_number()}.
-type client_opts() :: #{ -type client_opts() :: #{
endpoint := endpoint(), endpoint := endpoint(),
transport => tcp | tls, transport => tcp | tls,
tcp_opts => [gen_tcp:connect_option()], tcp_opts => [gen_tcp:connect_option()],
tls_opts => [ssl:tls_client_option()], tls_opts => [ssl:tls_client_option()],
connect_timeout => timeout(), connect_timeout => timeout(),
domain_lookup_timeout => timeout(), domain_lookup_timeout => timeout(),
request_timeout => timeout(), request_timeout => timeout(),
http_opts => gun:http_opts(), http_opts => gun:http_opts(),
http2_opts => gun:http2_opts(), http2_opts => gun:http2_opts(),
% TODO % TODO
% Pulse over gun event handler mechanic. % Pulse over gun event handler mechanic.
event_handler => {module(), _State} event_handler => {module(), _State}
}. }.
-define(DEFAULT_CLIENT_OPTS, #{ -define(DEFAULT_CLIENT_OPTS, #{
domain_lookup_timeout => 1000, domain_lookup_timeout => 1000,
connect_timeout => 1000, connect_timeout => 1000,
request_timeout => 1000 request_timeout => 1000
}). }).
-type client() :: {pid(), client_opts()}. -type client() :: {pid(), client_opts()}.
-type document() :: -type document() ::
null | null
binary() | | binary()
number() | | number()
boolean() | | boolean()
#{atom() | binary() => document()} | | #{atom() | binary() => document()}
[document()]. | [document()].
-spec mk_opa_client() -> -spec mk_opa_client() -> {ok, client()} | {error, {unavailable, _Reason}}.
{ok, client()} | {error, {unavailable, _Reason}}.
mk_opa_client() -> mk_opa_client() ->
Opts = get_opa_client_opts(), Opts = get_opa_client_opts(),
{Host, Port} = maps:get(endpoint, Opts), {Host, Port} = maps:get(endpoint, Opts),
@ -186,11 +182,10 @@ mk_opa_client() ->
end. end.
-spec request_opa_document(_ID :: iodata(), _Input :: document(), client()) -> -spec request_opa_document(_ID :: iodata(), _Input :: document(), client()) ->
{ok, document()} | {ok, document()}
{error, | {error,
notfound | notfound
{unknown, _Reason} | {unknown, _Reason}}.
}.
request_opa_document(ID, Input, {Client, Opts}) -> request_opa_document(ID, Input, {Client, Opts}) ->
Path = join_path(<<"/v1/data">>, ID), Path = join_path(<<"/v1/data">>, ID),
% TODO % TODO
@ -200,11 +195,12 @@ request_opa_document(ID, Input, {Client, Opts}) ->
CType = <<"application/json; charset=utf-8">>, CType = <<"application/json; charset=utf-8">>,
Headers = #{ Headers = #{
<<"content-type">> => CType, <<"content-type">> => CType,
<<"accept">> => CType <<"accept">> => CType
}, },
Timeout = maps:get(request_timeout, Opts), Timeout = maps:get(request_timeout, Opts),
StreamRef = gun:post(Client, Path, Headers, Body), StreamRef = gun:post(Client, Path, Headers, Body),
Deadline = erlang:monotonic_time(millisecond) + Timeout, % TODO think about implications % TODO think about implications
Deadline = erlang:monotonic_time(millisecond) + Timeout,
case gun:await(Client, StreamRef, Timeout) of case gun:await(Client, StreamRef, Timeout) of
{response, nofin, 200, _Headers} -> {response, nofin, 200, _Headers} ->
TimeoutLeft = Deadline - erlang:monotonic_time(millisecond), TimeoutLeft = Deadline - erlang:monotonic_time(millisecond),
@ -222,8 +218,7 @@ request_opa_document(ID, Input, {Client, Opts}) ->
{error, {unknown, Reason}} {error, {unknown, Reason}}
end. end.
-spec decode_document(binary()) -> -spec decode_document(binary()) -> {ok, document()} | {error, notfound}.
{ok, document()} | {error, notfound}.
decode_document(Response) -> decode_document(Response) ->
case jsx:decode(Response) of case jsx:decode(Response) of
#{<<"result">> := Result} -> #{<<"result">> := Result} ->
@ -232,8 +227,7 @@ decode_document(Response) ->
{error, notfound} {error, notfound}
end. end.
-spec get_opa_client_opts() -> -spec get_opa_client_opts() -> client_opts().
client_opts().
get_opa_client_opts() -> get_opa_client_opts() ->
maps:merge( maps:merge(
?DEFAULT_CLIENT_OPTS, ?DEFAULT_CLIENT_OPTS,
@ -249,7 +243,7 @@ normalize_path(P = <<$/, P1/binary>>) ->
S1 = byte_size(P1), S1 = byte_size(P1),
case S1 > 0 andalso binary:last(P1) of case S1 > 0 andalso binary:last(P1) of
$/ -> binary:part(P, 0, S1); $/ -> binary:part(P, 0, S1);
_ -> P _ -> P
end; end;
normalize_path(P) when is_binary(P) -> normalize_path(P) when is_binary(P) ->
normalize_path(<<$/, P/binary>>); normalize_path(<<$/, P/binary>>);

View File

@ -5,6 +5,7 @@
%% Woody handler %% Woody handler
-behaviour(woody_server_thrift_handler). -behaviour(woody_server_thrift_handler).
-export([handle_function/4]). -export([handle_function/4]).
%% %%
@ -14,7 +15,7 @@
}. }.
-record(st, { -record(st, {
pulse :: bouncer_arbiter_pulse:handlers(), pulse :: bouncer_arbiter_pulse:handlers(),
pulse_metadata :: bouncer_arbiter_pulse:metadata() pulse_metadata :: bouncer_arbiter_pulse:metadata()
}). }).
@ -27,10 +28,12 @@ handle_function(Fn, Args, WoodyCtx, Opts) ->
do_handle_function('Judge', {RulesetID, ContextIn}, WoodyCtx, Opts) -> do_handle_function('Judge', {RulesetID, ContextIn}, WoodyCtx, Opts) ->
St = #st{ St = #st{
pulse = maps:get(pulse, Opts, []), pulse = maps:get(pulse, Opts, []),
pulse_metadata = #{woody_ctx => WoodyCtx} pulse_metadata = #{woody_ctx => WoodyCtx}
}, },
try handle_judge(RulesetID, ContextIn, St) catch try
handle_judge(RulesetID, ContextIn, St)
catch
throw:{woody, Class, Details} -> throw:{woody, Class, Details} ->
woody_error:raise(Class, Details); woody_error:raise(Class, Details);
C:R:S -> C:R:S ->
@ -58,8 +61,7 @@ handle_judge(RulesetID, ContextIn, St0) ->
handle_network_error(Reason, St2) handle_network_error(Reason, St2)
end. end.
-spec handle_network_error(_Reason, st()) -> -spec handle_network_error(_Reason, st()) -> no_return().
no_return().
handle_network_error({unavailable, Reason} = Error, St) -> handle_network_error({unavailable, Reason} = Error, St) ->
ok = handle_judgement_beat({failed, Error}, St), ok = handle_judgement_beat({failed, Error}, St),
throw({woody, system, {external, resource_unavailable, genlib:format(Reason)}}); throw({woody, system, {external, resource_unavailable, genlib:format(Reason)}});
@ -69,16 +71,15 @@ handle_network_error({unknown, Reason} = Error, St) ->
%% %%
-type fragment_id() :: binary(). -type fragment_id() :: binary().
-type fragment_metadata() :: #{atom() => _}. -type fragment_metadata() :: #{atom() => _}.
-type thrift_judgement() :: bouncer_decisions_thrift:'Judgement'(). -type thrift_judgement() :: bouncer_decisions_thrift:'Judgement'().
-type thrift_context() :: bouncer_decisions_thrift:'Context'(). -type thrift_context() :: bouncer_decisions_thrift:'Context'().
-type thrift_fragment() :: bouncer_context_thrift:'ContextFragment'(). -type thrift_fragment() :: bouncer_context_thrift:'ContextFragment'().
-type thrift_fragment_type() :: bouncer_context_thrift:'ContextFragmentType'(). -type thrift_fragment_type() :: bouncer_context_thrift:'ContextFragmentType'().
-spec encode_judgement(bouncer_arbiter:judgement()) -> -spec encode_judgement(bouncer_arbiter:judgement()) -> thrift_judgement().
thrift_judgement().
encode_judgement({Resolution, _Assertions}) -> encode_judgement({Resolution, _Assertions}) ->
#bdcs_Judgement{ #bdcs_Judgement{
resolution = encode_resolution(Resolution) resolution = encode_resolution(Resolution)
@ -97,15 +98,14 @@ encode_restrictions(Restrictions) ->
{struct, _, StructDef} = bouncer_restriction_thrift:struct_info('Restrictions'), {struct, _, StructDef} = bouncer_restriction_thrift:struct_info('Restrictions'),
bouncer_thrift:json_to_thrift_struct(StructDef, Restrictions, #brstn_Restrictions{}). bouncer_thrift:json_to_thrift_struct(StructDef, Restrictions, #brstn_Restrictions{}).
-spec decode_context(thrift_context(), st()) -> -spec decode_context(thrift_context(), st()) -> {bouncer_context:ctx(), st()}.
{bouncer_context:ctx(), st()}.
decode_context(#bdcs_Context{fragments = FragmentsIn}, St0) -> decode_context(#bdcs_Context{fragments = FragmentsIn}, St0) ->
% 1. Decode each fragment. % 1. Decode each fragment.
{Fragments, St1} = decode_fragments(FragmentsIn, St0), {Fragments, St1} = decode_fragments(FragmentsIn, St0),
% 2. Merge each decoded context into an empty context. Accumulate conflicts associated with % 2. Merge each decoded context into an empty context. Accumulate conflicts associated with
% corresponding fragment id. % corresponding fragment id.
{Ctx, Conflicts} = maps:fold( {Ctx, Conflicts} = maps:fold(
fun (ID, Ctx, {CtxAcc, DiscardAcc}) -> fun(ID, Ctx, {CtxAcc, DiscardAcc}) ->
case bouncer_context:merge(CtxAcc, Ctx) of case bouncer_context:merge(CtxAcc, Ctx) of
{CtxAcc1, undefined} -> {CtxAcc1, undefined} ->
{CtxAcc1, DiscardAcc}; {CtxAcc1, DiscardAcc};
@ -135,14 +135,14 @@ decode_context(#bdcs_Context{fragments = FragmentsIn}, St0) ->
{#{fragment_id() => bouncer_context:ctx()}, st()}. {#{fragment_id() => bouncer_context:ctx()}, st()}.
decode_fragments(Fragments, St0) -> decode_fragments(Fragments, St0) ->
{Ctxs, Errors, PulseMeta} = maps:fold( {Ctxs, Errors, PulseMeta} = maps:fold(
fun (ID, Fragment, {CtxAcc, ErrorAcc, PulseMetaAcc}) -> fun(ID, Fragment, {CtxAcc, ErrorAcc, PulseMetaAcc}) ->
Type = Fragment#bctx_ContextFragment.type, Type = Fragment#bctx_ContextFragment.type,
Content = genlib:define(Fragment#bctx_ContextFragment.content, <<>>), Content = genlib:define(Fragment#bctx_ContextFragment.content, <<>>),
case decode_fragment(Type, Content) of case decode_fragment(Type, Content) of
{ok, Ctx, Meta} -> {ok, Ctx, Meta} ->
PulseMeta = #{ PulseMeta = #{
type => Type, type => Type,
context => Ctx, context => Ctx,
metadata => Meta metadata => Meta
}, },
{ {
@ -177,12 +177,10 @@ decode_fragment(v1_thrift_binary, Content) ->
%% %%
-spec append_pulse_metadata(bouncer_arbiter_pulse:metadata(), st()) -> -spec append_pulse_metadata(bouncer_arbiter_pulse:metadata(), st()) -> st().
st().
append_pulse_metadata(Metadata, St = #st{pulse_metadata = MetadataWas}) -> append_pulse_metadata(Metadata, St = #st{pulse_metadata = MetadataWas}) ->
St#st{pulse_metadata = maps:merge(MetadataWas, Metadata)}. St#st{pulse_metadata = maps:merge(MetadataWas, Metadata)}.
-spec handle_judgement_beat(_Beat, st()) -> -spec handle_judgement_beat(_Beat, st()) -> ok.
ok.
handle_judgement_beat(Beat, #st{pulse = Pulse, pulse_metadata = Metadata}) -> handle_judgement_beat(Beat, #st{pulse = Pulse, pulse_metadata = Metadata}) ->
bouncer_arbiter_pulse:handle_beat({judgement, Beat}, Metadata, Pulse). bouncer_arbiter_pulse:handle_beat({judgement, Beat}, Metadata, Pulse).

View File

@ -2,22 +2,21 @@
-type beat() :: -type beat() ::
{judgement, {judgement,
started | started
{completed, bouncer_arbiter:judgement()} | | {completed, bouncer_arbiter:judgement()}
{failed, _Reason} | {failed, _Reason}}.
}.
-type metadata() :: #{ -type metadata() :: #{
ruleset => id(), ruleset => id(),
context => bouncer_context:ctx(), context => bouncer_context:ctx(),
fragments => #{id() => fragment()}, fragments => #{id() => fragment()},
woody_ctx => woody_context:ctx() woody_ctx => woody_context:ctx()
}. }.
-type id() :: binary(). -type id() :: binary().
-type fragment() :: #{ -type fragment() :: #{
type => atom(), type => atom(),
context => bouncer_context:ctx(), context => bouncer_context:ctx(),
metadata => map() metadata => map()
}. }.
@ -30,18 +29,17 @@
-type handler(St) :: {module(), St}. -type handler(St) :: {module(), St}.
-type handlers() :: [handler()]. -type handlers() :: [handler()].
-type handlers(St) :: [handler(St)]. -type handlers(St) :: [handler(St)].
-export_type([handler/0]). -export_type([handler/0]).
-export_type([handler/1]). -export_type([handler/1]).
-export_type([handlers/0]). -export_type([handlers/0]).
-export_type([handlers/1]). -export_type([handlers/1]).
-callback handle_beat(beat(), metadata(), _Opts) -> -callback handle_beat(beat(), metadata(), _Opts) -> ok.
ok.
-export([handle_beat/3]). -export([handle_beat/3]).
-spec handle_beat(beat(), metadata(), handlers()) -> -spec handle_beat(beat(), metadata(), handlers()) -> ok.
ok.
handle_beat(Beat, Metadata, [{Mod, Opts} | Rest]) -> handle_beat(Beat, Metadata, [{Mod, Opts} | Rest]) ->
% NOTE % NOTE
% Generally, we don't want some fault to propagate from event handler to the business logic % Generally, we don't want some fault to propagate from event handler to the business logic

View File

@ -4,6 +4,7 @@
-export([stop/1]). -export([stop/1]).
-behaviour(bouncer_arbiter_pulse). -behaviour(bouncer_arbiter_pulse).
-export([handle_beat/3]). -export([handle_beat/3]).
-define(DEFAULT_LOG_LEVEL, notice). -define(DEFAULT_LOG_LEVEL, notice).
@ -54,8 +55,7 @@
-type st() :: -type st() ::
{log, logger:level()}. {log, logger:level()}.
-spec init(opts()) -> -spec init(opts()) -> bouncer_arbiter_pulse:handlers(st()).
bouncer_arbiter_pulse:handlers(st()).
init(Opts) -> init(Opts) ->
_ = assert_strict_opts(?OPTS, Opts), _ = assert_strict_opts(?OPTS, Opts),
init_log_handler(maps:get(log, Opts, #{})). init_log_handler(maps:get(log, Opts, #{})).
@ -66,7 +66,7 @@ init_log_handler(LogOpts = #{}) ->
BackendConfig = mk_logger_backend_config(maps:get(backend, LogOpts, #{})), BackendConfig = mk_logger_backend_config(maps:get(backend, LogOpts, #{})),
HandlerConfig0 = maps:with([formatter], LogOpts), HandlerConfig0 = maps:with([formatter], LogOpts),
HandlerConfig1 = HandlerConfig0#{ HandlerConfig1 = HandlerConfig0#{
config => BackendConfig, config => BackendConfig,
% NOTE % NOTE
% This two options together ensure that _only_ audit logs will flow through to the backend. % This two options together ensure that _only_ audit logs will flow through to the backend.
filters => [{domain, {fun logger_filters:domain/2, {log, sub, ?LOG_DOMAIN}}}], filters => [{domain, {fun logger_filters:domain/2, {log, sub, ?LOG_DOMAIN}}}],
@ -93,11 +93,7 @@ mk_logger_backend_config(BackendOpts) ->
Type = validate_log_type(maps:get(type, BackendOpts, standard_io)), Type = validate_log_type(maps:get(type, BackendOpts, standard_io)),
mk_logger_backend_config(Type, BackendOpts). mk_logger_backend_config(Type, BackendOpts).
validate_log_type(Type) when validate_log_type(Type) when Type == standard_io; Type == standard_error; Type == file ->
Type == standard_io;
Type == standard_error;
Type == file
->
Type; Type;
validate_log_type(Type) -> validate_log_type(Type) ->
erlang:error(badarg, [Type]). erlang:error(badarg, [Type]).
@ -158,13 +154,11 @@ assert_strict_opts(Ks, Opts) ->
%% %%
-spec stop(opts()) -> -spec stop(opts()) -> ok.
ok.
stop(Opts = #{}) -> stop(Opts = #{}) ->
stop_log_handler(maps:get(log, Opts, #{})). stop_log_handler(maps:get(log, Opts, #{})).
-spec stop_log_handler(log_opts()) -> -spec stop_log_handler(log_opts()) -> ok.
ok.
stop_log_handler(LogOpts = #{}) -> stop_log_handler(LogOpts = #{}) ->
Level = maps:get(level, LogOpts, ?DEFAULT_LOG_LEVEL), Level = maps:get(level, LogOpts, ?DEFAULT_LOG_LEVEL),
ok = log(Level, "audit log stopped", #{}), ok = log(Level, "audit log stopped", #{}),
@ -175,11 +169,10 @@ stop_log_handler(disabled) ->
%% %%
-type beat() :: bouncer_arbiter_pulse:beat(). -type beat() :: bouncer_arbiter_pulse:beat().
-type metadata() :: bouncer_arbiter_pulse:metadata(). -type metadata() :: bouncer_arbiter_pulse:metadata().
-spec handle_beat(beat(), metadata(), st()) -> -spec handle_beat(beat(), metadata(), st()) -> ok.
ok.
handle_beat(Beat, Metadata, {log, Level}) -> handle_beat(Beat, Metadata, {log, Level}) ->
log( log(
get_severity(Beat, Level), get_severity(Beat, Level),
@ -200,34 +193,35 @@ log(Severity, Message, Metadata) ->
ok. ok.
get_severity({judgement, started}, _Level) -> debug; get_severity({judgement, started}, _Level) -> debug;
get_severity(_, Level) -> Level. get_severity(_, Level) -> Level.
get_message({judgement, started}) -> <<"judgement started">>; get_message({judgement, started}) -> <<"judgement started">>;
get_message({judgement, {completed, _}}) -> <<"judgement completed">>; get_message({judgement, {completed, _}}) -> <<"judgement completed">>;
get_message({judgement, {failed, _}}) -> <<"judgement failed">>. get_message({judgement, {failed, _}}) -> <<"judgement failed">>.
get_beat_metadata({judgement, Event}) -> get_beat_metadata({judgement, Event}) ->
#{ #{
judgement => case Event of judgement =>
started -> case Event of
#{ started ->
event => started #{
}; event => started
{completed, {Resolution, Assertions}} -> };
encode_restrictions(Resolution, #{ {completed, {Resolution, Assertions}} ->
event => completed, encode_restrictions(Resolution, #{
resolution => encode_resolution(Resolution), event => completed,
assertions => lists:map(fun encode_assertion/1, Assertions) resolution => encode_resolution(Resolution),
}); assertions => lists:map(fun encode_assertion/1, Assertions)
{failed, Error} -> });
#{ {failed, Error} ->
event => failed, #{
error => encode_error(Error) event => failed,
} error => encode_error(Error)
end }
end
}. }.
encode_resolution(allowed) -> <<"allowed">>; encode_resolution(allowed) -> <<"allowed">>;
encode_resolution(forbidden) -> <<"forbidden">>; encode_resolution(forbidden) -> <<"forbidden">>;
encode_resolution({restricted, _Restrictions}) -> <<"restricted">>. encode_resolution({restricted, _Restrictions}) -> <<"restricted">>.
@ -257,7 +251,7 @@ extract_metadata(Metadata, Acc) ->
extract_opt_meta(K, Metadata, EncodeFun, Acc) -> extract_opt_meta(K, Metadata, EncodeFun, Acc) ->
case maps:find(K, Metadata) of case maps:find(K, Metadata) of
{ok, V} -> Acc#{K => EncodeFun(V)}; {ok, V} -> Acc#{K => EncodeFun(V)};
error -> Acc error -> Acc
end. end.
encode_id(ID) when is_binary(ID) -> encode_id(ID) when is_binary(ID) ->

View File

@ -23,17 +23,14 @@
%% %%
-spec empty() -> -spec empty() -> ctx().
ctx().
empty() -> empty() ->
#{}. #{}.
-spec merge(ctx(), ctx()) -> -spec merge(ctx(), ctx()) -> {_Merged :: ctx(), _Conflicting :: ctx() | undefined}.
{_Merged :: ctx(), _Conflicting :: ctx() | undefined}.
merge(Ctx1, Ctx2) -> merge(Ctx1, Ctx2) ->
maps:fold( maps:fold(
fun (K, V2, {CtxAcc, ConflictAcc}) -> fun(K, V2, {CtxAcc, ConflictAcc}) ->
case maps:find(K, CtxAcc) of case maps:find(K, CtxAcc) of
{ok, V1} -> {ok, V1} ->
{VM, Conflict} = merge_values(V1, V2), {VM, Conflict} = merge_values(V1, V2),
@ -54,10 +51,11 @@ merge_values(V1, V2) ->
case ordsets:is_set(V1) and ordsets:is_set(V2) of case ordsets:is_set(V1) and ordsets:is_set(V2) of
true -> true ->
Intersection = ordsets:intersection(V1, V2), Intersection = ordsets:intersection(V1, V2),
MaybeConflict = case ordsets:size(Intersection) of MaybeConflict =
0 -> undefined; case ordsets:size(Intersection) of
_ -> Intersection 0 -> undefined;
end, _ -> Intersection
end,
{ordsets:union(V1, V2), MaybeConflict}; {ordsets:union(V1, V2), MaybeConflict};
false when V1 =:= V2 -> false when V1 =:= V2 ->
{V2, undefined}; {V2, undefined};

View File

@ -7,9 +7,9 @@
-type metadata() :: #{ -type metadata() :: #{
version := #{ version := #{
current := vsn(), current := vsn(),
original := vsn(), original := vsn(),
latest := vsn() latest := vsn()
} }
}. }.
@ -19,13 +19,13 @@
%% %%
-define(THRIFT_TYPE, -define(THRIFT_TYPE,
{struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}}). {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}}
).
-type thrift_ctx_fragment() :: bouncer_context_v1_thrift:'ContextFragment'(). -type thrift_ctx_fragment() :: bouncer_context_v1_thrift:'ContextFragment'().
-spec decode(format(), _Content :: binary()) -> -spec decode(format(), _Content :: binary()) ->
{ok, bouncer_context:ctx(), metadata()} | {error, _Reason}. {ok, bouncer_context:ctx(), metadata()} | {error, _Reason}.
decode(thrift, Content) -> decode(thrift, Content) ->
Codec = thrift_strict_binary_codec:new(Content), Codec = thrift_strict_binary_codec:new(Content),
case thrift_strict_binary_codec:read(Codec, ?THRIFT_TYPE) of case thrift_strict_binary_codec:read(Codec, ?THRIFT_TYPE) of
@ -40,15 +40,14 @@ decode(thrift, Content) ->
Error Error
end. end.
-spec from_thrift(thrift_ctx_fragment()) -> -spec from_thrift(thrift_ctx_fragment()) -> {ok, bouncer_context:ctx(), metadata()}.
{ok, bouncer_context:ctx(), metadata()}.
from_thrift(#bctx_v1_ContextFragment{} = Ctx0) -> from_thrift(#bctx_v1_ContextFragment{} = Ctx0) ->
Ctx1 = try_upgrade(Ctx0), Ctx1 = try_upgrade(Ctx0),
Metadata = #{ Metadata = #{
version => #{ version => #{
current => Ctx1#bctx_v1_ContextFragment.vsn, current => Ctx1#bctx_v1_ContextFragment.vsn,
original => Ctx0#bctx_v1_ContextFragment.vsn, original => Ctx0#bctx_v1_ContextFragment.vsn,
latest => ?BCTX_V1_HEAD latest => ?BCTX_V1_HEAD
} }
}, },
{ok, from_thrift_context(Ctx1), Metadata}. {ok, from_thrift_context(Ctx1), Metadata}.
@ -60,15 +59,13 @@ from_thrift_context(Ctx) ->
% This 3 refers to the first data field in a ContextFragment, after version field. % This 3 refers to the first data field in a ContextFragment, after version field.
bouncer_thrift:from_thrift_struct(StructDef, Ctx, 3, #{}). bouncer_thrift:from_thrift_struct(StructDef, Ctx, 3, #{}).
-spec try_upgrade(thrift_ctx_fragment()) -> -spec try_upgrade(thrift_ctx_fragment()) -> thrift_ctx_fragment().
thrift_ctx_fragment().
try_upgrade(#bctx_v1_ContextFragment{vsn = ?BCTX_V1_HEAD} = Ctx) -> try_upgrade(#bctx_v1_ContextFragment{vsn = ?BCTX_V1_HEAD} = Ctx) ->
Ctx. Ctx.
%% %%
-spec encode(format(), bouncer_context:ctx()) -> -spec encode(format(), bouncer_context:ctx()) -> {ok, _Content} | {error, _}.
{ok, _Content} | {error, _}.
encode(thrift, Context) -> encode(thrift, Context) ->
Codec = thrift_strict_binary_codec:new(), Codec = thrift_strict_binary_codec:new(),
CtxThrift = to_thrift(Context), CtxThrift = to_thrift(Context),
@ -79,8 +76,7 @@ encode(thrift, Context) ->
Error Error
end. end.
-spec to_thrift(bouncer_context:ctx()) -> -spec to_thrift(bouncer_context:ctx()) -> thrift_ctx_fragment() | no_return().
thrift_ctx_fragment() | no_return().
to_thrift(Context) -> to_thrift(Context) ->
{struct, _, StructDef} = bouncer_context_v1_thrift:struct_info('ContextFragment'), {struct, _, StructDef} = bouncer_context_v1_thrift:struct_info('ContextFragment'),
bouncer_thrift:to_thrift_struct(StructDef, Context, #bctx_v1_ContextFragment{}). bouncer_thrift:to_thrift_struct(StructDef, Context, #bctx_v1_ContextFragment{}).

View File

@ -12,12 +12,18 @@
-type type_ref() :: {module(), atom()}. -type type_ref() :: {module(), atom()}.
-type field_type() :: -type field_type() ::
bool | byte | i16 | i32 | i64 | string | double | bool
{enum, type_ref()} | | byte
{struct, struct_flavour(), type_ref()} | | i16
{list, field_type()} | | i32
{set, field_type()} | | i64
{map, field_type(), field_type()}. | string
| double
| {enum, type_ref()}
| {struct, struct_flavour(), type_ref()}
| {list, field_type()}
| {set, field_type()}
| {map, field_type(), field_type()}.
-type struct_field_info() :: -type struct_field_info() ::
{field_num(), field_req(), field_type(), field_name(), any()}. {field_num(), field_req(), field_type(), field_name(), any()}.

View File

@ -27,9 +27,7 @@
-define(OPA_ENDPOINT, {?OPA_HOST, 8181}). -define(OPA_ENDPOINT, {?OPA_HOST, 8181}).
-define(API_RULESET_ID, "service/authz/api"). -define(API_RULESET_ID, "service/authz/api").
-spec all() -> -spec all() -> [atom()].
[atom()].
all() -> all() ->
[ [
invalid_config_fails_start, invalid_config_fails_start,
@ -43,31 +41,24 @@ all() ->
% write_queue_overload_fails_request % write_queue_overload_fails_request
]. ].
-spec init_per_suite(config()) -> -spec init_per_suite(config()) -> config().
config().
init_per_suite(C) -> init_per_suite(C) ->
Apps = Apps =
genlib_app:start_application(woody) ++ genlib_app:start_application(woody) ++
genlib_app:start_application_with(scoper, [ genlib_app:start_application_with(scoper, [
{storage, scoper_storage_logger} {storage, scoper_storage_logger}
]), ]),
[{suite_apps, Apps} | C]. [{suite_apps, Apps} | C].
-spec end_per_suite(config()) -> -spec end_per_suite(config()) -> ok.
ok.
end_per_suite(C) -> end_per_suite(C) ->
genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)). genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)).
-spec init_per_testcase(testcase_name(), config()) -> -spec init_per_testcase(testcase_name(), config()) -> config().
config().
init_per_testcase(Name, C) -> init_per_testcase(Name, C) ->
[{testcase, Name} | C]. [{testcase, Name} | C].
-spec end_per_testcase(testcase_name(), config()) -> -spec end_per_testcase(testcase_name(), config()) -> config().
config().
end_per_testcase(_Name, _C) -> end_per_testcase(_Name, _C) ->
ok. ok.
@ -83,48 +74,71 @@ end_per_testcase(_Name, _C) ->
invalid_config_fails_start(C) -> invalid_config_fails_start(C) ->
?assertError( ?assertError(
{bouncer, {{{badkey, file}, _Stacktrace}, _}}, {bouncer, {{{badkey, file}, _Stacktrace}, _}},
start_stop_bouncer([ start_stop_bouncer(
{audit, #{log => #{ [
backend => #{ {audit, #{
% NOTE log => #{
% Missing target filename here. backend => #{
type => file % NOTE
} % Missing target filename here.
}}} type => file
], C) }
}
}}
],
C
)
), ),
?assertError( ?assertError(
{bouncer, {{badarg, _Stacktrace}, _}}, {bouncer, {{badarg, _Stacktrace}, _}},
start_stop_bouncer([ start_stop_bouncer(
{audit, #{log => #{ [
level => blarg {audit, #{
}}} log => #{
], C) level => blarg
}
}}
],
C
)
). ).
unrecognized_config_fails_start(C) -> unrecognized_config_fails_start(C) ->
?assertError( ?assertError(
{bouncer, {{{unrecognized_opts, #{blarg := _}}, _Stacktrace}, _}}, {bouncer, {{{unrecognized_opts, #{blarg := _}}, _Stacktrace}, _}},
start_stop_bouncer([ start_stop_bouncer(
{audit, #{blarg => blorg}} [
], C) {audit, #{blarg => blorg}}
],
C
)
), ),
?assertError( ?assertError(
{bouncer, {{{unrecognized_opts, #{blarg := _}}, _Stacktrace}, _}}, {bouncer, {{{unrecognized_opts, #{blarg := _}}, _Stacktrace}, _}},
start_stop_bouncer([ start_stop_bouncer(
{audit, #{log => #{ [
blarg => blorg {audit, #{
}}} log => #{
], C) blarg => blorg
}
}}
],
C
)
), ),
?assertError( ?assertError(
{bouncer, {{{unrecognized_opts, #{hello := _}}, _Stacktrace}, _}}, {bouncer, {{{unrecognized_opts, #{hello := _}}, _Stacktrace}, _}},
start_stop_bouncer([ start_stop_bouncer(
{audit, #{log => #{ [
level => notice, {audit, #{
hello => <<"mike">> log => #{
}}} level => notice,
], C) hello => <<"mike">>
}
}}
],
C
)
). ).
start_stop_bouncer(Env, C) -> start_stop_bouncer(Env, C) ->
@ -135,11 +149,16 @@ start_stop_bouncer(Env, C) ->
write_error_fails_request(C) -> write_error_fails_request(C) ->
Dirname = mk_temp_dir(?CONFIG(testcase, C)), Dirname = mk_temp_dir(?CONFIG(testcase, C)),
Filename = filename:join(Dirname, "audit.log"), Filename = filename:join(Dirname, "audit.log"),
C1 = start_bouncer([{audit, #{ C1 = start_bouncer(
log => #{ [
backend => #{type => file, file => Filename} {audit, #{
} log => #{
}}], C), backend => #{type => file, file => Filename}
}
}}
],
C
),
Client = mk_client(C1), Client = mk_client(C1),
try try
ok = file:delete(Filename), ok = file:delete(Filename),
@ -158,24 +177,30 @@ write_queue_overload_fails_request(C) ->
Concurrency = QLen * 10, Concurrency = QLen * 10,
Dirname = mk_temp_dir(?CONFIG(testcase, C)), Dirname = mk_temp_dir(?CONFIG(testcase, C)),
Filename = filename:join(Dirname, "audit.log"), Filename = filename:join(Dirname, "audit.log"),
C1 = start_bouncer([{audit, #{ C1 = start_bouncer(
log => #{ [
backend => #{type => file, file => Filename, flush_qlen => QLen}, {audit, #{
formatter => {logger_logstash_formatter, #{single_line => true}} log => #{
} backend => #{type => file, file => Filename, flush_qlen => QLen},
}}], C), formatter => {logger_logstash_formatter, #{single_line => true}}
}
}}
],
C
),
Client = mk_client(C1), Client = mk_client(C1),
Results = genlib_pmap:safemap( Results = genlib_pmap:safemap(
fun (_) -> fun(_) ->
call_judge(?API_RULESET_ID, ?CONTEXT(#{}), Client) call_judge(?API_RULESET_ID, ?CONTEXT(#{}), Client)
end, end,
lists:seq(1, Concurrency) lists:seq(1, Concurrency)
), ),
_ = stop_bouncer(C1), _ = stop_bouncer(C1),
try try
{Succeeded, _Failed} = lists:partition(fun ({R, _}) -> R == ok end, Results), {Succeeded, _Failed} = lists:partition(fun({R, _}) -> R == ok end, Results),
{ok, LogfileContents} = file:read_file(Filename), {ok, LogfileContents} = file:read_file(Filename),
NumLogEvents = binary:matches(LogfileContents, <<"\"type\":\"audit\"">>), % TODO kinda hacky % TODO kinda hacky
NumLogEvents = binary:matches(LogfileContents, <<"\"type\":\"audit\"">>),
?assertEqual(length(Succeeded), length(NumLogEvents)) ?assertEqual(length(Succeeded), length(NumLogEvents))
after after
rm_temp_dir(Dirname) rm_temp_dir(Dirname)
@ -229,21 +254,24 @@ start_bouncer(Env, C) ->
IP = "127.0.0.1", IP = "127.0.0.1",
Port = 8022, Port = 8022,
ArbiterPath = <<"/v1/arbiter">>, ArbiterPath = <<"/v1/arbiter">>,
Apps = start_application_with(bouncer, [ Apps = start_application_with(
{ip, IP}, bouncer,
{port, Port}, [
{services, #{ {ip, IP},
arbiter => #{path => ArbiterPath} {port, Port},
}}, {services, #{
{transport_opts, #{ arbiter => #{path => ArbiterPath}
max_connections => 1000, }},
num_acceptors => 4 {transport_opts, #{
}}, max_connections => 1000,
{opa, #{ num_acceptors => 4
endpoint => ?OPA_ENDPOINT, }},
transport => tcp {opa, #{
}} endpoint => ?OPA_ENDPOINT,
] ++ Env), transport => tcp
}}
] ++ Env
),
Services = #{ Services = #{
arbiter => mk_url(IP, Port, ArbiterPath) arbiter => mk_url(IP, Port, ArbiterPath)
}, },
@ -253,8 +281,11 @@ mk_url(IP, Port, Path) ->
iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]). iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]).
stop_bouncer(C) -> stop_bouncer(C) ->
ct_helper:with_config(testcase_apps, C, ct_helper:with_config(
fun (Apps) -> genlib_app:stop_unload_applications(Apps) end). testcase_apps,
C,
fun(Apps) -> genlib_app:stop_unload_applications(Apps) end
).
start_application_with(App, Env) -> start_application_with(App, Env) ->
_ = application:load(App), _ = application:load(App),

View File

@ -28,16 +28,13 @@
-define(OPA_ENDPOINT, {?OPA_HOST, 8181}). -define(OPA_ENDPOINT, {?OPA_HOST, 8181}).
-define(API_RULESET_ID, "service/authz/api"). -define(API_RULESET_ID, "service/authz/api").
-spec all() -> -spec all() -> [atom()].
[atom()].
all() -> all() ->
[ [
{group, general} {group, general}
]. ].
-spec groups() -> -spec groups() -> [{group_name(), list(), [test_case_name()]}].
[{group_name(), list(), [test_case_name()]}].
groups() -> groups() ->
[ [
{general, [parallel], [ {general, [parallel], [
@ -45,27 +42,21 @@ groups() ->
]} ]}
]. ].
-spec init_per_suite(config()) -> -spec init_per_suite(config()) -> config().
config().
init_per_suite(C) -> init_per_suite(C) ->
Apps = Apps =
genlib_app:start_application(woody) ++ genlib_app:start_application(woody) ++
genlib_app:start_application_with(scoper, [ genlib_app:start_application_with(scoper, [
{storage, scoper_storage_logger} {storage, scoper_storage_logger}
]), ]),
[{suite_apps, Apps} | C]. [{suite_apps, Apps} | C].
-spec end_per_suite(config()) -> -spec end_per_suite(config()) -> ok.
ok.
end_per_suite(C) -> end_per_suite(C) ->
genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)). genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)).
-spec init_per_group(group_name(), config()) -> -spec init_per_group(group_name(), config()) -> config().
config(). init_per_group(Name, C) when Name == general ->
init_per_group(Name, C) when
Name == general
->
start_bouncer([], C); start_bouncer([], C);
init_per_group(_Name, C) -> init_per_group(_Name, C) ->
C. C.
@ -74,15 +65,18 @@ start_bouncer(Env, C) ->
IP = "127.0.0.1", IP = "127.0.0.1",
Port = 8022, Port = 8022,
OrgmgmtPath = <<"/v1/org_management_stub">>, OrgmgmtPath = <<"/v1/org_management_stub">>,
Apps = genlib_app:start_application_with(bouncer, [ Apps = genlib_app:start_application_with(
{ip, IP}, bouncer,
{port, Port}, [
{services, #{ {ip, IP},
org_management => #{ {port, Port},
path => OrgmgmtPath {services, #{
} org_management => #{
}} path => OrgmgmtPath
] ++ Env), }
}}
] ++ Env
),
Services = #{ Services = #{
org_management => mk_url(IP, Port, OrgmgmtPath) org_management => mk_url(IP, Port, OrgmgmtPath)
}, },
@ -91,24 +85,22 @@ start_bouncer(Env, C) ->
mk_url(IP, Port, Path) -> mk_url(IP, Port, Path) ->
iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]). iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]).
-spec end_per_group(group_name(), config()) -> -spec end_per_group(group_name(), config()) -> _.
_.
end_per_group(_Name, C) -> end_per_group(_Name, C) ->
stop_bouncer(C). stop_bouncer(C).
stop_bouncer(C) -> stop_bouncer(C) ->
ct_helper:with_config(group_apps, C, ct_helper:with_config(
fun (Apps) -> genlib_app:stop_unload_applications(Apps) end). group_apps,
C,
-spec init_per_testcase(atom(), config()) -> fun(Apps) -> genlib_app:stop_unload_applications(Apps) end
config(). ).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(Name, C) -> init_per_testcase(Name, C) ->
[{testcase, Name} | C]. [{testcase, Name} | C].
-spec end_per_testcase(atom(), config()) -> -spec end_per_testcase(atom(), config()) -> config().
config().
end_per_testcase(_Name, _C) -> end_per_testcase(_Name, _C) ->
ok. ok.

View File

@ -31,6 +31,7 @@
-export([request_timeout_means_unknown/1]). -export([request_timeout_means_unknown/1]).
-behaviour(bouncer_arbiter_pulse). -behaviour(bouncer_arbiter_pulse).
-export([handle_beat/3]). -export([handle_beat/3]).
-include_lib("bouncer_proto/include/bouncer_decisions_thrift.hrl"). -include_lib("bouncer_proto/include/bouncer_decisions_thrift.hrl").
@ -47,9 +48,7 @@
-define(OPA_ENDPOINT, {?OPA_HOST, 8181}). -define(OPA_ENDPOINT, {?OPA_HOST, 8181}).
-define(API_RULESET_ID, "service/authz/api"). -define(API_RULESET_ID, "service/authz/api").
-spec all() -> -spec all() -> [atom()].
[atom()].
all() -> all() ->
[ [
{group, general}, {group, general},
@ -57,8 +56,7 @@ all() ->
{group, network_error_mapping} {group, network_error_mapping}
]. ].
-spec groups() -> -spec groups() -> [{group_name(), list(), [test_case_name()]}].
[{group_name(), list(), [test_case_name()]}].
groups() -> groups() ->
[ [
{general, [parallel], [ {general, [parallel], [
@ -84,28 +82,21 @@ groups() ->
]} ]}
]. ].
-spec init_per_suite(config()) -> -spec init_per_suite(config()) -> config().
config().
init_per_suite(C) -> init_per_suite(C) ->
Apps = Apps =
genlib_app:start_application(woody) ++ genlib_app:start_application(woody) ++
genlib_app:start_application_with(scoper, [ genlib_app:start_application_with(scoper, [
{storage, scoper_storage_logger} {storage, scoper_storage_logger}
]), ]),
[{suite_apps, Apps} | C]. [{suite_apps, Apps} | C].
-spec end_per_suite(config()) -> -spec end_per_suite(config()) -> ok.
ok.
end_per_suite(C) -> end_per_suite(C) ->
genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)). genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)).
-spec init_per_group(group_name(), config()) -> -spec init_per_group(group_name(), config()) -> config().
config(). init_per_group(Name, C) when Name == general; Name == rules_authz_api ->
init_per_group(Name, C) when
Name == general;
Name == rules_authz_api
->
start_bouncer([], [{groupname, Name} | C]); start_bouncer([], [{groupname, Name} | C]);
init_per_group(Name, C) -> init_per_group(Name, C) ->
[{groupname, Name} | C]. [{groupname, Name} | C].
@ -115,24 +106,27 @@ start_bouncer(Env, C) ->
Port = 8022, Port = 8022,
ArbiterPath = <<"/v1/arbiter">>, ArbiterPath = <<"/v1/arbiter">>,
{ok, StashPid} = ct_stash:start(), {ok, StashPid} = ct_stash:start(),
Apps = genlib_app:start_application_with(bouncer, [ Apps = genlib_app:start_application_with(
{ip, IP}, bouncer,
{port, Port}, [
{services, #{ {ip, IP},
arbiter => #{ {port, Port},
path => ArbiterPath, {services, #{
pulse => [{?MODULE, StashPid}] arbiter => #{
} path => ArbiterPath,
}}, pulse => [{?MODULE, StashPid}]
{transport_opts, #{ }
max_connections => 1000, }},
num_acceptors => 4 {transport_opts, #{
}}, max_connections => 1000,
{opa, #{ num_acceptors => 4
endpoint => ?OPA_ENDPOINT, }},
transport => tcp {opa, #{
}} endpoint => ?OPA_ENDPOINT,
] ++ Env), transport => tcp
}}
] ++ Env
),
Services = #{ Services = #{
arbiter => mk_url(IP, Port, ArbiterPath) arbiter => mk_url(IP, Port, ArbiterPath)
}, },
@ -141,34 +135,34 @@ start_bouncer(Env, C) ->
mk_url(IP, Port, Path) -> mk_url(IP, Port, Path) ->
iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]). iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]).
-spec end_per_group(group_name(), config()) -> -spec end_per_group(group_name(), config()) -> _.
_.
end_per_group(_Name, C) -> end_per_group(_Name, C) ->
stop_bouncer(C). stop_bouncer(C).
stop_bouncer(C) -> stop_bouncer(C) ->
ct_helper:with_config(group_apps, C, ct_helper:with_config(
fun (Apps) -> genlib_app:stop_unload_applications(Apps) end), group_apps,
ct_helper:with_config(stash, C, C,
fun (Pid) -> ?assertEqual(ok, ct_stash:destroy(Pid)) end). fun(Apps) -> genlib_app:stop_unload_applications(Apps) end
),
-spec init_per_testcase(atom(), config()) -> ct_helper:with_config(
config(). stash,
C,
fun(Pid) -> ?assertEqual(ok, ct_stash:destroy(Pid)) end
).
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(Name, C) -> init_per_testcase(Name, C) ->
[{testcase, Name} | C]. [{testcase, Name} | C].
-spec end_per_testcase(atom(), config()) -> -spec end_per_testcase(atom(), config()) -> config().
config().
end_per_testcase(_Name, _C) -> end_per_testcase(_Name, _C) ->
ok. ok.
%% %%
-define(CONTEXT(Fragments), #bdcs_Context{fragments = Fragments}). -define(CONTEXT(Fragments), #bdcs_Context{fragments = Fragments}).
-define(JUDGEMENT(Resolution), -define(JUDGEMENT(Resolution), #bdcs_Judgement{resolution = Resolution}).
#bdcs_Judgement{resolution = Resolution}).
-spec missing_ruleset_notfound(config()) -> ok. -spec missing_ruleset_notfound(config()) -> ok.
-spec incorrect_ruleset_invalid1(config()) -> ok. -spec incorrect_ruleset_invalid1(config()) -> ok.
@ -198,9 +192,11 @@ incorrect_ruleset_invalid1(C) ->
call_judge("trivial/incorrect1", ?CONTEXT(#{}), Client) call_judge("trivial/incorrect1", ?CONTEXT(#{}), Client)
), ),
?assertMatch( ?assertMatch(
{judgement, {failed, {ruleset_invalid, [ {judgement,
{data_invalid, _, wrong_size, _, [<<"resolution">>]} {failed,
]}}}, {ruleset_invalid, [
{data_invalid, _, wrong_size, _, [<<"resolution">>]}
]}}},
lists:last(flush_beats(Client, C)) lists:last(flush_beats(Client, C))
). ).
@ -211,9 +207,11 @@ incorrect_ruleset_invalid2(C) ->
call_judge("trivial/incorrect2", ?CONTEXT(#{}), Client) call_judge("trivial/incorrect2", ?CONTEXT(#{}), Client)
), ),
?assertMatch( ?assertMatch(
{judgement, {failed, {ruleset_invalid, [ {judgement,
{data_invalid, _, wrong_type, _, [<<"resolution">>, _]} {failed,
]}}}, {ruleset_invalid, [
{data_invalid, _, wrong_type, _, [<<"resolution">>, _]}
]}}},
lists:last(flush_beats(Client, C)) lists:last(flush_beats(Client, C))
). ).
@ -224,9 +222,11 @@ incorrect_ruleset_invalid3(C) ->
call_judge("trivial/incorrect3", ?CONTEXT(#{}), Client) call_judge("trivial/incorrect3", ?CONTEXT(#{}), Client)
), ),
?assertMatch( ?assertMatch(
{judgement, {failed, {ruleset_invalid, [ {judgement,
{data_invalid, _, no_extra_items_allowed, [<<"forbidden">>, [#{}], #{}], _} {failed,
]}}}, {ruleset_invalid, [
{data_invalid, _, no_extra_items_allowed, [<<"forbidden">>, [#{}], #{}], _}
]}}},
lists:last(flush_beats(Client, C)) lists:last(flush_beats(Client, C))
). ).
@ -239,9 +239,11 @@ missing_content_invalid_context(C) ->
call_judge(?API_RULESET_ID, Context, Client) call_judge(?API_RULESET_ID, Context, Client)
), ),
?assertMatch( ?assertMatch(
{judgement, {failed, {malformed_context, #{ {judgement,
<<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}} {failed,
}}}}, {malformed_context, #{
<<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}}
}}}},
lists:last(flush_beats(Client, C)) lists:last(flush_beats(Client, C))
). ).
@ -255,9 +257,11 @@ junk_content_invalid_context(C) ->
call_judge(?API_RULESET_ID, Context, Client) call_judge(?API_RULESET_ID, Context, Client)
), ),
?assertMatch( ?assertMatch(
{judgement, {failed, {malformed_context, #{ {judgement,
<<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}} {failed,
}}}}, {malformed_context, #{
<<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}}
}}}},
lists:last(flush_beats(Client, C)) lists:last(flush_beats(Client, C))
). ).
@ -265,7 +269,7 @@ conflicting_context_invalid(C) ->
Client = mk_client(C), Client = mk_client(C),
Fragment1 = #{ Fragment1 = #{
user => #{ user => #{
id => <<"joeblow">>, id => <<"joeblow">>,
email => Email1 = <<"deadinside69@example.org">> email => Email1 = <<"deadinside69@example.org">>
}, },
requester => #{ requester => #{
@ -274,7 +278,7 @@ conflicting_context_invalid(C) ->
}, },
Fragment2 = #{ Fragment2 = #{
user => #{ user => #{
id => <<"joeblow">>, id => <<"joeblow">>,
email => <<"deadinside420@example.org">> email => <<"deadinside420@example.org">>
}, },
requester => #{ requester => #{
@ -290,9 +294,11 @@ conflicting_context_invalid(C) ->
call_judge(?API_RULESET_ID, Context, Client) call_judge(?API_RULESET_ID, Context, Client)
), ),
?assertEqual( ?assertEqual(
{judgement, {failed, {conflicting_context, #{ {judgement,
<<"frag2">> => #{user => #{email => Email1}} {failed,
}}}}, {conflicting_context, #{
<<"frag2">> => #{user => #{email => Email1}}
}}}},
lists:last(flush_beats(Client, C)) lists:last(flush_beats(Client, C))
). ).
@ -300,7 +306,7 @@ distinct_sets_context_valid(C) ->
Client = mk_client(C), Client = mk_client(C),
Fragment1 = #{ Fragment1 = #{
user => #{ user => #{
id => <<"joeblow">>, id => <<"joeblow">>,
orgs => mk_ordset([ orgs => mk_ordset([
#{ #{
id => <<"hoolie">>, id => <<"hoolie">>,
@ -315,7 +321,7 @@ distinct_sets_context_valid(C) ->
}, },
Fragment2 = #{ Fragment2 = #{
user => #{ user => #{
id => <<"joeblow">>, id => <<"joeblow">>,
orgs => mk_ordset([ orgs => mk_ordset([
#{ #{
id => <<"hoolie">>, id => <<"hoolie">>,
@ -354,11 +360,18 @@ restricted_search_invoices_shop_manager(C) ->
mk_auth_session_token(), mk_auth_session_token(),
mk_env(), mk_env(),
mk_op_search_invoices(mk_ordset([#{id => <<"SHOP">>}]), <<"PARTY">>), mk_op_search_invoices(mk_ordset([#{id => <<"SHOP">>}]), <<"PARTY">>),
mk_user(<<"USER">>, mk_ordset([ mk_user(
mk_user_org(<<"PARTY">>, <<"OWNER">>, mk_ordset([ <<"USER">>,
mk_role(<<"Manager">>, <<"SHOP">>) mk_ordset([
])) mk_user_org(
])) <<"PARTY">>,
<<"OWNER">>,
mk_ordset([
mk_role(<<"Manager">>, <<"SHOP">>)
])
)
])
)
]), ]),
Context = ?CONTEXT(#{<<"root">> => mk_ctx_v1_fragment(Fragment)}), Context = ?CONTEXT(#{<<"root">> => mk_ctx_v1_fragment(Fragment)}),
?assertMatch( ?assertMatch(
@ -375,8 +388,9 @@ forbidden_expired(C) ->
% Would be funny if this fails on some system too deep in the past. % Would be funny if this fails on some system too deep in the past.
Fragment = maps:merge(mk_env(), #{ Fragment = maps:merge(mk_env(), #{
auth => #{ auth => #{
method => <<"AccessToken">>, method => <<"AccessToken">>,
expiration => <<"1991-12-26T17:00:00Z">> % 😢 % 😢
expiration => <<"1991-12-26T17:00:00Z">>
} }
}), }),
Context = ?CONTEXT(#{<<"root">> => mk_ctx_v1_fragment(Fragment)}), Context = ?CONTEXT(#{<<"root">> => mk_ctx_v1_fragment(Fragment)}),
@ -429,10 +443,12 @@ forbidden_w_empty_context(C) ->
). ).
mk_user(UserID, UserOrgs) -> mk_user(UserID, UserOrgs) ->
#{user => #{ #{
id => UserID, user => #{
orgs => UserOrgs id => UserID,
}}. orgs => UserOrgs
}
}.
mk_user_org(OrgID, OwnerID, Roles) -> mk_user_org(OrgID, OwnerID, Roles) ->
#{ #{
@ -448,24 +464,30 @@ mk_auth_session_token() ->
mk_auth_session_token(erlang:system_time(second) + 3600). mk_auth_session_token(erlang:system_time(second) + 3600).
mk_auth_session_token(ExpiresAt) -> mk_auth_session_token(ExpiresAt) ->
#{auth => #{ #{
method => <<"SessionToken">>, auth => #{
expiration => format_ts(ExpiresAt, second) method => <<"SessionToken">>,
}}. expiration => format_ts(ExpiresAt, second)
}
}.
mk_op_search_invoices(Shops, PartyID) -> mk_op_search_invoices(Shops, PartyID) ->
#{anapi => #{ #{
op => #{ anapi => #{
id => <<"SearchInvoices">>, op => #{
shops => Shops, id => <<"SearchInvoices">>,
party => #{id => PartyID} shops => Shops,
party => #{id => PartyID}
}
} }
}}. }.
mk_env() -> mk_env() ->
#{env => #{ #{
now => format_now() env => #{
}}. now => format_now()
}
}.
format_now() -> format_now() ->
USec = erlang:system_time(second), USec = erlang:system_time(second),
@ -482,11 +504,16 @@ format_ts(Ts, Unit) ->
-spec request_timeout_means_unknown(config()) -> ok. -spec request_timeout_means_unknown(config()) -> ok.
connect_failed_means_unavailable(C) -> connect_failed_means_unavailable(C) ->
C1 = start_bouncer([{opa, #{ C1 = start_bouncer(
endpoint => {?OPA_HOST, 65535}, [
transport => tcp, {opa, #{
event_handler => {ct_gun_event_h, []} endpoint => {?OPA_HOST, 65535},
}}], C), transport => tcp,
event_handler => {ct_gun_event_h, []}
}}
],
C
),
Client = mk_client(C1), Client = mk_client(C1),
try try
?assertError( ?assertError(
@ -550,16 +577,24 @@ request_timeout_means_unknown(C) ->
end. end.
start_proxy_bouncer(Proxy, C) -> start_proxy_bouncer(Proxy, C) ->
start_bouncer([{opa, #{ start_bouncer(
endpoint => ct_proxy:endpoint(Proxy), [
transport => tcp, {opa, #{
event_handler => {ct_gun_event_h, []} endpoint => ct_proxy:endpoint(Proxy),
}}], C). transport => tcp,
event_handler => {ct_gun_event_h, []}
}}
],
C
).
change_proxy_mode(Proxy, Scope, Mode, C) -> change_proxy_mode(Proxy, Scope, Mode, C) ->
ModeWas = ct_proxy:mode(Proxy, Scope, Mode), ModeWas = ct_proxy:mode(Proxy, Scope, Mode),
_ = ct:pal(debug, "[~p] set proxy ~p from '~p' to '~p'", _ = ct:pal(
[?CONFIG(testcase, C), Scope, ModeWas, Mode]), debug,
"[~p] set proxy ~p from '~p' to '~p'",
[?CONFIG(testcase, C), Scope, ModeWas, Mode]
),
ok. ok.
%% %%
@ -599,8 +634,7 @@ get_service_spec(arbiter) ->
%% %%
-spec handle_beat(bouncer_arbiter_pulse:beat(), bouncer_arbiter_pulse:metadata(), pid()) -> -spec handle_beat(bouncer_arbiter_pulse:beat(), bouncer_arbiter_pulse:metadata(), pid()) -> ok.
ok.
handle_beat(Beat, Metadata, StashPid) -> handle_beat(Beat, Metadata, StashPid) ->
_ = stash_beat(Beat, Metadata, StashPid), _ = stash_beat(Beat, Metadata, StashPid),
ct:pal("~p [arbiter] ~0p:~nmetadata=~p", [self(), Beat, Metadata]). ct:pal("~p [arbiter] ~0p:~nmetadata=~p", [self(), Beat, Metadata]).

View File

@ -1,4 +1,5 @@
-module(ct_gun_event_h). -module(ct_gun_event_h).
-behavior(gun_event). -behavior(gun_event).
-export([init/2]). -export([init/2]).
@ -33,176 +34,147 @@
-type st() :: _. -type st() :: _.
-spec init(gun_event:init_event(), st()) -> -spec init(gun_event:init_event(), st()) -> st().
st().
init(Event, State) -> init(Event, State) ->
_ = ct:pal("~p [gun] init: ~p", [self(), Event]), _ = ct:pal("~p [gun] init: ~p", [self(), Event]),
State. State.
-spec domain_lookup_start(gun_event:domain_lookup_event(), st()) -> -spec domain_lookup_start(gun_event:domain_lookup_event(), st()) -> st().
st().
domain_lookup_start(Event, State) -> domain_lookup_start(Event, State) ->
_ = ct:pal("~p [gun] domain lookup start: ~p", [self(), Event]), _ = ct:pal("~p [gun] domain lookup start: ~p", [self(), Event]),
State. State.
-spec domain_lookup_end(gun_event:domain_lookup_event(), st()) -> -spec domain_lookup_end(gun_event:domain_lookup_event(), st()) -> st().
st().
domain_lookup_end(Event, State) -> domain_lookup_end(Event, State) ->
_ = ct:pal("~p [gun] domain lookup end: ~p", [self(), Event]), _ = ct:pal("~p [gun] domain lookup end: ~p", [self(), Event]),
State. State.
-spec connect_start(gun_event:connect_event(), st()) -> -spec connect_start(gun_event:connect_event(), st()) -> st().
st().
connect_start(Event, State) -> connect_start(Event, State) ->
_ = ct:pal("~p [gun] connect start: ~p", [self(), Event]), _ = ct:pal("~p [gun] connect start: ~p", [self(), Event]),
State. State.
-spec connect_end(gun_event:connect_event(), st()) -> -spec connect_end(gun_event:connect_event(), st()) -> st().
st().
connect_end(Event, State) -> connect_end(Event, State) ->
_ = ct:pal("~p [gun] connect end: ~p", [self(), Event]), _ = ct:pal("~p [gun] connect end: ~p", [self(), Event]),
State. State.
-spec tls_handshake_start(gun_event:tls_handshake_event(), st()) -> -spec tls_handshake_start(gun_event:tls_handshake_event(), st()) -> st().
st().
tls_handshake_start(Event, State) -> tls_handshake_start(Event, State) ->
_ = ct:pal("~p [gun] tls handshake start: ~p", [self(), Event]), _ = ct:pal("~p [gun] tls handshake start: ~p", [self(), Event]),
State. State.
-spec tls_handshake_end(gun_event:tls_handshake_event(), st()) -> -spec tls_handshake_end(gun_event:tls_handshake_event(), st()) -> st().
st().
tls_handshake_end(Event, State) -> tls_handshake_end(Event, State) ->
_ = ct:pal("~p [gun] tls handshake end: ~p", [self(), Event]), _ = ct:pal("~p [gun] tls handshake end: ~p", [self(), Event]),
State. State.
-spec request_start(gun_event:request_start_event(), st()) -> -spec request_start(gun_event:request_start_event(), st()) -> st().
st().
request_start(Event, State) -> request_start(Event, State) ->
_ = ct:pal("~p [gun] request start: ~p", [self(), Event]), _ = ct:pal("~p [gun] request start: ~p", [self(), Event]),
State. State.
-spec request_headers(gun_event:request_start_event(), st()) -> -spec request_headers(gun_event:request_start_event(), st()) -> st().
st().
request_headers(Event, State) -> request_headers(Event, State) ->
_ = ct:pal("~p [gun] request headers: ~p", [self(), Event]), _ = ct:pal("~p [gun] request headers: ~p", [self(), Event]),
State. State.
-spec request_end(gun_event:request_end_event(), st()) -> -spec request_end(gun_event:request_end_event(), st()) -> st().
st().
request_end(Event, State) -> request_end(Event, State) ->
_ = ct:pal("~p [gun] request end: ~p", [self(), Event]), _ = ct:pal("~p [gun] request end: ~p", [self(), Event]),
State. State.
-spec push_promise_start(gun_event:push_promise_start_event(), st()) -> -spec push_promise_start(gun_event:push_promise_start_event(), st()) -> st().
st().
push_promise_start(Event, State) -> push_promise_start(Event, State) ->
_ = ct:pal("~p [gun] push promise start: ~p", [self(), Event]), _ = ct:pal("~p [gun] push promise start: ~p", [self(), Event]),
State. State.
-spec push_promise_end(gun_event:push_promise_end_event(), st()) -> -spec push_promise_end(gun_event:push_promise_end_event(), st()) -> st().
st().
push_promise_end(Event, State) -> push_promise_end(Event, State) ->
_ = ct:pal("~p [gun] push promise end: ~p", [self(), Event]), _ = ct:pal("~p [gun] push promise end: ~p", [self(), Event]),
State. State.
-spec response_start(gun_event:response_start_event(), st()) -> -spec response_start(gun_event:response_start_event(), st()) -> st().
st().
response_start(Event, State) -> response_start(Event, State) ->
_ = ct:pal("~p [gun] response start: ~p", [self(), Event]), _ = ct:pal("~p [gun] response start: ~p", [self(), Event]),
State. State.
-spec response_inform(gun_event:response_headers_event(), st()) -> -spec response_inform(gun_event:response_headers_event(), st()) -> st().
st().
response_inform(Event, State) -> response_inform(Event, State) ->
_ = ct:pal("~p [gun] response inform: ~p", [self(), Event]), _ = ct:pal("~p [gun] response inform: ~p", [self(), Event]),
State. State.
-spec response_headers(gun_event:response_headers_event(), st()) -> -spec response_headers(gun_event:response_headers_event(), st()) -> st().
st().
response_headers(Event, State) -> response_headers(Event, State) ->
_ = ct:pal("~p [gun] response headers: ~p", [self(), Event]), _ = ct:pal("~p [gun] response headers: ~p", [self(), Event]),
State. State.
-spec response_trailers(gun_event:response_trailers_event(), st()) -> -spec response_trailers(gun_event:response_trailers_event(), st()) -> st().
st().
response_trailers(Event, State) -> response_trailers(Event, State) ->
_ = ct:pal("~p [gun] response trailers: ~p", [self(), Event]), _ = ct:pal("~p [gun] response trailers: ~p", [self(), Event]),
State. State.
-spec response_end(gun_event:response_end_event(), st()) -> -spec response_end(gun_event:response_end_event(), st()) -> st().
st().
response_end(Event, State) -> response_end(Event, State) ->
_ = ct:pal("~p [gun] response end: ~p", [self(), Event]), _ = ct:pal("~p [gun] response end: ~p", [self(), Event]),
State. State.
-spec ws_upgrade(gun_event:ws_upgrade_event(), st()) -> -spec ws_upgrade(gun_event:ws_upgrade_event(), st()) -> st().
st().
ws_upgrade(Event, State) -> ws_upgrade(Event, State) ->
_ = ct:pal("~p [gun] ws upgrade: ~p", [self(), Event]), _ = ct:pal("~p [gun] ws upgrade: ~p", [self(), Event]),
State. State.
-spec ws_recv_frame_start(gun_event:ws_recv_frame_start_event(), st()) -> -spec ws_recv_frame_start(gun_event:ws_recv_frame_start_event(), st()) -> st().
st().
ws_recv_frame_start(Event, State) -> ws_recv_frame_start(Event, State) ->
_ = ct:pal("~p [gun] ws recv frame start: ~p", [self(), Event]), _ = ct:pal("~p [gun] ws recv frame start: ~p", [self(), Event]),
State. State.
-spec ws_recv_frame_header(gun_event:ws_recv_frame_header_event(), st()) -> -spec ws_recv_frame_header(gun_event:ws_recv_frame_header_event(), st()) -> st().
st().
ws_recv_frame_header(Event, State) -> ws_recv_frame_header(Event, State) ->
_ = ct:pal("~p [gun] ws recv frame header: ~p", [self(), Event]), _ = ct:pal("~p [gun] ws recv frame header: ~p", [self(), Event]),
State. State.
-spec ws_recv_frame_end(gun_event:ws_recv_frame_end_event(), st()) -> -spec ws_recv_frame_end(gun_event:ws_recv_frame_end_event(), st()) -> st().
st().
ws_recv_frame_end(Event, State) -> ws_recv_frame_end(Event, State) ->
_ = ct:pal("~p [gun] ws recv frame end: ~p", [self(), Event]), _ = ct:pal("~p [gun] ws recv frame end: ~p", [self(), Event]),
State. State.
-spec ws_send_frame_start(gun_event:ws_send_frame_event(), st()) -> -spec ws_send_frame_start(gun_event:ws_send_frame_event(), st()) -> st().
st().
ws_send_frame_start(Event, State) -> ws_send_frame_start(Event, State) ->
_ = ct:pal("~p [gun] ws send frame start: ~p", [self(), Event]), _ = ct:pal("~p [gun] ws send frame start: ~p", [self(), Event]),
State. State.
-spec ws_send_frame_end(gun_event:ws_send_frame_event(), st()) -> -spec ws_send_frame_end(gun_event:ws_send_frame_event(), st()) -> st().
st().
ws_send_frame_end(Event, State) -> ws_send_frame_end(Event, State) ->
_ = ct:pal("~p [gun] ws send frame end: ~p", [self(), Event]), _ = ct:pal("~p [gun] ws send frame end: ~p", [self(), Event]),
State. State.
-spec protocol_changed(gun_event:protocol_changed_event(), st()) -> -spec protocol_changed(gun_event:protocol_changed_event(), st()) -> st().
st().
protocol_changed(Event, State) -> protocol_changed(Event, State) ->
_ = ct:pal("~p [gun] protocol changed: ~p", [self(), Event]), _ = ct:pal("~p [gun] protocol changed: ~p", [self(), Event]),
State. State.
-spec transport_changed(gun_event:transport_changed_event(), st()) -> -spec transport_changed(gun_event:transport_changed_event(), st()) -> st().
st().
transport_changed(Event, State) -> transport_changed(Event, State) ->
_ = ct:pal("~p [gun] transport changed: ~p", [self(), Event]), _ = ct:pal("~p [gun] transport changed: ~p", [self(), Event]),
State. State.
-spec origin_changed(gun_event:origin_changed_event(), st()) -> -spec origin_changed(gun_event:origin_changed_event(), st()) -> st().
st().
origin_changed(Event, State) -> origin_changed(Event, State) ->
_ = ct:pal("~p [gun] origin changed: ~p", [self(), Event]), _ = ct:pal("~p [gun] origin changed: ~p", [self(), Event]),
State. State.
-spec cancel(gun_event:cancel_event(), st()) -> -spec cancel(gun_event:cancel_event(), st()) -> st().
st().
cancel(Event, State) -> cancel(Event, State) ->
_ = ct:pal("~p [gun] cancel: ~p", [self(), Event]), _ = ct:pal("~p [gun] cancel: ~p", [self(), Event]),
State. State.
-spec disconnect(gun_event:disconnect_event(), st()) -> -spec disconnect(gun_event:disconnect_event(), st()) -> st().
st().
disconnect(Event, State) -> disconnect(Event, State) ->
_ = ct:pal("~p [gun] disconnect: ~p", [self(), Event]), _ = ct:pal("~p [gun] disconnect: ~p", [self(), Event]),
State. State.
-spec terminate(gun_event:terminate_event(), st()) -> -spec terminate(gun_event:terminate_event(), st()) -> st().
st().
terminate(Event, State) -> terminate(Event, State) ->
_ = ct:pal("~p [gun] terminate: ~p", [self(), Event]), _ = ct:pal("~p [gun] terminate: ~p", [self(), Event]),
State. State.

View File

@ -11,26 +11,25 @@
%% %%
-spec with_config(atom(), config(), fun ((_) -> R)) -> -spec with_config(atom(), config(), fun((_) -> R)) -> R | undefined.
R | undefined.
with_config(Name, C, Fun) -> with_config(Name, C, Fun) ->
case lists:keyfind(Name, 1, C) of case lists:keyfind(Name, 1, C) of
{_, V} -> Fun(V); {_, V} -> Fun(V);
false -> undefined false -> undefined
end. end.
-spec get_temp_dir() -> -spec get_temp_dir() -> file:filename_all().
file:filename_all().
get_temp_dir() -> get_temp_dir() ->
hd(genlib_list:compact([ hd(
get_env("TMPDIR"), genlib_list:compact([
get_env("TEMP"), get_env("TMPDIR"),
get_env("TMP"), get_env("TEMP"),
"/tmp" get_env("TMP"),
])). "/tmp"
])
).
-spec get_env(string()) -> -spec get_env(string()) -> string() | undefined.
string() | undefined.
get_env(Name) -> get_env(Name) ->
case os:getenv(Name) of case os:getenv(Name) of
V when is_list(V) -> V when is_list(V) ->

View File

@ -11,6 +11,7 @@
%% %%
-behaviour(gen_server). -behaviour(gen_server).
-export([ -export([
init/1, init/1,
handle_call/3, handle_call/3,
@ -25,23 +26,19 @@
-include_lib("kernel/include/inet.hrl"). -include_lib("kernel/include/inet.hrl").
-type endpoint() :: {inet:hostname(), inet:port_number()}. -type endpoint() :: {inet:hostname(), inet:port_number()}.
-type scope() :: listen | connection. -type scope() :: listen | connection.
-type mode() :: ignore | stop | relay. -type mode() :: ignore | stop | relay.
-type modes() :: #{scope() => mode()}. -type modes() :: #{scope() => mode()}.
-type proxy() :: pid(). -type proxy() :: pid().
-spec start_link(endpoint()) -> -spec start_link(endpoint()) -> {ok, proxy()}.
{ok, proxy()}.
-spec start_link(endpoint(), modes()) -> -spec start_link(endpoint(), modes()) -> {ok, proxy()}.
{ok, proxy()}.
-spec start_link(endpoint(), modes(), ranch_tcp:opts()) -> -spec start_link(endpoint(), modes(), ranch_tcp:opts()) -> {ok, proxy()}.
{ok, proxy()}.
-spec unlink(proxy()) -> -spec unlink(proxy()) -> proxy().
proxy().
start_link(Upstream) -> start_link(Upstream) ->
start_link(Upstream, #{}). start_link(Upstream, #{}).
@ -61,44 +58,35 @@ unlink(Proxy) when is_pid(Proxy) ->
true = erlang:unlink(Proxy), true = erlang:unlink(Proxy),
Proxy. Proxy.
-spec endpoint(proxy()) -> -spec endpoint(proxy()) -> endpoint().
endpoint().
endpoint(Proxy) when is_pid(Proxy) -> endpoint(Proxy) when is_pid(Proxy) ->
gen_server:call(Proxy, endpoint). gen_server:call(Proxy, endpoint).
-spec mode(proxy(), scope()) -> -spec mode(proxy(), scope()) -> {mode(), _Upstream :: endpoint()}.
{mode(), _Upstream :: endpoint()}.
mode(Proxy, Scope) when is_pid(Proxy) -> mode(Proxy, Scope) when is_pid(Proxy) ->
gen_server:call(Proxy, {mode, Scope}). gen_server:call(Proxy, {mode, Scope}).
-spec mode(proxy(), scope(), mode()) -> -spec mode(proxy(), scope(), mode()) -> mode().
mode().
mode(Proxy, Scope, Mode) when is_pid(Proxy) -> mode(Proxy, Scope, Mode) when is_pid(Proxy) ->
gen_server:call(Proxy, {mode, Scope, Mode}). gen_server:call(Proxy, {mode, Scope, Mode}).
-spec stop(proxy()) -> -spec stop(proxy()) -> ok.
ok.
stop(Proxy) when is_pid(Proxy) -> stop(Proxy) when is_pid(Proxy) ->
proc_lib:stop(Proxy, shutdown). proc_lib:stop(Proxy, shutdown).
%% %%
-record(st, { -record(st, {
lsock :: _Socket | undefined, lsock :: _Socket | undefined,
lsockopts :: list(), lsockopts :: list(),
acceptor :: pid() | undefined, acceptor :: pid() | undefined,
modes :: #{scope() => mode()}, modes :: #{scope() => mode()},
upstream :: {inet:ip_address(), inet:port_number()} upstream :: {inet:ip_address(), inet:port_number()}
}). }).
-type st() :: #st{}. -type st() :: #st{}.
-spec init(_) -> -spec init(_) -> {ok, st()}.
{ok, st()}.
init({Upstream, Modes0, SocketOpts}) -> init({Upstream, Modes0, SocketOpts}) ->
Modes = maps:merge(#{listen => relay, connection => relay}, Modes0), Modes = maps:merge(#{listen => relay, connection => relay}, Modes0),
St = #st{ St = #st{
@ -108,9 +96,7 @@ init({Upstream, Modes0, SocketOpts}) ->
}, },
{ok, sync_mode(listen, stop, maps:get(listen, Modes), St)}. {ok, sync_mode(listen, stop, maps:get(listen, Modes), St)}.
-spec handle_call(_Call, _From, st()) -> -spec handle_call(_Call, _From, st()) -> {noreply, st()}.
{noreply, st()}.
handle_call(endpoint, _From, St = #st{}) -> handle_call(endpoint, _From, St = #st{}) ->
{reply, get_endpoint(St), St}; {reply, get_endpoint(St), St};
handle_call({mode, Scope, Mode}, _From, St = #st{modes = Modes}) -> handle_call({mode, Scope, Mode}, _From, St = #st{modes = Modes}) ->
@ -122,27 +108,19 @@ handle_call({mode, Scope}, _From, St = #st{modes = Modes, upstream = Endpoint})
handle_call(_Call, _From, St) -> handle_call(_Call, _From, St) ->
{noreply, St}. {noreply, St}.
-spec handle_cast(_Cast, st()) -> -spec handle_cast(_Cast, st()) -> {noreply, st()}.
{noreply, st()}.
handle_cast(_Cast, St) -> handle_cast(_Cast, St) ->
{noreply, St}. {noreply, St}.
-spec handle_info(_Info, st()) -> -spec handle_info(_Info, st()) -> {noreply, st()}.
{noreply, st()}.
handle_info(_Info, St) -> handle_info(_Info, St) ->
{noreply, St}. {noreply, St}.
-spec terminate(_Reason, st()) -> -spec terminate(_Reason, st()) -> _.
_.
terminate(_Reason, _St) -> terminate(_Reason, _St) ->
ok. ok.
-spec code_change(_Vsn | {down, _Vsn}, st(), _Extra) -> -spec code_change(_Vsn | {down, _Vsn}, st(), _Extra) -> {ok, st()}.
{ok, st()}.
code_change(_Vsn, St, _Extra) -> code_change(_Vsn, St, _Extra) ->
{ok, St}. {ok, St}.
@ -190,7 +168,7 @@ stop_listener(St = #st{lsock = LSock}) when lsock /= undefined ->
start_acceptor(St = #st{acceptor = undefined, lsock = LSock}) -> start_acceptor(St = #st{acceptor = undefined, lsock = LSock}) ->
ct:pal("start_acceptor @ ~p", [St]), ct:pal("start_acceptor @ ~p", [St]),
Parent = self(), Parent = self(),
Pid = erlang:spawn_link(fun () -> loop_acceptor(Parent, LSock) end), Pid = erlang:spawn_link(fun() -> loop_acceptor(Parent, LSock) end),
St#st{acceptor = Pid}. St#st{acceptor = Pid}.
stop_acceptor(St = #st{acceptor = Pid}) when is_pid(Pid) -> stop_acceptor(St = #st{acceptor = Pid}) when is_pid(Pid) ->
@ -198,19 +176,21 @@ stop_acceptor(St = #st{acceptor = Pid}) when is_pid(Pid) ->
MRef = erlang:monitor(process, Pid), MRef = erlang:monitor(process, Pid),
true = erlang:unlink(Pid), true = erlang:unlink(Pid),
true = erlang:exit(Pid, shutdown), true = erlang:exit(Pid, shutdown),
receive {'DOWN', MRef, process, Pid, _Reason} -> receive
St#st{acceptor = undefined} {'DOWN', MRef, process, Pid, _Reason} ->
St#st{acceptor = undefined}
end. end.
loop_acceptor(Parent, LSock) -> loop_acceptor(Parent, LSock) ->
_ = case ranch_tcp:accept(LSock, infinity) of _ =
{ok, CSock} -> case ranch_tcp:accept(LSock, infinity) of
_ = ct:pal("accepted ~p from ~p", [CSock, ranch_tcp:peername(CSock)]), {ok, CSock} ->
_ = spawn_proxy_connection(Parent, CSock), _ = ct:pal("accepted ~p from ~p", [CSock, ranch_tcp:peername(CSock)]),
loop_acceptor(Parent, LSock); _ = spawn_proxy_connection(Parent, CSock),
{error, Reason} -> loop_acceptor(Parent, LSock);
exit(Reason) {error, Reason} ->
end. exit(Reason)
end.
%% %%
@ -228,7 +208,7 @@ loop_acceptor(Parent, LSock) ->
spawn_proxy_connection(Parent, CSock) -> spawn_proxy_connection(Parent, CSock) ->
ProxySt = #proxy{insock = CSock, parent = Parent}, ProxySt = #proxy{insock = CSock, parent = Parent},
erlang:spawn_link(fun () -> loop_proxy_connection(ProxySt) end). erlang:spawn_link(fun() -> loop_proxy_connection(ProxySt) end).
loop_proxy_connection(St = #proxy{insock = InSock, parent = Parent, buffer = Buffer}) -> loop_proxy_connection(St = #proxy{insock = InSock, parent = Parent, buffer = Buffer}) ->
case ranch_tcp:recv(InSock, 0, ?PROXY_RECV_TIMEOUT) of case ranch_tcp:recv(InSock, 0, ?PROXY_RECV_TIMEOUT) of
@ -241,7 +221,7 @@ loop_proxy_connection(St = #proxy{insock = InSock, parent = Parent, buffer = Buf
ignore -> ignore ->
loop_proxy_connection(St); loop_proxy_connection(St);
relay -> relay ->
loop_proxy_relay(St#proxy{buffer = Buffer1, upstream = Endpoint}) loop_proxy_relay(St#proxy{buffer = Buffer1, upstream = Endpoint})
end; end;
_ -> _ ->
terminate(St) terminate(St)

View File

@ -1,4 +1,5 @@
-module(ct_stash). -module(ct_stash).
-behaviour(gen_server). -behaviour(gen_server).
-export([start/0]). -export([start/0]).
@ -22,23 +23,19 @@
-type key() :: _. -type key() :: _.
-type entry() :: _. -type entry() :: _.
-spec start() -> -spec start() -> {ok, pid()}.
{ok, pid()}.
start() -> start() ->
gen_server:start(?MODULE, [], []). gen_server:start(?MODULE, [], []).
-spec destroy(pid()) -> -spec destroy(pid()) -> ok | {error, {nonempty, _Left :: #{key() => entry()}}}.
ok | {error, {nonempty, _Left :: #{key() => entry()}}}.
destroy(Pid) -> destroy(Pid) ->
call(Pid, destroy). call(Pid, destroy).
-spec append(pid(), key(), entry()) -> -spec append(pid(), key(), entry()) -> ok.
ok.
append(Pid, Key, Entry) -> append(Pid, Key, Entry) ->
call(Pid, {append, Key, Entry}). call(Pid, {append, Key, Entry}).
-spec flush(pid(), key()) -> -spec flush(pid(), key()) -> {ok, [entry()]} | error.
{ok, [entry()]} | error.
flush(Pid, Key) -> flush(Pid, Key) ->
call(Pid, {flush, Key}). call(Pid, {flush, Key}).
@ -47,13 +44,11 @@ call(Pid, Msg) ->
%%% gen_server callbacks %%% gen_server callbacks
-spec init(term()) -> -spec init(term()) -> {ok, atom()}.
{ok, atom()}.
init(_) -> init(_) ->
{ok, #{}}. {ok, #{}}.
-spec handle_call(term(), pid(), atom()) -> -spec handle_call(term(), pid(), atom()) -> {reply, atom(), atom()}.
{reply, atom(), atom()}.
handle_call({append, Key, Entry}, _From, State) -> handle_call({append, Key, Entry}, _From, State) ->
Entries = maps:get(Key, State, []), Entries = maps:get(Key, State, []),
State1 = maps:put(Key, [Entry | Entries], State), State1 = maps:put(Key, [Entry | Entries], State),
@ -70,27 +65,23 @@ handle_call(destroy, _From, State) ->
0 -> 0 ->
{stop, shutdown, ok, State}; {stop, shutdown, ok, State};
_ -> _ ->
Left = maps:map(fun (_, Entries) -> lists:reverse(Entries) end, State), Left = maps:map(fun(_, Entries) -> lists:reverse(Entries) end, State),
Reason = {error, {nonempty, Left}}, Reason = {error, {nonempty, Left}},
{stop, Reason, Reason, State} {stop, Reason, Reason, State}
end. end.
-spec handle_cast(term(), atom()) -> {noreply, atom()}. -spec handle_cast(term(), atom()) -> {noreply, atom()}.
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
-spec handle_info(term(), atom()) -> {noreply, atom()}. -spec handle_info(term(), atom()) -> {noreply, atom()}.
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
-spec terminate(term(), atom()) -> atom(). -spec terminate(term(), atom()) -> atom().
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.
-spec code_change(term(), term(), term()) -> {ok, atom()}. -spec code_change(term(), term(), term()) -> {ok, atom()}.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.