将与docker之间的通讯修改

This commit is contained in:
anlicheng 2025-09-23 17:22:10 +08:00
parent 743ed8813e
commit 6b73c1c0b7
2 changed files with 367 additions and 715 deletions

View File

@ -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]), true;
case catch erlang:open_port({spawn, ExecCmd}, PortSettings) of _ ->
Port when is_port(Port) ->
case gather_output(Port) of
{0, _} ->
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; {ok, 304, _Headers, _} ->
{_ExitCode, Error} -> {error, <<"container already stopped">>};
{error, Error} {ok, _StatusCode, _Header, ErrorResp} ->
end; case catch jiffy:decode(ErrorResp) of
_Error -> #{<<"message">> := Msg} ->
false {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, <<>>),
<<"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)
])
}.
-spec gather_pull_output(Port :: port(), Callback :: fun((Msg :: binary()) -> no_return())) -> ExitCode :: integer(). %%
gather_pull_output(Port, Callback) -> fold_merge(List) ->
receive lists:foldl(fun maps:merge/2, #{}, List).
{Port, {data, Data}} ->
Callback(Data),
gather_pull_output(Port, Callback);
{Port, {exit_status, Status}} ->
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_user(Config),
build_working_dir(Config),
build_hostname(Config),
build_privileged(Config),
build_cap_add_drop(Config),
build_devices(Config),
build_memory(Config),
build_cpu(Config),
build_ulimits(Config),
build_sysctls(Config),
build_tmpfs(Config),
build_extra_hosts(Config),
build_healthcheck(Config)
]).
build_entrypoint(Config) ->
case maps:get(<<"entrypoint">>, Config, []) of
[] -> [];
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 = #{},
CPU /= undefined -> HCfg1 = if
Bin = iolist_to_binary(io_lib:format("~p", [CPU])), CPU /= undefined ->
[<<"--cpus">>, Bin]; maps:put(<<"NanoCpus">>, trunc(CPU * 1000000000), HCfg);
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}) -> #{<<"ExtraHosts">> => Hosts}
case K of end.
<<"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">>];
_ ->
[]
end;
<<"interval">> ->
[<<"--health-interval">>, V];
<<"timeout">> ->
[<<"--health-timeout">>, V];
<<"retries">> ->
[<<"--health-retries">>, io_lib:format("~p", [V])];
_ ->
[]
end
end, maps:to_list(HC)).

View File

@ -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.