diff --git a/.gitignore b/.gitignore index 8e46d5a..fa5ee9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,20 @@ -.eunit -deps -*.o -*.beam -*.plt +# general +log +/_build/ +*~ erl_crash.dump -ebin -rel/example_project -.concrete/DEV_MODE -.rebar +/*.config +.tags* +*.sublime-workspace +.DS_Store + +# wercker +/_builds/ +/_cache/ +/_projects/ +/_steps/ +/_temp/ +/.wercker/ + +# compiled +swagger diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4b19df5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "schemes/swag"] + path = schemes/swag + url = git@github.com:rbkmoney/swag.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c2d90e2 --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3) +RELNAME = capi +SUBMODULES = schemes/swag +SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES)) + +SWAGGER_SCHEME = schemes/swag/swagger.yaml +SWAGGER_APP_PATH = apps/swagger +SWAGGER_APP_TARGET = $(SWAGGER_APP_PATH)/rebar.config + +which = $(if $(shell which $(1) 2>/dev/null),\ + $(shell which $(1) 2>/dev/null),\ + $(error "Error: could not locate $(1)!")) + +DOCKER = $(call which, docker) +PACKER = $(call which, packer) +SWAGGER_CODEGEN = $(call which, SWAGGER_CODEGEN) + +.PHONY: all submodules compile devrel start test clean distclean dialyze release containerize swagger_regenerate + +all: compile + +rebar-update: + $(REBAR) update + +$(SUBTARGETS): %/.git: % + git submodule update --init $< + touch $@ + +submodules: $(SUBTARGETS) $(SWAGGER_APP_TARGET) + +compile: submodules + $(REBAR) compile + +devrel: submodules + $(REBAR) release + +start: submodules + $(REBAR) run + +test: submodules + $(REBAR) ct + +lint: compile + elvis rock + +xref: submodules + $(REBAR) xref + +clean: + $(REBAR) clean + +distclean: + $(REBAR) clean -a + rm -rfv _build _builds _cache _steps _temp + +dialyze: + $(REBAR) dialyzer + +BASE_DIR := $(shell pwd) + +release: ~/.docker/config.json distclean + $(DOCKER) run --rm -v $(BASE_DIR):$(BASE_DIR) --workdir $(BASE_DIR) rbkmoney/build rebar3 as prod release + +containerize: release ./packer.json + $(PACKER) build packer.json + +~/.docker/config.json: + test -f ~/.docker/config.json || (echo "Please run: docker login" ; exit 1) + +# Shitty generation. Will be replaced when a container with swagger-codegen appear +define swagger_regenerate + rm -rf $(SWAGGER_APP_PATH) + $(SWAGGER_CODEGEN) generate -i $(SWAGGER_SCHEME) -l erlang-server -o $(SWAGGER_APP_PATH); +endef + +$(SWAGGER_APP_TARGET): $(SWAGGER_SCHEME) + $(call swagger_regenerate) + +swagger_regenerate: + $(call swagger_regenerate) diff --git a/README.md b/README.md index 15deea2..455d78e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ -# erlang_capi -Erlang CAPI version +# capi + +A service that does something + +## Сборка + +Для запуска процесса сборки достаточно выполнить просто: + + make + +Чтобы запустить полученную сборку в режиме разработки и получить стандартный [Erlang shell][2], нужно всего лишь: + + make start + +> _Хозяйке на заметку._ При этом используется стандартный Erlang релиз, собранный при помощи [relx][3] в режиме разработчика. + +Рекомендуется вести разработку и сборку проекта в рамках локальной виртуальной среды, предоставляемой [wercker][1]. Настоятельно рекомендуется прогоны тестовых сценариев проводить только в этой среде. + + $ wercker dev + +> _Хозяйке на заметку._ В зависимости от вашего окружения и операционной системы вам может понадобиться [Docker Machine][4]. + +## Документация + +Дальнейшую документацию можно почерпнуть, пройдясь по ссылкам в [соответствующем документе](doc/index.md). + +[1]: http://devcenter.wercker.com/learn/basics/the-wercker-cli.html +[2]: http://erlang.org/doc/man/shell.html +[3]: https://github.com/erlware/relx +[4]: https://docs.docker.com/machine/install-machine/ diff --git a/apps/capi/src/capi.app.src b/apps/capi/src/capi.app.src new file mode 100644 index 0000000..1107aca --- /dev/null +++ b/apps/capi/src/capi.app.src @@ -0,0 +1,19 @@ +{application, capi , [ + {description, "A service that does something"}, + {vsn, "1"}, + {registered, []}, + {mod, { capi , []}}, + {applications, [ + kernel, + stdlib, + genlib, + swagger + ]}, + {env, []}, + {modules, []}, + {maintainers, [ + "Artem Ocheredko " + ]}, + {licenses, []}, + {links, []} +]}. diff --git a/apps/capi/src/capi.erl b/apps/capi/src/capi.erl new file mode 100644 index 0000000..69e08c3 --- /dev/null +++ b/apps/capi/src/capi.erl @@ -0,0 +1,21 @@ +%% @doc Public API and application startup. +%% @end + +-module(capi). +-behaviour(application). + +%% Application callbacks +-export([start/2]). +-export([stop/1]). + +%% + +-spec start(normal, any()) -> {ok, pid()} | {error, any()}. + +start(_StartType, _StartArgs) -> + capi_sup:start_link(). + +-spec stop(any()) -> ok. + +stop(_State) -> + ok. diff --git a/apps/capi/src/capi_auth.erl b/apps/capi/src/capi_auth.erl new file mode 100644 index 0000000..191a9ee --- /dev/null +++ b/apps/capi/src/capi_auth.erl @@ -0,0 +1,25 @@ +-module(capi_auth). + +-export([auth_api_key/2]). + +-type context() :: #{binary() => any()}. + +-spec auth_api_key(ApiKey :: binary(), OperationID :: atom()) -> {true, Context :: context()} | false. +auth_api_key(ApiKey, OperationID) -> + {ok, Type, Credentials} = parse_auth_token(ApiKey), + {ok, Context} = process_auth(Type, Credentials, OperationID), + {true, Context}. + +-spec parse_auth_token(ApiKey :: binary()) -> {ok, bearer, Credentials :: binary()} | {error, Reason :: atom()}. +parse_auth_token(ApiKey) -> + case ApiKey of + <<"Bearer ", Credentials/binary>> -> + {ok, bearer, Credentials}; + _ -> + {error, unsupported_auth_scheme} + end. + +-spec process_auth(Type :: atom(), AuthToken :: binary(), OperationID :: atom()) -> {ok, Context :: context()} | {error, Reason :: atom()}. +process_auth(bearer, _AuthToken, _OperationID) -> + %% @TODO find a jwt library :( + {ok, #{}}. diff --git a/apps/capi/src/capi_mock_handler.erl b/apps/capi/src/capi_mock_handler.erl new file mode 100644 index 0000000..6f2f9c7 --- /dev/null +++ b/apps/capi/src/capi_mock_handler.erl @@ -0,0 +1,185 @@ +-module(capi_mock_handler). + +-behaviour(swagger_logic_handler). +-behaviour(gen_server). + +%% API callbacks +-export([start_link/0]). +-export([handle_request/2]). +-export([authorize_api_key/2]). + +%% gen_server callbacks +-export([init/1]). +-export([handle_call/3]). +-export([handle_cast/2]). +-export([handle_info/2]). +-export([terminate/2]). +-export([code_change/3]). + +-record(state, { + tid :: ets:tid(), + last_id = 0 ::integer() +}). + +-spec start_link() -> {ok, Pid :: pid()} | ignore | {error, Error :: any()}. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +-spec authorize_api_key(ApiKey :: binary(), OperationID :: atom()) -> Result :: boolean() | {boolean(), #{binary() => any()}}. +authorize_api_key(ApiKey, OperationID) -> capi_auth:auth_api_key(ApiKey, OperationID). + +-spec handle_request(OperationID :: atom(), Req :: #{}) -> {Code :: integer, Headers :: [], Response :: #{}}. +handle_request('CreateInvoice', Req) -> + InvoiceParams = maps:get('CreateInvoiceArgs', Req), + ID = new_id(), + Invoice = #{ + <<"id">> => ID, + <<"amount">> => maps:get(<<"amount">>, InvoiceParams), + <<"context">> => maps:get(<<"context">>, InvoiceParams), + <<"currency">> => maps:get(<<"currency">>, InvoiceParams), + <<"description">> => maps:get(<<"description">>, InvoiceParams), + <<"dueDate">> => maps:get(<<"dueDate">>, InvoiceParams), + <<"product">> => maps:get(<<"product">>, InvoiceParams), + <<"shopID">> => maps:get(<<"shopID">>, InvoiceParams) + }, + put_data(ID, invoice, Invoice), + Resp = #{ + <<"id">> => ID + }, + {201, [], Resp}; + +handle_request('CreatePayment', Req) -> + InvoiceID = maps:get('invoice_id', Req), + PaymentParams = maps:get('CreatePaymentArgs', Req), + PaymentSession = maps:get(<<"paymentSession">>, PaymentParams), + case match_data({{'$1', session}, PaymentSession}) of + [[_SessionID]] -> + delete_data({{'$1', session}, '_'}), + PaymentID = new_id(), + Payment = #{ + <<"id">> => PaymentID , + <<"invoiceID">> => InvoiceID, + <<"createdAt">> => <<"2016-12-12 17:00:00">>, + <<"status">> => <<"pending">>, + <<"paymentToolToken">> => maps:get(<<"paymentToolToken">>, PaymentParams) + }, + put_data(PaymentID, payment, Payment), + Resp = #{ + <<"id">> => PaymentID + }, + {201, [], Resp}; + _ -> + Resp = logic_error(<<"expired_session">>, <<"Payment session is not valid">>), + {400, [], Resp} + end; + +handle_request('CreatePaymentToolToken', Req) -> + Params = maps:get('PaymentTool', Req), + Token = tokenize_payment_tool(Params), + put_data(new_id(), token, Token), + Session = generate_session(), + put_data(new_id(), session, Session), + Resp = #{ + <<"token">> => Token, + <<"session">> => Session + }, + {201, [], Resp}; + +handle_request('GetInvoiceByID', Req) -> + InvoiceID = maps:get(invoice_id, Req), + [{_, Invoice}] = get_data(InvoiceID, invoice), + {200, [], Invoice}; + +handle_request('GetInvoiceEvents', _Req) -> + Events = [], + {200, [], Events}; + +handle_request('GetPaymentByID', Req) -> + PaymentID = maps:get(payment_id, Req), + [{_, Payment}] = get_data(PaymentID, payment), + {200, [], Payment}; + +handle_request(OperationID, Req) -> + io:format(user, "Got request to process: ~p~n", [{OperationID, Req}]), + {501, [], <<"Not implemented">>}. + + +%%% + +-type callref() :: {pid(), Tag :: reference()}. +-type st() :: #state{}. + +-spec init( Args :: any()) -> {ok, st()}. +init(_Args) -> + TID = ets:new(mock_storage, [ordered_set, private, {heir, none}]), + {ok, #state{tid = TID}}. + +-spec handle_call(Request :: any(), From :: callref(), st()) -> {reply, term(), st()} | {noreply, st()}. +handle_call({put, ID, Type, Data}, _From, State = #state{tid = TID}) -> + Result = ets:insert(TID, {wrap_id(ID, Type), Data}), + {reply, Result, State}; + +handle_call({get, ID, Type}, _From, State = #state{tid = TID}) -> + Result = ets:lookup(TID, wrap_id(ID, Type)), + {reply, Result, State}; + +handle_call({match, Pattern}, _From, State = #state{tid = TID}) -> + Result = ets:match(TID, Pattern), + {reply, Result, State}; + +handle_call({delete, Pattern}, _From, State = #state{tid = TID}) -> + Result = ets:match_delete(TID, Pattern), + {reply, Result, State}; + +handle_call(id, _From, State = #state{last_id = ID}) -> + NewID = ID + 1, + {reply, NewID, State#state{last_id = NewID}}. + +-spec handle_cast(Request :: any(), st()) -> {noreply, st()}. +handle_cast(_Request, State) -> + {noreply, State}. + +-spec handle_info(any(), st()) -> {noreply, st()}. +handle_info(_Info, State) -> + {noreply, State}. + +-spec terminate(any(), st()) -> ok. +terminate(_Reason, _State) -> + ok. + +-spec code_change(Vsn :: term() | {down, Vsn :: term()}, st(), term()) -> {error, noimpl}. +code_change(_OldVsn, _State, _Extra) -> + {error, noimpl}. + +put_data(ID, Type, Data) -> + gen_server:call(?MODULE, {put, ID, Type, Data}). + +get_data(ID, Type) -> + gen_server:call(?MODULE, {get, ID, Type}). + +match_data(Pattern) -> + gen_server:call(?MODULE, {match, Pattern}). + +delete_data(Pattern) -> + gen_server:call(?MODULE, {delete, Pattern}). + +new_id() -> + ID = gen_server:call(?MODULE, id), + genlib:to_binary(ID). + +tokenize_payment_tool(Params = #{<<"paymentToolType">> := <<"cardData">>}) -> + CardNumber = genlib:to_binary(maps:get(<<"cardNumber">>, Params)), + ExpDate = maps:get(<<"expDate">>, Params), + erlang:md5(<>); + +tokenize_payment_tool(_) -> + error(unsupported_payment_tool). %%@TODO move this error to upper level + +generate_session() -> + integer_to_binary(rand:uniform(100000)). + +logic_error(Code, Message) -> + #{code => Code, message => Message}. + +wrap_id(ID, Type) -> + {ID, Type}. diff --git a/apps/capi/src/capi_sup.erl b/apps/capi/src/capi_sup.erl new file mode 100644 index 0000000..b8d6b6e --- /dev/null +++ b/apps/capi/src/capi_sup.erl @@ -0,0 +1,46 @@ +%% @doc Top level supervisor. +%% @end + +-module(capi_sup). +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +%% + +-spec start_link() -> {ok, pid()} | {error, {already_started, pid()}}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% + +-spec init([]) -> {ok, tuple()}. +init([]) -> + {LogicHandler, LogicHandlerSpec} = get_logic_handler_info(), + SwaggerSpec = swagger_server:child_spec(swagger, #{ + ip => capi_utils:get_hostname_ip(genlib_app:env(capi, host, "0.0.0.0")), + port => genlib_app:env(capi, port, 8080), + net_opts => [], + logic_handler => LogicHandler + }), + {ok, { + {one_for_all, 0, 1}, [LogicHandlerSpec, SwaggerSpec] + }}. + +-spec get_logic_handler_info() -> {Handler :: atom(), Spec :: supervisor:child_spec()}. +get_logic_handler_info() -> + case genlib_app:env(capi, service_type) of + mock -> + Spec = genlib_app:permanent( + {capi_mock_handler, capi_mock_handler, start_link}, + none, + [] + ), + {capi_mock_handler, Spec}; + undefined -> exit(undefined_service_type) + end. diff --git a/apps/capi/src/capi_utils.erl b/apps/capi/src/capi_utils.erl new file mode 100644 index 0000000..5341c96 --- /dev/null +++ b/apps/capi/src/capi_utils.erl @@ -0,0 +1,16 @@ +-module(capi_utils). + +-export([get_hostname_ip/1]). + +-spec get_hostname_ip(Hostname | IP) -> IP when + Hostname :: string(), + IP :: inet:ip_address(). + +get_hostname_ip(Host) -> + % TODO: respect preferred address family + case inet:getaddr(Host, inet) of + {ok, IP} -> + IP; + {error, Error} -> + exit(Error) + end. diff --git a/apps/capi/test/capi_tests_SUITE.erl b/apps/capi/test/capi_tests_SUITE.erl new file mode 100644 index 0000000..cc3069d --- /dev/null +++ b/apps/capi/test/capi_tests_SUITE.erl @@ -0,0 +1,180 @@ +-module(capi_tests_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). + +%% test cases +-export([ + authorization_error_test/1, + create_invoice_badard_test/1, + create_invoice_ok_test/1, + create_payment_ok_test/1, + create_payment_tool_token_ok_test/1, + get_invoice_by_id_ok_test/1, + get_invoice_events_ok_test/1, + get_payment_by_id_ok_test/1 +]). + +-define(CAPI_HOST, "0.0.0.0"). +-define(CAPI_PORT, 8080). +-define(CAPI_SERVICE_TYPE, mock). + +all() -> + [ + authorization_error_test, + create_invoice_badard_test, + create_invoice_ok_test, + create_payment_ok_test, + create_payment_tool_token_ok_test, + get_invoice_by_id_ok_test, + get_invoice_events_ok_test, + get_payment_by_id_ok_test + ]. + +%% +%% starting/stopping +%% +init_per_suite(C) -> + {_, Seed} = calendar:local_time(), + random:seed(Seed), + test_configuration(), + {ok, Apps1} = application:ensure_all_started(capi), + {ok, Apps2} = application:ensure_all_started(hackney), + [{apps, Apps1 ++ Apps2} | C]. + +end_per_suite(C) -> + [application_stop(App) || App <- proplists:get_value(apps, C)]. + +application_stop(App = sasl) -> + %% hack for preventing sasl deadlock + %% http://erlang.org/pipermail/erlang-questions/2014-May/079012.html + error_logger:delete_report_handler(cth_log_redirect), + _ = application:stop(App), + error_logger:add_report_handler(cth_log_redirect), + ok; +application_stop(App) -> + application:stop(App). + +%% tests +authorization_error_test(_) -> + {ok, 401, _RespHeaders, _Body} = call(get, "/invoices/22?limit=22", #{}, []). + +create_invoice_badard_test(_) -> + {ok, 400, _RespHeaders, _Body} = default_call(post, "/invoices", #{}). + +create_invoice_ok_test(_) -> + #{<<"id">> := _InvoiceID} = default_create_invoice(). + +create_payment_ok_test(_) -> + #{<<"id">> := InvoiceID} = default_create_invoice(), + #{ + <<"session">> := PaymentSession, + <<"token">> := PaymentToolToken + } = default_tokenize_card(), + #{<<"id">> := _PaymentID} = default_create_payment(InvoiceID, PaymentSession, PaymentToolToken). + +create_payment_tool_token_ok_test(_) -> + #{<<"token">> := _Token, <<"session">> := _Session} = default_tokenize_card(). + +get_invoice_by_id_ok_test(_) -> + #{<<"id">> := InvoiceID} = default_create_invoice(), + Path = "/invoices/" ++ genlib:to_list(InvoiceID), + {ok, 200, _RespHeaders, _Body} = default_call(get, Path, #{}). + +get_invoice_events_ok_test(_) -> + #{<<"id">> := InvoiceID} = default_create_invoice(), + #{ + <<"session">> := PaymentSession, + <<"token">> := PaymentToolToken + } = default_tokenize_card(), + #{<<"id">> := _PaymentID} = default_create_payment(InvoiceID, PaymentSession, PaymentToolToken), + + timer:sleep(1000), + Path = "/invoices/" ++ genlib:to_list(InvoiceID) ++ "/events/?limit=100", + {ok, 200, _RespHeaders, _Body} = default_call(get, Path, #{}). + +get_payment_by_id_ok_test(_) -> + #{<<"id">> := InvoiceID} = default_create_invoice(), + #{ + <<"session">> := PaymentSession, + <<"token">> := PaymentToolToken + } = default_tokenize_card(), + #{<<"id">> := PaymentID} = default_create_payment(InvoiceID, PaymentSession, PaymentToolToken), + + Path = "/invoices/" ++ genlib:to_list(InvoiceID) ++ "/payments/" ++ genlib:to_list(PaymentID), + {ok, 200, _RespHeaders, _Body} = default_call(get, Path, #{}). +%% helpers + +test_configuration() -> + application:set_env(capi, host, ?CAPI_HOST), + application:set_env(capi, port, ?CAPI_PORT), + application:set_env(capi, service_type, ?CAPI_SERVICE_TYPE). + +default_call(Method, Path, Body) -> + call(Method, Path, Body, [x_request_id_header(), auth_header(), json_content_type_header()]). + +call(Method, Path, Body, Headers) -> + Url = get_url(Path), + PreparedBody = jsx:encode(Body), + {ok, Code, RespHeaders, ClientRef} = hackney:request(Method, Url, Headers, PreparedBody), + {ok, Code, RespHeaders, get_body(ClientRef)}. + +get_url(Path) -> + ?CAPI_HOST ++ ":" ++ integer_to_list(?CAPI_PORT) ++ Path. + +x_request_id_header() -> + {<<"X-Request-ID">>, integer_to_binary(rand:uniform(100000))}. + +auth_header() -> + {<<"Authorization">>, <<"Bearer ", (auth_token())/binary>>} . + +auth_token() -> + <<"I can't find JWT library :(">>. + +json_content_type_header() -> + {<<"Content-Type">>, <<"application/json">>}. + +default_create_invoice() -> + Req = #{ + <<"shopID">> => <<"test_shop_id">>, + <<"amount">> => 100000, + <<"currency">> => <<"RUB">>, + <<"context">> => #{ + <<"invoice_dummy_context">> => <<"test_value">> + }, + <<"dueDate">> => <<"2017-07-11 10:00:00">>, + <<"product">> => <<"test_product">>, + <<"description">> => <<"test_invoice_description">> + }, + {ok, 201, _RespHeaders, Body} = default_call(post, "/invoices", Req), + decode_body(Body). + +default_tokenize_card() -> + Req = #{ + <<"paymentToolType">> => <<"cardData">>, + <<"cardHolder">> => <<"Alexander Weinerschnitzel">>, + <<"cardNumber">> => 4111111111111111, + <<"expDate">> => <<"08/27">>, + <<"cvv">> => <<"232">> + }, + {ok, 201, _RespHeaders, Body} = default_call(post, "/payment_tools", Req), + decode_body(Body). + +default_create_payment(InvoiceID, PaymentSession, PaymentToolToken) -> + Req = #{ + <<"paymentSession">> => PaymentSession, + <<"paymentToolToken">> => PaymentToolToken + }, + Path = "/invoices/" ++ genlib:to_list(InvoiceID) ++ "/payments", + {ok, 201, _RespHeaders, Body} = default_call(post, Path, Req), + decode_body(Body). + +get_body(ClientRef) -> + {ok, Body} = hackney:body(ClientRef), + Body. + +decode_body(Body) -> + jsx:decode(Body, [return_maps]). diff --git a/config/sys.config b/config/sys.config new file mode 100644 index 0000000..778a80b --- /dev/null +++ b/config/sys.config @@ -0,0 +1,7 @@ +[ + { capi , [ + {host, "0.0.0.0"}, + {port, 8080}, + {service_type, mock} + ]} +]. diff --git a/config/vm.args b/config/vm.args new file mode 100644 index 0000000..f8a36e2 --- /dev/null +++ b/config/vm.args @@ -0,0 +1,6 @@ +-sname capi + +-setcookie capi_cookie + ++K true ++A 10 diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..223f7b8 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,5 @@ +# Документация + +1. [Общее описание](overview.md) +1. [Установка](install.md) +1. [Первоначалная настройка](configuration.md) diff --git a/packer.json b/packer.json new file mode 100644 index 0000000..956516d --- /dev/null +++ b/packer.json @@ -0,0 +1,24 @@ +{ + "builders": [ + { + "type": "docker", + "image": "rbkmoney/service_erlang", + "pull": "true", + "commit": "true" + } + ], + "provisioners": [ + { + "type": "file", + "source": "./_build/prod/rel/capi", + "destination": "/opt/" + } + ], + "post-processors": [ + { + "type": "docker-tag", + "repository": "rbkmoney/capi_erlang" + } + ] +} + diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..bf8ae80 --- /dev/null +++ b/rebar.lock @@ -0,0 +1,21 @@ +[{<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},1}, + {<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},0}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},1}, + {<<"genlib">>, + {git,"https://github.com/rbkmoney/genlib.git", + {ref,"66db7fe296465a875b6894eb5ac944c90f82f913"}}, + 0}, + {<<"hackney">>,{pkg,<<"hackney">>,<<"1.5.7">>},0}, + {<<"idna">>,{pkg,<<"idna">>,<<"1.2.0">>},1}, + {<<"jesse">>, + {git,"https://github.com/for-GET/jesse.git", + {ref,"f4270eb0a9bc64291c6e2205645b45b5b7b686f8"}}, + 0}, + {<<"jsx">>, + {git,"https://github.com/talentdeficit/jsx.git", + {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, + 0}, + {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1}, + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},1}, + {<<"ranch">>,{pkg,<<"ranch">>,<<"1.2.1">>},1}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.0">>},1}]. diff --git a/schemes/swag b/schemes/swag new file mode 160000 index 0000000..60b0337 --- /dev/null +++ b/schemes/swag @@ -0,0 +1 @@ +Subproject commit 60b0337c99be8546179f9473adcc1596395dc831 diff --git a/wercker.yml b/wercker.yml new file mode 100644 index 0000000..c094b49 --- /dev/null +++ b/wercker.yml @@ -0,0 +1,16 @@ +box: erlang:18 + +dev: + steps: + - internal/shell: + code: make compile + +build: + steps: + - script: + name: run test suite + code: make test + after-steps: + - slack-notifier: + url: ${SLACK_WEBHOOK_URL} + username: "wercker"