newcontainer.lua 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. --[[
  2. LuCI - Lua Configuration Interface
  3. Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
  4. ]]--
  5. require "luci.util"
  6. local uci = luci.model.uci.cursor()
  7. local docker = require "luci.model.docker"
  8. local dk = docker.new()
  9. local cmd_line = table.concat(arg, '/')
  10. local create_body = {}
  11. local images = dk.images:list().body
  12. local networks = dk.networks:list().body
  13. local containers = dk.containers:list({query = {all=true}}).body
  14. local is_quot_complete = function(str)
  15. require "math"
  16. if not str then return true end
  17. local num = 0, w
  18. for w in str:gmatch("\"") do
  19. num = num + 1
  20. end
  21. if math.fmod(num, 2) ~= 0 then return false end
  22. num = 0
  23. for w in str:gmatch("\'") do
  24. num = num + 1
  25. end
  26. if math.fmod(num, 2) ~= 0 then return false end
  27. return true
  28. end
  29. local resolve_cli = function(cmd_line)
  30. local config = {advance = 1}
  31. local key_no_val = '|t|d|i|tty|rm|read_only|interactive|init|help|detach|privileged|P|publish_all|'
  32. 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|'
  33. 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'}
  34. 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_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|'
  35. local key = nil
  36. local _key = nil
  37. local val = nil
  38. local is_cmd = false
  39. cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)")
  40. for w in cmd_line:gmatch("[^%s]+") do
  41. if w =='\\' then
  42. elseif not key and not _key and not is_cmd then
  43. --key=val
  44. key, val = w:match("^%-%-([%lP%-]-)=(.+)")
  45. if not key then
  46. --key val
  47. key = w:match("^%-%-([%lP%-]+)")
  48. if not key then
  49. -- -v val
  50. key = w:match("^%-([%lP%-]+)")
  51. if key then
  52. -- for -dit
  53. if key:match("i") or key:match("t") or key:match("d") then
  54. if key:match("i") then
  55. config[key_abb["i"]] = true
  56. key:gsub("i", "")
  57. end
  58. if key:match("t") then
  59. config[key_abb["t"]] = true
  60. key:gsub("t", "")
  61. end
  62. if key:match("d") then
  63. config[key_abb["d"]] = true
  64. key:gsub("d", "")
  65. end
  66. if key:match("P") then
  67. config[key_abb["P"]] = true
  68. key:gsub("P", "")
  69. end
  70. if key == "" then key = nil end
  71. end
  72. end
  73. end
  74. end
  75. if key then
  76. key = key:gsub("-","_")
  77. key = key_abb[key] or key
  78. if key_no_val:match("|"..key.."|") then
  79. config[key] = true
  80. val = nil
  81. key = nil
  82. elseif key_with_val:match("|"..key.."|") then
  83. -- if key == "cap_add" then config.privileged = true end
  84. else
  85. key = nil
  86. val = nil
  87. end
  88. else
  89. config.image = w
  90. key = nil
  91. val = nil
  92. is_cmd = true
  93. end
  94. elseif (key or _key) and not is_cmd then
  95. if key == "mount" then
  96. -- we need resolve mount options here
  97. -- type=bind,source=/source,target=/app
  98. local _type = w:match("^type=([^,]+),") or "bind"
  99. local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or ""
  100. local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
  101. local ro = w:match(",readonly") and "ro" or nil
  102. if source and target then
  103. if _type ~= "tmpfs" then
  104. -- bind or volume
  105. local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
  106. 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 ""))
  107. else
  108. -- tmpfs
  109. local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
  110. local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
  111. key = "tmpfs"
  112. 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 "")
  113. if not config[key] then config[key] = {} end
  114. table.insert( config[key], val )
  115. key = nil
  116. val = nil
  117. end
  118. end
  119. else
  120. val = w
  121. end
  122. elseif is_cmd then
  123. config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w
  124. end
  125. if (key or _key) and val then
  126. key = _key or key
  127. if key_with_list:match("|"..key.."|") then
  128. if not config[key] then config[key] = {} end
  129. if _key then
  130. config[key][#config[key]] = config[key][#config[key]] .. " " .. w
  131. else
  132. table.insert( config[key], val )
  133. end
  134. if is_quot_complete(config[key][#config[key]]) then
  135. -- clear quotation marks
  136. config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
  137. _key = nil
  138. else
  139. _key = key
  140. end
  141. else
  142. config[key] = (config[key] and (config[key] .. " ") or "") .. val
  143. if is_quot_complete(config[key]) then
  144. -- clear quotation marks
  145. config[key] = config[key]:gsub("[\"\']", "")
  146. _key = nil
  147. else
  148. _key = key
  149. end
  150. end
  151. key = nil
  152. val = nil
  153. end
  154. end
  155. return config
  156. end
  157. -- reslvo default config
  158. local default_config = {}
  159. if cmd_line and cmd_line:match("^DOCKERCLI.+") then
  160. default_config = resolve_cli(cmd_line)
  161. elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
  162. local container_id = cmd_line:match("^duplicate/(.+)")
  163. create_body = dk:containers_duplicate_config({id = container_id}) or {}
  164. if not create_body.HostConfig then create_body.HostConfig = {} end
  165. if next(create_body) ~= nil then
  166. default_config.name = nil
  167. default_config.image = create_body.Image
  168. default_config.hostname = create_body.Hostname
  169. default_config.tty = create_body.Tty and true or false
  170. default_config.interactive = create_body.OpenStdin and true or false
  171. default_config.privileged = create_body.HostConfig.Privileged and true or false
  172. default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
  173. -- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode
  174. -- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig
  175. default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil
  176. 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
  177. default_config.link = create_body.HostConfig.Links
  178. default_config.env = create_body.Env
  179. default_config.dns = create_body.HostConfig.Dns
  180. default_config.volume = create_body.HostConfig.Binds
  181. default_config.cap_add = create_body.HostConfig.CapAdd
  182. default_config.publish_all = create_body.HostConfig.PublishAllPorts
  183. if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then
  184. default_config.sysctl = {}
  185. for k, v in pairs(create_body.HostConfig.Sysctls) do
  186. table.insert( default_config.sysctl, k.."="..v )
  187. end
  188. end
  189. if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then
  190. default_config.log_opt = {}
  191. for k, v in pairs(create_body.HostConfig.LogConfig.Config) do
  192. table.insert( default_config.log_opt, k.."="..v )
  193. end
  194. end
  195. if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
  196. default_config.publish = {}
  197. for k, v in pairs(create_body.HostConfig.PortBindings) do
  198. table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
  199. end
  200. end
  201. default_config.user = create_body.User or nil
  202. default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil
  203. default_config.advance = 1
  204. default_config.cpus = create_body.HostConfig.NanoCPUs
  205. default_config.cpu_shares = create_body.HostConfig.CpuShares
  206. default_config.memory = create_body.HostConfig.Memory
  207. default_config.blkio_weight = create_body.HostConfig.BlkioWeight
  208. if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then
  209. default_config.device = {}
  210. for _, v in ipairs(create_body.HostConfig.Devices) do
  211. table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
  212. end
  213. end
  214. if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
  215. default_config.tmpfs = {}
  216. for k, v in pairs(create_body.HostConfig.Tmpfs) do
  217. table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v )
  218. end
  219. end
  220. end
  221. end
  222. local m = SimpleForm("docker", translate("Docker"))
  223. m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
  224. -- m.reset = false
  225. -- m.submit = false
  226. -- new Container
  227. docker_status = m:section(SimpleSection)
  228. docker_status.template = "dockerman/apply_widget"
  229. docker_status.err=docker:read_status()
  230. docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
  231. if docker_status.err then docker:clear_status() end
  232. local s = m:section(SimpleSection, translate("New Container"))
  233. s.addremove = true
  234. s.anonymous = true
  235. local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
  236. d.rawhtml = true
  237. d.template = "dockerman/newcontainer_resolve"
  238. d = s:option(Value, "name", translate("Container Name"))
  239. d.rmempty = true
  240. d.default = default_config.name or nil
  241. d = s:option(Flag, "interactive", translate("Interactive (-i)"))
  242. d.rmempty = true
  243. d.disabled = 0
  244. d.enabled = 1
  245. d.default = default_config.interactive and 1 or 0
  246. d = s:option(Flag, "tty", translate("TTY (-t)"))
  247. d.rmempty = true
  248. d.disabled = 0
  249. d.enabled = 1
  250. d.default = default_config.tty and 1 or 0
  251. d = s:option(Value, "image", translate("Docker Image"))
  252. d.rmempty = true
  253. d.default = default_config.image or nil
  254. for _, v in ipairs (images) do
  255. if v.RepoTags then
  256. d:value(v.RepoTags[1], v.RepoTags[1])
  257. end
  258. end
  259. d = s:option(Flag, "_force_pull", translate("Always pull image first"))
  260. d.rmempty = true
  261. d.disabled = 0
  262. d.enabled = 1
  263. d.default = 0
  264. d = s:option(Flag, "privileged", translate("Privileged"))
  265. d.rmempty = true
  266. d.disabled = 0
  267. d.enabled = 1
  268. d.default = default_config.privileged and 1 or 0
  269. d = s:option(ListValue, "restart", translate("Restart Policy"))
  270. d.rmempty = true
  271. d:value("no", "No")
  272. d:value("unless-stopped", "Unless stopped")
  273. d:value("always", "Always")
  274. d:value("on-failure", "On failure")
  275. d.default = default_config.restart or "unless-stopped"
  276. local d_network = s:option(ListValue, "network", translate("Networks"))
  277. d_network.rmempty = true
  278. d_network.default = default_config.network or "bridge"
  279. local d_ip = s:option(Value, "ip", translate("IPv4 Address"))
  280. d_ip.datatype="ip4addr"
  281. d_ip:depends("network", "nil")
  282. d_ip.default = default_config.ip or nil
  283. d = s:option(DynamicList, "link", translate("Links with other containers"))
  284. d.placeholder = "container_name:alias"
  285. d.rmempty = true
  286. d:depends("network", "bridge")
  287. d.default = default_config.link or nil
  288. d = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
  289. d.placeholder = "8.8.8.8"
  290. d.rmempty = true
  291. d.default = default_config.dns or nil
  292. d = s:option(Value, "user", translate("User(-u)"), translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
  293. d.placeholder = "1000:1000"
  294. d.rmempty = true
  295. d.default = default_config.user or nil
  296. d = s:option(DynamicList, "env", translate("Environmental Variable(-e)"), translate("Set environment variables to inside the container"))
  297. d.placeholder = "TZ=Asia/Shanghai"
  298. d.rmempty = true
  299. d.default = default_config.env or nil
  300. d = s:option(DynamicList, "volume", translate("Bind Mount(-v)"), translate("Bind mount a volume"))
  301. d.placeholder = "/media:/media:slave"
  302. d.rmempty = true
  303. d.default = default_config.volume or nil
  304. local d_publish = s:option(DynamicList, "publish", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
  305. d_publish.placeholder = "2200:22/tcp"
  306. d_publish.rmempty = true
  307. d_publish.default = default_config.publish or nil
  308. d = s:option(Value, "command", translate("Run command"))
  309. d.placeholder = "/bin/sh init.sh"
  310. d.rmempty = true
  311. d.default = default_config.command or nil
  312. d = s:option(Flag, "advance", translate("Advance"))
  313. d.rmempty = true
  314. d.disabled = 0
  315. d.enabled = 1
  316. d.default = default_config.advance or 0
  317. d = s:option(Value, "hostname", translate("Host Name"), translate("The hostname to use for the container"))
  318. d.rmempty = true
  319. d.default = default_config.hostname or nil
  320. d:depends("advance", 1)
  321. d = s:option(Flag, "publish_all", translate("Exposed All Ports(-P)"), translate("Allocates an ephemeral host port for all of a container's exposed ports"))
  322. d.rmempty = true
  323. d.disabled = 0
  324. d.enabled = 1
  325. d.default = default_config.publish_all and 1 or 0
  326. d:depends("advance", 1)
  327. d = s:option(DynamicList, "device", translate("Device(--device)"), translate("Add host device to the container"))
  328. d.placeholder = "/dev/sda:/dev/xvdc:rwm"
  329. d.rmempty = true
  330. d:depends("advance", 1)
  331. d.default = default_config.device or nil
  332. d = s:option(DynamicList, "tmpfs", translate("Tmpfs(--tmpfs)"), translate("Mount tmpfs directory"))
  333. d.placeholder = "/run:rw,noexec,nosuid,size=65536k"
  334. d.rmempty = true
  335. d:depends("advance", 1)
  336. d.default = default_config.tmpfs or nil
  337. d = s:option(DynamicList, "sysctl", translate("Sysctl(--sysctl)"), translate("Sysctls (kernel parameters) options"))
  338. d.placeholder = "net.ipv4.ip_forward=1"
  339. d.rmempty = true
  340. d:depends("advance", 1)
  341. d.default = default_config.sysctl or nil
  342. d = s:option(DynamicList, "cap_add", translate("CAP-ADD(--cap-add)"), translate("A list of kernel capabilities to add to the container"))
  343. d.placeholder = "NET_ADMIN"
  344. d.rmempty = true
  345. d:depends("advance", 1)
  346. d.default = default_config.cap_add or nil
  347. d = s:option(Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit"))
  348. d.placeholder = "1.5"
  349. d.rmempty = true
  350. d:depends("advance", 1)
  351. d.datatype="ufloat"
  352. d.default = default_config.cpus or nil
  353. d = 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"))
  354. d.placeholder = "1024"
  355. d.rmempty = true
  356. d:depends("advance", 1)
  357. d.datatype="uinteger"
  358. d.default = default_config.cpu_shares or nil
  359. d = 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"))
  360. d.placeholder = "128m"
  361. d.rmempty = true
  362. d:depends("advance", 1)
  363. d.default = default_config.memory or nil
  364. d = s:option(Value, "blkio_weight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
  365. d.placeholder = "500"
  366. d.rmempty = true
  367. d:depends("advance", 1)
  368. d.datatype="uinteger"
  369. d.default = default_config.blkio_weight or nil
  370. d = s:option(DynamicList, "log_opt", translate("Log driver options"), translate("The logging configuration for this container"))
  371. d.placeholder = "max-size=1m"
  372. d.rmempty = true
  373. d:depends("advance", 1)
  374. d.default = default_config.log_opt or nil
  375. for _, v in ipairs (networks) do
  376. if v.Name then
  377. local parent = v.Options and v.Options.parent or nil
  378. local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
  379. ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
  380. local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
  381. d_network:value(v.Name, network_name)
  382. if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then
  383. d_ip:depends("network", v.Name)
  384. end
  385. if v.Driver == "bridge" then
  386. d_publish:depends("network", v.Name)
  387. end
  388. end
  389. end
  390. m.handle = function(self, state, data)
  391. if state ~= FORM_VALID then return end
  392. local tmp
  393. local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
  394. local hostname = data.hostname
  395. local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false
  396. local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false
  397. local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false
  398. local image = data.image
  399. local user = data.user
  400. if image and not image:match(".-:.+") then
  401. image = image .. ":latest"
  402. end
  403. local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false
  404. local restart = data.restart
  405. local env = data.env
  406. local dns = data.dns
  407. local cap_add = data.cap_add
  408. local sysctl = {}
  409. tmp = data.sysctl
  410. if type(tmp) == "table" then
  411. for i, v in ipairs(tmp) do
  412. local k,v1 = v:match("(.-)=(.+)")
  413. if k and v1 then
  414. sysctl[k]=v1
  415. end
  416. end
  417. end
  418. local log_opt = {}
  419. tmp = data.log_opt
  420. if type(tmp) == "table" then
  421. for i, v in ipairs(tmp) do
  422. local k,v1 = v:match("(.-)=(.+)")
  423. if k and v1 then
  424. log_opt[k]=v1
  425. end
  426. end
  427. end
  428. local network = data.network
  429. local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
  430. local volume = data.volume
  431. local memory = data.memory or 0
  432. local cpu_shares = data.cpu_shares or 0
  433. local cpus = data.cpus or 0
  434. local blkio_weight = data.blkio_weight or 500
  435. local portbindings = {}
  436. local exposedports = {}
  437. local tmpfs = {}
  438. tmp = data.tmpfs
  439. if type(tmp) == "table" then
  440. for i, v in ipairs(tmp)do
  441. local k= v:match("([^:]+)")
  442. local v1 = v:match(".-:([^:]+)") or ""
  443. if k then
  444. tmpfs[k]=v1
  445. end
  446. end
  447. end
  448. local device = {}
  449. tmp = data.device
  450. if type(tmp) == "table" then
  451. for i, v in ipairs(tmp) do
  452. local t = {}
  453. local _,_, h, c, p = v:find("(.-):(.-):(.+)")
  454. if h and c then
  455. t['PathOnHost'] = h
  456. t['PathInContainer'] = c
  457. t['CgroupPermissions'] = p or "rwm"
  458. else
  459. local _,_, h, c = v:find("(.-):(.+)")
  460. if h and c then
  461. t['PathOnHost'] = h
  462. t['PathInContainer'] = c
  463. t['CgroupPermissions'] = "rwm"
  464. else
  465. t['PathOnHost'] = v
  466. t['PathInContainer'] = v
  467. t['CgroupPermissions'] = "rwm"
  468. end
  469. end
  470. if next(t) ~= nil then
  471. table.insert( device, t )
  472. end
  473. end
  474. end
  475. tmp = data.publish or {}
  476. for i, v in ipairs(tmp) do
  477. for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do
  478. local _,_,p= v2:find("^%d+/(%w+)")
  479. if p == nil then
  480. v2=v2..'/tcp'
  481. end
  482. portbindings[v2] = {{HostPort=v1}}
  483. exposedports[v2] = {HostPort=v1}
  484. end
  485. end
  486. local link = data.link
  487. tmp = data.command
  488. local command = {}
  489. if tmp ~= nil then
  490. for v in string.gmatch(tmp, "[^%s]+") do
  491. command[#command+1] = v
  492. end
  493. end
  494. if memory ~= 0 then
  495. _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
  496. if n then
  497. unit = unit and unit:sub(1,1):upper() or "B"
  498. if unit == "M" then
  499. memory = tonumber(n) * 1024 * 1024
  500. elseif unit == "G" then
  501. memory = tonumber(n) * 1024 * 1024 * 1024
  502. elseif unit == "K" then
  503. memory = tonumber(n) * 1024
  504. else
  505. memory = tonumber(n)
  506. end
  507. end
  508. end
  509. create_body.Hostname = network ~= "host" and (hostname or name) or nil
  510. create_body.Tty = tty and true or false
  511. create_body.OpenStdin = interactive and true or false
  512. create_body.User = user
  513. create_body.Cmd = command
  514. create_body.Env = env
  515. create_body.Image = image
  516. create_body.ExposedPorts = exposedports
  517. create_body.HostConfig = create_body.HostConfig or {}
  518. create_body.HostConfig.Dns = dns
  519. create_body.HostConfig.Binds = volume
  520. create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
  521. create_body.HostConfig.Privileged = privileged and true or false
  522. create_body.HostConfig.PortBindings = portbindings
  523. create_body.HostConfig.Memory = tonumber(memory)
  524. create_body.HostConfig.CpuShares = tonumber(cpu_shares)
  525. create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
  526. create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
  527. create_body.HostConfig.PublishAllPorts = publish_all
  528. if create_body.HostConfig.NetworkMode ~= network then
  529. -- network mode changed, need to clear duplicate config
  530. create_body.NetworkingConfig = nil
  531. end
  532. create_body.HostConfig.NetworkMode = network
  533. if ip then
  534. if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then
  535. -- ip + duplicate config
  536. for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
  537. if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
  538. v.IPAMConfig.IPv4Address = ip
  539. else
  540. create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } }
  541. end
  542. break
  543. end
  544. else
  545. -- ip + no duplicate config
  546. create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
  547. end
  548. elseif not create_body.NetworkingConfig then
  549. -- no ip + no duplicate config
  550. create_body.NetworkingConfig = nil
  551. end
  552. create_body["HostConfig"]["Tmpfs"] = tmpfs
  553. create_body["HostConfig"]["Devices"] = device
  554. create_body["HostConfig"]["Sysctls"] = sysctl
  555. create_body["HostConfig"]["CapAdd"] = cap_add
  556. create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil
  557. if network == "bridge" then
  558. create_body["HostConfig"]["Links"] = link
  559. end
  560. local pull_image = function(image)
  561. local json_stringify = luci.jsonc and luci.jsonc.stringify
  562. docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
  563. local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb)
  564. 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
  565. docker:append_status("done\n")
  566. else
  567. res.code = (res.code == 200) and 500 or res.code
  568. docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
  569. luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
  570. end
  571. end
  572. docker:clear_status()
  573. local exist_image = false
  574. if image then
  575. for _, v in ipairs (images) do
  576. if v.RepoTags and v.RepoTags[1] == image then
  577. exist_image = true
  578. break
  579. end
  580. end
  581. if not exist_image then
  582. pull_image(image)
  583. elseif data._force_pull == 1 then
  584. pull_image(image)
  585. end
  586. end
  587. create_body = docker.clear_empty_tables(create_body)
  588. docker:append_status("Container: " .. "create" .. " " .. name .. "...")
  589. local res = dk.containers:create({name = name, body = create_body})
  590. if res and res.code == 201 then
  591. docker:clear_status()
  592. luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
  593. else
  594. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  595. luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
  596. end
  597. end
  598. return m