TD-929: add implementation

This commit is contained in:
ttt161 2024-07-02 17:10:02 +03:00
parent ab2168b35a
commit 78152d43d1
7 changed files with 205 additions and 1 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# general
log
/_build/
/_checkouts/
*~
erl_crash.dump
rebar3.crashdump
.tags*
*.sublime-workspace
.edts
.DS_Store
/.idea/
*.beam
/test/log/
tags
.image.dev
bin

View File

@ -1,2 +1,2 @@
# epg_connector
Postgres connector fot Erlang
Postgres connector for Erlang

28
config/sys.config Normal file
View File

@ -0,0 +1,28 @@
[
{epg_connector, [
{databases, #{
default_db => #{
host =>"127.0.0.1",
port => 5432,
database => "db_name",
username => "postgres",
password => "postgres"
}
}},
{pools, #{
default_pool => #{
database => default_db,
size => 10
}
}},
%% Must specified for umbrella application
{vault_token_path, "/var/run/secrets/kubernetes.io/serviceaccount/token"},
{vault_role, "epg_connector"},
{vault_key_pg_creds, "epg_connector/pg_creds"}
]},
{canal, [
{url, "http://vault:8200"},
{engine, kvv2}
]}
].

13
rebar.config Normal file
View File

@ -0,0 +1,13 @@
{erl_opts, [debug_info]}.
{deps, [
jsx,
{canal, {git, "https://github.com/valitydev/canal", {branch, master}}},
{epgsql, {git, "https://github.com/epgsql/epgsql.git", {tag, "4.7.1"}}},
{epgsql_pool, {git, "https://github.com/wgnet/epgsql_pool", {branch, "master"}}},
{herd, {git, "https://github.com/wgnet/herd.git", {tag, "1.3.4"}}}
]}.
{shell, [
% {config, "config/sys.config"},
{apps, [epg_connector]}
]}.

18
src/epg_connector.app.src Normal file
View File

@ -0,0 +1,18 @@
{application, epg_connector,
[{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {epg_connector_app, []}},
{applications,
[kernel,
stdlib,
canal,
epgsql,
epgsql_pool
]},
{env,[]},
{modules, []},
{licenses, ["MIT"]},
{links, []}
]}.

101
src/epg_connector_app.erl Normal file
View File

@ -0,0 +1,101 @@
%%%-------------------------------------------------------------------
%% @doc epg_connector public API
%% @end
%%%-------------------------------------------------------------------
-module(epg_connector_app).
-behaviour(application).
-define(VAULT_TOKEN_PATH, "/var/run/secrets/kubernetes.io/serviceaccount/token").
-define(VAULT_ROLE, "api-key-mgmt-v2").
-define(VAULT_KEY_PG_CREDS, "api-key-mgmt-v2/pg_creds").
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
Databases = application:get_env(epg_connector, databases, #{}),
ok = maybe_set_secrets(Databases),
Pools = application:get_env(epg_connector, pools, #{}),
ok = start_pools(Pools, Databases),
epg_connector_sup:start_link().
stop(_State) ->
ok.
%% internal functions
start_pools(Pools, Databases) ->
maps:fold(
fun(PoolName, Opts, _Acc) ->
#{
database := DB,
size := Size
} = Opts,
DbParams = maps:get(DB, Databases),
{ok, _} = epgsql_pool:start(PoolName, Size, Size, DbParams),
ok
end,
ok,
Pools
).
maybe_set_secrets(Databases) ->
TokenPath = application:get_env(epg_connector, vault_token_path, ?VAULT_TOKEN_PATH),
try vault_client_auth(TokenPath) of
ok ->
Key = unicode:characters_to_binary(
application:get_env(epg_connector, vault_key_pg_creds, ?VAULT_KEY_PG_CREDS)
),
set_secrets(canal:read(Key), Databases);
Error ->
logger:error("can`t auth vault client with error: ~p", [Error]),
skip
catch
_:_ ->
logger:error("catch exception when auth vault client"),
skip
end,
ok.
vault_client_auth(TokenPath) ->
case read_maybe_linked_file(TokenPath) of
{ok, Token} ->
Role = unicode:characters_to_binary(application:get_env(epg_connector, vault_role, ?VAULT_ROLE)),
canal:auth({kubernetes, Role, Token});
Error ->
Error
end.
read_maybe_linked_file(MaybeLinkName) ->
case file:read_link(MaybeLinkName) of
{error, enoent} = Result ->
Result;
{error, einval} ->
file:read_file(MaybeLinkName);
{ok, Filename} ->
file:read_file(maybe_expand_relative(MaybeLinkName, Filename))
end.
maybe_expand_relative(BaseFilename, Filename) ->
filename:absname_join(filename:dirname(BaseFilename), Filename).
set_secrets({ok, #{<<"pg_creds">> := #{<<"pg_user">> := PgUser, <<"pg_password">> := PgPassword}}}, Databases) ->
logger:info("postgres credentials successfuly read from vault (as json)"),
NewDbConfig = maps:fold(fun(DbName, ConnOpts, Acc) ->
Acc#{
DbName => ConnOpts#{
username => unicode:characters_to_list(PgUser),
password => unicode:characters_to_list(PgPassword)
}
}
end, #{}, Databases),
application:set_env(epg_connector, databases, NewDbConfig),
ok;
set_secrets({ok, #{<<"pg_creds">> := PgCreds}}, Databases) ->
logger:info("postgres credentials successfuly read from vault (as string)"),
set_secrets({ok, #{<<"pg_creds">> => jsx:decode(PgCreds, [return_maps])}}, Databases);
set_secrets(Error, _Databases) ->
logger:error("can`t read postgres credentials from vault with error: ~p", [Error]),
skip.

26
src/epg_connector_sup.erl Normal file
View File

@ -0,0 +1,26 @@
%%%-------------------------------------------------------------------
%% @doc epg_connector top level supervisor.
%% @end
%%%-------------------------------------------------------------------
-module(epg_connector_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
init([]) ->
SupFlags = #{strategy => one_for_all,
intensity => 0,
period => 1},
ChildSpecs = [],
{ok, {SupFlags, ChildSpecs}}.
%% internal functions