ekfa/apps/efka/src/efka_manifest.erl
2025-05-19 23:52:34 +08:00

122 lines
4.1 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%% 用于管理manifest.json配置文件
%%% @end
%%% Created : 05. 5月 2025 22:39
%%%-------------------------------------------------------------------
-module(efka_manifest).
-author("anlicheng").
-record(manifest, {
work_dir = "" :: string(),
id = <<"">> :: binary(),
exec = <<"">>:: binary(),
args = [],
health_check = <<"">>
}).
-type manifest() :: #manifest{}.
-export_type([manifest/0]).
%% API
-export([new/1, startup/1]).
-spec new(WorkDir :: string()) -> {ok, #manifest{}} | {error, Reason :: binary()}.
new(WorkDir) when is_list(WorkDir) ->
case file:read_file(WorkDir ++ "manifest.json") of
{ok, ManifestInfo} ->
Settings = catch jiffy:decode(ManifestInfo, [return_maps]),
case check_manifest(Settings) of
{ok, Manifest} ->
{ok, Manifest#manifest{work_dir = WorkDir}};
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end.
-spec startup(Manifest :: #manifest{}) -> {ok, Port :: port()} | {error, Reason :: binary()}.
startup(#manifest{work_dir = WorkDir, exec = ExecCmd0, args = Args0}) ->
PortSettings = [
{cd, WorkDir},
{args, [binary_to_list(A) || A <- Args0]},
exit_status
],
ExecCmd = binary_to_list(ExecCmd0),
RealExecCmd = filename:absname_join(WorkDir, ExecCmd),
case catch erlang:open_port({spawn_executable, RealExecCmd}, PortSettings) of
Port when is_port(Port) ->
{ok, Port};
_Other ->
{error, <<"exec command startup failed">>}
end.
%% 检查配置是否合法
-spec check_manifest(Manifest :: map()) -> {ok, #manifest{}} | {error, Reason :: binary()}.
check_manifest(Manifest) when is_map(Manifest) ->
RequiredKeys = [<<"id">>, <<"exec">>, <<"args">>, <<"health_check">>],
check_manifest0(RequiredKeys, Manifest, #manifest{});
check_manifest(_Manifest) ->
{error, <<"invalid manifest json">>}.
check_manifest0([], _Settings, Manifest) ->
{ok, Manifest};
check_manifest0([<<"id">>|T], Settings, Manifest) ->
case maps:find(<<"id">>, Settings) of
error ->
{error, <<"miss service_id">>};
{ok, Id} when is_binary(Id) ->
check_manifest0(T, Settings, Manifest#manifest{id = Id});
{ok, _} ->
{error, <<"service_id is not string">>}
end;
check_manifest0([<<"health_check">>|T], Settings, Manifest) ->
case maps:find(<<"health_check">>, Settings) of
error ->
{error, <<"miss health_check">>};
{ok, Url} when is_binary(Url) ->
case is_url(Url) of
true ->
check_manifest0(T, Settings, Manifest#manifest{health_check = Url});
false ->
{error, <<"health_check is not a invalid url">>}
end;
{ok, _} ->
{error, <<"health_check is not string">>}
end;
check_manifest0([<<"exec">>|T], Settings, Manifest) ->
case maps:find(<<"exec">>, Settings) of
error ->
{error, <<"miss start">>};
{ok, Exec} when is_binary(Exec) ->
%% 不能包含空格
case binary:match(Exec, <<" ">>) of
nomatch ->
check_manifest0(T, Settings, Manifest#manifest{exec = Exec});
_ ->
{error, <<"start cmd cannot contain args">>}
end
end;
check_manifest0([<<"args">>|T], Settings, Manifest) ->
case maps:find(<<"args">>, Settings) of
error ->
check_manifest0(T, Settings, Manifest#manifest{args = []});
%% 对参数项目不进行检查
{ok, Args} when is_list(Args) ->
check_manifest0(T, Settings, Manifest#manifest{args = Args});
{ok, _} ->
{error, <<"args must be list">>}
end.
-spec is_url(binary()) -> boolean().
is_url(Input) when is_binary(Input) ->
try
uri_string:parse(Input),
true
catch
_:_ -> false
end.