mirror of
https://github.com/openwrt/luci.git
synced 2025-02-07 15:09:54 +00:00
903 lines
24 KiB
Lua
903 lines
24 KiB
Lua
--[[
|
|
LuCI - Lua Configuration Interface
|
|
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
|
|
]]--
|
|
|
|
local docker = require "luci.model.docker"
|
|
|
|
local m, s, o
|
|
|
|
local dk = docker.new()
|
|
|
|
local cmd_line = table.concat(arg, '/')
|
|
local create_body = {}
|
|
|
|
local images = dk.images:list().body
|
|
local networks = dk.networks:list().body
|
|
local containers = dk.containers:list({
|
|
query = {
|
|
all=true
|
|
}
|
|
}).body
|
|
|
|
local is_quot_complete = function(str)
|
|
local num = 0, w
|
|
require "math"
|
|
|
|
if not str then
|
|
return true
|
|
end
|
|
|
|
local num = 0, w
|
|
for w in str:gmatch("\"") do
|
|
num = num + 1
|
|
end
|
|
|
|
if math.fmod(num, 2) ~= 0 then
|
|
return false
|
|
end
|
|
|
|
num = 0
|
|
for w in str:gmatch("\'") do
|
|
num = num + 1
|
|
end
|
|
|
|
if math.fmod(num, 2) ~= 0 then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function contains(list, x)
|
|
for _, v in pairs(list) do
|
|
if v == x then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local resolve_cli = function(cmd_line)
|
|
local config = {
|
|
advance = 1
|
|
}
|
|
|
|
local key_no_val = {
|
|
't',
|
|
'd',
|
|
'i',
|
|
'tty',
|
|
'rm',
|
|
'read_only',
|
|
'interactive',
|
|
'init',
|
|
'help',
|
|
'detach',
|
|
'privileged',
|
|
'P',
|
|
'publish_all',
|
|
}
|
|
|
|
local key_with_val = {
|
|
'sysctl',
|
|
'add_host',
|
|
'a',
|
|
'attach',
|
|
'blkio_weight_device',
|
|
'cap_add',
|
|
'cap_drop',
|
|
'device',
|
|
'device_cgroup_rule',
|
|
'device_read_bps',
|
|
'device_read_iops',
|
|
'device_write_bps',
|
|
'device_write_iops',
|
|
'dns',
|
|
'dns_option',
|
|
'dns_search',
|
|
'e',
|
|
'env',
|
|
'env_file',
|
|
'expose',
|
|
'group_add',
|
|
'l',
|
|
'label',
|
|
'label_file',
|
|
'link',
|
|
'link_local_ip',
|
|
'log_driver',
|
|
'log_opt',
|
|
'network_alias',
|
|
'p',
|
|
'publish',
|
|
'security_opt',
|
|
'storage_opt',
|
|
'tmpfs',
|
|
'v',
|
|
'volume',
|
|
'volumes_from',
|
|
'blkio_weight',
|
|
'cgroup_parent',
|
|
'cidfile',
|
|
'cpu_period',
|
|
'cpu_quota',
|
|
'cpu_rt_period',
|
|
'cpu_rt_runtime',
|
|
'c',
|
|
'cpu_shares',
|
|
'cpus',
|
|
'cpuset_cpus',
|
|
'cpuset_mems',
|
|
'detach_keys',
|
|
'disable_content_trust',
|
|
'domainname',
|
|
'entrypoint',
|
|
'gpus',
|
|
'health_cmd',
|
|
'health_interval',
|
|
'health_retries',
|
|
'health_start_period',
|
|
'health_timeout',
|
|
'h',
|
|
'hostname',
|
|
'ip',
|
|
'ip6',
|
|
'ipc',
|
|
'isolation',
|
|
'kernel_memory',
|
|
'log_driver',
|
|
'mac_address',
|
|
'm',
|
|
'memory',
|
|
'memory_reservation',
|
|
'memory_swap',
|
|
'memory_swappiness',
|
|
'mount',
|
|
'name',
|
|
'network',
|
|
'no_healthcheck',
|
|
'oom_kill_disable',
|
|
'oom_score_adj',
|
|
'pid',
|
|
'pids_limit',
|
|
'restart',
|
|
'runtime',
|
|
'shm_size',
|
|
'sig_proxy',
|
|
'stop_signal',
|
|
'stop_timeout',
|
|
'ulimit',
|
|
'u',
|
|
'user',
|
|
'userns',
|
|
'uts',
|
|
'volume_driver',
|
|
'w',
|
|
'workdir'
|
|
}
|
|
|
|
local key_abb = {
|
|
net='network',
|
|
a='attach',
|
|
c='cpu-shares',
|
|
d='detach',
|
|
e='env',
|
|
h='hostname',
|
|
i='interactive',
|
|
l='label',
|
|
m='memory',
|
|
p='publish',
|
|
P='publish_all',
|
|
t='tty',
|
|
u='user',
|
|
v='volume',
|
|
w='workdir'
|
|
}
|
|
|
|
local key_with_list = {
|
|
'sysctl',
|
|
'add_host',
|
|
'a',
|
|
'attach',
|
|
'blkio_weight_device',
|
|
'cap_add',
|
|
'cap_drop',
|
|
'device',
|
|
'device_cgroup_rule',
|
|
'device_read_bps',
|
|
'device_read_iops',
|
|
'device_write_bps',
|
|
'device_write_iops',
|
|
'dns',
|
|
'dns_optiondns_search',
|
|
'e',
|
|
'env',
|
|
'env_file',
|
|
'expose',
|
|
'group_add',
|
|
'l',
|
|
'label',
|
|
'label_file',
|
|
'link',
|
|
'link_local_ip',
|
|
'log_driver',
|
|
'log_opt',
|
|
'network_alias',
|
|
'p',
|
|
'publish',
|
|
'security_opt',
|
|
'storage_opt',
|
|
'tmpfs',
|
|
'v',
|
|
'volume',
|
|
'volumes_from',
|
|
}
|
|
|
|
local key = nil
|
|
local _key = nil
|
|
local val = nil
|
|
local is_cmd = false
|
|
|
|
cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)")
|
|
for w in cmd_line:gmatch("[^%s]+") do
|
|
if w =='\\' then
|
|
elseif not key and not _key and not is_cmd then
|
|
--key=val
|
|
key, val = w:match("^%-%-([%lP%-]-)=(.+)")
|
|
if not key then
|
|
--key val
|
|
key = w:match("^%-%-([%lP%-]+)")
|
|
if not key then
|
|
-- -v val
|
|
key = w:match("^%-([%lP%-]+)")
|
|
if key then
|
|
-- for -dit
|
|
if key:match("i") or key:match("t") or key:match("d") then
|
|
if key:match("i") then
|
|
config[key_abb["i"]] = true
|
|
key:gsub("i", "")
|
|
end
|
|
if key:match("t") then
|
|
config[key_abb["t"]] = true
|
|
key:gsub("t", "")
|
|
end
|
|
if key:match("d") then
|
|
config[key_abb["d"]] = true
|
|
key:gsub("d", "")
|
|
end
|
|
if key:match("P") then
|
|
config[key_abb["P"]] = true
|
|
key:gsub("P", "")
|
|
end
|
|
if key == "" then
|
|
key = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if key then
|
|
key = key:gsub("-","_")
|
|
key = key_abb[key] or key
|
|
if contains(key_no_val, key) then
|
|
config[key] = true
|
|
val = nil
|
|
key = nil
|
|
elseif contains(key_with_val, key) then
|
|
-- if key == "cap_add" then config.privileged = true end
|
|
else
|
|
key = nil
|
|
val = nil
|
|
end
|
|
else
|
|
config.image = w
|
|
key = nil
|
|
val = nil
|
|
is_cmd = true
|
|
end
|
|
elseif (key or _key) and not is_cmd then
|
|
if key == "mount" then
|
|
-- we need resolve mount options here
|
|
-- type=bind,source=/source,target=/app
|
|
local _type = w:match("^type=([^,]+),") or "bind"
|
|
local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or ""
|
|
local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
|
|
local ro = w:match(",readonly") and "ro" or nil
|
|
|
|
if source and target then
|
|
if _type ~= "tmpfs" then
|
|
local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
|
|
val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or ""))
|
|
else
|
|
local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
|
|
local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
|
|
key = "tmpfs"
|
|
val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "")
|
|
if not config[key] then
|
|
config[key] = {}
|
|
end
|
|
table.insert( config[key], val )
|
|
key = nil
|
|
val = nil
|
|
end
|
|
end
|
|
else
|
|
val = w
|
|
end
|
|
elseif is_cmd then
|
|
config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w
|
|
end
|
|
if (key or _key) and val then
|
|
key = _key or key
|
|
if contains(key_with_list, key) then
|
|
if not config[key] then
|
|
config[key] = {}
|
|
end
|
|
if _key then
|
|
config[key][#config[key]] = config[key][#config[key]] .. " " .. w
|
|
else
|
|
table.insert( config[key], val )
|
|
end
|
|
if is_quot_complete(config[key][#config[key]]) then
|
|
config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
|
|
_key = nil
|
|
else
|
|
_key = key
|
|
end
|
|
else
|
|
config[key] = (config[key] and (config[key] .. " ") or "") .. val
|
|
if is_quot_complete(config[key]) then
|
|
config[key] = config[key]:gsub("[\"\']", "")
|
|
_key = nil
|
|
else
|
|
_key = key
|
|
end
|
|
end
|
|
key = nil
|
|
val = nil
|
|
end
|
|
end
|
|
|
|
return config
|
|
end
|
|
|
|
local default_config = {}
|
|
|
|
if cmd_line and cmd_line:match("^DOCKERCLI.+") then
|
|
default_config = resolve_cli(cmd_line)
|
|
elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
|
|
local container_id = cmd_line:match("^duplicate/(.+)")
|
|
create_body = dk:containers_duplicate_config({id = container_id}) or {}
|
|
|
|
if not create_body.HostConfig then
|
|
create_body.HostConfig = {}
|
|
end
|
|
|
|
if next(create_body) ~= nil then
|
|
default_config.name = nil
|
|
default_config.image = create_body.Image
|
|
default_config.hostname = create_body.Hostname
|
|
default_config.tty = create_body.Tty and true or false
|
|
default_config.interactive = create_body.OpenStdin and true or false
|
|
default_config.privileged = create_body.HostConfig.Privileged and true or false
|
|
default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
|
|
default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil
|
|
default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil
|
|
default_config.link = create_body.HostConfig.Links
|
|
default_config.env = create_body.Env
|
|
default_config.dns = create_body.HostConfig.Dns
|
|
default_config.volume = create_body.HostConfig.Binds
|
|
default_config.cap_add = create_body.HostConfig.CapAdd
|
|
default_config.publish_all = create_body.HostConfig.PublishAllPorts
|
|
|
|
if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then
|
|
default_config.sysctl = {}
|
|
for k, v in pairs(create_body.HostConfig.Sysctls) do
|
|
table.insert( default_config.sysctl, k.."="..v )
|
|
end
|
|
end
|
|
|
|
if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then
|
|
default_config.log_opt = {}
|
|
for k, v in pairs(create_body.HostConfig.LogConfig.Config) do
|
|
table.insert( default_config.log_opt, k.."="..v )
|
|
end
|
|
end
|
|
|
|
if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
|
|
default_config.publish = {}
|
|
for k, v in pairs(create_body.HostConfig.PortBindings) do
|
|
table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
|
|
end
|
|
end
|
|
|
|
default_config.user = create_body.User or nil
|
|
default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil
|
|
default_config.advance = 1
|
|
default_config.cpus = create_body.HostConfig.NanoCPUs
|
|
default_config.cpu_shares = create_body.HostConfig.CpuShares
|
|
default_config.memory = create_body.HostConfig.Memory
|
|
default_config.blkio_weight = create_body.HostConfig.BlkioWeight
|
|
|
|
if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then
|
|
default_config.device = {}
|
|
for _, v in ipairs(create_body.HostConfig.Devices) do
|
|
table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
|
|
end
|
|
end
|
|
|
|
if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
|
|
default_config.tmpfs = {}
|
|
for k, v in pairs(create_body.HostConfig.Tmpfs) do
|
|
table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
m = SimpleForm("docker", translate("Docker - Containers"))
|
|
m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
|
|
|
|
s = m:section(SimpleSection)
|
|
s.template = "dockerman/apply_widget"
|
|
s.err=docker:read_status()
|
|
s.err=s.err and s.err:gsub("\n","<br />"):gsub(" "," ")
|
|
if s.err then
|
|
docker:clear_status()
|
|
end
|
|
|
|
s = m:section(SimpleSection, translate("Create new docker container"))
|
|
s.addremove = true
|
|
s.anonymous = true
|
|
|
|
o = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
|
|
o.rawhtml = true
|
|
o.template = "dockerman/newcontainer_resolve"
|
|
|
|
o = s:option(Value, "name", translate("Container Name"))
|
|
o.rmempty = true
|
|
o.default = default_config.name or nil
|
|
|
|
o = s:option(Flag, "interactive", translate("Interactive (-i)"))
|
|
o.rmempty = true
|
|
o.disabled = 0
|
|
o.enabled = 1
|
|
o.default = default_config.interactive and 1 or 0
|
|
|
|
o = s:option(Flag, "tty", translate("TTY (-t)"))
|
|
o.rmempty = true
|
|
o.disabled = 0
|
|
o.enabled = 1
|
|
o.default = default_config.tty and 1 or 0
|
|
|
|
o = s:option(Value, "image", translate("Docker Image"))
|
|
o.rmempty = true
|
|
o.default = default_config.image or nil
|
|
for _, v in ipairs (images) do
|
|
if v.RepoTags then
|
|
o:value(v.RepoTags[1], v.RepoTags[1])
|
|
end
|
|
end
|
|
|
|
o = s:option(Flag, "_force_pull", translate("Always pull image first"))
|
|
o.rmempty = true
|
|
o.disabled = 0
|
|
o.enabled = 1
|
|
o.default = 0
|
|
|
|
o = s:option(Flag, "privileged", translate("Privileged"))
|
|
o.rmempty = true
|
|
o.disabled = 0
|
|
o.enabled = 1
|
|
o.default = default_config.privileged and 1 or 0
|
|
|
|
o = s:option(ListValue, "restart", translate("Restart Policy"))
|
|
o.rmempty = true
|
|
o:value("no", "No")
|
|
o:value("unless-stopped", "Unless stopped")
|
|
o:value("always", "Always")
|
|
o:value("on-failure", "On failure")
|
|
o.default = default_config.restart or "unless-stopped"
|
|
|
|
local d_network = s:option(ListValue, "network", translate("Networks"))
|
|
d_network.rmempty = true
|
|
d_network.default = default_config.network or "bridge"
|
|
|
|
local d_ip = s:option(Value, "ip", translate("IPv4 Address"))
|
|
d_ip.datatype="ip4addr"
|
|
d_ip:depends("network", "nil")
|
|
d_ip.default = default_config.ip or nil
|
|
|
|
o = s:option(DynamicList, "link", translate("Links with other containers"))
|
|
o.placeholder = "container_name:alias"
|
|
o.rmempty = true
|
|
o:depends("network", "bridge")
|
|
o.default = default_config.link or nil
|
|
|
|
o = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
|
|
o.placeholder = "8.8.8.8"
|
|
o.rmempty = true
|
|
o.default = default_config.dns or nil
|
|
|
|
o = s:option(Value, "user",
|
|
translate("User(-u)"),
|
|
translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
|
|
o.placeholder = "1000:1000"
|
|
o.rmempty = true
|
|
o.default = default_config.user or nil
|
|
|
|
o = s:option(DynamicList, "env",
|
|
translate("Environmental Variable(-e)"),
|
|
translate("Set environment variables to inside the container"))
|
|
o.placeholder = "TZ=Asia/Shanghai"
|
|
o.rmempty = true
|
|
o.default = default_config.env or nil
|
|
|
|
o = s:option(DynamicList, "volume",
|
|
translate("Bind Mount(-v)"),
|
|
translate("Bind mount a volume"))
|
|
o.placeholder = "/media:/media:slave"
|
|
o.rmempty = true
|
|
o.default = default_config.volume or nil
|
|
|
|
local d_publish = s:option(DynamicList, "publish",
|
|
translate("Exposed Ports(-p)"),
|
|
translate("Publish container's port(s) to the host"))
|
|
d_publish.placeholder = "2200:22/tcp"
|
|
d_publish.rmempty = true
|
|
d_publish.default = default_config.publish or nil
|
|
|
|
o = s:option(Value, "command", translate("Run command"))
|
|
o.placeholder = "/bin/sh init.sh"
|
|
o.rmempty = true
|
|
o.default = default_config.command or nil
|
|
|
|
o = s:option(Flag, "advance", translate("Advance"))
|
|
o.rmempty = true
|
|
o.disabled = 0
|
|
o.enabled = 1
|
|
o.default = default_config.advance or 0
|
|
|
|
o = s:option(Value, "hostname",
|
|
translate("Host Name"),
|
|
translate("The hostname to use for the container"))
|
|
o.rmempty = true
|
|
o.default = default_config.hostname or nil
|
|
o:depends("advance", 1)
|
|
|
|
o = s:option(Flag, "publish_all",
|
|
translate("Exposed All Ports(-P)"),
|
|
translate("Allocates an ephemeral host port for all of a container's exposed ports"))
|
|
o.rmempty = true
|
|
o.disabled = 0
|
|
o.enabled = 1
|
|
o.default = default_config.publish_all and 1 or 0
|
|
o:depends("advance", 1)
|
|
|
|
o = s:option(DynamicList, "device",
|
|
translate("Device(--device)"),
|
|
translate("Add host device to the container"))
|
|
o.placeholder = "/dev/sda:/dev/xvdc:rwm"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.default = default_config.device or nil
|
|
|
|
o = s:option(DynamicList, "tmpfs",
|
|
translate("Tmpfs(--tmpfs)"),
|
|
translate("Mount tmpfs directory"))
|
|
o.placeholder = "/run:rw,noexec,nosuid,size=65536k"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.default = default_config.tmpfs or nil
|
|
|
|
o = s:option(DynamicList, "sysctl",
|
|
translate("Sysctl(--sysctl)"),
|
|
translate("Sysctls (kernel parameters) options"))
|
|
o.placeholder = "net.ipv4.ip_forward=1"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.default = default_config.sysctl or nil
|
|
|
|
o = s:option(DynamicList, "cap_add",
|
|
translate("CAP-ADD(--cap-add)"),
|
|
translate("A list of kernel capabilities to add to the container"))
|
|
o.placeholder = "NET_ADMIN"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.default = default_config.cap_add or nil
|
|
|
|
o = s:option(Value, "cpus",
|
|
translate("CPUs"),
|
|
translate("Number of CPUs. Number is a fractional number. 0.000 means no limit"))
|
|
o.placeholder = "1.5"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.datatype="ufloat"
|
|
o.default = default_config.cpus or nil
|
|
|
|
o = s:option(Value, "cpu_shares",
|
|
translate("CPU Shares Weight"),
|
|
translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024"))
|
|
o.placeholder = "1024"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.datatype="uinteger"
|
|
o.default = default_config.cpu_shares or nil
|
|
|
|
o = s:option(Value, "memory",
|
|
translate("Memory"),
|
|
translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M"))
|
|
o.placeholder = "128m"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.default = default_config.memory or nil
|
|
|
|
o = s:option(Value, "blkio_weight",
|
|
translate("Block IO Weight"),
|
|
translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
|
|
o.placeholder = "500"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.datatype="uinteger"
|
|
o.default = default_config.blkio_weight or nil
|
|
|
|
o = s:option(DynamicList, "log_opt",
|
|
translate("Log driver options"),
|
|
translate("The logging configuration for this container"))
|
|
o.placeholder = "max-size=1m"
|
|
o.rmempty = true
|
|
o:depends("advance", 1)
|
|
o.default = default_config.log_opt or nil
|
|
|
|
for _, v in ipairs (networks) do
|
|
if v.Name then
|
|
local parent = v.Options and v.Options.parent or nil
|
|
local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
|
|
ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
|
|
local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
|
|
d_network:value(v.Name, network_name)
|
|
|
|
if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then
|
|
d_ip:depends("network", v.Name)
|
|
end
|
|
|
|
if v.Driver == "bridge" then
|
|
d_publish:depends("network", v.Name)
|
|
end
|
|
end
|
|
end
|
|
|
|
m.handle = function(self, state, data)
|
|
if state ~= FORM_VALID then
|
|
return
|
|
end
|
|
|
|
local tmp
|
|
local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
|
|
local hostname = data.hostname
|
|
local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false
|
|
local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false
|
|
local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false
|
|
local image = data.image
|
|
local user = data.user
|
|
|
|
if image and not image:match(".-:.+") then
|
|
image = image .. ":latest"
|
|
end
|
|
|
|
local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false
|
|
local restart = data.restart
|
|
local env = data.env
|
|
local dns = data.dns
|
|
local cap_add = data.cap_add
|
|
local sysctl = {}
|
|
|
|
tmp = data.sysctl
|
|
if type(tmp) == "table" then
|
|
for i, v in ipairs(tmp) do
|
|
local k,v1 = v:match("(.-)=(.+)")
|
|
if k and v1 then
|
|
sysctl[k]=v1
|
|
end
|
|
end
|
|
end
|
|
|
|
local log_opt = {}
|
|
tmp = data.log_opt
|
|
if type(tmp) == "table" then
|
|
for i, v in ipairs(tmp) do
|
|
local k,v1 = v:match("(.-)=(.+)")
|
|
if k and v1 then
|
|
log_opt[k]=v1
|
|
end
|
|
end
|
|
end
|
|
|
|
local network = data.network
|
|
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
|
|
local volume = data.volume
|
|
local memory = data.memory or 0
|
|
local cpu_shares = data.cpu_shares or 0
|
|
local cpus = data.cpus or 0
|
|
local blkio_weight = data.blkio_weight or nil
|
|
|
|
local portbindings = {}
|
|
local exposedports = {}
|
|
|
|
local tmpfs = {}
|
|
tmp = data.tmpfs
|
|
if type(tmp) == "table" then
|
|
for i, v in ipairs(tmp)do
|
|
local k= v:match("([^:]+)")
|
|
local v1 = v:match(".-:([^:]+)") or ""
|
|
if k then
|
|
tmpfs[k]=v1
|
|
end
|
|
end
|
|
end
|
|
|
|
local device = {}
|
|
tmp = data.device
|
|
if type(tmp) == "table" then
|
|
for i, v in ipairs(tmp) do
|
|
local t = {}
|
|
local _,_, h, c, p = v:find("(.-):(.-):(.+)")
|
|
if h and c then
|
|
t['PathOnHost'] = h
|
|
t['PathInContainer'] = c
|
|
t['CgroupPermissions'] = p or "rwm"
|
|
else
|
|
local _,_, h, c = v:find("(.-):(.+)")
|
|
if h and c then
|
|
t['PathOnHost'] = h
|
|
t['PathInContainer'] = c
|
|
t['CgroupPermissions'] = "rwm"
|
|
else
|
|
t['PathOnHost'] = v
|
|
t['PathInContainer'] = v
|
|
t['CgroupPermissions'] = "rwm"
|
|
end
|
|
end
|
|
|
|
if next(t) ~= nil then
|
|
table.insert( device, t )
|
|
end
|
|
end
|
|
end
|
|
|
|
tmp = data.publish or {}
|
|
for i, v in ipairs(tmp) do
|
|
for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do
|
|
local _,_,p= v2:find("^%d+/(%w+)")
|
|
if p == nil then
|
|
v2=v2..'/tcp'
|
|
end
|
|
portbindings[v2] = {{HostPort=v1}}
|
|
exposedports[v2] = {HostPort=v1}
|
|
end
|
|
end
|
|
|
|
local link = data.link
|
|
tmp = data.command
|
|
local command = {}
|
|
if tmp ~= nil then
|
|
for v in string.gmatch(tmp, "[^%s]+") do
|
|
command[#command+1] = v
|
|
end
|
|
end
|
|
|
|
if memory ~= 0 then
|
|
_,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
|
|
if n then
|
|
unit = unit and unit:sub(1,1):upper() or "B"
|
|
if unit == "M" then
|
|
memory = tonumber(n) * 1024 * 1024
|
|
elseif unit == "G" then
|
|
memory = tonumber(n) * 1024 * 1024 * 1024
|
|
elseif unit == "K" then
|
|
memory = tonumber(n) * 1024
|
|
else
|
|
memory = tonumber(n)
|
|
end
|
|
end
|
|
end
|
|
|
|
create_body.Hostname = network ~= "host" and (hostname or name) or nil
|
|
create_body.Tty = tty and true or false
|
|
create_body.OpenStdin = interactive and true or false
|
|
create_body.User = user
|
|
create_body.Cmd = command
|
|
create_body.Env = env
|
|
create_body.Image = image
|
|
create_body.ExposedPorts = exposedports
|
|
create_body.HostConfig = create_body.HostConfig or {}
|
|
create_body.HostConfig.Dns = dns
|
|
create_body.HostConfig.Binds = volume
|
|
create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
|
|
create_body.HostConfig.Privileged = privileged and true or false
|
|
create_body.HostConfig.PortBindings = portbindings
|
|
create_body.HostConfig.Memory = tonumber(memory)
|
|
create_body.HostConfig.CpuShares = tonumber(cpu_shares)
|
|
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
|
|
create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
|
|
create_body.HostConfig.PublishAllPorts = publish_all
|
|
|
|
if create_body.HostConfig.NetworkMode ~= network then
|
|
create_body.NetworkingConfig = nil
|
|
end
|
|
|
|
create_body.HostConfig.NetworkMode = network
|
|
|
|
if ip then
|
|
if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then
|
|
for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
|
|
if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
|
|
v.IPAMConfig.IPv4Address = ip
|
|
else
|
|
create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } }
|
|
end
|
|
break
|
|
end
|
|
else
|
|
create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
|
|
end
|
|
elseif not create_body.NetworkingConfig then
|
|
create_body.NetworkingConfig = nil
|
|
end
|
|
|
|
create_body["HostConfig"]["Tmpfs"] = tmpfs
|
|
create_body["HostConfig"]["Devices"] = device
|
|
create_body["HostConfig"]["Sysctls"] = sysctl
|
|
create_body["HostConfig"]["CapAdd"] = cap_add
|
|
create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil
|
|
|
|
if network == "bridge" then
|
|
create_body["HostConfig"]["Links"] = link
|
|
end
|
|
|
|
local pull_image = function(image)
|
|
local json_stringify = luci.jsonc and luci.jsonc.stringify
|
|
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
|
|
local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb)
|
|
if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then
|
|
docker:append_status("done\n")
|
|
else
|
|
res.code = (res.code == 200) and 500 or res.code
|
|
docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
|
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
|
end
|
|
end
|
|
|
|
docker:clear_status()
|
|
local exist_image = false
|
|
|
|
if image then
|
|
for _, v in ipairs (images) do
|
|
if v.RepoTags and v.RepoTags[1] == image then
|
|
exist_image = true
|
|
break
|
|
end
|
|
end
|
|
if not exist_image then
|
|
pull_image(image)
|
|
elseif data._force_pull == 1 then
|
|
pull_image(image)
|
|
end
|
|
end
|
|
|
|
create_body = docker.clear_empty_tables(create_body)
|
|
|
|
docker:append_status("Container: " .. "create" .. " " .. name .. "...")
|
|
local res = dk.containers:create({name = name, body = create_body})
|
|
if res and res.code == 201 then
|
|
docker:clear_status()
|
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
|
|
else
|
|
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
|
|
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
|
|
end
|
|
end
|
|
|
|
return m
|