diff --git a/src/docker/docker_commands.erl b/src/docker/docker_commands.erl index b039306..8a1ecaa 100644 --- a/src/docker/docker_commands.erl +++ b/src/docker/docker_commands.erl @@ -40,10 +40,7 @@ create_container(ContainerDir, #'ContainerDeployParams'{ }) when is_binary(ContainerName), is_list(ContainerDir) -> Url = lists:flatten(io_lib:format("/containers/create?name=~s", [binary_to_list(ContainerName)])), - %% 挂载预留的目录,用来作为配置文件的存放 - ConfigFile = list_to_binary(docker_helper:get_config_file(ContainerDir)), - Create = patch_create_options(ContainerName, ConfigFile, Create0), - Options = build_options(Create), + Options = docker_container_builder:build_options(ContainerName, ContainerDir, Create0), display_options(Options), Body = iolist_to_binary(jiffy:encode(Options, [force_utf8])), @@ -230,311 +227,11 @@ inspect_container(ContainerId) when is_binary(ContainerId) -> {error, Reason} end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% helper methods -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -patch_create_options(ContainerName, ConfigFile, undefined) -> - patch_create_options(ContainerName, ConfigFile, #'DockerCreateOptions'{}); -patch_create_options(ContainerName, ConfigFile, Create = #'DockerCreateOptions'{ - config = Config0, - host_config = HostConfig0 -}) when is_binary(ContainerName), is_binary(ConfigFile) -> - Config = patch_container_config(ContainerName, ensure_container_config(Config0)), - HostConfig = patch_host_config(ConfigFile, ensure_host_config(HostConfig0)), - Create#'DockerCreateOptions'{config = Config, host_config = HostConfig}. - -patch_container_config(ContainerName, Config = #'DockerContainerConfig'{env = Env0, volumes = Volumes0}) - when is_binary(ContainerName) -> - ConfigVolume = <<"/usr/local/etc/service.conf">>, - Envs0 = [<<"CONTAINER_NAME=", ContainerName/binary>>], - Envs = add_unique_front(Envs0, [to_binary(EnvItem) || EnvItem <- Env0]), - Volumes = add_unique_front([ConfigVolume], [to_binary(Volume) || Volume <- Volumes0]), - Config#'DockerContainerConfig'{env = Envs, volumes = Volumes}. - -patch_host_config(ConfigFile, HostConfig = #'DockerHostConfig'{binds = Binds0}) when is_binary(ConfigFile) -> - ConfigBind = <>, - Binds = add_unique_front([ConfigBind], [to_binary(Bind) || Bind <- Binds0]), - HostConfig#'DockerHostConfig'{binds = Binds}. - -ensure_container_config(undefined) -> - #'DockerContainerConfig'{}; -ensure_container_config(Config = #'DockerContainerConfig'{}) -> - Config. - -ensure_host_config(undefined) -> - #'DockerHostConfig'{}; -ensure_host_config(HostConfig = #'DockerHostConfig'{}) -> - HostConfig. - -%% 构建最终 JSON Map -build_options(#'DockerCreateOptions'{ - config = #'DockerContainerConfig'{ - image = Image, - cmd = Cmd, - entrypoint = Entrypoint, - env = Env, - labels = Labels, - volumes = Volumes, - user = User, - working_dir = WorkingDir, - hostname = Hostname, - exposed_ports = ExposedPorts, - healthcheck = Healthcheck - }, - host_config = #'DockerHostConfig'{ - binds = Binds, - network_mode = NetworkMode, - restart_policy = RestartPolicy, - privileged = Privileged, - cap_add = CapAdd, - cap_drop = CapDrop, - devices = Devices, - memory = Memory, - memory_reservation = MemoryReservation, - nano_cpus = NanoCpus, - cpu_shares = CpuShares, - ulimits = Ulimits, - tmpfs = Tmpfs, - sysctls = Sysctls, - extra_hosts = ExtraHosts - }, - networking_config = #'DockerNetworkingConfig'{ - endpoints = Endpoints - } -}) -> - #{ - <<"Image">> => to_binary(Image), - <<"Cmd">> => [to_binary(CommandItem) || CommandItem <- Cmd], - <<"Entrypoint">> => [to_binary(EntrypointItem) || EntrypointItem <- Entrypoint], - <<"Env">> => [to_binary(EnvItem) || EnvItem <- Env], - <<"Labels">> => maps:from_list([{to_binary(Key), to_binary(Value)} || {Key, Value} <- Labels]), - <<"Volumes">> => build_volumes(Volumes), - <<"User">> => to_binary(User), - <<"WorkingDir">> => to_binary(WorkingDir), - <<"Hostname">> => to_binary(Hostname), - <<"ExposedPorts">> => build_expose(ExposedPorts), - <<"NetworkingConfig">> => build_networking_config(Endpoints), - <<"Healthcheck">> => build_healthcheck(Healthcheck), - <<"HostConfig">> => fold_merge([ - build_binds(Binds), - build_network_mode(NetworkMode), - build_restart(RestartPolicy), - build_privileged(Privileged), - build_cap_add_drop(CapAdd, CapDrop), - build_devices(Devices), - build_resources(Memory, MemoryReservation, NanoCpus, CpuShares), - build_ulimits(Ulimits), - build_tmpfs(Tmpfs), - build_sysctls(Sysctls), - build_extra_hosts(ExtraHosts) - ]) - }. - -%% 工具函数 -fold_merge(List) -> - lists:foldl(fun maps:merge/2, #{}, List). - -%% --- 构建子字段 --- -build_expose(Ports) when is_list(Ports) -> - case Ports of - [] -> #{}; - _ -> - maps:from_list([{normalize_expose_port(P), #{}} || P <- Ports]) - end. - -build_volumes(Vols) when is_list(Vols) -> - case Vols of - [] -> - #{}; - _ -> - maps:from_list([{to_binary(Cont), #{}} || Cont <- Vols]) - end. - -build_binds(Binds) when is_list(Binds) -> - case Binds of - [] -> - #{}; - _ -> - #{<<"Binds">> => [to_binary(Bind) || Bind <- Binds]} - end. - -build_networking_config(Endpoints) when is_list(Endpoints) -> - case Endpoints of - [] -> #{}; - _ -> - NetCfg = maps:from_list([{to_binary(Name), #{}} || #'DockerNetworkEndpoint'{name = Name} <- Endpoints]), - #{<<"EndpointsConfig">> => NetCfg} - end. - -build_network_mode(<<>>) -> - #{}; -build_network_mode(NetworkMode) -> - #{<<"NetworkMode">> => to_binary(NetworkMode)}. - -build_healthcheck(undefined) -> - #{}; -build_healthcheck(#'Healthcheck'{ - test = Test, - interval_ns = IntervalNs, - timeout_ns = TimeoutNs, - retries = Retries -}) -> - #{ - <<"Test">> => [to_binary(Item) || Item <- Test], - <<"Interval">> => IntervalNs, - <<"Timeout">> => TimeoutNs, - <<"Retries">> => Retries - }. - -build_restart(undefined) -> - #{}; -build_restart(#'RestartPolicy'{ - name = Name, - maximum_retry_count = RetryCount -}) -> - RestartPolicy = #{ - <<"Name">> => to_binary(Name) - }, - case RetryCount of - 0 -> - #{<<"RestartPolicy">> => RestartPolicy}; - _ -> - #{<<"RestartPolicy">> => RestartPolicy#{ - <<"MaximumRetryCount">> => RetryCount - }} - end. - -build_privileged(Privileged) -> - case to_bool(Privileged) of - true -> - #{<<"Privileged">> => true}; - _ -> - #{} - end. - -build_cap_add_drop(Add, Drop) when is_list(Add), is_list(Drop) -> - case {Add, Drop} of - {[], []} -> - #{}; - _ -> - #{ - <<"CapAdd">> => [to_binary(Item) || Item <- Add], - <<"CapDrop">> => [to_binary(Item) || Item <- Drop] - } - end. - -build_devices(Devs) when is_list(Devs) -> - case Devs of - [] -> - #{}; - _ -> - DevObjs = [#{ - <<"PathOnHost">> => to_binary(HostPath), - <<"PathInContainer">> => to_binary(ContainerPath), - <<"CgroupPermissions">> => device_permissions(Permissions) - } || #'DeviceMapping'{ - host_path = HostPath, - container_path = ContainerPath, - cgroup_permissions = Permissions - } <- Devs], - #{<<"Devices">> => DevObjs} - end. - -build_resources(MemoryBytes, ReservationBytes, NanoCpus, CpuShares) -> - HostConfig0 = #{}, - HostConfig1 = case MemoryBytes of - 0 -> - HostConfig0; - _ -> - HostConfig0#{<<"Memory">> => MemoryBytes} - end, - HostConfig2 = case ReservationBytes of - 0 -> - HostConfig1; - _ -> - HostConfig1#{<<"MemoryReservation">> => ReservationBytes} - end, - HostConfig3 = case NanoCpus of - 0 -> - HostConfig2; - _ -> - HostConfig2#{<<"NanoCpus">> => NanoCpus} - end, - case CpuShares of - 0 -> - HostConfig3; - _ -> - HostConfig3#{<<"CpuShares">> => CpuShares} - end. - -build_ulimits(Ulimits) when is_list(Ulimits) -> - case Ulimits of - [] -> - #{}; - _ -> - #{<<"Ulimits">> => [#{ - <<"Name">> => to_binary(Name), - <<"Soft">> => Soft, - <<"Hard">> => Hard - } || #'Ulimit'{name = Name, soft = Soft, hard = Hard} <- Ulimits]} - end. - -build_sysctls(Sysctls) when is_list(Sysctls) -> - case Sysctls of - [] -> - #{}; - _ -> - #{<<"Sysctls">> => maps:from_list([{to_binary(Key), to_binary(Value)} || {Key, Value} <- Sysctls])} - end. - -build_tmpfs(Tmpfs) when is_list(Tmpfs) -> - case Tmpfs of - [] -> - #{}; - _ -> - #{<<"Tmpfs">> => maps:from_list([{to_binary(Path), to_binary(Options)} || {Path, Options} <- Tmpfs])} - end. - -build_extra_hosts(Hosts) when is_list(Hosts) -> - case Hosts of - [] -> - #{}; - _ -> - #{<<"ExtraHosts">> => [to_binary(Host) || Host <- Hosts]} - end. - -spec display_options(Options :: map()) -> no_return(). display_options(Options) when is_map(Options) -> logger:debug("deploy options: ~p", [jiffy:encode(Options, [force_utf8])]), lists:foreach(fun({K, V}) -> logger:debug("~p => ~p", [K, V]) end, maps:to_list(Options)). -normalize_expose_port(#'DockerExposedPort'{container_port = Port, protocol = Protocol}) -> - PortBin = integer_to_binary(Port), - ProtocolBin = to_binary(Protocol), - case ProtocolBin of - <<>> -> - <>; - <<"tcp">> -> - <>; - _ -> - <> - end. - -device_permissions(<<>>) -> - <<"rwm">>; -device_permissions(Permissions) -> - to_binary(Permissions). - -add_unique_front([], List) -> - List; -add_unique_front([Item | Rest], List) -> - NList = case lists:member(Item, List) of - true -> List; - false -> [Item | List] - end, - add_unique_front(Rest, NList). - build_stop_container_url(ContainerName, 0) -> lists:flatten(io_lib:format("/containers/~s/stop", [binary_to_list(ContainerName)])); build_stop_container_url(ContainerName, TimeoutSeconds) -> @@ -558,17 +255,3 @@ boolean_to_query_value(true) -> "true"; boolean_to_query_value(false) -> "false". - -to_binary(Value) when is_binary(Value) -> - Value; -to_binary(Value) when is_list(Value) -> - unicode:characters_to_binary(Value). - -to_bool(true) -> - true; -to_bool(1) -> - true; -to_bool(false) -> - false; -to_bool(0) -> - false. diff --git a/src/docker/docker_container_builder.erl b/src/docker/docker_container_builder.erl new file mode 100644 index 0000000..f7534d9 --- /dev/null +++ b/src/docker/docker_container_builder.erl @@ -0,0 +1,369 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2025, +%%% @doc +%%% +%%% @end +%%% Created : 21. 4月 2026 15:58 +%%%------------------------------------------------------------------- +-module(docker_container_builder). +-author("anlicheng"). +-include("message_pb.hrl"). + +%% API +-export([build_options/3]). + +-spec build_options(ContainerName :: binary(), ContainerDir :: string(), + Create0 :: message_pb:'DockerCreateOptions'() | undefined) -> map(). +build_options(ContainerName, ContainerDir, Create0) when is_binary(ContainerName), is_list(ContainerDir) -> + ConfigFile = list_to_binary(docker_helper:get_config_file(ContainerDir)), + Create = patch_create_options(ContainerName, ConfigFile, Create0), + build_create_options(Create). + +-spec patch_create_options(ContainerName :: binary(), ConfigFile :: binary(), + Create0 :: message_pb:'DockerCreateOptions'() | undefined) -> message_pb:'DockerCreateOptions'(). +patch_create_options(ContainerName, ConfigFile, undefined) -> + patch_create_options(ContainerName, ConfigFile, #'DockerCreateOptions'{}); +patch_create_options(ContainerName, ConfigFile, Create = #'DockerCreateOptions'{ + config = Config0, + host_config = HostConfig0 +}) when is_binary(ContainerName), is_binary(ConfigFile) -> + Config = patch_container_config(ContainerName, ensure_container_config(Config0)), + HostConfig = patch_host_config(ConfigFile, ensure_host_config(HostConfig0)), + Create#'DockerCreateOptions'{config = Config, host_config = HostConfig}. + +-spec patch_container_config(ContainerName :: binary(), + Config :: message_pb:'DockerContainerConfig'()) -> message_pb:'DockerContainerConfig'(). +patch_container_config(ContainerName, Config = #'DockerContainerConfig'{env = Env0, volumes = Volumes0}) + when is_binary(ContainerName) -> + ConfigVolume = <<"/usr/local/etc/service.conf">>, + Envs0 = [<<"CONTAINER_NAME=", ContainerName/binary>>], + Envs = add_unique_front(Envs0, [to_binary(EnvItem) || EnvItem <- Env0]), + Volumes = add_unique_front([ConfigVolume], [to_binary(Volume) || Volume <- Volumes0]), + Config#'DockerContainerConfig'{env = Envs, volumes = Volumes}. + +-spec patch_host_config(ConfigFile :: binary(), + HostConfig :: message_pb:'DockerHostConfig'()) -> message_pb:'DockerHostConfig'(). +patch_host_config(ConfigFile, HostConfig = #'DockerHostConfig'{binds = Binds0}) when is_binary(ConfigFile) -> + ConfigBind = <>, + Binds = add_unique_front([ConfigBind], [to_binary(Bind) || Bind <- Binds0]), + HostConfig#'DockerHostConfig'{binds = Binds}. + +-spec ensure_container_config(Config :: message_pb:'DockerContainerConfig'() | undefined) -> + message_pb:'DockerContainerConfig'(). +ensure_container_config(undefined) -> + #'DockerContainerConfig'{}; +ensure_container_config(Config = #'DockerContainerConfig'{}) -> + Config. + +-spec ensure_host_config(HostConfig :: message_pb:'DockerHostConfig'() | undefined) -> + message_pb:'DockerHostConfig'(). +ensure_host_config(undefined) -> + #'DockerHostConfig'{}; +ensure_host_config(HostConfig = #'DockerHostConfig'{}) -> + HostConfig. + +-spec build_create_options(Create :: message_pb:'DockerCreateOptions'()) -> map(). +build_create_options(#'DockerCreateOptions'{ + config = #'DockerContainerConfig'{ + image = Image, + cmd = Cmd, + entrypoint = Entrypoint, + env = Env, + labels = Labels, + volumes = Volumes, + user = User, + working_dir = WorkingDir, + hostname = Hostname, + exposed_ports = ExposedPorts, + healthcheck = Healthcheck + }, + host_config = #'DockerHostConfig'{ + binds = Binds, + network_mode = NetworkMode, + restart_policy = RestartPolicy, + privileged = Privileged, + cap_add = CapAdd, + cap_drop = CapDrop, + devices = Devices, + memory = Memory, + memory_reservation = MemoryReservation, + nano_cpus = NanoCpus, + cpu_shares = CpuShares, + ulimits = Ulimits, + tmpfs = Tmpfs, + sysctls = Sysctls, + extra_hosts = ExtraHosts + }, + networking_config = #'DockerNetworkingConfig'{ + endpoints = Endpoints + } +}) -> + #{ + <<"Image">> => to_binary(Image), + <<"Cmd">> => [to_binary(CommandItem) || CommandItem <- Cmd], + <<"Entrypoint">> => [to_binary(EntrypointItem) || EntrypointItem <- Entrypoint], + <<"Env">> => [to_binary(EnvItem) || EnvItem <- Env], + <<"Labels">> => maps:from_list([{to_binary(Key), to_binary(Value)} || {Key, Value} <- Labels]), + <<"Volumes">> => build_volumes(Volumes), + <<"User">> => to_binary(User), + <<"WorkingDir">> => to_binary(WorkingDir), + <<"Hostname">> => to_binary(Hostname), + <<"ExposedPorts">> => build_expose(ExposedPorts), + <<"NetworkingConfig">> => build_networking_config(Endpoints), + <<"Healthcheck">> => build_healthcheck(Healthcheck), + <<"HostConfig">> => fold_merge([ + build_binds(Binds), + build_network_mode(NetworkMode), + build_restart(RestartPolicy), + build_privileged(Privileged), + build_cap_add_drop(CapAdd, CapDrop), + build_devices(Devices), + build_resources(Memory, MemoryReservation, NanoCpus, CpuShares), + build_ulimits(Ulimits), + build_tmpfs(Tmpfs), + build_sysctls(Sysctls), + build_extra_hosts(ExtraHosts) + ]) + }. + +-spec fold_merge(List :: [map()]) -> map(). +fold_merge(List) -> + lists:foldl(fun maps:merge/2, #{}, List). + +-spec build_expose(Ports :: [message_pb:'DockerExposedPort'()]) -> map(). +build_expose(Ports) when is_list(Ports) -> + case Ports of + [] -> + #{}; + _ -> + maps:from_list([{normalize_expose_port(Port), #{}} || Port <- Ports]) + end. + +-spec build_volumes(Volumes :: [binary() | list() | atom()]) -> map(). +build_volumes(Volumes) when is_list(Volumes) -> + case Volumes of + [] -> + #{}; + _ -> + maps:from_list([{to_binary(ContainerPath), #{}} || ContainerPath <- Volumes]) + end. + +-spec build_binds(Binds :: [binary() | list() | atom()]) -> map(). +build_binds(Binds) when is_list(Binds) -> + case Binds of + [] -> + #{}; + _ -> + #{<<"Binds">> => [to_binary(Bind) || Bind <- Binds]} + end. + +-spec build_networking_config(Endpoints :: [message_pb:'DockerNetworkEndpoint'()]) -> map(). +build_networking_config(Endpoints) when is_list(Endpoints) -> + case Endpoints of + [] -> + #{}; + _ -> + NetCfg = maps:from_list([{to_binary(Name), #{}} || #'DockerNetworkEndpoint'{name = Name} <- Endpoints]), + #{<<"EndpointsConfig">> => NetCfg} + end. + +-spec build_network_mode(NetworkMode :: binary() | list() | atom()) -> map(). +build_network_mode(<<>>) -> + #{}; +build_network_mode(NetworkMode) -> + #{<<"NetworkMode">> => to_binary(NetworkMode)}. + +-spec build_healthcheck(Healthcheck :: message_pb:'Healthcheck'() | undefined) -> map(). +build_healthcheck(undefined) -> + #{}; +build_healthcheck(#'Healthcheck'{ + test = Test, + interval_ns = IntervalNs, + timeout_ns = TimeoutNs, + retries = Retries +}) -> + #{ + <<"Test">> => [to_binary(Item) || Item <- Test], + <<"Interval">> => IntervalNs, + <<"Timeout">> => TimeoutNs, + <<"Retries">> => Retries + }. + +-spec build_restart(RestartPolicy :: message_pb:'RestartPolicy'() | undefined) -> map(). +build_restart(undefined) -> + #{}; +build_restart(#'RestartPolicy'{ + name = Name, + maximum_retry_count = RetryCount +}) -> + RestartPolicy = #{ + <<"Name">> => to_binary(Name) + }, + case RetryCount of + 0 -> + #{<<"RestartPolicy">> => RestartPolicy}; + _ -> + #{<<"RestartPolicy">> => RestartPolicy#{ + <<"MaximumRetryCount">> => RetryCount + }} + end. + +-spec build_privileged(Privileged :: any()) -> map(). +build_privileged(Privileged) -> + case to_bool(Privileged) of + true -> + #{<<"Privileged">> => true}; + _ -> + #{} + end. + +-spec build_cap_add_drop(Add :: [binary() | list() | atom()], Drop :: [binary() | list() | atom()]) -> map(). +build_cap_add_drop(Add, Drop) when is_list(Add), is_list(Drop) -> + case {Add, Drop} of + {[], []} -> + #{}; + _ -> + #{ + <<"CapAdd">> => [to_binary(Item) || Item <- Add], + <<"CapDrop">> => [to_binary(Item) || Item <- Drop] + } + end. + +-spec build_devices(Devices :: [message_pb:'DeviceMapping'()]) -> map(). +build_devices(Devices) when is_list(Devices) -> + case Devices of + [] -> + #{}; + _ -> + DevObjs = [#{ + <<"PathOnHost">> => to_binary(HostPath), + <<"PathInContainer">> => to_binary(ContainerPath), + <<"CgroupPermissions">> => device_permissions(Permissions) + } || #'DeviceMapping'{ + host_path = HostPath, + container_path = ContainerPath, + cgroup_permissions = Permissions + } <- Devices], + #{<<"Devices">> => DevObjs} + end. + +-spec build_resources(MemoryBytes :: integer(), ReservationBytes :: integer(), + NanoCpus :: integer(), CpuShares :: integer()) -> map(). +build_resources(MemoryBytes, ReservationBytes, NanoCpus, CpuShares) -> + HostConfig0 = #{}, + HostConfig1 = case MemoryBytes of + 0 -> + HostConfig0; + _ -> + HostConfig0#{<<"Memory">> => MemoryBytes} + end, + HostConfig2 = case ReservationBytes of + 0 -> + HostConfig1; + _ -> + HostConfig1#{<<"MemoryReservation">> => ReservationBytes} + end, + HostConfig3 = case NanoCpus of + 0 -> + HostConfig2; + _ -> + HostConfig2#{<<"NanoCpus">> => NanoCpus} + end, + case CpuShares of + 0 -> + HostConfig3; + _ -> + HostConfig3#{<<"CpuShares">> => CpuShares} + end. + +-spec build_ulimits(Ulimits :: [message_pb:'Ulimit'()]) -> map(). +build_ulimits(Ulimits) when is_list(Ulimits) -> + case Ulimits of + [] -> + #{}; + _ -> + #{<<"Ulimits">> => [#{ + <<"Name">> => to_binary(Name), + <<"Soft">> => Soft, + <<"Hard">> => Hard + } || #'Ulimit'{name = Name, soft = Soft, hard = Hard} <- Ulimits]} + end. + +-spec build_sysctls(Sysctls :: [{binary() | list() | atom(), binary() | list() | atom()}]) -> map(). +build_sysctls(Sysctls) when is_list(Sysctls) -> + case Sysctls of + [] -> + #{}; + _ -> + #{<<"Sysctls">> => maps:from_list([{to_binary(Key), to_binary(Value)} || {Key, Value} <- Sysctls])} + end. + +-spec build_tmpfs(Tmpfs :: [{binary() | list() | atom(), binary() | list() | atom()}]) -> map(). +build_tmpfs(Tmpfs) when is_list(Tmpfs) -> + case Tmpfs of + [] -> + #{}; + _ -> + #{<<"Tmpfs">> => maps:from_list([{to_binary(Path), to_binary(Options)} || {Path, Options} <- Tmpfs])} + end. + +-spec build_extra_hosts(Hosts :: [binary() | list() | atom()]) -> map(). +build_extra_hosts(Hosts) when is_list(Hosts) -> + case Hosts of + [] -> + #{}; + _ -> + #{<<"ExtraHosts">> => [to_binary(Host) || Host <- Hosts]} + end. + +-spec normalize_expose_port(Port :: message_pb:'DockerExposedPort'()) -> binary(). +normalize_expose_port(#'DockerExposedPort'{container_port = Port, protocol = Protocol}) -> + PortBin = integer_to_binary(Port), + ProtocolBin = to_binary(Protocol), + case ProtocolBin of + <<>> -> + <>; + <<"tcp">> -> + <>; + _ -> + <> + end. + +-spec device_permissions(Permissions :: binary() | list() | atom()) -> binary(). +device_permissions(<<>>) -> + <<"rwm">>; +device_permissions(Permissions) -> + to_binary(Permissions). + +-spec add_unique_front(Items :: [binary()], List :: [binary()]) -> [binary()]. +add_unique_front([], List) -> + List; +add_unique_front([Item | Rest], List) -> + NList = case lists:member(Item, List) of + true -> + List; + false -> + [Item | List] + end, + add_unique_front(Rest, NList). + +-spec to_binary(Value :: binary() | list() | atom() | any()) -> binary(). +to_binary(Value) when is_binary(Value) -> + Value; +to_binary(Value) when is_list(Value) -> + list_to_binary(Value); +to_binary(Value) when is_atom(Value) -> + atom_to_binary(Value, utf8); +to_binary(Value) -> + iolist_to_binary(io_lib:format("~p", [Value])). + +-spec to_bool(Value :: any()) -> boolean(). +to_bool(true) -> + true; +to_bool(<<"true">>) -> + true; +to_bool("true") -> + true; +to_bool(_) -> + false.