aboutsummaryrefslogtreecommitdiff
path: root/apps/dreki/src/tasks
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dreki/src/tasks')
-rw-r--r--apps/dreki/src/tasks/dreki_task.erl58
-rw-r--r--apps/dreki/src/tasks/dreki_tasks.erl166
-rw-r--r--apps/dreki/src/tasks/dreki_tasks_cloyster.erl21
-rw-r--r--apps/dreki/src/tasks/dreki_tasks_script.erl24
4 files changed, 269 insertions, 0 deletions
diff --git a/apps/dreki/src/tasks/dreki_task.erl b/apps/dreki/src/tasks/dreki_task.erl
new file mode 100644
index 0000000..b762aea
--- /dev/null
+++ b/apps/dreki/src/tasks/dreki_task.erl
@@ -0,0 +1,58 @@
+-module(dreki_task).
+
+-include("dreki.hrl").
+
+-type new_params() :: #{handler := dreki_task_handler(),
+ id => dreki_id(),
+ description => binary(),
+ params => #{}}.
+
+-export([new/1, validate/1, to_map/1]).
+-export([id/1, description/1, handler/1, params/1]).
+-export([description/2, handler/2, params/2]).
+
+-spec new(new_params()) -> {ok, dreki_task()} | {error, Reason::term()}.
+new(Map) ->
+ Id = maps:get(id, Map, dreki_id:get()),
+ Description = maps:get(description, Map, undefined),
+ Params = maps:get(params, Map, #{}),
+ Handler = maps:get(handler, Map, undefined),
+ Task = #dreki_task{id=Id, handler=Handler, params=Params, description=Description,
+ persisted=false, dirty=true},
+ validate(Task).
+
+-spec validate(dreki_task()) -> {ok, dreki_task()} | {error, Reason::term()}.
+validate(#dreki_task{handler = undefined}) ->
+ {error, {required, handler}};
+validate(Task = #dreki_task{id = Id, handler = _Handler}) ->
+ case dreki_id:valid(Id) of
+ {error, Err} -> {error, Err};
+ ok -> {ok, Task}
+ end.
+
+id(Task = #dreki_task{}) ->
+ Task#dreki_task.id.
+
+description(Task = #dreki_task{}) ->
+ Task#dreki_task.description.
+
+handler(Task = #dreki_task{}) ->
+ Task#dreki_task.handler.
+
+params(Task = #dreki_task{}) ->
+ Task#dreki_task.params.
+
+handler(Task = #dreki_task{}, NewHandler) ->
+ Task#dreki_task{handler=NewHandler, dirty=true}.
+
+description(Task = #dreki_task{}, NewDescription) ->
+ Task#dreki_task{description=NewDescription, dirty=true}.
+
+params(Task = #dreki_task{}, NewParams) ->
+ Task#dreki_task{params=NewParams, dirty=true}.
+
+to_map(Task = #dreki_task{id = Id, handler = Handler, description = Description, params = Params}) ->
+ #{id => Id,
+ handler => Handler,
+ description => Description,
+ params => Params}.
diff --git a/apps/dreki/src/tasks/dreki_tasks.erl b/apps/dreki/src/tasks/dreki_tasks.erl
new file mode 100644
index 0000000..750aed0
--- /dev/null
+++ b/apps/dreki/src/tasks/dreki_tasks.erl
@@ -0,0 +1,166 @@
+-module(dreki_tasks).
+-include("dreki.hrl").
+
+-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]).
+
+%% old stuff
+-export([resolve/1, exists/1, read_uri/2]).
+
+start() -> ok.
+
+valid_store(_Namespace, _Location, _Name, _BackendMod) -> ok.
+
+format_item(Item) -> ok.
+
+handlers() -> [dreki_tasks_script, dreki_tasks_cloyster, drekid_function].
+
+-record(t, {
+ id,
+ version,
+ schema,
+ module,
+ params
+}).
+
+version() -> 1.
+
+new() ->
+ #{
+ <<"@schema">> => <<"task:1.0">>,
+ <<"id">> => ksuid:gen_id(),
+ <<"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()),
+ maps:merge(Subs, #{
+ default => <<"task">>,
+ <<"task">> => schemas(task)
+ }).
+
+schemas(task) ->
+ #{
+ default_version => <<"1.0">>,
+ <<"1.0">> => schemas(task, <<"1.0">>)
+ }.
+
+schemas(task, <<"1.0">>) ->
+ Handlers = handlers(),
+ Manifests0 = lists:map(fun (Handler) -> {Handler, Handler:schema_field(handler_manifest)} end, Handlers),
+ Manifests = lists:foldr(fun
+ ({Handler, undefined}, Acc) -> Acc;
+ ({Handler, Schema}, Acc) ->
+ Schemas = maps:fold(fun
+ (Atom, _, Acc) when is_atom(Atom) -> Acc;
+ (Vsn, _, Acc) -> [#{'$$ref' => <<Schema/binary, ":", Vsn/binary>>} | Acc]
+ end, [], maps:get(Schema, Handler:schemas())),
+ Acc ++ Schemas
+ end, [], Manifests0),
+
+ #{
+ version => 'draft-06',
+ title => <<"Task">>,
+ type => object,
+ properties => #{
+ id => #{type => string, <<"dreki:form">> => #{default => {ksuid, gen_id, []}}},
+ schema => #{type => string},
+ name => #{type => string, title => <<"Name">>},
+ handler => #{type => string, title => <<"Handler">>, enum => handlers()},
+ handler_manifest => #{'$ref' => <<"handler_manifest">>}
+ },
+ '$defs' => #{
+ <<"handler_manifest">> => #{anyOf => Manifests}
+ },
+ 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) ->
+ {ok, #{stores => #{}}};
+read_uri(Path, Uri) ->
+ case binary:split(Path, <<":">>, [global]) of
+ [<<>>, <<>>] -> {error, invalid_resource};
+ [<<>>, Id] -> {ok, #{resolve => #{kind => tasks, id => Id}}};
+ [S] -> {ok, #{store => #{kind => tasks, id => S}}};
+ [S, <<>>] -> {ok, #{store => #{kind => tasks, id => S}}};
+ [S, Id] -> {ok, #{resource => #{kind => tasks, store => S, id => Id}}};
+ R -> {error, {invalid_address, {Path, R}}}
+ end.
+
+all(Uri) when is_binary(Uri) ->
+ all(dreki_world:read_uri(Uri));
+all({ok, Uri = #{kind := tasks, uri := Uri, resource := Res = #{store := #{id := _S}}}}) ->
+ {ok, Mod, Store} = dreki_node:get_store(Uri),
+ Mod:all(Store);
+all({ok, Uri}) -> {error, unresolvable_uri, Uri};
+all(Error = {error, _}) -> Error.
+
+
+resolve(Id) ->
+ Path = dreki_world:path(),
+ case binary:match(Id, <<Path/binary, "::tasks:">>) of
+ {0,End} ->
+ StoreAndId = binary:part(Id, {End, byte_size(Id) - End}),
+ [StoreN, LId] = binary:split(StoreAndId, <<":">>),
+ {ok, {node(), StoreN, LId}}
+ end.
+
+exists({Node, StoreN, LId}) when Node =:= node() ->
+ #{mod := Mod, args := Args} = maps:get(StoreN, load_local_stores()),
+ {ok, Db} = Mod:open(Args),
+ Mod:exists(Db, LId);
+exists(N = {Node, _, _}) ->
+ erpc:call(Node, dreki_tasks, exists, [N]);
+exists(Id) when is_binary(Id) ->
+ case resolve(Id) of
+ {ok, N} -> exists(N);
+ Error = {error, _} -> Error
+ end.
+
+load_local_stores() ->
+ {ok, Val} = application:get_env(dreki, local_tasks_stores),
+ _World = #{path := Path, me := Me} = dreki_world:to_map(),
+ MapFn = fun ({Name, Mod, Args, Env}, Acc) ->
+ Store = #{local => true,
+ node => Me,
+ path => <<Path/binary, "::tasks:", Name/binary>>,
+ mod => Mod,
+ args => Args,
+ env => Env,
+ name => Name
+ },
+ maps:put(Name, Store, Acc)
+ end,
+ lists:foldr(MapFn, #{}, Val).
diff --git a/apps/dreki/src/tasks/dreki_tasks_cloyster.erl b/apps/dreki/src/tasks/dreki_tasks_cloyster.erl
new file mode 100644
index 0000000..3fb045d
--- /dev/null
+++ b/apps/dreki/src/tasks/dreki_tasks_cloyster.erl
@@ -0,0 +1,21 @@
+-module(dreki_tasks_cloyster).
+-export([schemas/0, schema_field/1]).
+
+schema_field(handler_manifest) -> <<"cloyster-task">>.
+
+schemas() ->
+ #{
+ <<"cloyster-task">> => #{
+ default_version => <<"1.0">>,
+ <<"1.0">> => #{
+ version => 'draft-06',
+ title => <<"Cloyster Task Definition">>,
+ type => object,
+ properties => #{
+ <<"script">> => #{type => string}
+ },
+ required => [script]
+ }
+ }
+ }.
+
diff --git a/apps/dreki/src/tasks/dreki_tasks_script.erl b/apps/dreki/src/tasks/dreki_tasks_script.erl
new file mode 100644
index 0000000..8eeb563
--- /dev/null
+++ b/apps/dreki/src/tasks/dreki_tasks_script.erl
@@ -0,0 +1,24 @@
+-module(dreki_tasks_script).
+-export([schemas/0, schema_field/1]).
+
+schema_field(handler_manifest) -> <<"script-task">>.
+
+schemas() ->
+ #{
+ <<"script-task">> => #{
+ default_version => <<"1.0">>,
+ <<"1.0">> => #{
+ version => 'draft-06',
+ title => <<"Executable Script Task">>,
+ type => object,
+ properties => #{
+ <<"id">> => #{type => string, <<"dreki:form">> => #{default => generate_id}},
+ <<"name">> => #{type => string},
+ <<"description">> => #{type => string, <<"dreki:form">> => #{input => textarea, textarea_mode => markdown}},
+ <<"executable">> => #{type => string, default => <<"/bin/sh">>},
+ <<"script">> => #{type => string, <<"dreki:form">> => #{input => textarea}}
+ },
+ required => [script]
+ }
+ }
+ }.