122 lines
4.1 KiB
Erlang
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. |