aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/dreki/include/dreki_otel.hrl11
-rw-r--r--apps/dreki/include/dreki_plum.hrl6
-rw-r--r--apps/dreki/src/dreki.app.src6
-rw-r--r--apps/dreki/src/dreki_app.erl8
-rw-r--r--apps/dreki/src/dreki_config.erl26
-rw-r--r--apps/dreki/src/dreki_freebsd_schemas.erl59
-rw-r--r--apps/dreki/src/dreki_plum.erl30
-rw-r--r--apps/dreki/src/dreki_sup.erl10
-rw-r--r--apps/dreki/src/drekid.erl95
-rw-r--r--apps/dreki/src/drekid_tasks_store.erl71
-rw-r--r--apps/dreki/src/funs/dreki_fun_exec.erl28
-rw-r--r--apps/dreki/src/funs/dreki_funs.erl31
-rw-r--r--apps/dreki/src/node/dreki_node.erl (renamed from apps/dreki/src/dreki_node.erl)69
-rw-r--r--apps/dreki/src/node/dreki_node_server.erl (renamed from apps/dreki/src/dreki_node_server.erl)0
-rw-r--r--apps/dreki/src/node/rebar.conf0
-rw-r--r--apps/dreki/src/requests/dreki_requests.erl97
-rw-r--r--apps/dreki/src/runs/dreki_runs.erl1
-rw-r--r--apps/dreki/src/storages/dreki_storages.erl98
-rw-r--r--apps/dreki/src/storages/dreki_storages_zfs.erl2
-rw-r--r--apps/dreki/src/store/dreki_dets_store.erl (renamed from apps/dreki/src/dreki_dets_store.erl)5
-rw-r--r--apps/dreki/src/store/dreki_mnesia_store.erl94
-rw-r--r--apps/dreki/src/store/dreki_store.erl (renamed from apps/dreki/src/dreki_store.erl)144
-rw-r--r--apps/dreki/src/store/dreki_store_backend.erl (renamed from apps/dreki/src/dreki_store_backend.erl)0
-rw-r--r--apps/dreki/src/store/dreki_store_namespace.erl (renamed from apps/dreki/src/dreki_store_namespace.erl)0
-rw-r--r--apps/dreki/src/tasks/dreki_task.erl (renamed from apps/dreki/src/dreki_task.erl)0
-rw-r--r--apps/dreki/src/tasks/dreki_tasks.erl (renamed from apps/dreki/src/dreki_tasks.erl)41
-rw-r--r--apps/dreki/src/tasks/dreki_tasks_cloyster.erl (renamed from apps/dreki/src/dreki_tasks_cloyster.erl)0
-rw-r--r--apps/dreki/src/tasks/dreki_tasks_script.erl (renamed from apps/dreki/src/dreki_tasks_script.erl)0
-rw-r--r--apps/dreki/src/world/dreki_world.erl (renamed from apps/dreki/src/dreki_world.erl)0
-rw-r--r--apps/dreki/src/world/dreki_world_dns.erl (renamed from apps/dreki/src/dreki_world_dns.erl)0
-rw-r--r--apps/dreki/src/world/dreki_world_plum_events.erl (renamed from apps/dreki/src/dreki_world_plum_events.erl)0
-rw-r--r--apps/dreki/src/world/dreki_world_server.erl (renamed from apps/dreki/src/dreki_world_server.erl)0
-rw-r--r--apps/dreki/src/world/dreki_world_store.erl (renamed from apps/dreki/src/dreki_world_store.erl)4
-rw-r--r--apps/dreki/src/world/dreki_world_tasks.erl (renamed from apps/dreki/src/dreki_world_tasks.erl)0
-rw-r--r--apps/dreki_web/.gitignore1
-rw-r--r--apps/dreki_web/assets/package-lock.json13
-rw-r--r--apps/dreki_web/assets/package.json3
-rw-r--r--apps/dreki_web/rebar.config3
-rw-r--r--apps/dreki_web/src/admin/dreki_web_admin_tasks.erl (renamed from apps/dreki_web/src/dreki_web_admin_tasks.erl)0
-rw-r--r--apps/dreki_web/src/admin/dreki_web_admin_world.erl (renamed from apps/dreki_web/src/dreki_web_admin_world.erl)0
-rw-r--r--apps/dreki_web/src/api/dreki_web_index.erl (renamed from apps/dreki_web/src/dreki_web_index.erl)0
-rw-r--r--apps/dreki_web/src/api/dreki_web_task.erl (renamed from apps/dreki_web/src/dreki_web_task.erl)0
-rw-r--r--apps/dreki_web/src/cowboy_access_log_h.erl238
-rw-r--r--apps/dreki_web/src/dreki_web.app.src3
-rw-r--r--apps/dreki_web/src/dreki_web_app.erl12
-rw-r--r--apps/dreki_web/src/dreki_web_handler.erl99
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui.erl (renamed from apps/dreki_web/src/dreki_web_ui.erl)3
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_error.erl (renamed from apps/dreki_web/src/dreki_web_ui_error.erl)0
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_index.erl (renamed from apps/dreki_web/src/dreki_web_ui_index.erl)0
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_json_form.erl (renamed from apps/dreki_web/src/dreki_web_ui_json_form.erl)79
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_node.erl (renamed from apps/dreki_web/src/dreki_web_ui_node.erl)0
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_stores.erl (renamed from apps/dreki_web/src/dreki_web_ui_stores.erl)75
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_task.erl (renamed from apps/dreki_web/src/dreki_web_ui_task.erl)0
-rw-r--r--apps/dreki_web/src/ui/dreki_web_ui_tasks.erl (renamed from apps/dreki_web/src/dreki_web_ui_tasks.erl)0
-rw-r--r--apps/dreki_web/templates/crash.dtl52
-rw-r--r--apps/dreki_web/templates/layout.dtl7
-rw-r--r--apps/dreki_web/templates/store_list.dtl8
-rw-r--r--apps/dreki_web/templates/store_new.dtl10
-rw-r--r--apps/dreki_web/templates/store_show.dtl8
-rw-r--r--config/mgmt2/sys.config114
-rw-r--r--config/mgmt2/vm.args9
-rw-r--r--config/sys.config56
-rw-r--r--rebar.config11
-rw-r--r--rebar.lock45
64 files changed, 1602 insertions, 183 deletions
diff --git a/apps/dreki/include/dreki_otel.hrl b/apps/dreki/include/dreki_otel.hrl
new file mode 100644
index 0000000..e784874
--- /dev/null
+++ b/apps/dreki/include/dreki_otel.hrl
@@ -0,0 +1,11 @@
+-include_lib("opentelemetry_api/include/opentelemetry.hrl").
+-include_lib("opentelemetry_api/include/otel_tracer.hrl").
+
+-define(FUN_NAME, binary:list_to_bin([
+ atom_to_binary(?MODULE), <<":">>,
+ atom_to_binary(?FUNCTION_NAME), <<"/">>,
+ integer_to_binary(?FUNCTION_ARITY)
+ ])
+).
+
+-define(FUN_NAME(D), binary:list_to_bin([?FUN_NAME, atom_to_binary(D)])).
diff --git a/apps/dreki/include/dreki_plum.hrl b/apps/dreki/include/dreki_plum.hrl
index bea65aa..aed2e05 100644
--- a/apps/dreki/include/dreki_plum.hrl
+++ b/apps/dreki/include/dreki_plum.hrl
@@ -5,14 +5,13 @@
%% dreki_store tabs
-define(PLUM_DB_STORE_TASKS_TAB, dreki_tasks).
-
%% indices tabs
-define(PLUM_DB_IDX_ROLE_TAB, 'dreki_idx:roles').
-define(PLUM_DB_IDX_TAGS_TAB, 'dreki_idx:tags').
-define(PLUM_DB_PREFIXES, [
- {?PLUM_DB_REGIONS_TAB, ram_disk},
- {?PLUM_DB_NODES_TAB, ram_disk},
+ {?PLUM_DB_REGIONS_TAB, disk},
+ {?PLUM_DB_NODES_TAB, disk},
{?PLUM_DB_PATHS_TAB, ram_disk},
{?PLUM_DB_STORES_TAB, ram_disk},
@@ -23,4 +22,3 @@
{?PLUM_DB_IDX_ROLE_TAB, disk},
{?PLUM_DB_IDX_TAGS_TAB, disk}
]).
-
diff --git a/apps/dreki/src/dreki.app.src b/apps/dreki/src/dreki.app.src
index 78009bf..2e152a7 100644
--- a/apps/dreki/src/dreki.app.src
+++ b/apps/dreki/src/dreki.app.src
@@ -8,9 +8,15 @@
stdlib,
mnesia,
logger_colorful,
+ ipee,
opentelemetry,
opentelemetry_api,
opentelemetry_exporter,
+ opentelemetry_logger_metadata,
+ telemetry,
+ opentelemetry_telemetry,
+ prometheus,
+ genlib,
uuid,
mnesia_rocksdb,
plum_db,
diff --git a/apps/dreki/src/dreki_app.erl b/apps/dreki/src/dreki_app.erl
index a1259a7..0442b4c 100644
--- a/apps/dreki/src/dreki_app.erl
+++ b/apps/dreki/src/dreki_app.erl
@@ -31,6 +31,8 @@ before_start(Type, Args) ->
logger:set_application_level(dreki_web, debug),
logger:set_application_level(partisan, info),
logger:set_application_level(plum_db, info),
+ logger:set_handler_config(default, level, debug),
+ opentelemetry_logger_metadata:setup(),
?LOG_NOTICE(#{message => "Dreki starting...."}),
application:stop(partisan),
ok = dreki_config:init(Args),
@@ -48,6 +50,7 @@ after_start() ->
ok = setup_event_manager(),
ok = dreki_store:start(),
?LOG_NOTICE(#{message => "Dreki Ready"}),
+ ok = dreki_config:set([dreki, status], ready),
dreki_event_manager:notify(dreki_ready),
ok.
@@ -70,12 +73,11 @@ setup_event_manager() ->
Mod = partisan_peer_service:manager(),
Mod:on_up('_', fun(Node) ->
- dreki_event_manager:notify({peer_up, Node})
+ dreki_event_manager:notify({partisan_peer_service, peer_up, Node})
end),
Mod:on_down('_', fun(Node) ->
- dreki_event_manager:notify({peer_down, Node})
+ dreki_event_manager:notify({partisan_peer_service, peer_down, Node})
end),
ok.
-
diff --git a/apps/dreki/src/dreki_config.erl b/apps/dreki/src/dreki_config.erl
index 69df2d6..457f559 100644
--- a/apps/dreki/src/dreki_config.erl
+++ b/apps/dreki/src/dreki_config.erl
@@ -2,8 +2,14 @@
-include_lib("kernel/include/logger.hrl").
-include_lib("partisan/include/partisan.hrl").
-include("dreki_plum.hrl").
+-include("dreki_otel.hrl").
+
+-compile({no_auto_import,[get/0]}).
+
-export([init/1]).
+-export([get/1]).
+-export([set/2]).
-define(CONFIG, [
%% All stolen from bondy as partisan isn't that well documented, eh.
@@ -44,15 +50,29 @@
-define(PT, dreki_config_cache).
init(_Args) ->
- persistent_term:put(?PT, application:get_all_env(dreki)),
+ persistent_term:put(?PT, [{dreki, application:get_all_env(dreki)},
+ {dreki_web, application:get_all_env(dreki_web)
+ }]),
ok = set_app_configs(?CONFIG),
- ?LOG_INFO(#{message => "Configured Dreki and dependencies"}),
+ ?LOG_NOTICE(#{message => "Configured Dreki and dependencies"}),
ok = partisan_config:init(),
ok.
+get() ->
+ persistent_term:get(?PT).
+
+get(Key) ->
+ ?with_span(?FUN_NAME, #{}, fun (_) -> key_value:get(Key, get()) end).
+
+set(Key, Value) ->
+ ?with_span(?FUN_NAME, #{},
+ fun (_) ->
+ persistent_term:put(?PT, key_value:put(Key, Value, get())),
+ ok
+ end).
+
set_app_configs(Configs) ->
lists:foreach(fun ({App, Params}) ->
[application:set_env(App, Key, Value) || {Key, Value} <- Params]
end, Configs),
ok.
-
diff --git a/apps/dreki/src/dreki_freebsd_schemas.erl b/apps/dreki/src/dreki_freebsd_schemas.erl
new file mode 100644
index 0000000..de97ded
--- /dev/null
+++ b/apps/dreki/src/dreki_freebsd_schemas.erl
@@ -0,0 +1,59 @@
+-module(dreki_freebsd_schemas).
+
+-export([schema/1]).
+
+schema(<<"dreki.v1.freebsd.jails.list">>) ->
+ #{
+ <<"@schema">> => "dreki.v1.freebsd.jails.list",
+ version => 'draft-06',
+ title => <<"List running jails ID/Names">>,
+ properties => #{}
+ };
+schema(<<"dreki.v1.freebsd.jails.get">>) ->
+ #{
+ <<"@schema">> => "dreki.v1.freebsd.jails.get",
+ version => 'draft-06',
+ title => <<"Get jail information">>,
+ properties => #{
+ jail_id => #{type => integer}
+ },
+ required => [jail_id]
+ };
+schema(<<"dreki.v1.freebsd.jails.start">>) ->
+ #{
+ <<"@schema">> => "dreki.v1.freebsd.jails.start",
+ version => 'draft-06',
+ title => <<"Start a jail">>,
+ properties => #{
+ name => #{type => string},
+ path => #{type => string},
+ hostname => #{type => string},
+ params => #{type => object}
+ },
+ required => [name, path, hostname, params]
+ };
+schema(<<"dreki.v1.freebsd.jails.exec">>) ->
+ #{
+ <<"@schema">> => "dreki.v1.freebsd.jails.exec",
+ version => 'draft-06',
+ title => <<"Execute a command in a jail">>,
+ properties => #{
+ jail_id => #{type => string},
+ command => #{type => string},
+ args => #{type => array},
+ env => #{type => object}
+ },
+ required => [id, command, args, env]
+ };
+schema(<<"dreki.v1.exec">>) ->
+ #{
+ <<"@schema">> => "dreki.v1.exec",
+ version => 'draft-06',
+ title => <<"Execute a command">>,
+ properties => #{
+ command => #{type => string},
+ args => #{type => array},
+ env => #{type => object}
+ },
+ required => [command, args, env]
+ }.
diff --git a/apps/dreki/src/dreki_plum.erl b/apps/dreki/src/dreki_plum.erl
index 9c03e46..dccdad7 100644
--- a/apps/dreki/src/dreki_plum.erl
+++ b/apps/dreki/src/dreki_plum.erl
@@ -8,7 +8,6 @@ before_start() ->
%% We temporarily disable plum_db's AAE to avoid rebuilding hashtrees
%% until we are ready to do it
ok = suspend_aae(),
- logger:debug("-- DISABLED AAE ! --"),
_ = application:ensure_all_started(plum_db, permanent),
ok.
@@ -27,9 +26,7 @@ suspend_aae() ->
true ->
ok = application:set_env(plum_db, priv_aae_enabled, true),
ok = application:set_env(plum_db, aae_enabled, false),
- ?LOG_NOTICE(#{
- description => "Temporarily disabled active anti-entropy (AAE) during initialisation"
- }),
+ ?LOG_NOTICE(#{message => "Temporarily disabled plum_db aae during initialisation"}),
ok;
false ->
ok
@@ -40,9 +37,7 @@ restore_aae() ->
true ->
%% plum_db should have started so we call plum_db_config
ok = plum_db_config:set(aae_enabled, true),
- ?LOG_NOTICE(#{
- description => "Active anti-entropy (AAE) re-enabled"
- }),
+ ?LOG_NOTICE(#{message => "plum_db aae re-enabled"}),
ok;
false ->
ok
@@ -51,11 +46,10 @@ restore_aae() ->
maybe_wait_for_plum_db_partitions() ->
case wait_for_partitions() of
true ->
- %% We block until all partitions are initialised
- ?LOG_NOTICE(#{
- description => "Application master is waiting for plum_db partitions to be initialised"
- }),
- plum_db_startup_coordinator:wait_for_partitions();
+ ?LOG_NOTICE(#{domain => [dreki_plum], message => "Waiting for plum_db partitions to be initialised"}),
+ ok = plum_db_startup_coordinator:wait_for_partitions(),
+ ?LOG_NOTICE(#{domain => [dreki_plum], message => "plum_db partitions initialised"}),
+ ok;
false ->
ok
end.
@@ -64,10 +58,10 @@ maybe_wait_for_plum_db_hashtrees() ->
case wait_for_hashtrees() of
true ->
%% We block until all hashtrees are built
- ?LOG_NOTICE(#{
- description => "Application master is waiting for plum_db hashtrees to be built"
- }),
- plum_db_startup_coordinator:wait_for_hashtrees();
+ ?LOG_NOTICE(#{domain => [dreki_plum], message => "Waiting for plum_db hashtrees to be built"}),
+ ok = plum_db_startup_coordinator:wait_for_hashtrees(),
+ ?LOG_NOTICE(#{domain => [dreki_plum], message => "plum_db hashtrees built"}),
+ ok;
false ->
ok
end,
@@ -88,9 +82,7 @@ maybe_wait_for_aae_exchange() ->
%% We have not yet joined a cluster, so we finish
ok;
Peers ->
- ?LOG_NOTICE(#{
- description => "Application master is waiting for plum_db AAE to perform exchange"
- }),
+ ?LOG_NOTICE(#{domain => [plum_db], message => "Waiting for plum_db AAE to perform exchange"}),
%% We are in a cluster, we randomnly pick a peer and
%% perform an AAE exchange
[Peer|_] = lists_utils:shuffle(Peers),
diff --git a/apps/dreki/src/dreki_sup.erl b/apps/dreki/src/dreki_sup.erl
index c1aa636..60b33e5 100644
--- a/apps/dreki/src/dreki_sup.erl
+++ b/apps/dreki/src/dreki_sup.erl
@@ -16,20 +16,12 @@
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
-%% sup_flags() = #{strategy => strategy(), % optional
-%% intensity => non_neg_integer(), % optional
-%% period => pos_integer()} % optional
-%% child_spec() = #{id => child_id(), % mandatory
-%% start => mfargs(), % mandatory
-%% restart => restart(), % optional
-%% shutdown => shutdown(), % optional
-%% type => worker(), % optional
-%% modules => modules()} % optional
init([]) ->
SupFlags = #{strategy => one_for_all,
intensity => 10,
period => 5},
ChildSpecs = [
+ #{id => drekid, start => {drekid, start_link, [[]]}},
#{id => dreki_event_manager, start => {dreki_event_manager, start_link, [[]]}},
#{id => dreki_world_server, start => {dreki_world_server, start_link, [[]]}},
#{id => dreki_node_server, start => {dreki_node_server, start_link, [[]]}}
diff --git a/apps/dreki/src/drekid.erl b/apps/dreki/src/drekid.erl
new file mode 100644
index 0000000..73beffd
--- /dev/null
+++ b/apps/dreki/src/drekid.erl
@@ -0,0 +1,95 @@
+-module(drekid).
+-behaviour(partisan_gen_fsm).
+-include_lib("kernel/include/logger.hrl").
+-include_lib("dreki_otel.hrl").
+-compile({no_auto_import, [get/0]}).
+-export([get/0]).
+-export([request/1, request/2, request/3]).
+-export([start_link/1]).
+-export([init/1, waiting/2, handle_info/3, handle_event/3, handle_sync_event/4]).
+-define(DREKID_PT, drekid).
+-record(drekid, {node, pid}).
+
+-spec get() -> {ok, pid()} | {error, drekid_unavailable}.
+get() ->
+ persistent_term:get(drekid, {error, drekid_unavailable}).
+
+request(Fun) ->
+ request(Fun, []).
+
+request(Fun, Args) ->
+ request(Fun, Args, 5000).
+
+request(Fun, Args, Timeout) when is_binary(Fun) ->
+ ?with_span(?FUN_NAME, fun(_SpanCtx) ->
+ send_request(Fun, Args, Timeout)
+ end).
+
+send_request(Fun, Args, Timeout) ->
+ ?set_attributes(#{function => Fun, args => Args, timeout => Timeout}),
+ case get() of
+ {ok, Pid} ->
+ Ref = make_ref(),
+ logger:info("drekid_request: ~p ~p to ~p", [Fun, Args, Pid]),
+ Pid ! {drekid_request_v1, self(), Ref, Fun, Args, Timeout},
+ ?with_span(?FUN_NAME(await_request), fun(_) ->
+ await_request(Ref, Timeout)
+ end);
+ Error ->
+ Error
+ end.
+
+await_request(Ref, Timeout) ->
+ receive
+ {drekid_response_v1, Ref, Result} ->
+ case Result of
+ {ok, Data = {_, _}} ->
+ Data;
+ {ok, Data} ->
+ {ok, Data};
+ {error, Error} ->
+ {error, {drekid_error, Error}}
+ end
+ after Timeout ->
+ {error, {drekid_error, timeout}}
+ end.
+
+start_link(Args) ->
+ partisan_gen_fsm:start_link({local, ?MODULE}, ?MODULE, Args, []).
+
+%%
+
+init(_Args) ->
+ ?LOG_DEBUG("drekid initialized"),
+ {ok, waiting, #drekid{}, 0}.
+
+waiting(timeout, Data) ->
+ persistent_term:put(drekid, {error, drekid_unavailable}),
+ {next_state, waiting, Data};
+waiting({drekid, {connect, Node, Pid}}, Data) ->
+ Data0 = Data#drekid{node = Node, pid = Pid},
+ monitor_node(Node, true),
+ Pid ! {drekid, hello, node(), self()},
+ ?LOG_NOTICE("drekid ready, node: ~p ~p", [Node, Pid]),
+ persistent_term:put(drekid, {ok, Pid}),
+ {next_state, ready, Data}.
+
+down(timeout, {Reason, State, Data}) ->
+ ?LOG_ERROR(#{code => drekid_down, reason => Reason, state => State}),
+ {next_state, waiting, Data#drekid{node = undefined, pid = undefined}}.
+
+handle_info({drekid, Msg}, waiting, Data) ->
+ waiting({drekid, Msg}, Data);
+handle_info({nodedown, Node}, State, Data = #drekid{node = Node}) ->
+ {next_state, down, {nodedown, State, Data}, 0};
+handle_info(Info, State, Data) ->
+ ?LOG_ERROR(#{code => unhandled_info, state => State, info => Info}),
+ {stop, badinfo, Data}.
+
+handle_event(Event, State, Data) ->
+ ?LOG_ERROR(#{code => unhandled_event, state => State, event => Event}),
+ {stop, badevent, Data}.
+
+handle_sync_event(Event, From, State, Data) ->
+ ?LOG_ERROR(#{code => unhandled_sync_event, state => State, event => Event}),
+ {stop, badevent, badevent, Data}.
diff --git a/apps/dreki/src/drekid_tasks_store.erl b/apps/dreki/src/drekid_tasks_store.erl
new file mode 100644
index 0000000..e16182a
--- /dev/null
+++ b/apps/dreki/src/drekid_tasks_store.erl
@@ -0,0 +1,71 @@
+-module(drekid_tasks_store).
+-behaviour(dreki_store_backend).
+
+-export([install/0]).
+-export([start/0, start/5, checkout/1, checkin/1, stop/0, stop/1]).
+-export([valid_store/5]).
+-export([list/1, count/1, exists/2, get/2, create/2, update/2, delete/2]).
+
+tasks() ->
+ Tasks = #{<<"dreki.v1.exec">> => #{id => <<"dreki.v1.exec">>, module => drekid_task_handler, params => #{}}},
+ FreeBSD = [<<"dreki.v1.freebsd.jails.list">>,
+ <<"dreki.v1.freebsd.jails.get">>,
+ <<"dreki.v1.freebsd.jails.start">>,
+ <<"dreki.v1.freebsd.jails.exec">>
+ ],
+ Tasks0 = lists:foldr(fun (Id, Acc) ->
+ Task = #{id => Id, module => drekid_task_handler, params => dreki_freebsd_schemas:schema(Id)},
+ maps:put(Id, Task, Acc)
+ end, Tasks, FreeBSD),
+ Tasks0.
+
+urn() ->
+ Urn = dreki_node:urn(),
+ <<Urn/binary, "::tasks:drekid">>.
+
+install() ->
+ dreki_store:create_store(urn(), ?MODULE, #{}, #{}).
+
+valid_store(<<"tasks">>, _Loc, _Name, _NSMod, _Args) ->
+ ok;
+valid_store(_Ns, _Loc, _Name, _NSMod, _Args) ->
+ error.
+
+start() ->
+ ok.
+
+start(_Ns, _NsMod, _Loc, _XUrn, Args) ->
+ {ok, Args}.
+
+checkout(Args) ->
+ {ok, Args}.
+
+checkin(Args) ->
+ ok.
+
+stop() ->
+ ok.
+
+stop(Args) ->
+ ok.
+
+list(_) ->
+ {ok, [V || {K, V} <- maps:to_list(tasks())]}.
+
+get(_, Id) ->
+ maps:get(Id, tasks(), not_found).
+
+count(_) ->
+ {ok, maps:size(tasks())}.
+
+exists(_, Id) ->
+ {ok, maps:is_key(Id, tasks())}.
+
+create(_, _) ->
+ {error, not_supported}.
+
+update(_, _) ->
+ {error, not_supported}.
+
+delete(_, _) ->
+ {error, not_supported}.
diff --git a/apps/dreki/src/funs/dreki_fun_exec.erl b/apps/dreki/src/funs/dreki_fun_exec.erl
new file mode 100644
index 0000000..d704e80
--- /dev/null
+++ b/apps/dreki/src/funs/dreki_fun_exec.erl
@@ -0,0 +1,28 @@
+-module(dreki_fun_exec).
+-behaviour(dreki_funs).
+-export([schemas/0]).
+
+schemas() ->
+ #{<<"exec">> => #{default_version => <<"1.0">>, <<"1.0">> => schemas('1.0')}}.
+
+schemas('1.0') ->
+ #{
+ version => 'draft-06',
+ title => <<"Execute command">>,
+ type => object,
+ properties => #{
+ <<"id">> => #{type => string,
+ <<"dreki:form">> => #{default => generate_id}},
+ <<"name">> => #{type => string},
+ <<"description">> => #{type => string,
+ <<"dreki:form">> => #{
+ input => textarea,
+ textarea_mode => markdown
+ }},
+ <<"command">> => #{type => string, <<"dreki:form">> => #{
+ input_style => monospace,
+ placeholder => <<"/bin/true">>
+ }}
+ },
+ required => [id, name, command]
+ }.
diff --git a/apps/dreki/src/funs/dreki_funs.erl b/apps/dreki/src/funs/dreki_funs.erl
new file mode 100644
index 0000000..1d15f21
--- /dev/null
+++ b/apps/dreki/src/funs/dreki_funs.erl
@@ -0,0 +1,31 @@
+-module(dreki_funs).
+-include("dreki.hrl").
+
+-behaviour(dreki_store_namespace).
+-export([start/0, valid_store/4, format_item/1, schemas/0]).
+
+-callback(schemas() -> #{}).
+
+start() ->
+ ok.
+
+valid_store(_Namespace, _Location, _Name, _BackendMod) ->
+ ok.
+
+format_item(Item) ->
+ ok.
+
+handlers() ->
+ [dreki_fun_exec].
+
+-record(?MODULE, {
+ id,
+ version,
+ name,
+ handler,
+ content
+ }).
+
+schemas() ->
+ Schemas = lists:foldr(fun (Handler, Acc) -> maps:merge(Acc, Handler:schemas()) end, #{}, handlers()),
+ maps:put(default, <<"exec:1.0">>, Schemas).
diff --git a/apps/dreki/src/dreki_node.erl b/apps/dreki/src/node/dreki_node.erl
index 87dbc73..5ed7fac 100644
--- a/apps/dreki/src/dreki_node.erl
+++ b/apps/dreki/src/node/dreki_node.erl
@@ -1,10 +1,11 @@
-module(dreki_node).
-include("dreki.hrl").
--include_lib("opentelemetry_api/include/otel_tracer.hrl").
+-include_lib("kernel/include/logger.hrl").
+-include("dreki_otel.hrl").
-behaviour(partisan_gen_fsm).
-compile({no_auto_import,[get/0]}).
-export([get/0, urn/0, stores/0]).
--export([rpc/4, rpc/5]).
+-export([rpc/4, rpc/5, remote_rpc/7]).
-export([uri/0]). % deprecated
-export([parents/0, parents/1, parent/0, parent/1]).
-export([neighbours/0, neighbours/1]).
@@ -18,17 +19,64 @@ rpc(Path, Mod, Fun, Args) ->
-spec rpc(dreki_urn(), module(), function(), Args :: [], #{timeout => non_neg_integer()}) -> {ok, any()} | {error, rpc_error()}.
rpc(Path, Mod, Fun, Args, #{timeout := Timeout}) ->
- ?with_span(<<"dreki_node:rpc">>, #{}, fun(ChildSpanCtx) ->
+ Myself = self(),
+ ?with_span(?FUN_NAME, fun(SpanCtx) ->
+ ?set_attributes(#{mod => Mod, fn => Fun, remote_node_path => Path}),
+ ?LOG_INFO("Ctx ~p ~p ~p ~p", [?FUN_NAME, otel_ctx:get_current(), otel_tracer:current_span_ctx(), SpanCtx]),
case dreki_world_dns:node_param(dreki_world:path_to_domain(Path), node_name) of
- {ok, NodeName} ->
- case partisan_rpc_backend:call(NodeName, Mod, Fun, Args, Timeout) of
- {badrpc, Error} -> {error, {rpc_error, Path, Error}};
- Result -> Result
- end;
- Error -> Error
- end
+ {ok, NodeName} ->
+ ?set_attribute(remote_node, NodeName),
+ Ctx = otel_ctx:get_current(),
+ ChildSpanCtx = ?start_span(?FUN_NAME(remote), #{}),
+ TraceId = otel_span:hex_trace_id(SpanCtx),
+ SpanId = otel_span:hex_span_id(SpanCtx),
+ case partisan_rpc_backend:call(NodeName, ?MODULE, remote_rpc, [Myself, Mod, Fun, Args, Timeout, TraceId, SpanId], Timeout) of
+ {badrpc, RpcError} ->
+ ?set_status(error, <<"RPC_ERROR">>),
+ {error, {rpc, RpcError}};
+ Error = {error, _} ->
+ ?set_status(error, <<"RESULT">>),
+ Error;
+ Result ->
+ ?set_status(ok),
+ Result
+ end;
+ Error ->
+ ?set_status(error, <<"NODE_NOT_FOUND">>),
+ Error
+ end
end).
+remote_rpc(Pid, Mod, Fun, Args, Timeout, TraceId, SpanId) ->
+ ParentCtx = otel_tracer:from_remote_span(TraceId, SpanId, 1),
+ otel_tracer:with_span(ParentCtx, opentelemetry:get_tracer(), ?FUN_NAME, #{},
+ fun (Ctx) -> remote_rpc(Pid, Mod, Fun, Args, Timeout, Ctx) end).
+
+remote_rpc(Pid, Mod, Fun, Args, Timeout, Ctx) ->
+ ?set_attributes(#{remote_node => node(), module => Mod, function => Fun}),
+ try apply(Mod, Fun, Args) of
+ {badrpc, RpcError} ->
+ ?set_status(error, <<"RPC_ERROR">>),
+ {error, {rpc, RpcError}};
+ Error = {error, _} ->
+ ?set_status(error, <<"RESULT">>),
+ Error;
+ Result ->
+ Result
+ catch
+ throw:Term:Stacktrace ->
+ ?set_status(error, <<"THROW">>),
+ {error, {throw, Term, Stacktrace}};
+ exit:Reason:Stacktrace ->
+ ?set_status(error, <<"EXIT">>),
+ {error, {exit, Reason, Stacktrace}};
+ error:Reason:Stacktrace ->
+ ?set_status(error, <<"RUNTIME">>),
+ {error, {runtime_error, Reason, Stacktrace}}
+ after
+ ?end_span()
+ end.
+
get() ->
{ok, Node} = dreki_world:get_node(dreki_world:node()),
Node.
@@ -100,4 +148,3 @@ ensure_local_node() ->
create_local_node() ->
dreki_world:create_node(uri(), #{}).
-
diff --git a/apps/dreki/src/dreki_node_server.erl b/apps/dreki/src/node/dreki_node_server.erl
index c8bca51..c8bca51 100644
--- a/apps/dreki/src/dreki_node_server.erl
+++ b/apps/dreki/src/node/dreki_node_server.erl
diff --git a/apps/dreki/src/node/rebar.conf b/apps/dreki/src/node/rebar.conf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/dreki/src/node/rebar.conf
diff --git a/apps/dreki/src/requests/dreki_requests.erl b/apps/dreki/src/requests/dreki_requests.erl
new file mode 100644
index 0000000..d970dbd
--- /dev/null
+++ b/apps/dreki/src/requests/dreki_requests.erl
@@ -0,0 +1,97 @@
+-module(dreki_requests).
+-behaviour(dreki_store_namespace).
+-export([start/0, version/0, valid_store/4, format_item/1, actions/0, actions/1, schemas/0, new/0, as_tuple/1, as_map/1]).
+-export([record_name/0, record_attributes/0]).
+-export([after_create/1]).
+-export([new/1, create/2]).
+-export([install/0]).
+
+-record(?MODULE, {
+ id,
+ version,
+ schema,
+ task_urn,
+ identity_urn,
+ node,
+ params
+ }).
+
+create(#{'@ns' := <<"tasks">>, '@location' := Loc, '@id' := TaskUrn}, Params0) ->
+ LocalStore = <<Loc/binary, "::requests:local">>,
+ Params = Params0#{task_urn => TaskUrn},
+ dreki_store:create(LocalStore, Params).
+
+install() ->
+ NodeUrn = dreki_node:urn(),
+ Urn = <<NodeUrn/binary, "::requests:local">>,
+ dreki_store:create_store(Urn, dreki_mnesia_store, #{}, #{}).
+
+record_name() ->
+ ?MODULE.
+
+record_attributes() ->
+ record_info(fields, ?MODULE).
+
+version() ->
+ 1.
+
+start() ->
+ ok.
+
+valid_store(_Ns, _Loc, _Name, _BackendMod) ->
+ ok.
+
+format_item(Item) ->
+ ok.
+
+actions() ->
+ [].
+
+actions(_) ->
+ [].
+
+schemas() ->
+ #{default => <<"request">>,
+ <<"request">> => #{
+ default_version => <<"1.0">>,
+ <<"1.0">> => schemas(<<"request">>, <<"1.0">>)
+ }
+ }.
+
+schemas(<<"request">>, <<"1.0">>) ->
+ #{
+ version => 'draft-06',
+ title => <<"Run Request">>,
+ type => object,
+ properties => #{
+ id => #{type => string, <<"dreki:form">> => #{readonly => true}},
+ node => #{type => string},
+ params => #{type => object}
+ },
+ required => [node, params]
+ }.
+
+new() ->
+ schemas(<<"request">>, <<"1.0">>).
+
+new(#{'@ns' := <<"tasks">>, '@id' := TaskUrn, id := TaskId, module := Module, params := TaskParams}) ->
+ Schema0 = schemas(<<"request">>, <<"1.0">>),
+ Schema = key_value:set([properties, params], TaskParams, Schema0),
+ {ok, Schema}.
+
+as_tuple(#{id := Id, task_urn := TaskUrn, node := Node, params := Params}) ->
+ #?MODULE{id = Id, version = undefined, schema = undefined, task_urn = TaskUrn,
+ node = Node, params = Params}.
+
+as_map(R = #?MODULE{}) ->
+ #{
+ id => R#?MODULE.id,
+ version => R#?MODULE.version,
+ schema => R#?MODULE.schema,
+ task_urn => R#?MODULE.task_urn,
+ node => R#?MODULE.node,
+ params => R#?MODULE.params
+ }.
+
+after_create(Request) ->
+ ok.
diff --git a/apps/dreki/src/runs/dreki_runs.erl b/apps/dreki/src/runs/dreki_runs.erl
new file mode 100644
index 0000000..9509fa6
--- /dev/null
+++ b/apps/dreki/src/runs/dreki_runs.erl
@@ -0,0 +1 @@
+-module(dreki_runs).
diff --git a/apps/dreki/src/storages/dreki_storages.erl b/apps/dreki/src/storages/dreki_storages.erl
new file mode 100644
index 0000000..3a87347
--- /dev/null
+++ b/apps/dreki/src/storages/dreki_storages.erl
@@ -0,0 +1,98 @@
+-module(dreki_storages).
+-behaviour(dreki_store_namespace).
+-export([start/0, version/0, valid_store/4, format_item/1, actions/0, actions/1, schemas/0, as_tuple/1, as_map/1]).
+-export([record_name/0, record_attributes/0]).
+-export([after_create/1]).
+-export([install_local_store/0, create_local_storage/4]).
+
+-record(?MODULE, {
+ id,
+ version,
+ schema,
+ name,
+ type,
+ handler,
+ params,
+ tags = []
+ }).
+
+install_local_store() ->
+ NodeUrn = dreki_node:urn(),
+ Urn = <<NodeUrn/binary, "::storages:local">>,
+ dreki_store:create_store(Urn, dreki_mnesia_store, #{}, #{}).
+
+create_local_storage(Type, Handler, Params, Tags) ->
+ NodeUrn = dreki_node:urn(),
+ LocalStoreUrn = <<NodeUrn/binary, "::storages:local">>,
+ Storage = #{
+ type => Type,
+ handler => Handler,
+ params => Params,
+ tags => Tags
+ },
+ dreki_store:create(LocalStoreUrn, Storage).
+
+record_name() -> ?MODULE.
+record_attributes() -> record_info(fields, ?MODULE).
+version() -> 1.
+
+start() ->
+ ok.
+
+valid_store(_Ns, _Loc, _Name, _BackendMod) ->
+ ok.
+
+format_item(Item) ->
+ ok.
+
+actions() ->
+ [].
+
+actions(_) ->
+ [].
+
+schemas() ->
+ #{default => <<"storage">>,
+ <<"storage">> => #{
+ default_version => <<"1.0">>,
+ <<"1.0">> => schemas(<<"storage">>, <<"1.0">>)
+ }
+ }.
+
+schemas(<<"storage">>, <<"1.0">>) ->
+ #{
+ version => 'draft-06',
+ title => <<"Storage">>,
+ type => object,
+ required => [id, type, handler, params],
+ properties => #{
+ id => #{type => string, <<"dreki:form">> => #{}},
+ name => #{type => string},
+ type => #{type => string, enum => [<<"fs">>, <<"os">>, <<"bs">>]},
+ handler => #{type => string, enum => [<<"zfs">>, <<"fs">>]},
+ params => #{type => object},
+ tags => #{type => array, items => #{type => string}}
+ }
+ }.
+
+as_tuple(Map = #{id := Id, type := Type, handler := Handler, params := Params, tags := Tags}) ->
+ #?MODULE{id = Id,
+ name = maps:get(name, Map, undefined),
+ type = Type,
+ handler = Handler,
+ params = Params,
+ tags = maps:get(tags, Map, [])
+ }.
+
+as_map(R = #?MODULE{}) ->
+ #{
+ id => R#?MODULE.id,
+ name => R#?MODULE.name,
+ type => R#?MODULE.type,
+ handler => R#?MODULE.handler,
+ params => R#?MODULE.params,
+ tags => R#?MODULE.tags
+ }.
+
+after_create(_Storage) ->
+ ok.
diff --git a/apps/dreki/src/storages/dreki_storages_zfs.erl b/apps/dreki/src/storages/dreki_storages_zfs.erl
new file mode 100644
index 0000000..fc8ce00
--- /dev/null
+++ b/apps/dreki/src/storages/dreki_storages_zfs.erl
@@ -0,0 +1,2 @@
+-module(dreki_storages_zfs).
+
diff --git a/apps/dreki/src/dreki_dets_store.erl b/apps/dreki/src/store/dreki_dets_store.erl
index 05cf9fb..aaa4c23 100644
--- a/apps/dreki/src/dreki_dets_store.erl
+++ b/apps/dreki/src/store/dreki_dets_store.erl
@@ -4,7 +4,7 @@
-behaviour(dreki_store_backend).
--export([start/0, start/4, checkout/1, checkin/1, stop/0, stop/1]).
+-export([start/0, start/5, checkout/1, checkin/1, stop/0, stop/1]).
-export([valid_store/5]).
-export([list/1, count/1, exists/2, get/2, create/2, update/2, delete/2]).
@@ -14,7 +14,7 @@ start() -> ok.
valid_store(_Namespace, _Location, _Name, _NSMod, _Args) -> ok.
-start(Namespace, Name, _XUrn, Args) ->
+start(Namespace, _NsMod, Name, _XUrn, Args) ->
StoreName = {?MODULE, Namespace, Name},
File = maps:get(file_name, Args, <<Namespace/binary, ".", Name/binary, ".dets">>),
FileName = binary:bin_to_list(File),
@@ -71,4 +71,3 @@ update({dreki_dets_store_ref, Tab}, Task = #dreki_task{persisted=true, dirty=tru
ok -> {ok, Task#dreki_task{dirty=false}};
{error, Error} -> {error, Error}
end.
-
diff --git a/apps/dreki/src/store/dreki_mnesia_store.erl b/apps/dreki/src/store/dreki_mnesia_store.erl
new file mode 100644
index 0000000..aceae09
--- /dev/null
+++ b/apps/dreki/src/store/dreki_mnesia_store.erl
@@ -0,0 +1,94 @@
+-module(dreki_mnesia_store).
+-include("dreki.hrl").
+-include("dreki_otel.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include_lib("kernel/include/logger.hrl").
+-behaviour(dreki_store_backend).
+
+-export([start/0, start/5, checkout/1, checkin/1, stop/0, stop/1]).
+-export([valid_store/5]).
+-export([list/1, count/1, exists/2, get/2, create/2, update/2, delete/2]).
+
+start() ->
+ ok.
+
+valid_store(_NS, _Loc, _Name, _NSMod, _Args) ->
+ ok.
+
+start(Namespace, NsMod, Name, _XUrn, Args) ->
+ TabName = binary_to_atom(iolist_to_binary(["dreki_mnesia_store", "-", Namespace, "-", Name])),
+ case lists:member(TabName, mnesia:system_info(tables)) of
+ true ->
+ {ok, TabName};
+ false ->
+ DefaultOptions = [{rocksdb_copies, [node()]}],
+ Options0 = maps:get(mnesia_table_options, Args, DefaultOptions),
+ Options = [{attributes, NsMod:record_attributes()}, {record_name, NsMod:record_name()} | Options0],
+ {atomic, ok} = mnesia:create_table(TabName, Options),
+ ?LOG_NOTICE(#{message => "Created mnesia table for store", mnesia_table => TabName, store_namespace => Namespace, store_name => Name}),
+ {ok, TabName}
+ end.
+
+checkout(TabName) ->
+ {ok, TabName}.
+
+checkin(_) ->
+ ok.
+
+stop() ->
+ ok.
+
+stop(_) ->
+ ok.
+
+list(TabName) ->
+ transaction(fun () ->
+ [get(TabName, Id) || Id <- iter_all(mnesia:first(TabName), TabName)]
+ end).
+
+count(TabName) ->
+ mnesia:table_info(TabName, size).
+
+get(TabName, Id) ->
+ transaction(fun() ->
+ case mnesia:read(TabName, Id) of
+ [Tuple] ->
+ Tuple;
+ [] ->
+ not_found
+ end
+ end).
+
+exists(TabName, Id) ->
+ case get(TabName, Id) of
+ not_found ->
+ false;
+ _ ->
+ true
+ end.
+
+create(Table, Tuple) ->
+ transaction(fun() ->
+ mnesia:write(Table, Tuple, write),
+ get(Table, element(2, Tuple))
+ end).
+
+update(Table, Tuple) ->
+ create(Table, Tuple).
+
+delete(TabName, Id) ->
+ transaction(fun() ->
+ mnesia:delete(TabName, Id, write)
+ end).
+
+transaction(Fun) ->
+ ?with_span(?FUN_NAME, #{}, fun(_) -> {atomic, Result} = mnesia:transaction(Fun), Result end).
+
+iter_all(Id, TabName) ->
+ ?with_span(?FUN_NAME, #{}, fun(_) -> iter_all(Id, TabName, []) end).
+
+iter_all('$end_of_table', _, Acc) ->
+ Acc;
+iter_all(Id, TabName, Acc) ->
+ [Tuple] = mnesia:read(TabName, Id),
+ iter_all(mnesia:next(TabName, Id), TabName, [Tuple | Acc]).
diff --git a/apps/dreki/src/dreki_store.erl b/apps/dreki/src/store/dreki_store.erl
index ae6fd66..63d87a8 100644
--- a/apps/dreki/src/dreki_store.erl
+++ b/apps/dreki/src/store/dreki_store.erl
@@ -1,7 +1,7 @@
-module(dreki_store).
-include("dreki.hrl").
-include("dreki_plum.hrl").
--include_lib("opentelemetry_api/include/otel_tracer.hrl").
+-include("dreki_otel.hrl").
-define(BACKENDS_PT, {dreki_stores, backends}).
-compile({no_auto_import,[get/1]}).
-export([backends/0]).
@@ -16,13 +16,20 @@
-export([callback/3, list_/2, get_/2, new_/2, create_/3, update_/3, delete_/2]).
--record(store, {urn, xurn, namespace, name, namespace_mod, backend_mod, backend_params}).
+-record(store, {urn, xurn, namespace, name, namespace_mod, format, backend_mod, backend_params}).
-type t() :: #store{}.
-type store() :: t() | dreki_uri() | dreki_expanded_uri().
-backends() -> [dreki_dets_store, dreki_world_store].
-namespaces() -> [{<<"tasks">>, dreki_tasks, #{}}].
+backends() ->
+ [dreki_dets_store, dreki_world_store].
+
+namespaces() ->
+ [
+ {<<"tasks">>, dreki_tasks, #{}},
+ {<<"requests">>, dreki_requests, #{}},
+ {<<"storages">>, dreki_storages, #{}}
+ ].
namespace(Name) ->
lists:keyfind(Name, 1, namespaces()).
@@ -80,7 +87,7 @@ start_store__(Store) ->
_Started -> {error, {store_already_started, Store#store.urn}}
end.
start_store___(Store = #store{backend_mod = Mod}) ->
- case Mod:start(Store#store.namespace, Store#store.name, Store#store.urn, Store#store.backend_params) of
+ case Mod:start(Store#store.namespace, Store#store.namespace_mod, Store#store.name, Store#store.urn, Store#store.backend_params) of
{ok, Backend} -> {ok, Backend};
{error, Error} -> {error, {store_start_failed, Store#store.urn, Error}}
end.
@@ -163,10 +170,11 @@ get_store(Location, Namespace, Name) ->
end.
as_record([Map]) -> as_record(Map);
-as_record(#{urn := Urn, name := Name, namespace := Namespace, module := Module, module_params := ModuleParams}) ->
+as_record(Store = #{urn := Urn, name := Name, namespace := Namespace, module := Module, module_params := ModuleParams}) ->
{ok, XUrn} = dreki_urn:expand(Urn),
{_, NSMod, _} = lists:keyfind(Namespace, 1, namespaces()),
- #store{urn = Urn, xurn = XUrn, name = Name, namespace = Namespace, namespace_mod = NSMod, backend_mod = Module, backend_params = ModuleParams}.
+ Format = maps:get(format, Store, tuple),
+ #store{urn = Urn, xurn = XUrn, name = Name, namespace = Namespace, namespace_mod = NSMod, format = Format, backend_mod = Module, backend_params = ModuleParams}.
store_as_map(#store{urn = Urn, name = Name, namespace = Namespace, namespace_mod = NSMod, backend_mod = Module, backend_params = ModuleParams}) ->
#{urn => Urn, name => Name, namespace => Namespace, namespace_mod => NSMod, backend_mod => Module, backend_params => ModuleParams}.
@@ -191,14 +199,14 @@ delete(SArg) ->
get_store_(SArg, delete_, []).
list_(#{resource := #{directory := _}}, Store) ->
- handle_result(Store, collection, callback(Store, list, []));
+ handle_result(Store, collection, callback(Store, list, []), list);
list_(#{schema := _}, Store) ->
{todo, schema};
list_(_, _) ->
not_supported.
get_(#{resource := #{resource := #{id := Id}}}, Store) ->
- handle_result(Store, single, callback(Store, get, [Id]));
+ handle_result(Store, single, callback(Store, get, [Id]), get);
get_(#{schema := _}, Store) ->
{todo, get_schema};
get_(_, _) -> not_supported.
@@ -208,21 +216,36 @@ new_(#{resource := #{directory := _}}, Store) ->
New = NSMod:new(),
{ok, New}.
-create_(XUrn = #{directory := _}, Store, Data) ->
- case validate(XUrn, Data) of
- {ok, _} ->
- handle_result(Store, single, callback(Store, create, [Data]));
- {error, Errors} -> {error, #{code => validation_failed, status => 422, errors => jesse_error:to_json({error, Errors}, [])}}
- end;
+create_(XUrn = #{resource := #{directory := _}}, Store, Data0) ->
+ Id = maps:get(id, Data0, ksuid:gen_id()),
+ Data1 = Data0#{id => Id},
+ case validate(XUrn, Data1) of
+ {ok, _} ->
+ Data = case Store#store.format of
+ tuple ->
+ Mod = Store#store.namespace_mod,
+ Mod:as_tuple(Data1);
+ map -> Data1
+ end,
+ handle_result(Store, single, callback(Store, create, [Data]), create);
+ {error, Errors} ->
+ {error, #{code => validation_failed, status => 422, errors => jesse_error:to_json({error, Errors}, [])}}
+ end;
create_(_, _, _) ->
- not_supported.
+ not_supported.
-update_(XUrn = #{resource := _}, Store, Data) ->
+update_(XUrn = #{resource := #{resource := _}}, Store, Data0) ->
case get(XUrn) of
{ok, Prev} ->
- case validate(XUrn, Data) of
+ case validate(XUrn, Data0) of
{ok, _} ->
- handle_result(Store, single, callback(Store, create, [Data]));
+ Data = case Store#store.format of
+ tuple ->
+ Mod = Store#store.namespace_mod,
+ Mod:as_tuple(Data0);
+ map -> Data0
+ end,
+ handle_result(Store, single, callback(Store, update, [XUrn, Data]), update);
{error, Errors} ->
{error, #{code => validation_failed, status => 422, errors => jesse_error:to_json({error, Errors}, [])}}
end;
@@ -240,21 +263,27 @@ get_store_(StoreArg, Fun, Args) when is_binary(StoreArg) ->
logger:debug("Expanding in get_store_ :) ~p", [StoreArg]),
case dreki_urn:expand(StoreArg) of
Error = {error, _} -> Error;
- {ok, XUrn = #{resource := #{schemas := _}}} -> get_schema_(XUrn, Fun, Args);
- {ok, XUrn = #{resource := #{schema := _}}} -> get_schema_(XUrn, Fun, Args);
{ok, XUrn} -> get_store_(XUrn, Fun, Args)
end;
+get_store_(XUrn = #{resource := #{shemas := _}}, Fun, Args) ->
+ get_schema_(XUrn, Fun, Args);
+get_store_(XUrn = #{resource := #{schema := _}}, Fun, Args) ->
+ get_schema_(XUrn, Fun, Args);
get_store_(XUrn = #{resource := _}, Fun, Args) ->
- ?with_span(<<"dreki_store:get_store_">>, #{}, fun(_Ctx) ->
- logger:debug("Getting store usually! ~p", [XUrn]),
- case get_store(XUrn) of
+ %%T = otel_tracer:start_span(opentelemetry:get_tracer(), <<"dreki_store">>, #{}),
+ %%?set_current_span(T),
+ ?with_span(opentelemetry:get_tracer(), #{}, fun(SpanCtx) ->
+ logger:debug("Getting store usually! ~p / Ctx: ~p / Span Ctx ~p / Cur Span Ctx ~p", [XUrn, otel_ctx:get_current(), SpanCtx, otel_tracer:current_span_ctx()]),
+ Result = case get_store(XUrn) of
Error = {error, _} -> Error;
{ok, Store} ->
case apply(?MODULE, Fun, [XUrn, Store | Args]) of
not_supported -> {error, {not_supported, Fun, maps:get(urn, XUrn)}};
Out -> Out
end
- end
+ end,
+ ?end_span(),
+ Result
end).
is_local(#store{xurn = #{kind := region}}) -> true;
@@ -279,38 +308,50 @@ callback(true, #store{urn = Urn, backend_mod = Mod, backend_params = Params}, Fu
Res
end.
-handle_result(_, _, {error, Err}) -> {error, Err};
-handle_result(Store, collection, {ok, Collection}) when is_list(Collection) ->
- handle_result(Store, collection, Collection);
-handle_result(Store, collection, Collection) when is_list(Collection) ->
- {ok, handle_collection_result(Collection, Store, [])};
-handle_result(Store, single, {ok, Item}) when is_map(Item) ->
- {ok, handle_single_result(Item, Store)}.
-
-handle_collection_result([Item | Rest], Store, Acc0) ->
- Acc = case handle_single_result(Item, Store) of
+handle_result(_, _, {error, Err}, _) -> {error, Err};
+handle_result(Store, collection, {ok, Collection}, PostCallback) when is_list(Collection) ->
+ handle_result(Store, collection, Collection, PostCallback);
+handle_result(Store, collection, Collection, PostCallback) when is_list(Collection) ->
+ {ok, handle_collection_result(Collection, Store, [], PostCallback)};
+handle_result(Store, single, Item, PostCallback) ->
+ handle_single_result(Item, Store, PostCallback);
+handle_result(Store, single, {ok, Item}, PostCallback)->
+ handle_single_result(Item, Store, PostCallback).
+
+handle_collection_result([Item | Rest], Store, Acc0, PostCallback) ->
+ Acc = case handle_single_result(Item, Store, PostCallback) of
ignore -> Acc0;
{ok, I} -> [I | Acc0]
end,
- handle_collection_result(Rest, Store, Acc);
-handle_collection_result([], Store, Acc) ->
+ handle_collection_result(Rest, Store, Acc, PostCallback);
+handle_collection_result([], Store, Acc, _PostCallback) ->
format_collection(Acc, Store).
-handle_single_result(Item = #{id := LId}, Store = #store{namespace_mod = Mod}) ->
+handle_single_result(Tuple, Store = #store{format = tuple, namespace_mod = Mod}, PostCallback) when is_tuple(Tuple) ->
+ handle_single_result(Mod:as_map(Tuple), Store, PostCallback);
+handle_single_result(Item = #{id := LId}, Store = #store{namespace_mod = Mod}, PostCallback) ->
case Mod:format_item(Item) of
- ok -> format_item(Item, Store);
- {ok, M} -> format_item(M, Store);
+ ok -> format_item(Item, Item, Store, PostCallback);
+ {ok, M} -> format_item(M, Item, Store, PostCallback);
Err -> Err
end.
-format_item(Item = #{id := LId}, Store = #store{urn = Urn}) ->
+format_item(Item = #{id := LId}, OriginalItem, Store = #store{urn = Urn, namespace = NS, namespace_mod = Mod}, PostCallback) ->
+ {ok, XUrn} = dreki_urn:expand(Urn),
+ SelfUrn = <<Urn/binary, ":", LId/binary>>,
AtLinks = #{
- self => <<Urn/binary, ":", LId>>,
- parent => maps:get(location, Urn)
+ self => SelfUrn,
+ parent => Urn
},
AllAtLinks = maps:merge(AtLinks, maps:get('@links', Item, #{})),
- I = #{'@id' => <<Urn/binary, ":", LId>>, '@links' => AllAtLinks},
- {ok, maps:merge(I, Item)}.
+ Actions = [#{id => Id, title => Title, new => New, create => Create} || {Id, Title, New, Create} <- Mod:actions(OriginalItem)],
+ I = #{'@id' => SelfUrn, '@ns' => NS, '@location' => maps:get(location, XUrn), '@links' => AllAtLinks, '@actions' => Actions},
+ FullItem = maps:merge(I, Item),
+ case PostCallback of
+ create -> Mod:after_create(FullItem);
+ _ -> undefined
+ end,
+ {ok, FullItem}.
format_collection(Data, Store = #store{urn = Urn}) ->
#{'@links' => #{self => Urn},
@@ -362,11 +403,12 @@ get_schema_(#{urn := Urn, resource := #{namespace := NS, schema := #{schema := d
case maps:get(default_version, Schema, not_found) of
not_found -> not_found;
SchemaVer ->
- NewUrn = binary:replace(Urn, <<"schemas::">>, <<"schemas:", SchemaName/binary, ":", SchemaVer/binary>>),
+ NewUrn0 = binary:replace(Urn, <<"::schemas::">>, <<>>),
+ NewUrn = <<NewUrn0/binary, "::schemas:", SchemaName/binary, ":", SchemaVer/binary>>,
{ok, XUrn} = dreki_urn:expand(NewUrn),
- logger:debug("Looking up default schema as ~p", [NewUrn, XUrn]),
+ logger:debug("Looking up default schema as ~p (~p)", [NewUrn, XUrn]),
get_schema_(XUrn, get_, Args)
- end
+ end
end;
get_schema_(XUrn = #{resource := #{namespace := NS, schema := #{schema := SchemaName, version := Version}}}, get_, _) ->
{_, NSMod, _} = namespace(NS),
@@ -423,7 +465,11 @@ get_xurn_namespace(#{resource := #{namespace := NS}}) -> NS;
get_xurn_namespace(#{resource := #{directory := #{namespace := NS}}}) -> NS;
get_xurn_namespace(#{resource := #{resource := #{namespace := NS}}}) -> NS.
+schema_loader(SchemaRef) ->
+ logger:debug("Trying to load schema ~p", [SchemaRef]),
+ get(SchemaRef).
+
validate_(XUrn, Schema, Data) ->
JesseOpts = [{allowed_errors, infinity},
- {schema_loader_fun, fun get/1}],
+ {schema_loader_fun, fun schema_loader/1}],
jesse:validate_with_schema(Schema, Data, JesseOpts).
diff --git a/apps/dreki/src/dreki_store_backend.erl b/apps/dreki/src/store/dreki_store_backend.erl
index 55db90e..55db90e 100644
--- a/apps/dreki/src/dreki_store_backend.erl
+++ b/apps/dreki/src/store/dreki_store_backend.erl
diff --git a/apps/dreki/src/dreki_store_namespace.erl b/apps/dreki/src/store/dreki_store_namespace.erl
index 3095ef7..3095ef7 100644
--- a/apps/dreki/src/dreki_store_namespace.erl
+++ b/apps/dreki/src/store/dreki_store_namespace.erl
diff --git a/apps/dreki/src/dreki_task.erl b/apps/dreki/src/tasks/dreki_task.erl
index b762aea..b762aea 100644
--- a/apps/dreki/src/dreki_task.erl
+++ b/apps/dreki/src/tasks/dreki_task.erl
diff --git a/apps/dreki/src/dreki_tasks.erl b/apps/dreki/src/tasks/dreki_tasks.erl
index 0899386..750aed0 100644
--- a/apps/dreki/src/dreki_tasks.erl
+++ b/apps/dreki/src/tasks/dreki_tasks.erl
@@ -2,7 +2,7 @@
-include("dreki.hrl").
-behaviour(dreki_store_namespace).
--export([start/0, version/0, valid_store/4, format_item/1, schemas/0, new/0]).
+-export([start/0, version/0, valid_store/4, format_item/1, actions/0, actions/1, schemas/0, new/0, as_tuple/1, as_map/1]).
%% old stuff
-export([resolve/1, exists/1, read_uri/2]).
@@ -13,14 +13,14 @@ valid_store(_Namespace, _Location, _Name, _BackendMod) -> ok.
format_item(Item) -> ok.
-handlers() -> [dreki_tasks_script, dreki_tasks_cloyster].
+handlers() -> [dreki_tasks_script, dreki_tasks_cloyster, drekid_function].
--record(?MODULE, {
+-record(t, {
id,
version,
schema,
- handler,
- handler_manifest
+ module,
+ params
}).
version() -> 1.
@@ -29,13 +29,25 @@ new() ->
#{
<<"@schema">> => <<"task:1.0">>,
<<"id">> => ksuid:gen_id(),
- <<"handler">> => <<"dreki_tasks_cloyster">>,
- <<"handler_manifest">> => #{
+ <<"module">> => <<"dreki_tasks_cloyster">>,
+ <<"params">> => #{
<<"@schema">> => <<"cloyster-task:1.0">>,
<<"script">> => <<>>
}
}.
+actions() ->
+ [
+ {new, <<"New task">>, {dreki_tasks, new, []}, {dreki_store, create, []}}
+ ].
+
+actions(_) ->
+ [
+ {request, <<"Request run">>,
+ {dreki_requests, new, []},
+ {dreki_store, create, []}}
+ ].
+
schemas() ->
Subs = lists:foldr(fun (Handler, Acc) -> maps:merge(Acc, Handler:schemas()) end,
#{}, handlers()),
@@ -80,6 +92,19 @@ schemas(task, <<"1.0">>) ->
required => [handler, handler_manifest]
}.
+as_tuple(#{id := Id, module := Module, params := Params}) ->
+ #t{id = Id, version = undefined, schema = undefined,
+ module = Module, params = Params}.
+
+as_map(Task = #t{}) ->
+ #{
+ id => Task#t.id,
+ version => Task#t.version,
+ schema => Task#t.schema,
+ module => Task#t.module,
+ params => Task#t.params
+ }.
+
%% old stuff
read_uri(undefined, Uri) ->
@@ -136,6 +161,6 @@ load_local_stores() ->
env => Env,
name => Name
},
- maps:put(Name, Store, Acc)
+ maps:put(Name, Store, Acc)
end,
lists:foldr(MapFn, #{}, Val).
diff --git a/apps/dreki/src/dreki_tasks_cloyster.erl b/apps/dreki/src/tasks/dreki_tasks_cloyster.erl
index 3fb045d..3fb045d 100644
--- a/apps/dreki/src/dreki_tasks_cloyster.erl
+++ b/apps/dreki/src/tasks/dreki_tasks_cloyster.erl
diff --git a/apps/dreki/src/dreki_tasks_script.erl b/apps/dreki/src/tasks/dreki_tasks_script.erl
index 8eeb563..8eeb563 100644
--- a/apps/dreki/src/dreki_tasks_script.erl
+++ b/apps/dreki/src/tasks/dreki_tasks_script.erl
diff --git a/apps/dreki/src/dreki_world.erl b/apps/dreki/src/world/dreki_world.erl
index 437b6c8..437b6c8 100644
--- a/apps/dreki/src/dreki_world.erl
+++ b/apps/dreki/src/world/dreki_world.erl
diff --git a/apps/dreki/src/dreki_world_dns.erl b/apps/dreki/src/world/dreki_world_dns.erl
index 058c9db..058c9db 100644
--- a/apps/dreki/src/dreki_world_dns.erl
+++ b/apps/dreki/src/world/dreki_world_dns.erl
diff --git a/apps/dreki/src/dreki_world_plum_events.erl b/apps/dreki/src/world/dreki_world_plum_events.erl
index b32ae09..b32ae09 100644
--- a/apps/dreki/src/dreki_world_plum_events.erl
+++ b/apps/dreki/src/world/dreki_world_plum_events.erl
diff --git a/apps/dreki/src/dreki_world_server.erl b/apps/dreki/src/world/dreki_world_server.erl
index 2bc41ed..2bc41ed 100644
--- a/apps/dreki/src/dreki_world_server.erl
+++ b/apps/dreki/src/world/dreki_world_server.erl
diff --git a/apps/dreki/src/dreki_world_store.erl b/apps/dreki/src/world/dreki_world_store.erl
index 71ef2ce..050efd0 100644
--- a/apps/dreki/src/dreki_world_store.erl
+++ b/apps/dreki/src/world/dreki_world_store.erl
@@ -8,7 +8,7 @@
-type db() :: #store{}.
-type args() :: #{}.
--export([start/0, start/4, checkout/1, checkin/1, stop/0, stop/1]).
+-export([start/0, start/5, checkout/1, checkin/1, stop/0, stop/1]).
-export([valid_store/5]).
-export([list/1, count/1, exists/2, get/2, create/2, update/2, delete/2]).
@@ -19,7 +19,7 @@ valid_store(_Namespace, Location, _Name, _NSMod, _Args) ->
end.
start() -> ok.
-start(_, _, _, _) -> {ok, dreki_world_store}.
+start(_, _, _, _, _) -> {ok, dreki_world_store}.
checkout(_) -> {ok, #store{}}.
checkin(_) -> ok.
diff --git a/apps/dreki/src/dreki_world_tasks.erl b/apps/dreki/src/world/dreki_world_tasks.erl
index c27a7dc..c27a7dc 100644
--- a/apps/dreki/src/dreki_world_tasks.erl
+++ b/apps/dreki/src/world/dreki_world_tasks.erl
diff --git a/apps/dreki_web/.gitignore b/apps/dreki_web/.gitignore
index 8dec94f..8946870 100644
--- a/apps/dreki_web/.gitignore
+++ b/apps/dreki_web/.gitignore
@@ -18,3 +18,4 @@ _build
rebar3.crashdump
*~
/priv/static/
+/assets/node_modules/
diff --git a/apps/dreki_web/assets/package-lock.json b/apps/dreki_web/assets/package-lock.json
index c99c8b3..b6aa028 100644
--- a/apps/dreki_web/assets/package-lock.json
+++ b/apps/dreki_web/assets/package-lock.json
@@ -12,7 +12,8 @@
"@tailwindcss/forms": "^0.5.0",
"@tailwindcss/line-clamp": "^0.3.1",
"@tailwindcss/typography": "^0.5.2",
- "crossfilter": "^1.3.12"
+ "crossfilter": "^1.3.12",
+ "xterm": "^4.18.0"
},
"devDependencies": {
"@hotwired/stimulus": "^3.0.1",
@@ -2063,6 +2064,11 @@
"node": ">=0.4"
}
},
+ "node_modules/xterm": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.18.0.tgz",
+ "integrity": "sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ=="
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -3567,6 +3573,11 @@
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
+ "xterm": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.18.0.tgz",
+ "integrity": "sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ=="
+ },
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/apps/dreki_web/assets/package.json b/apps/dreki_web/assets/package.json
index 07072c5..1aa07ff 100644
--- a/apps/dreki_web/assets/package.json
+++ b/apps/dreki_web/assets/package.json
@@ -27,6 +27,7 @@
"@tailwindcss/forms": "^0.5.0",
"@tailwindcss/line-clamp": "^0.3.1",
"@tailwindcss/typography": "^0.5.2",
- "crossfilter": "^1.3.12"
+ "crossfilter": "^1.3.12",
+ "xterm": "^4.18.0"
}
}
diff --git a/apps/dreki_web/rebar.config b/apps/dreki_web/rebar.config
index b16060a..e439af6 100644
--- a/apps/dreki_web/rebar.config
+++ b/apps/dreki_web/rebar.config
@@ -5,7 +5,8 @@
{erlydtl, "0.14.0"},
{oauth2c, {git, "https://github.com/kivra/oauth2_client", {branch, "master"}}},
{cowboy_telemetry, "~> 0.4.0"},
- {opentelemetry_cowboy, "~> 0.1.0"}
+ {opentelemetry_cowboy, "~> 0.1.0"},
+ {prometheus_cowboy, "0.1.8"}
]}.
{plugins, [
diff --git a/apps/dreki_web/src/dreki_web_admin_tasks.erl b/apps/dreki_web/src/admin/dreki_web_admin_tasks.erl
index adc2689..adc2689 100644
--- a/apps/dreki_web/src/dreki_web_admin_tasks.erl
+++ b/apps/dreki_web/src/admin/dreki_web_admin_tasks.erl
diff --git a/apps/dreki_web/src/dreki_web_admin_world.erl b/apps/dreki_web/src/admin/dreki_web_admin_world.erl
index 1ceaaee..1ceaaee 100644
--- a/apps/dreki_web/src/dreki_web_admin_world.erl
+++ b/apps/dreki_web/src/admin/dreki_web_admin_world.erl
diff --git a/apps/dreki_web/src/dreki_web_index.erl b/apps/dreki_web/src/api/dreki_web_index.erl
index 2ed8a38..2ed8a38 100644
--- a/apps/dreki_web/src/dreki_web_index.erl
+++ b/apps/dreki_web/src/api/dreki_web_index.erl
diff --git a/apps/dreki_web/src/dreki_web_task.erl b/apps/dreki_web/src/api/dreki_web_task.erl
index fdcf9cd..fdcf9cd 100644
--- a/apps/dreki_web/src/dreki_web_task.erl
+++ b/apps/dreki_web/src/api/dreki_web_task.erl
diff --git a/apps/dreki_web/src/cowboy_access_log_h.erl b/apps/dreki_web/src/cowboy_access_log_h.erl
new file mode 100644
index 0000000..433d706
--- /dev/null
+++ b/apps/dreki_web/src/cowboy_access_log_h.erl
@@ -0,0 +1,238 @@
+-module(cowboy_access_log_h).
+-behaviour(cowboy_stream).
+
+-dialyzer(no_undefined_callbacks).
+
+-type extra_info_fun() :: fun((cowboy_req:req()) -> #{atom() => term()}).
+-export_type([extra_info_fun/0]).
+
+%% API exports
+
+-export([set_extra_info_fun/2]).
+
+%% callback exports
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-type state() :: #{
+ next := any(),
+ req := cowboy_req:req(),
+ meta := #{started_at => genlib_time:ts()},
+ ext_fun := extra_info_fun()
+}.
+
+%% API
+
+-spec set_extra_info_fun(extra_info_fun(), cowboy:opts())
+ -> cowboy:opts().
+set_extra_info_fun(Fun, Opts) when is_function(Fun, 1) ->
+ Opts#{extra_info_fun => Fun}.
+
+%% callbacks
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {cowboy_stream:commands(), state()}.
+init(StreamID, Req, Opts) ->
+ State = make_state(Req, Opts),
+ {Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ {Commands0, State#{next => Next}}.
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::state().
+data(StreamID, IsFin, Data, #{next := Next0} = State) ->
+ {Commands0, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ {Commands0, State#{next => Next}}.
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::state().
+info(StreamID, {IsResponse, Code, Headers, _} = Info, #{req := Req, next := Next0} = State) when
+ IsResponse == response;
+ IsResponse == error_response
+->
+ _ = log_access_safe(Code, Headers, State, get_request_body_length(Req)),
+ {Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ {Commands0, State#{next => Next}};
+info(StreamID, Info, #{next := Next0} = State) ->
+ {Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ {Commands0, State#{next => Next}}.
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), state()) -> any().
+terminate(StreamID, Reason, #{next := Next}) ->
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+
+%% NOTE: in early_error cowboy uses PartialReq, a cowboy_req:req() - like structure
+%% for more info see https://ninenines.eu/docs/en/cowboy/2.7/manual/cowboy_stream/#_callbacks
+
+early_error(StreamID, Reason, PartialReq, {_, Code, Headers, _} = Resp, Opts) ->
+ State = make_state(PartialReq, Opts),
+ _ = log_access_safe(Code, Headers, State, undefined),
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, State).
+
+%% private functions
+
+log_access_safe(Code, Headers, #{req := Req} = State, ReqBodyLength) ->
+ try
+ logger:log(info, prepare_meta(Code, Headers, State, ReqBodyLength)),
+ Req
+ catch
+ Class:Reason:Stacktrace ->
+ Stack = genlib_format:format_stacktrace(Stacktrace, [newlines]),
+ _ = logger:error(
+ "Log access failed for: [~p, ~p, ~p]~nwith: ~p:~p~nstacktrace: ~ts",
+ [Code, Headers, Req, Class, Reason, Stack]
+ ),
+ Req
+ end.
+
+get_process_meta() ->
+ case logger:get_process_metadata() of
+ undefined ->
+ #{};
+ Meta ->
+ Meta
+ end.
+
+% domain field specifies the functional area that send log event
+% as we want to save logs from this app in a separate file,
+% we can easily filter logs by their domain using OTP filter functions.
+prepare_meta(Code, Headers, #{req := Req, meta:= Meta0, ext_fun := F}, ReqBodyLength) ->
+ AccessMeta = genlib_map:compact(#{
+ domain => [cowboy_access_log],
+ status => Code,
+ remote_addr => get_remote_addr(Req),
+ peer_addr => get_peer_addr(Req),
+ request_method => cowboy_req:method(Req),
+ request_path => cowboy_req:path(Req),
+ request_length => ReqBodyLength,
+ response_length => get_response_len(Headers),
+ request_time => get_request_duration(Meta0),
+ trace_id => maps:get(<<"trace-id">>, Req, <<"no-trace">>),
+ 'http_x-request-id' => cowboy_req:header(<<"x-request-id">>, Req, undefined)
+ }),
+ AccessMeta1 = maps:merge(get_process_meta(), AccessMeta),
+ maps:merge(F(Req), AccessMeta1).
+
+get_request_body_length(Req) ->
+ case cowboy_req:has_body(Req) of
+ false -> undefined;
+ true -> cowboy_req:body_length(Req)
+ end.
+
+get_peer_addr(Req) ->
+ {IP, _Port} = cowboy_req:peer(Req),
+ genlib:to_binary(inet:ntoa(IP)).
+
+get_remote_addr(Req) ->
+ case determine_remote_addr(Req) of
+ {ok, RemoteAddr} ->
+ genlib:to_binary(inet:ntoa(RemoteAddr));
+ _ ->
+ undefined
+ end.
+
+determine_remote_addr(Req) ->
+ Peer = cowboy_req:peer(Req),
+ Value = cowboy_req:header(<<"x-forwarded-for">>, Req),
+ determine_remote_addr_from_header(Value, Peer).
+
+determine_remote_addr_from_header(undefined, {IP, _Port}) ->
+ % undefined, assuming no proxies were involved
+ {ok, IP};
+determine_remote_addr_from_header(Value, _Peer) when is_binary(Value) ->
+ ClientPeer = string:strip(binary_to_list(Value)),
+ case string:tokens(ClientPeer, ", ") of
+ [ClientIP | _Proxies] ->
+ inet:parse_strict_address(ClientIP);
+ _ ->
+ {error, malformed}
+ end.
+
+get_request_duration(Meta) ->
+ case maps:get(started_at, Meta, undefined) of
+ undefined ->
+ undefined;
+ StartTime ->
+ (genlib_time:ticks() - StartTime) / 1000000
+ end.
+
+get_response_len(Headers) ->
+ case maps:get(<<"content-length">>, Headers, undefined) of
+ undefined ->
+ undefined;
+ Len ->
+ genlib:to_int(Len)
+ end.
+
+make_state(Req, Opts) ->
+ ExtFun = make_ext_fun(Opts),
+ set_meta(#{req => Req, ext_fun => ExtFun}).
+
+set_meta(State) ->
+ State#{meta => #{started_at => genlib_time:ticks()}}.
+
+make_ext_fun(Opts) ->
+ maps:get(extra_info_fun, Opts, fun(_Req) -> #{} end).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+-spec test() -> _.
+
+-spec filter_meta_test() -> _.
+filter_meta_test() ->
+ Req = #{
+ pid => self(),
+ peer => {{42, 42, 42, 42}, 4242},
+ method => <<"GET">>,
+ path => <<>>,
+ qs => <<>>,
+ version => 'HTTP/1.1',
+ headers => #{},
+ host => <<>>,
+ port => undefined,
+ has_body => true
+ },
+ State = make_state(Req, #{}),
+ #{
+ request_method := <<"GET">>,
+ request_path := <<>>,
+ request_time := _,
+ response_length := 33,
+ request_length := 100,
+ peer_addr := <<"42.42.42.42">>,
+ status := 200
+ } = prepare_meta(200, #{<<"content-length">> => <<"33">>}, State, 100).
+
+-spec filter_meta_for_error_test() -> _.
+filter_meta_for_error_test() ->
+ Req = #{
+ pid => self(),
+ peer => {{42, 42, 42, 42}, 4242},
+ method => <<"GET">>,
+ path => <<>>,
+ qs => <<>>,
+ version => 'HTTP/1.1',
+ headers => #{},
+ host => <<>>,
+ port => undefined,
+ has_body => true
+ },
+ State = make_state(Req, #{}),
+ #{
+ peer_addr := <<"42.42.42.42">>,
+ remote_addr := <<"42.42.42.42">>,
+ request_method := <<"GET">>,
+ request_path := <<>>,
+ request_time := _,
+ status := 400
+ } = prepare_meta(400, #{}, State, undefined).
+
+-endif.
diff --git a/apps/dreki_web/src/dreki_web.app.src b/apps/dreki_web/src/dreki_web.app.src
index cf731fc..4122e3b 100644
--- a/apps/dreki_web/src/dreki_web.app.src
+++ b/apps/dreki_web/src/dreki_web.app.src
@@ -10,7 +10,8 @@
cowboy,
trails,
cowboy_telemetry,
- opentelemetry_cowboy
+ opentelemetry_cowboy,
+ prometheus_cowboy
]},
{env,[]},
{modules, []},
diff --git a/apps/dreki_web/src/dreki_web_app.erl b/apps/dreki_web/src/dreki_web_app.erl
index 5b6454e..5b3c1a0 100644
--- a/apps/dreki_web/src/dreki_web_app.erl
+++ b/apps/dreki_web/src/dreki_web_app.erl
@@ -13,8 +13,12 @@ start(_StartType, _StartArgs) ->
Config = application:get_all_env(dreki_web),
Transport = proplists:get_value(transport, Config),
CowboyEnv = #{
- middlewares => [dreki_web_auth, cowboy_router, cowboy_handler],
- stream_handlers => [cowboy_telemetry_h, cowboy_stream_h],
+ middlewares => [dreki_web_auth, cowboy_router, dreki_web_handler],
+ stream_handlers => [cowboy_telemetry_h,
+ cowboy_access_log_h,
+ cowboy_metrics_h,
+ cowboy_stream_h],
+ metrics_callback => fun prometheus_cowboy2_instrumenter:observe/1,
env => #{
dispatch => routes()
}
@@ -35,7 +39,8 @@ routes() ->
Trails = [
{"/", dreki_web_index, undefined},
{"/static/[...]", cowboy_static,
- {priv_dir, dreki_web, "static", [{mimetypes, dreki_web, detect_web_mimetype}]}},
+ {priv_dir, dreki_web, "static", [{mimetypes, dreki_web, detect_web_mimetype}]}},
+ {"/metrics/[:registry]", prometheus_cowboy2_handler, []},
%% API
{"/api/tasks/:id", dreki_web_task, undefined},
@@ -59,6 +64,7 @@ routes() ->
{"/admin/:location/:namespace/:directory", dreki_web_ui_stores, undefined},
{"/admin/:location/:namespace/:directory/_/:action", dreki_web_ui_stores, action},
{"/admin/:location/:namespace/:directory/:id", dreki_web_ui_stores, undefined},
+ {"/admin/:location/:namespace/:directory/:id/_/:action", dreki_web_ui_stores, action},
{"/admin/[...]", dreki_web_ui_error, #{code => 404, status => <<"Not found">>}},
diff --git a/apps/dreki_web/src/dreki_web_handler.erl b/apps/dreki_web/src/dreki_web_handler.erl
new file mode 100644
index 0000000..a30fd1d
--- /dev/null
+++ b/apps/dreki_web/src/dreki_web_handler.erl
@@ -0,0 +1,99 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Handler middleware.
+%%
+%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
+%% environment values. The result of this execution is added to the
+%% environment under the <em>result</em> value.
+-module(dreki_web_handler).
+-include_lib("opentelemetry_api/include/otel_tracer.hrl").
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+-export([terminate/4]).
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback terminate(any(), map(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-spec execute(Req, Env) -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req, Env=#{handler := Handler}) ->
+ Headers = maps:get(headers, Req),
+ otel_propagator_text_map:extract(maps:to_list(Headers)),
+ HandlerB = atom_to_binary(Handler),
+ Method = maps:get(method, Req),
+ Attributes = [
+ {'http.host', maps:get(host, Req)},
+ {'http.host.port', maps:get(port, Req)},
+ {'http.method', Method},
+ {'http.scheme', maps:get(scheme, Req)},
+ {'http.target', maps:get(path, Req)},
+ {'http.user_agent', maps:get(<<"user-agent">>, Headers, <<"">>)}
+ ],
+ SpanName = iolist_to_binary([<<"HTTP ">>, Method, <<" ">>, HandlerB]),
+ logger:debug("SpanName: ~p", [SpanName]),
+ ?with_span(SpanName, #{}, fun(Ctx) -> execute_(Req, Env, Ctx) end).
+
+execute_(Req0, Env=#{handler := Handler, handler_opts := HandlerOpts}, Ctx) ->
+ TraceId = otel_span:hex_trace_id(otel_tracer:current_span_ctx()),
+ Req = Req0#{<<"trace-id">> => TraceId},
+ try Handler:init(Req, HandlerOpts) of
+ {ok, Req2, State} ->
+ Headers0 = maps:get(headers, Req2),
+ Headers = otel_propagator_text_map:inject(Headers0,
+ fun(Headers, Key, Value) ->
+ maps:put(Key, Value, Headers)
+ end),
+ Req3 = maps:put(headers, Headers, Req2),
+ Result = terminate(normal, Req3, State, Handler),
+ {ok, Req3, Env#{result => Result, trace_id => TraceId}};
+ {Mod, Req2, State} ->
+ ?add_event(<<"HTTP_UPGRADE">>, #{module => Mod}),
+ Mod:upgrade(Req2, Env, Handler, State);
+ {Mod, Req2, State, Opts} ->
+ ?add_event(<<"HTTP_UPGRADE">>, #{module => Mod}),
+ Mod:upgrade(Req2, Env, Handler, State, Opts)
+ catch Class:Reason:Stacktrace ->
+ ?set_status(error, iolist_to_binary([atom_to_binary(Class)])),
+ render_error(Class, Reason, Stacktrace, Handler, Req, Env, Ctx)
+ end.
+
+render_error(Class, Reason, Stacktrace, Handler, Req, Env, Ctx) ->
+ ReasonS = lists:flatten(io_lib:format("~p", [Reason])),
+ Assigns = [
+ {dreki_node, node()},
+ {site_title, "Dreki"},
+ {class, atom_to_binary(Class)},
+ {reason, ReasonS},
+ {stacktrace, [lists:flatten(io_lib:format("~p", [Line])) || Line <- Stacktrace]},
+ {trace_id, maps:get(<<"trace-id">>, Req, <<"no-trace">>)}
+ ],
+ logger:error(#{app => dreki_web, handler => Handler, error => {Class, Reason, Stacktrace}}),
+ {ok, Html} = crash_dtl:render(Assigns),
+ cowboy_req:reply(500, #{<<"content-type">> => <<"text/html">>}, Html, Req).
+
+-spec terminate(any(), Req | undefined, any(), module()) -> ok when Req::cowboy_req:req().
+terminate(Reason, Req, State, Handler) ->
+ case erlang:function_exported(Handler, terminate, 3) of
+ true ->
+ Handler:terminate(Reason, Req, State);
+ false ->
+ ok
+ end.
diff --git a/apps/dreki_web/src/dreki_web_ui.erl b/apps/dreki_web/src/ui/dreki_web_ui.erl
index d60cc6e..e394632 100644
--- a/apps/dreki_web/src/dreki_web_ui.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui.erl
@@ -24,7 +24,8 @@ assigns(Req, Assigns0) ->
{"identity", maps:get(identity, Req)},
{"identity_name", dreki_web:identity_name(Req)},
{"dreki_node", node()},
- {"dreki_world", dreki_world:to_map()}
+ {"dreki_world", dreki_world:to_map()},
+ {"trace_id", maps:get(<<"trace-id">>, Req, <<"no-trace">>)}
| Assigns].
content_types_accepted(Req, State) ->
diff --git a/apps/dreki_web/src/dreki_web_ui_error.erl b/apps/dreki_web/src/ui/dreki_web_ui_error.erl
index ccda150..ccda150 100644
--- a/apps/dreki_web/src/dreki_web_ui_error.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_error.erl
diff --git a/apps/dreki_web/src/dreki_web_ui_index.erl b/apps/dreki_web/src/ui/dreki_web_ui_index.erl
index 9f4684e..9f4684e 100644
--- a/apps/dreki_web/src/dreki_web_ui_index.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_index.erl
diff --git a/apps/dreki_web/src/dreki_web_ui_json_form.erl b/apps/dreki_web/src/ui/dreki_web_ui_json_form.erl
index 49f69f8..51a9de4 100644
--- a/apps/dreki_web/src/dreki_web_ui_json_form.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_json_form.erl
@@ -28,6 +28,8 @@ to_html([{Node, Attrs, Content} | Rest], Acc) ->
AttrsS = attrs_to_html(Attrs),
logger:debug("Node ~p Attrs ~p Content ~p", [Node, Attrs, Content]),
to_html(Rest, [[<<"<">>, BNode, <<" ">>, AttrsS, <<">">>, to_html(Content), <<"</">>, BNode, <<">">>] | Acc]);
+to_html([List | Rest], Acc) when is_list(List) ->
+ to_html(Rest, [to_html(List) | Acc]);
to_html([], Acc) ->
lists:reverse(Acc).
@@ -45,13 +47,27 @@ attrs_to_html([{Attr, Value} | Rest], Acc) ->
attrs_to_html([], Acc) ->
Acc.
+put_new(Key, Value, Map) ->
+ case maps:is_key(Key, Map) of
+ true ->
+ Map;
+ false ->
+ maps:put(Key, Value, Map)
+ end.
+
render(Schema, Opts) ->
+ render(Schema, Opts, {'div', [{class, <<"mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">>}]}).
+
+render(Schema, Opts, {Elem, Attrs}) ->
+ [{Elem, Attrs, render(Schema, Opts, undefined)}];
+render(Schema, Opts0, undefined) ->
+ Opts = put_new(name, <<"form">>, Opts0),
lists:reverse(maps:fold(fun (Key, Value, Acc) ->
- case render_property(Key, Value, Schema, Schema, Opts) of
- {ok, Html} -> [Html | Acc];
- ignore -> Acc
- end
- end, [], maps:get(properties, Schema))).
+ case render_property(Key, Value, Schema, Schema, Opts) of
+ {ok, Html} -> [Html | Acc];
+ ignore -> Acc
+ end
+ end, [], maps:get(properties, Schema))).
render_property(Field, Config = #{enum := Enum}, Parent, Schema, Opts) ->
FormAttrs = maps:get(<<"dreki:form">>, Config, #{}),
@@ -69,6 +85,19 @@ render_property(Field, Config = #{type := Type}, Parent, Schema, Opts) ->
InputOpts = #{required => Required, label => Label, input_type => InputType},
{ok, render_input(Input, Field, Type, InputOpts, Opts)};
+render_property(Key, SubSchema = #{<<"@schema">> := SubSchemaUrn}, Parent, Schema, Opts) ->
+ Name = maps:get(name, Opts),
+ KeyB = atom_to_binary(Key),
+ InnerForm = render(SubSchema, Opts#{name => <<Name/binary, "[", KeyB/binary, "]">>}, undefined),
+ Head = {'div', [{class, <<"sm:col-span-6 mb-3">>}], [
+ {h3, [{class, <<"text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">>}], maps:get(title, SubSchema, SubSchemaUrn)},
+ {p, [{class, <<"mt-1 text-sm text-gray-500 dark:text-gray-400">>}], maps:get(description, SubSchema, <<>>)}
+ ]},
+ {ok, {'div', [{class, <<"sm:col-span-6">>}],
+ [
+ {'div', [{class, <<"mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">>}], [Head, InnerForm]}
+ ]}};
+
render_property(Field, #{'$ref' := Ref}, Parent, Schema = #{'$defs' := Defs}, Opts) ->
case maps:get(Ref, Defs, undefined) of
undefined -> logger:error("didn't get ref ~p", [Ref]);
@@ -88,26 +117,30 @@ base_attributes(Field, IOpts, FOpts) ->
render_input(select, Field, _, IOpts, FOpts) ->
{Name, Attributes} = base_attributes(Field, IOpts, FOpts),
OptionsHtml = lists:map(fun (Opt) -> {option, [], Opt} end, maps:get(values, IOpts, [])),
- {'div', [{class, <<"json-field">>}], [
- {label, [{for, Name}], maps:get(label, IOpts, Field)},
+ {'div', [{class, <<"json-field">>}],
+ [
+ label(Field, Name, maps:get(label, IOpts, Field)),
{select, Attributes, OptionsHtml}
- ]};
+ ]};
render_input(input, Field, Type, IOpts, FOpts) ->
- {Name, Attributes0} = base_attributes(Field, IOpts, FOpts),
- Attributes = [
- {value, maps:get(value, IOpts, undefined)},
- {placeholder, maps:get(placeholder, IOpts, undefined)},
- {readonly, maps:get(readonly, IOpts, false)},
- {autocomplete, maps:get(autocomplete, IOpts, <<"off">>)},
- {type, maps:get(input_type, IOpts)},
- {class, <<"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md">>}
- | Attributes0],
- HtmlNode = {'div', [{class, <<"json-field">>}], [
- {label, [{for, Name}, {class, <<"block text-sm font-medium text-gray-700">>}], maps:get(label, IOpts, Field)},
- {'div', [{class, <<"mt-1">>}], [{input, Attributes}]}
- ]},
- HtmlNode.
+ {Name, Attributes0} = base_attributes(Field, IOpts, FOpts),
+ Attributes = [
+ {value, maps:get(value, IOpts, undefined)},
+ {placeholder, maps:get(placeholder, IOpts, undefined)},
+ {readonly, maps:get(readonly, IOpts, false)},
+ {autocomplete, maps:get(autocomplete, IOpts, <<"off">>)},
+ {type, maps:get(input_type, IOpts)},
+ {class, <<"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md dark:bg-gray-800">>}
+ | Attributes0],
+ {'div', [{class, <<"json-field sm:col-span-6">>}],
+ [
+ label(Field, Name, maps:get(label, IOpts, Field)),
+ {'div', [{class, <<"mt-1">>}], [{input, Attributes}]}
+ ]}.
+
+label(_Field, Name, Label) ->
+ {label, [{for, Name}, {class, <<"block text-sm font-medium dark:text-gray-400 text-gray-700 dark:text-gray-400">>}], Label}.
as_binary(Atom) when is_atom(Atom) ->
atom_to_binary(Atom);
@@ -119,7 +152,7 @@ input_type(_) -> <<"text">>.
field_name(Field, Opts) ->
FB = as_binary(Field),
BaseName = maps:get(name, Opts, <<"form">>),
- <<"[", BaseName/binary, "]", FB/binary>>.
+ <<BaseName/binary, "[", FB/binary, "]">>.
field_id(Field, Opts) ->
FB = as_binary(Field),
diff --git a/apps/dreki_web/src/dreki_web_ui_node.erl b/apps/dreki_web/src/ui/dreki_web_ui_node.erl
index fdb7c77..fdb7c77 100644
--- a/apps/dreki_web/src/dreki_web_ui_node.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_node.erl
diff --git a/apps/dreki_web/src/dreki_web_ui_stores.erl b/apps/dreki_web/src/ui/dreki_web_ui_stores.erl
index d39e571..e39cd48 100644
--- a/apps/dreki_web/src/dreki_web_ui_stores.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_stores.erl
@@ -63,13 +63,17 @@ request(<<"GET">>, undefined, #{resource := #{namespace := NS}}, Req) ->
%% List
request(<<"GET">>, undefined, Urn = #{location := Loc, resource := #{directory := #{directory := Dir, namespace := NS}}}, Req) ->
{ok, Result} = dreki_store:list(Urn),
- Results = lists:map(fun(Result) ->
+ Results0 = lists:map(fun(Result) ->
Result#{href => urn_to_path(maps:get('@id', Result))}
end, maps:get(data, Result)),
+ Results = deep_map_to_list(Results0),
Html = dreki_web_ui:render(Req, store_list_dtl, [
- {location, Loc}, {namespace, NS}, {directory, Dir}, {results, Results},
- {new, href(Req, <<"_/new">>)}
- ]),
+ {location, Loc}, {namespace, NS}, {directory, Dir},
+ {results, Results},
+ {actions, [
+ [{title, "New"}, {href, href(Req, <<"_/new">>)}]
+ ]}
+ ]),
{ok, dreki_web_ui:reply_html(Req, 200, Html), undefined};
%% New
@@ -90,8 +94,54 @@ request(<<"GET">>, <<"new">>, #{urn := Urn, location := Loc, resource := #{direc
request(<<"GET">>, undefined, Urn = #{location := Loc, resource := #{resource := #{id := Id, directory := Dir, namespace := NS}}}, Req) ->
{ok, Result0} = dreki_store:get(Urn),
Result = Result0#{'@href' => urn_to_path(Urn)},
- Html = dreki_web_ui:render(Req, store_show_dtl, [{location, Loc}, {id, Id}, {directory, Dir}, {namespace, NS}, {result, Result}]),
- {ok, dreki_web_ui:reply_html(Req, 200, Html), undefined}.
+ Actions = lists:foldr(fun (#{id := Id, title := Title}, Acc) ->
+ IdB = atom_to_binary(Id),
+ Action = [{id, Id}, {title, Title}, {href, href(Req, <<"_/", IdB/binary>>)}],
+ [Action | Acc]
+ end, [], maps:get('@actions', Result)),
+ Html = dreki_web_ui:render(Req, store_show_dtl, [{location, Loc}, {id, Id}, {directory, Dir}, {namespace, NS},
+ {result, deep_map_to_list(Result)},
+ {actions, [
+ [{title, "Edit (UI)"}, {href, href(Req, <<"_/edit">>)}]
+ | Actions
+ ]}
+ ]),
+ {ok, dreki_web_ui:reply_html(Req, 200, Html), undefined};
+
+%% Show action
+request(<<"GET">>, Action0, Urn = #{location := Loc, resource := #{resource := #{id := Id, directory := Dir, namespace := NS}}}, Req) ->
+ {ok, Result0} = dreki_store:get(Urn),
+ Result = Result0#{'@href' => urn_to_path(Urn)},
+ Action = binary_to_existing_atom(Action0),
+ Path = href(Req, <<"_/", Action0/binary>>),
+ Act = lists:filter(fun (A) -> maps:get(id, A) =:= Action end, maps:get('@actions', Result)),
+ case Act of
+ [#{title := Title, new := {Mod, Fun, Args}}] ->
+ {ok, Schema} = apply(Mod, Fun, [Result | Args]),
+ Form = dreki_web_ui_json_form:render_html(Schema, #{}),
+ Html = dreki_web_ui:render(Req, store_new_dtl, [
+ {location, Loc}, {id, Id}, {directory, Dir}, {namespace, NS},
+ {method, <<"POST">>},
+ {title, Title},
+ {result, deep_map_to_list(Result)},
+ {target, Path},
+ {form, Form}
+ ]),
+ {dreki_web_ui:reply_html(Req, 200, Html), undefined};
+ _ ->
+ dreki_web_ui_error:init(Req, #{code => 404, status => "Not Found", message => "No such action"})
+ end;
+request(<<"POST">>, Action0, Urn = #{location := Loc, resource := #{resource := #{id := Id, directory := Dir, namespace := NS}}}, Req) ->
+ {ok, Result0} = dreki_store:get(Urn),
+ Result = Result0#{'@href' => urn_to_path(Urn)},
+ Action = binary_to_existing_atom(Action0),
+ Act = lists:filter(fun (A) -> maps:get(id, A) =:= Action end, maps:get('@actions', Result)),
+ case Act of
+ [#{title := Title, new := {Mod, Fun, Args}}] ->
+ Req;
+ _ ->
+ dreki_web_ui_error:init(Req, #{code => 404, status => "Not Found", message => "No such action"})
+ end.
derpinit(Req, _) ->
Json = #{<<"error">> => false, <<"service">> => <<"dreki">>},
@@ -112,6 +162,19 @@ location_to_path(Location) ->
false -> binary:replace(Location, <<Root/binary, ":">>, <<"/admin/">>)
end.
+deep_map_to_list(List) when is_list(List) ->
+ [deep_map_to_list(Map) || Map <- List];
+deep_map_to_list(Map) when is_map(Map) ->
+ deep_map_to_list(maps:to_list(Map), []).
+
+deep_map_to_list([{Key, Map} | Rest], Acc) when is_map(Map) ->
+ List = deep_map_to_list(Map),
+ deep_map_to_list(Rest, [{Key, List} | Acc]);
+deep_map_to_list([Item | Rest], Acc) ->
+ deep_map_to_list(Rest, [Item | Acc]);
+deep_map_to_list([], Acc) ->
+ Acc.
+
urn_to_path(Urn) when is_binary(Urn) ->
{ok, XUrn} = dreki_urn:expand(Urn),
urn_to_path(XUrn);
diff --git a/apps/dreki_web/src/dreki_web_ui_task.erl b/apps/dreki_web/src/ui/dreki_web_ui_task.erl
index 1ced583..1ced583 100644
--- a/apps/dreki_web/src/dreki_web_ui_task.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_task.erl
diff --git a/apps/dreki_web/src/dreki_web_ui_tasks.erl b/apps/dreki_web/src/ui/dreki_web_ui_tasks.erl
index e9748b8..e9748b8 100644
--- a/apps/dreki_web/src/dreki_web_ui_tasks.erl
+++ b/apps/dreki_web/src/ui/dreki_web_ui_tasks.erl
diff --git a/apps/dreki_web/templates/crash.dtl b/apps/dreki_web/templates/crash.dtl
new file mode 100644
index 0000000..b0a291c
--- /dev/null
+++ b/apps/dreki_web/templates/crash.dtl
@@ -0,0 +1,52 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>{% if page_title %}{{ page_title }} - {% endif %}{{site_title}}</title>
+ <link rel="stylesheet" href="/static/app.css">
+ <script src="/static/app.js" defer></script>
+ <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
+</head>
+<body class="dark:bg-[#2E0E02] dark:text-orange-100 bg-orange-100 text-orange-900">
+<header class="bg-orange-600 dark:bg-red-900">
+<div class="container mx-auto sm:px-6 lg:px-8">
+
+ <nav class="" aria-label="Top">
+ <div class="w-full py-6 flex items-center justify-between border-b border-primary-500 lg:border-none">
+ <div class="flex items-center">
+ <a href="/admin" class="text-primary-100 hover:text-white text-lg">
+ <span class="sr-only">{{site_title}}</span>
+<div class="text-[2rem] inline-block">😈</span>
+<!--<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 inline-block" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
+</svg>-->
+ </a>
+ </nav>
+</div>
+</header>
+
+<div class="container mx-auto sm:px-6 lg:px-8 sm:py-4 lg:py-6">
+<div class="content">
+ <h1 class="text-7xl text-red-600 dark:text-red-100 font-bold font-mono">😤<br/>{{ class }}</h1>
+ <h2 class="text-4xl text-orange-800 dark:text-orange-300 font-bold font-mono mt-10 mb-10">{{ reason }}</h2>
+ {% for line in stacktrace %}
+ <h3 class="text-lg text-stone-400 dark:text-stone:500 -mb-2 font-mono">{{ line }}</h3>
+ {% endfor %}
+</div>
+</div>
+
+<footer class="">
+ <div class="container mx-auto sm:px-6 lg:px-8 sm:py-4 lg:py-6">
+ <p class="mt-8 text-xs text-gray-400 font-mono">
+ node: {{ dreki_node }}
+ </p>
+ <p class="mt-2 text-xs text-gray-400 font-mono">
+ trace: <a href="https://grafana.adm.random.sh/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22Tempo%20(Stairway)%22,%7B%22refId%22:%22A%22,%22queryType%22:%22traceId%22,%22query%22:%22{{trace_id}}%22%7D%5D" target="_blank">{{ trace_id }}</a>
+ </p>
+ </footer>
+</div>
+</body>
+</html>
diff --git a/apps/dreki_web/templates/layout.dtl b/apps/dreki_web/templates/layout.dtl
index b27fe30..a326305 100644
--- a/apps/dreki_web/templates/layout.dtl
+++ b/apps/dreki_web/templates/layout.dtl
@@ -73,10 +73,11 @@
<footer class="">
<div class="container mx-auto sm:px-6 lg:px-8 sm:py-4 lg:py-6">
<p class="mt-8 text-center text-sm text-gray-400">
- dreki $nodename
+ dreki {{dreki_node}}
</p>
- <p class="mt-2 text-center text-xs text-gray-300">
- identity:{{identity_id}}
+ <p class="mt-2 text-center text-xs text-gray-400">
+ identity: {{identity_id}}<br />
+ trace: <a href="https://grafana.adm.random.sh/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22Tempo%20(Stairway)%22,%7B%22refId%22:%22A%22,%22queryType%22:%22traceId%22,%22query%22:%22{{trace_id}}%22%7D%5D" target="_blank">{{trace_id}}>
</p>
</footer>
</div>
diff --git a/apps/dreki_web/templates/store_list.dtl b/apps/dreki_web/templates/store_list.dtl
index 65dc5af..81209a6 100644
--- a/apps/dreki_web/templates/store_list.dtl
+++ b/apps/dreki_web/templates/store_list.dtl
@@ -1,9 +1,11 @@
<h1 class="text-lg">{{ location }} :: {{ namespace }}:{{ directory }}</h1>
<ul>
-{% for res in result %}
-<li><a href="{{ res.href }}">{{ res.id }}</a></li>
+{% for res in results %}
+<li><a href="{{ res.href }}" class="text-primary-700 dark:text-primary-100">{{ res.id }}</a> {{ res.params.title }}</li>
{% endfor %}
</ul>
-<a href="{{ new }}">Create</a>
+{% for action in actions %}
+<a href="{{ action.href }}">{{ action.title }}</a>
+{% endfor %}
diff --git a/apps/dreki_web/templates/store_new.dtl b/apps/dreki_web/templates/store_new.dtl
index 5a85854..0ae8d59 100644
--- a/apps/dreki_web/templates/store_new.dtl
+++ b/apps/dreki_web/templates/store_new.dtl
@@ -2,14 +2,18 @@
<div class="text-base">
{{ location }} / {{ namespace }} / {{ directory }}
</div>
- New
+ {% if result %}
+ {{ result.id }}: {{ title }}
+ {% else %}
+ New
+ {% endif %}
</h1>
-<form action="{{ target }}" method="{{ method }}">
+<form action="{{ target }}" method="{{ method }}" class="space-y-8 divide-y divide-gray-200">
{{ form | safe }}
<div class="pt-5">
- <div class="flex justify-end">
+ <div class="flex justify-start">
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Save</button>
</div>
</div>
diff --git a/apps/dreki_web/templates/store_show.dtl b/apps/dreki_web/templates/store_show.dtl
index e944bee..3b139bb 100644
--- a/apps/dreki_web/templates/store_show.dtl
+++ b/apps/dreki_web/templates/store_show.dtl
@@ -5,4 +5,12 @@
{{ result.id }}
</h1>
+<ul>
+{% for res in result %}
+<li>{{res}}</li>
+{% endfor %}
+</ul>
+{% for action in actions %}
+<a href="{{ action.href }}">{{ action.title }}</a>
+{% endfor %}
diff --git a/config/mgmt2/sys.config b/config/mgmt2/sys.config
new file mode 100644
index 0000000..cb272d4
--- /dev/null
+++ b/config/mgmt2/sys.config
@@ -0,0 +1,114 @@
+[
+{kernel,
+ [
+ {logger_level, debug},
+ {logger, [
+ {handler, default, logger_std_h,
+ #{level => debug,
+ formatter => {logger_colorful_formatter,
+ #{
+ colors => #{debug => blue, notice => {bright, green}, warning => yellow, error => red,
+ critical => {bright, red}, alert => {bright, magenta}, emergency => {bg, red}},
+ template => [
+ time, " ",
+ "level=",level," ",
+ {trace_id, ["trace_id=", trace_id, " "], []},
+ {domain, ["domain=", domain, " "], []},
+ {mfa, ["mfa=", mfa, " "], []},
+ {pid, ["pid=", pid, " "], []},
+ msg, " ",
+ %%{file, ["file=", file, ":", line, " "], []},
+ "\n"
+ ]
+ }},
+ filters => [
+ {opentelemetry_logger_metadata, {fun opentelemetry_logger_metadata:filter/2, []}}
+ ]
+ }}
+ ]}
+ ]},
+
+
+{opentelemetry, [
+ {span_processor, batch},
+ {exporter, otlp},
+ {text_map_propagators, [baggage, trace_context]}
+]},
+
+{opentelemetry_exporter, [
+ {otlp_protocol, grpc},
+ {otlp_endpoint, "http://tempo.stairway.internal.random.sh:4317"},
+ {otlp_headers, []}
+]},
+
+
+
+{plum_db, [
+ {aae_enabled, true},
+ {store_open_retries_delay, 2000},
+ {store_open_retry_Limit, 30},
+ {data_exchange_timeout, 60000},
+ {hashtree_timer, 10000},
+ {data_dir, "data2/plumdb"},
+ {partitions, 8},
+ {prefixes, [
+ {state, ram},
+ {world, ram_disk},
+ {names, ram_disk},
+ {regions, ram_disk},
+ {nodes, ram_disk},
+ {paths, ram_disk},
+ {tasks, ram_disk},
+ {objects, ram_disk},
+ {stores, ram_disk},
+ {'idx:roles', ram_disk}, {'idx:tags', ram_disk}
+ ]}
+]},
+{partisan, [
+ {peer_ip, {0,0,0,0}},
+ {peer_port, 18087}, % port for inter-node communication
+ {parallelism, 4}, % number of tcp connections
+ {pid_encoding, false},
+ {ref_encoding, false},
+ {exchange_tick_period, 60000},
+ {lazy_tick_period, 1000},
+ {partisan_peer_service_manager,
+ partisan_pluggable_peer_service_manager}
+]},
+{plumtree, [
+ {broadcast_exchange_timer, 60000} % Perform AAE exchange every 1 min.
+]},
+ {dreki, [
+ {root_domain, <<"random.sh">>},
+ {internal_domain, <<"inf.random.sh">>},
+ {domain, <<"mgmt2.stairway.dc2.scw.fr.eu.inf.random.sh">>},
+ {local_tasks_stores, [
+ {<<"local">>, dreki_dets_tasks, #{}, #{}}
+ ]},
+ {autojoin, [<<"mgmt.stairway.dc2.scw.fr.eu.inf.random.sh">>]},
+ {local_names_store, [
+ %% Store for *.DOMAIN
+ {local, dreki_dets_names, []},
+ %% Store for XXX.DOMAIN
+ {<<"service">>, dreki_dets_names, []}
+ ]},
+ {local_sequences_store, {dreki_dets_sequences, []}}
+ ]},
+ {ory, [
+ {kratos_url, <<"https://kratos.sso.internal.random.sh">>},
+ {hydra_url, <<"https://hydra.sso.internal.random.sh">>},
+ {keto_url, <<"https://keto.sso.internal.random.sh">>},
+ {hackney_ssl_opts, [
+ {verify, verify_peer},
+ {versions, ['tlsv1.2', 'tlsv1.3']},
+ {cacertfile, "/usr/local/etc/ssl/certs/ca.internal.random.sh.crt"},
+ {crl_check, false},
+ {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}
+ ]}
+ ]},
+ {dreki_web, [
+ {transport, [
+ {port, 5001}
+ ]}
+ ]}
+].
diff --git a/config/mgmt2/vm.args b/config/mgmt2/vm.args
new file mode 100644
index 0000000..ea807ca
--- /dev/null
+++ b/config/mgmt2/vm.args
@@ -0,0 +1,9 @@
+-name dreki2@mgmt2.stairway.dc2.scw.fr.eu.inf.random.sh
+
+-setcookie dreki_cookie
+
++K true
++A30
+
+## Enable multi_time_warp
++C multi_time_warp
diff --git a/config/sys.config b/config/sys.config
index be53a1f..5037a42 100644
--- a/config/sys.config
+++ b/config/sys.config
@@ -1,20 +1,32 @@
[
-{kernel, [
- {logger_level, info},
- {logger, [
- {handler, default, logger_std_h, #{
- formatter =>
- {logger_colorful_formatter, #{
- colors => #{debug => blue, notice => {bright, green}, warning => yellow, error => red,
- critical => {bright, red}, alert => {bright, magenta}, emergency => {bg, red}},
- template => [time," [",level,"] ", file,":",line," ",msg,"\n"]
- }}
- }}
-%%#{ formatter =>
-%% {logger_colorful_formatter, #{}} %%#{ template => [time," [",level,"] ", file,":",line," ",msg,"\n\n"] }}
-%% }}
- ]}
-]},
+{kernel,
+ [
+ {logger_level, debug},
+ {logger, [
+ {handler, default, logger_std_h,
+ #{level => info,
+ formatter => {logger_colorful_formatter,
+ #{
+ colors => #{debug => blue, notice => {bright, green}, warning => yellow, error => red,
+ critical => {bright, red}, alert => {bright, magenta}, emergency => {bg, red}},
+ template => [
+ time, " ",
+ "level=",level," ",
+ {trace_id, ["trace_id=", trace_id, " "], []},
+ {domain, ["domain=", domain, " "], []},
+ {mfa, ["mfa=", mfa, " "], []},
+ {pid, ["pid=", pid, " "], []},
+ msg, " ",
+ %%{file, ["file=", file, ":", line, " "], []},
+ "\n"
+ ]
+ }},
+ filters => [
+ {opentelemetry_logger_metadata, {fun opentelemetry_logger_metadata:filter/2, []}}
+ ]
+ }}
+ ]}
+ ]},
%%{lager, [
%% {error_logger_redirect, false},
@@ -23,18 +35,14 @@
{opentelemetry, [
{span_processor, batch},
- {exporter, {otel_exporter_stdout, []}},
+ {exporter, otlp},
{text_map_propagators, [baggage, trace_context]}
]},
{opentelemetry_exporter, [
- {oltp_protocol, grpc},
- {otlp_endpoint, "https://tempo-us-central1.grafana.net:443"},
- {oltp_headers, [
- %% 38972:eyJrIjoiMzg1ZjEwZTg3YjU0ZDY4ZGQzZTg3MzllNzU4NGZlZjI1NmQ5YWRhMCIsIm4iOiJkcmVraSBkZXYiLCJpZCI6NDg1NjI5fQ==
- {"authorization",
- "Basic Mzg5NzI6ZXlKcklqb2lNemcxWmpFd1pUZzNZalUwWkRZNFpHUXpaVGczTXpsbE56VTROR1psWmpJMU5tUTVZV1JoTUNJc0ltNGlPaUprY21WcmFTQmtaWFlpTENKcFpDSTZORGcxTmpJNWZRPT0="}
- ]}
+ {otlp_protocol, grpc},
+ {otlp_endpoint, "http://tempo.stairway.internal.random.sh:4317"},
+ {otlp_headers, []}
]},
{plum_db, [
diff --git a/rebar.config b/rebar.config
index 05d8e4c..99d2e26 100644
--- a/rebar.config
+++ b/rebar.config
@@ -5,6 +5,15 @@
{opentelemetry_api, "~> 1.0"},
{opentelemetry, "~> 1.0"},
{opentelemetry_exporter, "1.0.2"},
+ {opentelemetry_process_propagator, "0.1.1"},
+ {opentelemetry_logger_metadata, "0.1.0"},
+ {opentelemetry_telemetry, "~> 1.0.0-beta.7"},
+ {telemetry, "~> 1.0"},
+ {prometheus, "~> 4.8.2"},
+
+ {circuit_breaker, {git, "https://github.com/klarna/circuit_breaker", {branch, "master"}}},
+ {sbroker, "1.0.0"},
+ {genlib, {git, "https://github.com/rbkmoney/genlib", {branch, "master"}}},
{uuid, "2.0.4", {pkg, uuid_erl}},
{ory, {git, "https://git.random.sh/erlang-ory.git", {branch, "main"}}},
{mnesia_rocksdb, {git, "https://github.com/aeternity/mnesia_rocksdb", {branch, "master"}}},
@@ -30,7 +39,7 @@
rebar3_lint
]}.
-%%{elvis_output_format, plain | colors | parsable}.
+%%{elvis_output_format, plain | colors | parsable}.
{elvis_output_format, parsable}.
{elvis, [
#{ dirs => ["apps/*/src/**", "src/**"],
diff --git a/rebar.lock b/rebar.lock
index 2f0a5bf..c5d97ad 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,5 +1,6 @@
{"1.2.0",
-[{<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},1},
+[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2},
+ {<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},1},
{<<"app_config">>,
{git,"https://gitlab.com/leapsight/app_config.git",
{ref,"e6a4dc99c0c9f17a6d4d865e40aefc409986f849"}},
@@ -14,6 +15,10 @@
{<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},2},
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.6.1">>},2},
{<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.11.0">>},2},
+ {<<"circuit_breaker">>,
+ {git,"https://github.com/klarna/circuit_breaker",
+ {ref,"ca94b561605e6319fabd45a2120c0cfd3edf6820"}},
+ 0},
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},0},
{<<"cowboy_telemetry">>,{pkg,<<"cowboy_telemetry">>,<<"0.4.0">>},0},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},1},
@@ -37,6 +42,10 @@
{ref,"dd6fb59a82ad0b920baee97271826b1ae255d1b4"}},
0},
{<<"erlsom">>,{pkg,<<"erlsom">>,<<"1.5.0">>},2},
+ {<<"erltrace">>,
+ {git,"https://gitlab.com/Project-FiFo/FiFo/erltrace.git",
+ {ref,"66d0c3c42284fb692525dd65f89cf1446b8787b0"}},
+ 0},
{<<"erlydtl">>,{pkg,<<"erlydtl">>,<<"0.14.0">>},0},
{<<"fast_yaml">>,
{git,"https://github.com/processone/fast_yaml",
@@ -44,12 +53,17 @@
0},
{<<"folsom">>,{pkg,<<"folsom">>,<<"0.8.8">>},1},
{<<"gen_batch_server">>,{pkg,<<"gen_batch_server">>,<<"0.8.7">>},1},
+ {<<"genlib">>,
+ {git,"https://github.com/rbkmoney/genlib",
+ {ref,"82c5ff3866e3019eb347c7f1d8f1f847bed28c10"}},
+ 0},
{<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1},
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1},
{<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.14.0">>},1},
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.17.4">>},1},
{<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},3},
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2},
+ {<<"inet_cidr">>,{pkg,<<"erl_cidr">>,<<"1.2.0">>},0},
{<<"iso8601">>,{pkg,<<"iso8601">>,<<"1.3.1">>},1},
{<<"jesse">>,
{git,"https://github.com/for-GET/jesse",
@@ -97,6 +111,12 @@
{<<"opentelemetry_exporter">>,
{pkg,<<"opentelemetry_exporter">>,<<"1.0.2">>},
0},
+ {<<"opentelemetry_logger_metadata">>,
+ {pkg,<<"opentelemetry_logger_metadata">>,<<"0.1.0">>},
+ 0},
+ {<<"opentelemetry_process_propagator">>,
+ {pkg,<<"opentelemetry_process_propagator">>,<<"0.1.1">>},
+ 0},
{<<"opentelemetry_telemetry">>,
{pkg,<<"opentelemetry_telemetry">>,<<"1.0.0-beta.7">>},
1},
@@ -114,6 +134,10 @@
{git,"https://gitlab.com/leapsight/plum_db",
{ref,"49ab98faed623e26a2293eac0b73bed34b02e47a"}},
0},
+ {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.8.2">>},0},
+ {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0},
+ {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1},
+ {<<"quantile_estimator">>,{pkg,<<"quantile_estimator">>,<<"0.2.1">>},1},
{<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.4">>},1},
{<<"ra">>,{pkg,<<"ra">>,<<"2.0.6">>},0},
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1},
@@ -124,6 +148,7 @@
1},
{<<"rfc3339">>,{pkg,<<"rfc3339">>,<<"0.2.2">>},1},
{<<"rocksdb">>,{pkg,<<"rocksdb">>,<<"1.7.0">>},1},
+ {<<"sbroker">>,{pkg,<<"sbroker">>,<<"1.0.0">>},0},
{<<"sext">>,{pkg,<<"sext">>,<<"1.8.0">>},1},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},2},
{<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.1.0">>},1},
@@ -142,6 +167,7 @@
{<<"yamerl">>,{pkg,<<"yamerl">>,<<"0.10.0">>},0}]}.
[
{pkg_hash,[
+ {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>},
{<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>},
{<<"aten">>, <<"F88EB38CEADC0710B84B5D78F0357D766733D80902D50AC69B70CDF834992DAA">>},
{<<"base32">>, <<"044F6DC95709727CA2176F3E97A41DDAA76B5BC690D3536908618C0CB32616A2">>},
@@ -165,6 +191,7 @@
{<<"hackney">>, <<"99DA4674592504D3FB0CFEF0DB84C3BA02B4508BAE2DFF8C0108BAA0D6E0977C">>},
{<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>},
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
+ {<<"inet_cidr">>, <<"9205FFB290C0DE8D2B82147976602FBF5BFA6D594834E60556AFAF3B82856B95">>},
{<<"iso8601">>, <<"D1CEE73F56D71C35590C6B2DB2074873BF410BABAAB768F6EA566366D8CA4810">>},
{<<"jsone">>, <<"7EA1098FE004C4127320FE0E3CF6A951B01F82039FEAA56C322DC7E34DD59762">>},
{<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>},
@@ -178,15 +205,22 @@
{<<"opentelemetry_api">>, <<"91353EE40583B1D4F07D7B13ED62642ABFEC6AAA0D8A2114F07EDAFB2DF781C5">>},
{<<"opentelemetry_cowboy">>, <<"CE16BE94932DFCF9A036DE0B516413A5180AA6A4F31AD4073DD86EABE73F8837">>},
{<<"opentelemetry_exporter">>, <<"19A102D1F04776399A915BE27121852468318C27146E553FAF28008E3E474972">>},
+ {<<"opentelemetry_logger_metadata">>, <<"0D1B7A4669521D75B142C9C9585771ED0707CCD515312CF06135C5BFC5C60AB9">>},
+ {<<"opentelemetry_process_propagator">>, <<"81EC6825971903486EE73BE23230D06764DF39EE11011E520F601AA2BB21C893">>},
{<<"opentelemetry_telemetry">>, <<"BA1DF62515AED63F99A80DDF17E7A3873D1F686F23598EDEBF1633942772856E">>},
{<<"p1_utils">>, <<"7F94466ADA69BD982EA7BB80FBCA18E7053E7D0B82C9D9E37621FA508587069B">>},
{<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>},
+ {<<"prometheus">>, <<"B88F24279DD7A1F512CB090595FF6C88B50AAD0A6B394A4C4983725723DCD834">>},
+ {<<"prometheus_cowboy">>, <<"CFCE0BC7B668C5096639084FCD873826E6220EA714BF60A716F5BD080EF2A99C">>},
+ {<<"prometheus_httpd">>, <<"F616ED9B85B536B195D94104063025A91F904A4CFC20255363F49A197D96C896">>},
+ {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>},
{<<"quickrand">>, <<"168CA3A8466A26912B8C3A1D6AA58975E1BB49E5C7AFB4998B80F6B90F910490">>},
{<<"ra">>, <<"C1AD68DE00B5DD3B32E6A30E1359AC676C3CAE47EECBA951789B470CBD4E1087">>},
{<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>},
{<<"recon">>, <<"CBA53FA8DB83AD968C9A652E09C3ED7DDCC4DA434F27C3EAA9CA47FFB2B1FF03">>},
{<<"rfc3339">>, <<"1552DF616ACA368D982E9F085A0E933B6688A3F4938A671798978EC2C0C58730">>},
{<<"rocksdb">>, <<"5D23319998A7FCE5FFD5D7824116C905CABA7F91BAF8EDDABD0180F1BB272CEF">>},
+ {<<"sbroker">>, <<"28FF1B5E58887C5098539F236307B36FE1D3EDAA2ACFF9D6A3D17C2DCAFEBBD0">>},
{<<"sext">>, <<"90A95B889F5C781B70BBCF44278B763148E313C376B60D87CE664CB1C1DD29B5">>},
{<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>},
{<<"telemetry">>, <<"A589817034A27EAB11144AD24D5C0F9FAB1F58173274B1E9BAE7074AF9CBEE51">>},
@@ -198,6 +232,7 @@
{<<"uuid">>, <<"77C3E3EE1E1701A2856CE945846D7CEB71931C60633A305D0B0FEAE03B2B3B5C">>},
{<<"yamerl">>, <<"4FF81FEE2F1F6A46F1700C0D880B24D193DDB74BD14EF42CB0BCF46E81EF2F8E">>}]},
{pkg_hash_ext,[
+ {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>},
{<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>},
{<<"aten">>, <<"8B623C8BE27B59A911D16AB0AF41777B504C147BC0D60A29015FAB58321C04B0">>},
{<<"base32">>, <<"10A73951D857D8CB1ECEEA8EB96C6941F6A76E105947AD09C2B73977DEE07638">>},
@@ -221,6 +256,7 @@
{<<"hackney">>, <<"DE16FF4996556C8548D512F4DBE22DD58A587BF3332E7FD362430A7EF3986B16">>},
{<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>},
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
+ {<<"inet_cidr">>, <<"3505F5DFAC7D862806C7051A3DD475363A45BCCF39CA1FAEE8EDA6A6B33CF335">>},
{<<"iso8601">>, <<"A8B00594F4309A41D17BA4AEAB2B94DFE1F4BE99F263BC1F46DAC9002CE99A29">>},
{<<"jsone">>, <<"A6C1DF6081DF742068D2ED747A4FE8A7740C56421B53E02BC9D4907DD3502922">>},
{<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>},
@@ -234,15 +270,22 @@
{<<"opentelemetry_api">>, <<"2A8247F85C44216B883900067478D59955D11E58E5CFCA7C884CD4F203ACE3AC">>},
{<<"opentelemetry_cowboy">>, <<"455263DD177ACB8724391A5F2AF809E99F5D4A6DC71684E34A462F4D4AD02BD0">>},
{<<"opentelemetry_exporter">>, <<"43DE904DD7F482009BF1A40D591AB5ED25F603F1072A04A162E87F7F8C08DBB5">>},
+ {<<"opentelemetry_logger_metadata">>, <<"772976D3C59651CF9C4600EDC238BB9CADF7F5EDAED1A1C5C59BF3E773DFE9FC">>},
+ {<<"opentelemetry_process_propagator">>, <<"0572F26066BBB0457E22E169F966C0140A8F95237716C9C6BA4458D6DBAA724B">>},
{<<"opentelemetry_telemetry">>, <<"480F4FA1E992D597F931E7BC9E68478E8D904AD84489D2C5CA6EB6D48BBD7801">>},
{<<"p1_utils">>, <<"47F21618694EEEE5006AF1C88731AD86B757161E7823C29B6F73921B571C8502">>},
{<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>},
+ {<<"prometheus">>, <<"C3ABD6521E52CEC4F0D8ECA603CF214DFC84D8A27AA85946639F1424B8554D98">>},
+ {<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>},
+ {<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>},
+ {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>},
{<<"quickrand">>, <<"4CB18E9304CF28E054E8DC6E151D1AC7F174E6FE31D5C1A07F71279B92A90800">>},
{<<"ra">>, <<"75C664334D4F294327DD8AAD06385294CC5CCE4763FBEF13EBF5075E930CEDF6">>},
{<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>},
{<<"recon">>, <<"2C7523C8DEE91DFF41F6B3D63CBA2BD49EB6D2FE5BF1EEC0DF7F87EB5E230E1C">>},
{<<"rfc3339">>, <<"986D7F9BAC6891AA4D5051690058DE4E623245620BBEADA7F239F85C4DF8F23C">>},
{<<"rocksdb">>, <<"A4BDC5DD80ED137161549713062131E8240523787EBE7B51DF61CFB48B1786CE">>},
+ {<<"sbroker">>, <<"BA952BFA35B374E1E5D84BC5F5EFE8360C6F99DC93B3118F714A9A2DFF6C9E19">>},
{<<"sext">>, <<"BC6016CB8690BAF677EACACFE6E7CADFEC8DC7E286CBBED762F6CD55B0678E73">>},
{<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>},
{<<"telemetry">>, <<"B727B2A1F75614774CFF2D7565B64D0DFA5BD52BA517F16543E6FC7EFCC0DF48">>},