将与docker之间的通讯修改
This commit is contained in:
parent
743ed8813e
commit
6b73c1c0b7
@ -12,337 +12,456 @@
|
|||||||
%% API
|
%% API
|
||||||
-export([pull_image/2, check_image_exist/1]).
|
-export([pull_image/2, check_image_exist/1]).
|
||||||
-export([create_container/3, check_container_exist/1, is_container_running/1, start_container/1, stop_container/1]).
|
-export([create_container/3, check_container_exist/1, is_container_running/1, start_container/1, stop_container/1]).
|
||||||
|
-export([test/0, test_create_container/0, test1/0]).
|
||||||
|
|
||||||
|
test() ->
|
||||||
|
Image = <<"docker.1ms.run/library/nginx:latest">>,
|
||||||
|
pull_image(Image, fun(Msg) -> lager:debug("msg is: ~p", [Msg]) end).
|
||||||
|
|
||||||
|
test1() ->
|
||||||
|
Id = <<"redpanda-console">>,
|
||||||
|
StopRes = stop_container(Id),
|
||||||
|
lager:debug("stop res: ~p", [StopRes]),
|
||||||
|
StartRes = start_container(Id),
|
||||||
|
lager:debug("start res: ~p", [StartRes]).
|
||||||
|
|
||||||
|
test_create_container() ->
|
||||||
|
M = #{
|
||||||
|
<<"image">> => <<"docker.1ms.run/library/nginx:latest">>,
|
||||||
|
<<"container_name">> => <<"my_nginx_new1">>,
|
||||||
|
<<"command">> => [
|
||||||
|
<<"nginx">>,
|
||||||
|
<<"-g">>,
|
||||||
|
<<"daemon off;">>
|
||||||
|
],
|
||||||
|
<<"entrypoint">> => [
|
||||||
|
<<"/docker-entrypoint.sh">>
|
||||||
|
],
|
||||||
|
<<"envs">> => [
|
||||||
|
<<"ENV1=val1">>,
|
||||||
|
<<"ENV2=val2">>
|
||||||
|
],
|
||||||
|
<<"env_file">> => [
|
||||||
|
<<"./env.list">>
|
||||||
|
],
|
||||||
|
<<"ports">> => [
|
||||||
|
<<"8080:80">>,
|
||||||
|
<<"443:443">>
|
||||||
|
],
|
||||||
|
<<"expose">> => [
|
||||||
|
<<"80">>,
|
||||||
|
<<"443">>
|
||||||
|
],
|
||||||
|
<<"volumes">> => [
|
||||||
|
<<"/host/data:/data">>,
|
||||||
|
<<"/host/log:/var/log">>
|
||||||
|
],
|
||||||
|
<<"networks">> => [
|
||||||
|
<<"mynet">>
|
||||||
|
],
|
||||||
|
<<"labels">> => #{
|
||||||
|
<<"role">> => <<"web">>,
|
||||||
|
<<"env">> => <<"prod">>
|
||||||
|
},
|
||||||
|
<<"restart">> => <<"always">>,
|
||||||
|
<<"user">> => <<"www-data">>,
|
||||||
|
<<"working_dir">> => <<"/app">>,
|
||||||
|
<<"hostname">> => <<"myhost">>,
|
||||||
|
<<"privileged">> => true,
|
||||||
|
<<"cap_add">> => [
|
||||||
|
<<"NET_ADMIN">>
|
||||||
|
],
|
||||||
|
<<"cap_drop">> => [
|
||||||
|
<<"MKNOD">>
|
||||||
|
],
|
||||||
|
<<"devices">> => [
|
||||||
|
<<"/dev/snd:/dev/snd">>
|
||||||
|
],
|
||||||
|
<<"mem_limit">> => <<"512m">>,
|
||||||
|
<<"mem_reservation">> => <<"256m">>,
|
||||||
|
<<"cpu_shares">> => 512,
|
||||||
|
<<"cpus">> => 1.5,
|
||||||
|
<<"ulimits">> => #{
|
||||||
|
<<"nofile">> => <<"1024:2048">>
|
||||||
|
},
|
||||||
|
<<"sysctls">> => #{
|
||||||
|
<<"net.ipv4.ip_forward">> => <<"1">>
|
||||||
|
},
|
||||||
|
<<"tmpfs">> => [
|
||||||
|
<<"/tmp">>
|
||||||
|
],
|
||||||
|
<<"extra_hosts">> => [
|
||||||
|
<<"host1:192.168.0.1">>
|
||||||
|
],
|
||||||
|
<<"healthcheck">> => #{
|
||||||
|
<<"test">> => [
|
||||||
|
<<"CMD-SHELL">>,
|
||||||
|
<<"curl -f http://localhost || exit 1">>
|
||||||
|
],
|
||||||
|
<<"interval">> => <<"30s">>,
|
||||||
|
<<"timeout">> => <<"10s">>,
|
||||||
|
<<"retries">> => 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
create_container(<<"my_nginx_xx3">>, "/usr/local/code/efka/", M).
|
||||||
|
|
||||||
-spec pull_image(Image :: binary(), Callback :: fun((Msg :: binary()) -> no_return())) -> ok | {error, ExitCode :: integer()}.
|
-spec pull_image(Image :: binary(), Callback :: fun((Msg :: binary()) -> no_return())) -> ok | {error, ExitCode :: integer()}.
|
||||||
pull_image(Image, Callback) when is_binary(Image), is_function(Callback, 1) ->
|
pull_image(Image, Callback) when is_binary(Image), is_function(Callback, 1) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, stderr_to_stdout, binary],
|
Url = lists:flatten(io_lib:format("/images/create?fromImage=~s", [binary_to_list(Image)])),
|
||||||
ExecCmd = "docker pull " ++ binary_to_list(Image),
|
docker_http:stream_request(Callback, "POST", Url, undefined, []).
|
||||||
lager:debug("cmd : ~p", [ExecCmd]),
|
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
|
||||||
Port when is_port(Port) ->
|
|
||||||
case gather_pull_output(Port, Callback) of
|
|
||||||
0 ->
|
|
||||||
ok;
|
|
||||||
ExitCode ->
|
|
||||||
{error, ExitCode}
|
|
||||||
end;
|
|
||||||
Error ->
|
|
||||||
lager:debug("error: ~p", [Error]),
|
|
||||||
{error, -1}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec check_image_exist(Image :: binary()) -> boolean().
|
-spec check_image_exist(Image :: binary()) -> boolean().
|
||||||
check_image_exist(Image) when is_binary(Image) ->
|
check_image_exist(Image) when is_binary(Image) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, binary],
|
Url = io_lib:format("/images/~s/json", [binary_to_list(Image)]),
|
||||||
ExecCmd = "docker images -q " ++ binary_to_list(Image),
|
case docker_http:request("GET", Url, undefined, []) of
|
||||||
lager:debug("cmd : ~p", [ExecCmd]),
|
{ok, 200, _Headers, Resp} ->
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
M = catch jiffy:decode(Resp, [return_maps]),
|
||||||
Port when is_port(Port) ->
|
is_map(M) andalso maps:is_key(<<"Id">>, M);
|
||||||
case gather_output(Port) of
|
{ok, 404, _Headers, _Error} ->
|
||||||
{0, <<>>} ->
|
|
||||||
false;
|
|
||||||
{0, <<_:12/binary, $\n>>} ->
|
|
||||||
true;
|
|
||||||
{_ExitCode, _Error} ->
|
|
||||||
false
|
|
||||||
end;
|
|
||||||
_Error ->
|
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec create_container(ContainerName :: binary(), ContainerDir :: string(), Config :: map()) -> {ok, ContainerId :: binary()} | {error, Reason :: any()}.
|
-spec create_container(ContainerName :: binary(), ContainerDir :: string(), Config :: map()) -> {ok, ContainerId :: binary()} | {error, Reason :: any()}.
|
||||||
create_container(ContainerName, ContainerDir, Config) when is_binary(ContainerName), is_list(ContainerDir), is_map(Config) ->
|
create_container(ContainerName, ContainerDir, Config) when is_binary(ContainerName), is_list(ContainerDir), is_map(Config) ->
|
||||||
Image = maps:get(<<"image">>, Config),
|
Url = lists:flatten(io_lib:format("/containers/create?name=~s", [binary_to_list(ContainerName)])),
|
||||||
Cmd = maps:get(<<"command">>, Config, []),
|
|
||||||
|
|
||||||
%% 挂载预留的目录,用来作为配置文件的存放
|
%% 挂载预留的目录,用来作为配置文件的存放
|
||||||
BinContainerDir = list_to_binary(docker_container_helper:make_etc_dir_name(ContainerDir)),
|
BinContainerDir = list_to_binary(docker_container_helper:make_etc_dir_name(ContainerDir)),
|
||||||
BaseOptions = [<<"-v">>, <<BinContainerDir/binary, ":/usr/local/etc/">>],
|
|
||||||
|
|
||||||
Options = build_options(Config),
|
%% 增加自定义的用来放配置文件的目录
|
||||||
Args = lists:flatten([Image | BaseOptions ++ Options ++ Cmd]),
|
Volumes0 = maps:get(<<"volumes">>, Config, []),
|
||||||
CreateArgs = iolist_to_binary(lists:join(<<" ">>, Args)),
|
Volumes = [<<BinContainerDir/binary, ":/usr/local/etc/">>|Volumes0],
|
||||||
|
NewConfig = Config#{<<"volumes">> => Volumes},
|
||||||
|
|
||||||
PortSettings = [stream, exit_status, stderr_to_stdout, use_stdio, binary],
|
Options = build_options(NewConfig),
|
||||||
ExecCmd = "docker create --name " ++ binary_to_list(ContainerName) ++ " " ++ binary_to_list(CreateArgs),
|
lists:foreach(fun({K, V}) -> lager:debug("~p => ~p", [K, V]) end, maps:to_list(Options)),
|
||||||
lager:debug("create_container cmd : ~p", [ExecCmd]),
|
|
||||||
|
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
Body = iolist_to_binary(jiffy:encode(Options, [force_utf8])),
|
||||||
Port when is_port(Port) ->
|
Headers = [
|
||||||
case gather_output(Port) of
|
{<<"Content-Type">>, <<"application/json">>}
|
||||||
{0, ContainerId} ->
|
],
|
||||||
{ok, string:trim(ContainerId)};
|
case docker_http:request("POST", Url, Body, Headers) of
|
||||||
{ExitCode, Error} ->
|
{ok, 201, _Headers, Resp} ->
|
||||||
{error, {ExitCode, Error}}
|
#{<<"Id">> := ContainerId} = jiffy:decode(Resp, [return_maps]),
|
||||||
end;
|
{ok, ContainerId};
|
||||||
Error ->
|
{ok, _StatusCode, _, ErrorResp} ->
|
||||||
lager:debug("error: ~p", [Error]),
|
case catch jiffy:decode(ErrorResp, [return_maps]) of
|
||||||
{error, <<"exec command startup failed">>}
|
#{<<"message">> := Msg} ->
|
||||||
|
{error, Msg};
|
||||||
|
_ ->
|
||||||
|
{error, ErrorResp}
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec is_container_running(ContainerId :: binary()) -> boolean().
|
-spec is_container_running(ContainerId :: binary()) -> boolean().
|
||||||
is_container_running(ContainerId) when is_binary(ContainerId) ->
|
is_container_running(ContainerId) when is_binary(ContainerId) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, stderr_to_stdout, binary],
|
case inspect_container(ContainerId) of
|
||||||
ExecCmd = "docker inspect -f '{{.State.Running}}' " ++ binary_to_list(ContainerId),
|
{ok, #{<<"State">> := #{<<"Running">> := Running}}} ->
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
Running;
|
||||||
Port when is_port(Port) ->
|
{error, _} ->
|
||||||
case gather_output(Port) of
|
|
||||||
{0, Val0} ->
|
|
||||||
Val = string:trim(Val0),
|
|
||||||
Val =:= <<"true">>;
|
|
||||||
_ ->
|
|
||||||
false
|
|
||||||
end;
|
|
||||||
_Error ->
|
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec check_container_exist(ContainerName :: binary()) -> boolean().
|
-spec check_container_exist(ContainerName :: binary()) -> boolean().
|
||||||
check_container_exist(ContainerName) when is_binary(ContainerName) ->
|
check_container_exist(ContainerName) when is_binary(ContainerName) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, binary],
|
case inspect_container(ContainerName) of
|
||||||
ExecCmd = "docker inspect --type=container " ++ binary_to_list(ContainerName) ++ " >/dev/null 2>&1",
|
{ok, #{<<"Id">> := Id}} when is_binary(Id) ->
|
||||||
lager:debug("check_container_exist cmd : ~p", [ExecCmd]),
|
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
|
||||||
Port when is_port(Port) ->
|
|
||||||
case gather_output(Port) of
|
|
||||||
{0, _} ->
|
|
||||||
true;
|
true;
|
||||||
{_ExitCode, _Error} ->
|
_ ->
|
||||||
false
|
|
||||||
end;
|
|
||||||
_Error ->
|
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec start_container(ContainerName :: binary()) -> ok | {error, Reason :: binary()}.
|
-spec start_container(ContainerName :: binary()) -> ok | {error, Reason :: binary()}.
|
||||||
start_container(ContainerName) when is_binary(ContainerName) ->
|
start_container(ContainerName) when is_binary(ContainerName) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, binary],
|
Url = lists:flatten(io_lib:format("/containers/~s/start", [binary_to_list(ContainerName)])),
|
||||||
ExecCmd = "docker start " ++ binary_to_list(ContainerName) ++ " 2>&1",
|
Headers = [
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
{<<"Content-Type">>, <<"application/json">>}
|
||||||
Port when is_port(Port) ->
|
],
|
||||||
case gather_output(Port) of
|
case docker_http:request("POST", Url, undefined, Headers) of
|
||||||
{0, _} ->
|
{ok, 204, _Headers, _} ->
|
||||||
ok;
|
ok;
|
||||||
{_ExitCode, Error} ->
|
{ok, 304, _Headers, _} ->
|
||||||
{error, Error}
|
{error, <<"container already started">>};
|
||||||
end;
|
{ok, _StatusCode, _Header, ErrorResp} ->
|
||||||
_Error ->
|
case catch jiffy:decode(ErrorResp) of
|
||||||
{error, <<"启动失败"/utf8>>}
|
#{<<"message">> := Msg} ->
|
||||||
|
{error, Msg};
|
||||||
|
_ ->
|
||||||
|
{error, ErrorResp}
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec stop_container(ContainerName :: binary()) -> ok | {error, Reason :: binary()}.
|
-spec stop_container(ContainerName :: binary()) -> ok | {error, Reason :: binary()}.
|
||||||
stop_container(ContainerName) when is_binary(ContainerName) ->
|
stop_container(ContainerName) when is_binary(ContainerName) ->
|
||||||
PortSettings = [stream, exit_status, use_stdio, binary],
|
Url = lists:flatten(io_lib:format("/containers/~s/stop", [binary_to_list(ContainerName)])),
|
||||||
ExecCmd = "docker stop " ++ binary_to_list(ContainerName) ++ " 2>&1",
|
Headers = [
|
||||||
lager:debug("[docker_commands] cmd : ~p", [ExecCmd]),
|
{<<"Content-Type">>, <<"application/json">>}
|
||||||
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of
|
],
|
||||||
Port when is_port(Port) ->
|
case docker_http:request("POST", Url, undefined, Headers) of
|
||||||
case gather_output(Port) of
|
{ok, 204, _Headers, _} ->
|
||||||
{0, _} ->
|
|
||||||
ok;
|
ok;
|
||||||
{_ExitCode, Error} ->
|
{ok, 304, _Headers, _} ->
|
||||||
{error, Error}
|
{error, <<"container already stopped">>};
|
||||||
end;
|
{ok, _StatusCode, _Header, ErrorResp} ->
|
||||||
_Error ->
|
case catch jiffy:decode(ErrorResp) of
|
||||||
false
|
#{<<"message">> := Msg} ->
|
||||||
|
{error, Msg};
|
||||||
|
_ ->
|
||||||
|
{error, ErrorResp}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec inspect_container(ContainerId :: binary()) -> {ok, Json :: map()} | {error, Error :: any()}.
|
||||||
|
inspect_container(ContainerId) when is_binary(ContainerId) ->
|
||||||
|
Url = lists:flatten(io_lib:format("/containers/~s/json", [binary_to_list(ContainerId)])),
|
||||||
|
Headers = [
|
||||||
|
{<<"Content-Type">>, <<"application/json">>}
|
||||||
|
],
|
||||||
|
case docker_http:request("GET", Url, undefined, Headers) of
|
||||||
|
{ok, 200, _Headers, Resp} ->
|
||||||
|
Json = jiffy:decode(Resp, [return_maps]),
|
||||||
|
{ok, Json};
|
||||||
|
{ok, _StatusCode, _Header, ErrorResp} ->
|
||||||
|
case catch jiffy:decode(ErrorResp) of
|
||||||
|
#{<<"message">> := Msg} ->
|
||||||
|
{error, Msg};
|
||||||
|
_ ->
|
||||||
|
{error, ErrorResp}
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%% helper methods
|
%%% helper methods
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
gather_output(Port) ->
|
%% 构建最终 JSON Map
|
||||||
gather_output(Port, <<>>).
|
build_options(Config) when is_map(Config) ->
|
||||||
gather_output(Port, Acc) ->
|
#{
|
||||||
receive
|
<<"Image">> => maps:get(<<"image">>, Config, <<>>),
|
||||||
{Port, {data, Data}} ->
|
<<"Cmd">> => maps:get(<<"command">>, Config, []),
|
||||||
gather_output(Port, [Acc, Data]);
|
<<"Entrypoint">> => maps:get(<<"entrypoint">>, Config, []),
|
||||||
{Port, {exit_status, Status}} ->
|
<<"Env">> => maps:get(<<"envs">>, Config, []),
|
||||||
{Status, iolist_to_binary(Acc)}
|
<<"Labels">> => maps:get(<<"labels">>, Config, #{}),
|
||||||
end.
|
<<"Volumes">> => build_volumes(Config),
|
||||||
|
<<"User">> => maps:get(<<"user">>, Config, <<>>),
|
||||||
-spec gather_pull_output(Port :: port(), Callback :: fun((Msg :: binary()) -> no_return())) -> ExitCode :: integer().
|
<<"WorkingDir">> => maps:get(<<"working_dir">>, Config, <<>>),
|
||||||
gather_pull_output(Port, Callback) ->
|
<<"Hostname">> => maps:get(<<"hostname">>, Config, <<>>),
|
||||||
receive
|
<<"ExposedPorts">> => build_expose(Config),
|
||||||
{Port, {data, Data}} ->
|
<<"NetworkingConfig">> => build_networks(Config),
|
||||||
Callback(Data),
|
<<"Healthcheck">> => build_healthcheck(Config),
|
||||||
gather_pull_output(Port, Callback);
|
<<"HostConfig">> => fold_merge([
|
||||||
{Port, {exit_status, Status}} ->
|
build_binds(Config),
|
||||||
Status
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% 构建所有参数
|
|
||||||
build_options(Config) ->
|
|
||||||
lists:flatten([
|
|
||||||
build_entrypoint(Config),
|
|
||||||
build_ports(Config),
|
|
||||||
build_expose(Config),
|
|
||||||
build_volumes(Config),
|
|
||||||
build_env(Config),
|
|
||||||
build_env_file(Config),
|
|
||||||
build_networks(Config),
|
|
||||||
build_labels(Config),
|
|
||||||
build_restart(Config),
|
build_restart(Config),
|
||||||
build_user(Config),
|
|
||||||
build_working_dir(Config),
|
|
||||||
build_hostname(Config),
|
|
||||||
build_privileged(Config),
|
build_privileged(Config),
|
||||||
build_cap_add_drop(Config),
|
build_cap_add_drop(Config),
|
||||||
build_devices(Config),
|
build_devices(Config),
|
||||||
build_memory(Config),
|
build_memory(Config),
|
||||||
build_cpu(Config),
|
build_cpu(Config),
|
||||||
build_ulimits(Config),
|
build_ulimits(Config),
|
||||||
build_sysctls(Config),
|
|
||||||
build_tmpfs(Config),
|
build_tmpfs(Config),
|
||||||
build_extra_hosts(Config),
|
build_sysctls(Config),
|
||||||
build_healthcheck(Config)
|
build_extra_hosts(Config)
|
||||||
]).
|
])
|
||||||
|
}.
|
||||||
|
|
||||||
build_entrypoint(Config) ->
|
%% 工具函数
|
||||||
case maps:get(<<"entrypoint">>, Config, []) of
|
fold_merge(List) ->
|
||||||
[] -> [];
|
lists:foldl(fun maps:merge/2, #{}, List).
|
||||||
EP -> [<<"--entrypoint">> | EP]
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_ports(Config) ->
|
|
||||||
Ports = maps:get(<<"ports">>, Config, []),
|
|
||||||
lists:map(fun(P) -> [<<"-p">>, P] end, Ports).
|
|
||||||
|
|
||||||
|
%% --- 构建子字段 ---
|
||||||
build_expose(Config) ->
|
build_expose(Config) ->
|
||||||
Ports = maps:get(<<"expose">>, Config, []),
|
Ports = maps:get(<<"expose">>, Config, []),
|
||||||
lists:map(fun(P) -> [<<"--expose">>, P] end, Ports).
|
case Ports of
|
||||||
|
[] -> #{};
|
||||||
|
_ ->
|
||||||
|
maps:from_list([{<<P/binary, "/tcp">>, #{}} || P <- Ports])
|
||||||
|
end.
|
||||||
|
|
||||||
build_volumes(Config) ->
|
build_volumes(Config) ->
|
||||||
Vols = maps:get(<<"volumes">>, Config, []),
|
Vols = maps:get(<<"volumes">>, Config, []),
|
||||||
lists:map(fun(V) -> [<<"-v">>, V] end, Vols).
|
case Vols of
|
||||||
|
[] ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
maps:from_list(lists:map(fun(V) ->
|
||||||
|
[_Host, Cont] = binary:split(V, <<":">>, []),
|
||||||
|
{Cont, #{}}
|
||||||
|
end, Vols))
|
||||||
|
end.
|
||||||
|
|
||||||
build_env(Config) ->
|
build_binds(Config) ->
|
||||||
Envs = maps:get(<<"envs">>, Config, []),
|
Vols = maps:get(<<"volumes">>, Config, []),
|
||||||
lists:map(fun(E) -> [<<"-e">>, E] end, Envs).
|
case Vols of
|
||||||
|
[] ->
|
||||||
build_env_file(Config) ->
|
#{};
|
||||||
Files = maps:get(<<"env_file">>, Config, []),
|
_ ->
|
||||||
lists:map(fun(F) -> [<<"--env-file">>, F] end, Files).
|
#{<<"Binds">> => Vols}
|
||||||
|
end.
|
||||||
|
|
||||||
build_networks(Config) ->
|
build_networks(Config) ->
|
||||||
Nets = maps:get(<<"networks">>, Config, []),
|
Nets = maps:get(<<"networks">>, Config, []),
|
||||||
lists:map(fun(Net) -> [<<"--network">>, Net] end, Nets).
|
case Nets of
|
||||||
|
[] -> #{};
|
||||||
build_labels(Config) ->
|
_ ->
|
||||||
case maps:get(<<"labels">>, Config, #{}) of
|
NetCfg = maps:from_list([{N, #{}} || N <- Nets]),
|
||||||
#{} ->
|
#{<<"EndpointsConfig">> => NetCfg}
|
||||||
[];
|
|
||||||
Labels ->
|
|
||||||
lists:map(fun({K, V}) -> [<<"--label">>, <<K/binary, "=", V/binary>>] end, maps:to_list(Labels))
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
parse_mem(Val) ->
|
||||||
|
case binary:last(Val) of
|
||||||
|
$m ->
|
||||||
|
N = binary:part(Val, {0, byte_size(Val)-1}),
|
||||||
|
list_to_integer(binary_to_list(N)) * 1024 * 1024;
|
||||||
|
$g ->
|
||||||
|
N = binary:part(Val, {0, byte_size(Val)-1}),
|
||||||
|
list_to_integer(binary_to_list(N)) * 1024 * 1024 * 1024;
|
||||||
|
_ ->
|
||||||
|
list_to_integer(binary_to_list(Val))
|
||||||
|
end.
|
||||||
|
|
||||||
|
build_healthcheck(Config) ->
|
||||||
|
HC = maps:get(<<"healthcheck">>, Config, #{}),
|
||||||
|
case maps:size(HC) of
|
||||||
|
0 ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
#{
|
||||||
|
<<"Test">> => maps:get(<<"test">>, HC, []),
|
||||||
|
<<"Interval">> => parse_duration(maps:get(<<"interval">>, HC, <<"0s">>)),
|
||||||
|
<<"Timeout">> => parse_duration(maps:get(<<"timeout">>, HC, <<"0s">>)),
|
||||||
|
<<"Retries">> => maps:get(<<"retries">>, HC, 0)
|
||||||
|
}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_duration(Bin) ->
|
||||||
|
%% "30s" -> 30000000000
|
||||||
|
Sz = byte_size(Bin),
|
||||||
|
NBin = binary:part(Bin, {0, Sz-1}),
|
||||||
|
N = list_to_integer(binary_to_list(NBin)),
|
||||||
|
case binary:last(Bin) of
|
||||||
|
$s ->
|
||||||
|
N * 1000000000;
|
||||||
|
$m ->
|
||||||
|
N * 60000000000;
|
||||||
|
_ ->
|
||||||
|
N
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% --- 构建子字段 ---
|
||||||
|
|
||||||
build_restart(Config) ->
|
build_restart(Config) ->
|
||||||
case maps:get(<<"restart">>, Config, undefined) of
|
case maps:get(<<"restart">>, Config, undefined) of
|
||||||
undefined -> [];
|
undefined ->
|
||||||
Policy -> [<<"--restart">>, Policy]
|
#{};
|
||||||
end.
|
Policy ->
|
||||||
|
#{<<"RestartPolicy">> => #{<<"Name">> => Policy}}
|
||||||
build_user(Config) ->
|
|
||||||
case maps:get(<<"user">>, Config, undefined) of
|
|
||||||
undefined -> [];
|
|
||||||
U -> [<<"--user">>, U]
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_working_dir(Config) ->
|
|
||||||
case maps:get(<<"working_dir">>, Config, undefined) of
|
|
||||||
undefined -> [];
|
|
||||||
D -> [<<"--workdir">>, D]
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_hostname(Config) ->
|
|
||||||
case maps:get(<<"hostname">>, Config, undefined) of
|
|
||||||
undefined -> [];
|
|
||||||
H -> [<<"--hostname">>, H]
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
build_privileged(Config) ->
|
build_privileged(Config) ->
|
||||||
case maps:get(<<"privileged">>, Config, false) of
|
case maps:get(<<"privileged">>, Config, false) of
|
||||||
true -> [<<"--privileged">>];
|
true ->
|
||||||
_ -> []
|
#{<<"Privileged">> => true};
|
||||||
|
_ ->
|
||||||
|
#{}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
build_cap_add_drop(Config) ->
|
build_cap_add_drop(Config) ->
|
||||||
Add = maps:get(<<"cap_add">>, Config, []),
|
Add = maps:get(<<"cap_add">>, Config, []),
|
||||||
Drop = maps:get(<<"cap_drop">>, Config, []),
|
Drop = maps:get(<<"cap_drop">>, Config, []),
|
||||||
lists:map(fun(C) -> [<<"--cap-add">>, C] end, Add) ++ lists:map(fun(C0) -> [<<"--cap-drop">>, C0] end, Drop).
|
case {Add, Drop} of
|
||||||
|
{[], []} ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
#{<<"CapAdd">> => Add, <<"CapDrop">> => Drop}
|
||||||
|
end.
|
||||||
|
|
||||||
build_devices(Config) ->
|
build_devices(Config) ->
|
||||||
Devs = maps:get(<<"devices">>, Config, []),
|
Devs = maps:get(<<"devices">>, Config, []),
|
||||||
lists:map(fun(D) -> [<<"--device">>, D] end, Devs).
|
case Devs of
|
||||||
|
[] ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
DevObjs = [#{<<"PathOnHost">> => H, <<"PathInContainer">> => C,
|
||||||
|
<<"CgroupPermissions">> => <<"rwm">>}
|
||||||
|
|| D <- Devs,
|
||||||
|
[H, C] <- [binary:split(D, <<":">>, [])]],
|
||||||
|
#{<<"Devices">> => DevObjs}
|
||||||
|
end.
|
||||||
|
|
||||||
build_memory(Config) ->
|
build_memory(Config) ->
|
||||||
Mem = maps:get(<<"mem_limit">>, Config, undefined),
|
Mem = maps:get(<<"mem_limit">>, Config, undefined),
|
||||||
MemRes = maps:get(<<"mem_reservation">>, Config, undefined),
|
MemRes = maps:get(<<"mem_reservation">>, Config, undefined),
|
||||||
Res1 = if Mem /= undefined -> [<<"--memory">>, Mem]; true -> [] end,
|
HCfg = #{},
|
||||||
Res2 = if MemRes /= undefined -> [<<"--memory-reservation">>, MemRes]; true -> [] end,
|
HCfg1 = if
|
||||||
Res1 ++ Res2.
|
Mem /= undefined ->
|
||||||
|
maps:put(<<"Memory">>, parse_mem(Mem), HCfg);
|
||||||
|
true ->
|
||||||
|
HCfg
|
||||||
|
end,
|
||||||
|
if
|
||||||
|
MemRes /= undefined ->
|
||||||
|
maps:put(<<"MemoryReservation">>, parse_mem(MemRes), HCfg1);
|
||||||
|
true ->
|
||||||
|
HCfg1
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
build_cpu(Config) ->
|
build_cpu(Config) ->
|
||||||
CPU = maps:get(<<"cpus">>, Config, undefined),
|
CPU = maps:get(<<"cpus">>, Config, undefined),
|
||||||
Shares = maps:get(<<"cpu_shares">>, Config, undefined),
|
Shares = maps:get(<<"cpu_shares">>, Config, undefined),
|
||||||
Res1 = if
|
HCfg = #{},
|
||||||
|
HCfg1 = if
|
||||||
CPU /= undefined ->
|
CPU /= undefined ->
|
||||||
Bin = iolist_to_binary(io_lib:format("~p", [CPU])),
|
maps:put(<<"NanoCpus">>, trunc(CPU * 1000000000), HCfg);
|
||||||
[<<"--cpus">>, Bin];
|
|
||||||
true ->
|
true ->
|
||||||
[]
|
HCfg
|
||||||
end,
|
end,
|
||||||
Res2 = if
|
if
|
||||||
Shares /= undefined ->
|
Shares /= undefined ->
|
||||||
Bin1 = iolist_to_binary(io_lib:format("~p", [Shares])),
|
maps:put(<<"CpuShares">>, Shares, HCfg1);
|
||||||
[<<"--cpu-shares">>, Bin1];
|
|
||||||
true ->
|
true ->
|
||||||
[]
|
HCfg1
|
||||||
end,
|
end.
|
||||||
Res1 ++ Res2.
|
|
||||||
|
|
||||||
build_ulimits(Config) ->
|
build_ulimits(Config) ->
|
||||||
UL = maps:get(<<"ulimits">>, Config, #{}),
|
UL = maps:get(<<"ulimits">>, Config, #{}),
|
||||||
lists:map(fun({K, V}) -> [<<"--ulimit">>, <<K/binary, "=", V/binary>>] end, maps:to_list(UL)).
|
case maps:size(UL) of
|
||||||
|
0 ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
ULList = lists:map(fun({K, V}) ->
|
||||||
|
[S1, H1] = binary:split(V, <<":">>, []),
|
||||||
|
S = list_to_integer(binary_to_list(S1)),
|
||||||
|
H = list_to_integer(binary_to_list(H1)),
|
||||||
|
#{<<"Name">> => K, <<"Soft">> => S, <<"Hard">> => H}
|
||||||
|
end, maps:to_list(UL)),
|
||||||
|
|
||||||
|
#{<<"Ulimits">> => ULList}
|
||||||
|
end.
|
||||||
|
|
||||||
build_sysctls(Config) ->
|
build_sysctls(Config) ->
|
||||||
SC = maps:get(<<"sysctls">>, Config, #{}),
|
SC = maps:get(<<"sysctls">>, Config, #{}),
|
||||||
lists:map(fun({K, V}) -> [<<"--sysctl ">>, <<K/binary, "=", V/binary>>] end, maps:to_list(SC)).
|
case maps:size(SC) of
|
||||||
|
0 ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
#{<<"Sysctls">> => SC}
|
||||||
|
end.
|
||||||
|
|
||||||
build_tmpfs(Config) ->
|
build_tmpfs(Config) ->
|
||||||
Tmp = maps:get(<<"tmpfs">>, Config, []),
|
Tmp = maps:get(<<"tmpfs">>, Config, []),
|
||||||
lists:map(fun(T) -> [<<"--tmpfs">>, T] end, Tmp).
|
case Tmp of
|
||||||
|
[] ->
|
||||||
|
#{};
|
||||||
|
_ ->
|
||||||
|
#{<<"Tmpfs">> => maps:from_list([{T, <<>>} || T <- Tmp])}
|
||||||
|
end.
|
||||||
|
|
||||||
build_extra_hosts(Config) ->
|
build_extra_hosts(Config) ->
|
||||||
Hosts = maps:get(<<"extra_hosts">>, Config, []),
|
Hosts = maps:get(<<"extra_hosts">>, Config, []),
|
||||||
lists:map(fun(H) -> [<<"--add-host">>, H] end, Hosts).
|
case Hosts of
|
||||||
|
[] ->
|
||||||
build_healthcheck(Config) ->
|
#{};
|
||||||
HC = maps:get(<<"healthcheck">>, Config, #{}),
|
|
||||||
lists:map(fun({K, V}) ->
|
|
||||||
case K of
|
|
||||||
<<"test">> ->
|
|
||||||
case V of
|
|
||||||
%% Test 是 ["CMD-SHELL", Cmd]
|
|
||||||
[<<"CMD-SHELL">>, Cmd] ->
|
|
||||||
[<<"--health-cmd">>, <<$", Cmd/binary, $">>];
|
|
||||||
%% Test 是 ["CMD", Arg1, Arg2...]
|
|
||||||
[<<"CMD">> | CmdList] ->
|
|
||||||
CmdArgs = iolist_to_binary(lists:join(<<" ">>, CmdList)),
|
|
||||||
[<<"--health-cmd">>, <<$", CmdArgs/binary, $">>];
|
|
||||||
%% Test 是 <<"NONE">>
|
|
||||||
[<<"NONE">>] ->
|
|
||||||
[<<"--no-healthcheck">>];
|
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
#{<<"ExtraHosts">> => Hosts}
|
||||||
end;
|
end.
|
||||||
<<"interval">> ->
|
|
||||||
[<<"--health-interval">>, V];
|
|
||||||
<<"timeout">> ->
|
|
||||||
[<<"--health-timeout">>, V];
|
|
||||||
<<"retries">> ->
|
|
||||||
[<<"--health-retries">>, io_lib:format("~p", [V])];
|
|
||||||
_ ->
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end, maps:to_list(HC)).
|
|
||||||
@ -1,467 +0,0 @@
|
|||||||
%%%-------------------------------------------------------------------
|
|
||||||
%%% @author anlicheng
|
|
||||||
%%% @copyright (C) 2025, <COMPANY>
|
|
||||||
%%% @doc
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%% Created : 15. 9月 2025 16:11
|
|
||||||
%%%-------------------------------------------------------------------
|
|
||||||
-module(docker_uds_commands).
|
|
||||||
-author("anlicheng").
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([pull_image/2, check_image_exist/1]).
|
|
||||||
-export([create_container/3, check_container_exist/1, is_container_running/1, start_container/1, stop_container/1]).
|
|
||||||
-export([test/0, test_create_container/0, test1/0]).
|
|
||||||
|
|
||||||
test() ->
|
|
||||||
Image = <<"docker.1ms.run/library/nginx:latest">>,
|
|
||||||
pull_image(Image, fun(Msg) -> lager:debug("msg is: ~p", [Msg]) end).
|
|
||||||
|
|
||||||
test1() ->
|
|
||||||
Id = <<"redpanda-console">>,
|
|
||||||
StopRes = stop_container(Id),
|
|
||||||
lager:debug("stop res: ~p", [StopRes]),
|
|
||||||
StartRes = start_container(Id),
|
|
||||||
lager:debug("start res: ~p", [StartRes]).
|
|
||||||
|
|
||||||
test_create_container() ->
|
|
||||||
M = #{
|
|
||||||
<<"image">> => <<"docker.1ms.run/library/nginx:latest">>,
|
|
||||||
<<"container_name">> => <<"my_nginx_new1">>,
|
|
||||||
<<"command">> => [
|
|
||||||
<<"nginx">>,
|
|
||||||
<<"-g">>,
|
|
||||||
<<"daemon off;">>
|
|
||||||
],
|
|
||||||
<<"entrypoint">> => [
|
|
||||||
<<"/docker-entrypoint.sh">>
|
|
||||||
],
|
|
||||||
<<"envs">> => [
|
|
||||||
<<"ENV1=val1">>,
|
|
||||||
<<"ENV2=val2">>
|
|
||||||
],
|
|
||||||
<<"env_file">> => [
|
|
||||||
<<"./env.list">>
|
|
||||||
],
|
|
||||||
<<"ports">> => [
|
|
||||||
<<"8080:80">>,
|
|
||||||
<<"443:443">>
|
|
||||||
],
|
|
||||||
<<"expose">> => [
|
|
||||||
<<"80">>,
|
|
||||||
<<"443">>
|
|
||||||
],
|
|
||||||
<<"volumes">> => [
|
|
||||||
<<"/host/data:/data">>,
|
|
||||||
<<"/host/log:/var/log">>
|
|
||||||
],
|
|
||||||
<<"networks">> => [
|
|
||||||
<<"mynet">>
|
|
||||||
],
|
|
||||||
<<"labels">> => #{
|
|
||||||
<<"role">> => <<"web">>,
|
|
||||||
<<"env">> => <<"prod">>
|
|
||||||
},
|
|
||||||
<<"restart">> => <<"always">>,
|
|
||||||
<<"user">> => <<"www-data">>,
|
|
||||||
<<"working_dir">> => <<"/app">>,
|
|
||||||
<<"hostname">> => <<"myhost">>,
|
|
||||||
<<"privileged">> => true,
|
|
||||||
<<"cap_add">> => [
|
|
||||||
<<"NET_ADMIN">>
|
|
||||||
],
|
|
||||||
<<"cap_drop">> => [
|
|
||||||
<<"MKNOD">>
|
|
||||||
],
|
|
||||||
<<"devices">> => [
|
|
||||||
<<"/dev/snd:/dev/snd">>
|
|
||||||
],
|
|
||||||
<<"mem_limit">> => <<"512m">>,
|
|
||||||
<<"mem_reservation">> => <<"256m">>,
|
|
||||||
<<"cpu_shares">> => 512,
|
|
||||||
<<"cpus">> => 1.5,
|
|
||||||
<<"ulimits">> => #{
|
|
||||||
<<"nofile">> => <<"1024:2048">>
|
|
||||||
},
|
|
||||||
<<"sysctls">> => #{
|
|
||||||
<<"net.ipv4.ip_forward">> => <<"1">>
|
|
||||||
},
|
|
||||||
<<"tmpfs">> => [
|
|
||||||
<<"/tmp">>
|
|
||||||
],
|
|
||||||
<<"extra_hosts">> => [
|
|
||||||
<<"host1:192.168.0.1">>
|
|
||||||
],
|
|
||||||
<<"healthcheck">> => #{
|
|
||||||
<<"test">> => [
|
|
||||||
<<"CMD-SHELL">>,
|
|
||||||
<<"curl -f http://localhost || exit 1">>
|
|
||||||
],
|
|
||||||
<<"interval">> => <<"30s">>,
|
|
||||||
<<"timeout">> => <<"10s">>,
|
|
||||||
<<"retries">> => 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
create_container(<<"my_nginx_xx3">>, "/usr/local/code/efka/", M).
|
|
||||||
|
|
||||||
-spec pull_image(Image :: binary(), Callback :: fun((Msg :: binary()) -> no_return())) -> ok | {error, ExitCode :: integer()}.
|
|
||||||
pull_image(Image, Callback) when is_binary(Image), is_function(Callback, 1) ->
|
|
||||||
Url = lists:flatten(io_lib:format("/images/create?fromImage=~s", [binary_to_list(Image)])),
|
|
||||||
docker_http:stream_request(Callback, "POST", Url, undefined, []).
|
|
||||||
|
|
||||||
-spec check_image_exist(Image :: binary()) -> boolean().
|
|
||||||
check_image_exist(Image) when is_binary(Image) ->
|
|
||||||
Url = io_lib:format("/images/~s/json", [binary_to_list(Image)]),
|
|
||||||
case docker_http:request("GET", Url, undefined, []) of
|
|
||||||
{ok, 200, _Headers, Resp} ->
|
|
||||||
M = catch jiffy:decode(Resp, [return_maps]),
|
|
||||||
is_map(M) andalso maps:is_key(<<"Id">>, M);
|
|
||||||
{ok, 404, _Headers, _Error} ->
|
|
||||||
false
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec create_container(ContainerName :: binary(), ContainerDir :: string(), Config :: map()) -> {ok, ContainerId :: binary()} | {error, Reason :: any()}.
|
|
||||||
create_container(ContainerName, ContainerDir, Config) when is_binary(ContainerName), is_list(ContainerDir), is_map(Config) ->
|
|
||||||
Url = lists:flatten(io_lib:format("/containers/create?name=~s", [binary_to_list(ContainerName)])),
|
|
||||||
%% 挂载预留的目录,用来作为配置文件的存放
|
|
||||||
BinContainerDir = list_to_binary(docker_container_helper:make_etc_dir_name(ContainerDir)),
|
|
||||||
|
|
||||||
%% 增加自定义的用来放配置文件的目录
|
|
||||||
Volumes0 = maps:get(<<"volumes">>, Config, []),
|
|
||||||
Volumes = [<<BinContainerDir/binary, ":/usr/local/etc/">>|Volumes0],
|
|
||||||
NewConfig = Config#{<<"volumes">> => Volumes},
|
|
||||||
|
|
||||||
Options = build_options(NewConfig),
|
|
||||||
lists:foreach(fun({K, V}) -> lager:debug("~p => ~p", [K, V]) end, maps:to_list(Options)),
|
|
||||||
|
|
||||||
Body = iolist_to_binary(jiffy:encode(Options, [force_utf8])),
|
|
||||||
Headers = [
|
|
||||||
{<<"Content-Type">>, <<"application/json">>}
|
|
||||||
],
|
|
||||||
case docker_http:request("POST", Url, Body, Headers) of
|
|
||||||
{ok, 201, _Headers, Resp} ->
|
|
||||||
#{<<"Id">> := ContainerId} = jiffy:decode(Resp, [return_maps]),
|
|
||||||
{ok, ContainerId};
|
|
||||||
{ok, _StatusCode, _, ErrorResp} ->
|
|
||||||
case catch jiffy:decode(ErrorResp, [return_maps]) of
|
|
||||||
#{<<"message">> := Msg} ->
|
|
||||||
{error, Msg};
|
|
||||||
_ ->
|
|
||||||
{error, ErrorResp}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec is_container_running(ContainerId :: binary()) -> boolean().
|
|
||||||
is_container_running(ContainerId) when is_binary(ContainerId) ->
|
|
||||||
case inspect_container(ContainerId) of
|
|
||||||
{ok, #{<<"State">> := #{<<"Running">> := Running}}} ->
|
|
||||||
Running;
|
|
||||||
{error, _} ->
|
|
||||||
false
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec check_container_exist(ContainerName :: binary()) -> boolean().
|
|
||||||
check_container_exist(ContainerName) when is_binary(ContainerName) ->
|
|
||||||
case inspect_container(ContainerName) of
|
|
||||||
{ok, #{<<"Id">> := Id}} when is_binary(Id) ->
|
|
||||||
true;
|
|
||||||
_ ->
|
|
||||||
false
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec start_container(ContainerName :: binary()) -> ok | {error, Reason :: binary()}.
|
|
||||||
start_container(ContainerName) when is_binary(ContainerName) ->
|
|
||||||
Url = lists:flatten(io_lib:format("/containers/~s/start", [binary_to_list(ContainerName)])),
|
|
||||||
Headers = [
|
|
||||||
{<<"Content-Type">>, <<"application/json">>}
|
|
||||||
],
|
|
||||||
case docker_http:request("POST", Url, undefined, Headers) of
|
|
||||||
{ok, 204, _Headers, _} ->
|
|
||||||
ok;
|
|
||||||
{ok, 304, _Headers, _} ->
|
|
||||||
{error, <<"container already started">>};
|
|
||||||
{ok, _StatusCode, _Header, ErrorResp} ->
|
|
||||||
case catch jiffy:decode(ErrorResp) of
|
|
||||||
#{<<"message">> := Msg} ->
|
|
||||||
{error, Msg};
|
|
||||||
_ ->
|
|
||||||
{error, ErrorResp}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec stop_container(ContainerName :: binary()) -> ok | {error, Reason :: binary()}.
|
|
||||||
stop_container(ContainerName) when is_binary(ContainerName) ->
|
|
||||||
Url = lists:flatten(io_lib:format("/containers/~s/stop", [binary_to_list(ContainerName)])),
|
|
||||||
Headers = [
|
|
||||||
{<<"Content-Type">>, <<"application/json">>}
|
|
||||||
],
|
|
||||||
case docker_http:request("POST", Url, undefined, Headers) of
|
|
||||||
{ok, 204, _Headers, _} ->
|
|
||||||
ok;
|
|
||||||
{ok, 304, _Headers, _} ->
|
|
||||||
{error, <<"container already stopped">>};
|
|
||||||
{ok, _StatusCode, _Header, ErrorResp} ->
|
|
||||||
case catch jiffy:decode(ErrorResp) of
|
|
||||||
#{<<"message">> := Msg} ->
|
|
||||||
{error, Msg};
|
|
||||||
_ ->
|
|
||||||
{error, ErrorResp}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec inspect_container(ContainerId :: binary()) -> {ok, Json :: map()} | {error, Error :: any()}.
|
|
||||||
inspect_container(ContainerId) when is_binary(ContainerId) ->
|
|
||||||
Url = lists:flatten(io_lib:format("/containers/~s/json", [binary_to_list(ContainerId)])),
|
|
||||||
Headers = [
|
|
||||||
{<<"Content-Type">>, <<"application/json">>}
|
|
||||||
],
|
|
||||||
case docker_http:request("GET", Url, undefined, Headers) of
|
|
||||||
{ok, 200, _Headers, Resp} ->
|
|
||||||
Json = jiffy:decode(Resp, [return_maps]),
|
|
||||||
{ok, Json};
|
|
||||||
{ok, _StatusCode, _Header, ErrorResp} ->
|
|
||||||
case catch jiffy:decode(ErrorResp) of
|
|
||||||
#{<<"message">> := Msg} ->
|
|
||||||
{error, Msg};
|
|
||||||
_ ->
|
|
||||||
{error, ErrorResp}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
%%% helper methods
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
%% 构建最终 JSON Map
|
|
||||||
build_options(Config) when is_map(Config) ->
|
|
||||||
#{
|
|
||||||
<<"Image">> => maps:get(<<"image">>, Config, <<>>),
|
|
||||||
<<"Cmd">> => maps:get(<<"command">>, Config, []),
|
|
||||||
<<"Entrypoint">> => maps:get(<<"entrypoint">>, Config, []),
|
|
||||||
<<"Env">> => maps:get(<<"envs">>, Config, []),
|
|
||||||
<<"Labels">> => maps:get(<<"labels">>, Config, #{}),
|
|
||||||
<<"Volumes">> => build_volumes(Config),
|
|
||||||
<<"User">> => maps:get(<<"user">>, Config, <<>>),
|
|
||||||
<<"WorkingDir">> => maps:get(<<"working_dir">>, Config, <<>>),
|
|
||||||
<<"Hostname">> => maps:get(<<"hostname">>, Config, <<>>),
|
|
||||||
<<"ExposedPorts">> => build_expose(Config),
|
|
||||||
<<"NetworkingConfig">> => build_networks(Config),
|
|
||||||
<<"Healthcheck">> => build_healthcheck(Config),
|
|
||||||
<<"HostConfig">> => fold_merge([
|
|
||||||
build_binds(Config),
|
|
||||||
build_restart(Config),
|
|
||||||
build_privileged(Config),
|
|
||||||
build_cap_add_drop(Config),
|
|
||||||
build_devices(Config),
|
|
||||||
build_memory(Config),
|
|
||||||
build_cpu(Config),
|
|
||||||
build_ulimits(Config),
|
|
||||||
build_tmpfs(Config),
|
|
||||||
build_sysctls(Config),
|
|
||||||
build_extra_hosts(Config)
|
|
||||||
])
|
|
||||||
}.
|
|
||||||
|
|
||||||
%% 工具函数
|
|
||||||
fold_merge(List) ->
|
|
||||||
lists:foldl(fun maps:merge/2, #{}, List).
|
|
||||||
|
|
||||||
%% --- 构建子字段 ---
|
|
||||||
build_expose(Config) ->
|
|
||||||
Ports = maps:get(<<"expose">>, Config, []),
|
|
||||||
case Ports of
|
|
||||||
[] -> #{};
|
|
||||||
_ ->
|
|
||||||
maps:from_list([{<<P/binary, "/tcp">>, #{}} || P <- Ports])
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_volumes(Config) ->
|
|
||||||
Vols = maps:get(<<"volumes">>, Config, []),
|
|
||||||
case Vols of
|
|
||||||
[] ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
maps:from_list(lists:map(fun(V) ->
|
|
||||||
[_Host, Cont] = binary:split(V, <<":">>, []),
|
|
||||||
{Cont, #{}}
|
|
||||||
end, Vols))
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_binds(Config) ->
|
|
||||||
Vols = maps:get(<<"volumes">>, Config, []),
|
|
||||||
case Vols of
|
|
||||||
[] ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
#{<<"Binds">> => Vols}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_networks(Config) ->
|
|
||||||
Nets = maps:get(<<"networks">>, Config, []),
|
|
||||||
case Nets of
|
|
||||||
[] -> #{};
|
|
||||||
_ ->
|
|
||||||
NetCfg = maps:from_list([{N, #{}} || N <- Nets]),
|
|
||||||
#{<<"EndpointsConfig">> => NetCfg}
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
parse_mem(Val) ->
|
|
||||||
case binary:last(Val) of
|
|
||||||
$m ->
|
|
||||||
N = binary:part(Val, {0, byte_size(Val)-1}),
|
|
||||||
list_to_integer(binary_to_list(N)) * 1024 * 1024;
|
|
||||||
$g ->
|
|
||||||
N = binary:part(Val, {0, byte_size(Val)-1}),
|
|
||||||
list_to_integer(binary_to_list(N)) * 1024 * 1024 * 1024;
|
|
||||||
_ ->
|
|
||||||
list_to_integer(binary_to_list(Val))
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_healthcheck(Config) ->
|
|
||||||
HC = maps:get(<<"healthcheck">>, Config, #{}),
|
|
||||||
case maps:size(HC) of
|
|
||||||
0 ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
#{
|
|
||||||
<<"Test">> => maps:get(<<"test">>, HC, []),
|
|
||||||
<<"Interval">> => parse_duration(maps:get(<<"interval">>, HC, <<"0s">>)),
|
|
||||||
<<"Timeout">> => parse_duration(maps:get(<<"timeout">>, HC, <<"0s">>)),
|
|
||||||
<<"Retries">> => maps:get(<<"retries">>, HC, 0)
|
|
||||||
}
|
|
||||||
end.
|
|
||||||
|
|
||||||
parse_duration(Bin) ->
|
|
||||||
%% "30s" -> 30000000000
|
|
||||||
Sz = byte_size(Bin),
|
|
||||||
NBin = binary:part(Bin, {0, Sz-1}),
|
|
||||||
N = list_to_integer(binary_to_list(NBin)),
|
|
||||||
case binary:last(Bin) of
|
|
||||||
$s ->
|
|
||||||
N * 1000000000;
|
|
||||||
$m ->
|
|
||||||
N * 60000000000;
|
|
||||||
_ ->
|
|
||||||
N
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% --- 构建子字段 ---
|
|
||||||
|
|
||||||
build_restart(Config) ->
|
|
||||||
case maps:get(<<"restart">>, Config, undefined) of
|
|
||||||
undefined ->
|
|
||||||
#{};
|
|
||||||
Policy ->
|
|
||||||
#{<<"RestartPolicy">> => #{<<"Name">> => Policy}}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_privileged(Config) ->
|
|
||||||
case maps:get(<<"privileged">>, Config, false) of
|
|
||||||
true ->
|
|
||||||
#{<<"Privileged">> => true};
|
|
||||||
_ ->
|
|
||||||
#{}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_cap_add_drop(Config) ->
|
|
||||||
Add = maps:get(<<"cap_add">>, Config, []),
|
|
||||||
Drop = maps:get(<<"cap_drop">>, Config, []),
|
|
||||||
case {Add, Drop} of
|
|
||||||
{[], []} ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
#{<<"CapAdd">> => Add, <<"CapDrop">> => Drop}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_devices(Config) ->
|
|
||||||
Devs = maps:get(<<"devices">>, Config, []),
|
|
||||||
case Devs of
|
|
||||||
[] ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
DevObjs = [#{<<"PathOnHost">> => H, <<"PathInContainer">> => C,
|
|
||||||
<<"CgroupPermissions">> => <<"rwm">>}
|
|
||||||
|| D <- Devs,
|
|
||||||
[H, C] <- [binary:split(D, <<":">>, [])]],
|
|
||||||
#{<<"Devices">> => DevObjs}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_memory(Config) ->
|
|
||||||
Mem = maps:get(<<"mem_limit">>, Config, undefined),
|
|
||||||
MemRes = maps:get(<<"mem_reservation">>, Config, undefined),
|
|
||||||
HCfg = #{},
|
|
||||||
HCfg1 = if
|
|
||||||
Mem /= undefined ->
|
|
||||||
maps:put(<<"Memory">>, parse_mem(Mem), HCfg);
|
|
||||||
true ->
|
|
||||||
HCfg
|
|
||||||
end,
|
|
||||||
if
|
|
||||||
MemRes /= undefined ->
|
|
||||||
maps:put(<<"MemoryReservation">>, parse_mem(MemRes), HCfg1);
|
|
||||||
true ->
|
|
||||||
HCfg1
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
build_cpu(Config) ->
|
|
||||||
CPU = maps:get(<<"cpus">>, Config, undefined),
|
|
||||||
Shares = maps:get(<<"cpu_shares">>, Config, undefined),
|
|
||||||
HCfg = #{},
|
|
||||||
HCfg1 = if
|
|
||||||
CPU /= undefined ->
|
|
||||||
maps:put(<<"NanoCpus">>, trunc(CPU * 1000000000), HCfg);
|
|
||||||
true ->
|
|
||||||
HCfg
|
|
||||||
end,
|
|
||||||
if
|
|
||||||
Shares /= undefined ->
|
|
||||||
maps:put(<<"CpuShares">>, Shares, HCfg1);
|
|
||||||
true ->
|
|
||||||
HCfg1
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_ulimits(Config) ->
|
|
||||||
UL = maps:get(<<"ulimits">>, Config, #{}),
|
|
||||||
case maps:size(UL) of
|
|
||||||
0 ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
ULList = lists:map(fun({K, V}) ->
|
|
||||||
[S1, H1] = binary:split(V, <<":">>, []),
|
|
||||||
S = list_to_integer(binary_to_list(S1)),
|
|
||||||
H = list_to_integer(binary_to_list(H1)),
|
|
||||||
#{<<"Name">> => K, <<"Soft">> => S, <<"Hard">> => H}
|
|
||||||
end, maps:to_list(UL)),
|
|
||||||
|
|
||||||
#{<<"Ulimits">> => ULList}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_sysctls(Config) ->
|
|
||||||
SC = maps:get(<<"sysctls">>, Config, #{}),
|
|
||||||
case maps:size(SC) of
|
|
||||||
0 ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
#{<<"Sysctls">> => SC}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_tmpfs(Config) ->
|
|
||||||
Tmp = maps:get(<<"tmpfs">>, Config, []),
|
|
||||||
case Tmp of
|
|
||||||
[] ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
#{<<"Tmpfs">> => maps:from_list([{T, <<>>} || T <- Tmp])}
|
|
||||||
end.
|
|
||||||
|
|
||||||
build_extra_hosts(Config) ->
|
|
||||||
Hosts = maps:get(<<"extra_hosts">>, Config, []),
|
|
||||||
case Hosts of
|
|
||||||
[] ->
|
|
||||||
#{};
|
|
||||||
_ ->
|
|
||||||
#{<<"ExtraHosts">> => Hosts}
|
|
||||||
end.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user