diff options
Diffstat (limited to 'apps/dreki/src/tasks')
-rw-r--r-- | apps/dreki/src/tasks/dreki_task.erl | 58 | ||||
-rw-r--r-- | apps/dreki/src/tasks/dreki_tasks.erl | 166 | ||||
-rw-r--r-- | apps/dreki/src/tasks/dreki_tasks_cloyster.erl | 21 | ||||
-rw-r--r-- | apps/dreki/src/tasks/dreki_tasks_script.erl | 24 |
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] + } + } + }. |