newcontainer.lua 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. --[[
  2. LuCI - Lua Configuration Interface
  3. Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
  4. ]]--
  5. local docker = require "luci.model.docker"
  6. local m, s, o
  7. local dk = docker.new()
  8. local cmd_line = table.concat(arg, '/')
  9. local create_body = {}
  10. local images = dk.images:list().body
  11. local networks = dk.networks:list().body
  12. local containers = dk.containers:list({
  13. query = {
  14. all=true
  15. }
  16. }).body
  17. local is_quot_complete = function(str)
  18. local num = 0, w
  19. require "math"
  20. if not str then
  21. return true
  22. end
  23. local num = 0, w
  24. for w in str:gmatch("\"") do
  25. num = num + 1
  26. end
  27. if math.fmod(num, 2) ~= 0 then
  28. return false
  29. end
  30. num = 0
  31. for w in str:gmatch("\'") do
  32. num = num + 1
  33. end
  34. if math.fmod(num, 2) ~= 0 then
  35. return false
  36. end
  37. return true
  38. end
  39. function contains(list, x)
  40. for _, v in pairs(list) do
  41. if v == x then
  42. return true
  43. end
  44. end
  45. return false
  46. end
  47. local resolve_cli = function(cmd_line)
  48. local config = {
  49. advance = 1
  50. }
  51. local key_no_val = {
  52. 't',
  53. 'd',
  54. 'i',
  55. 'tty',
  56. 'rm',
  57. 'read_only',
  58. 'interactive',
  59. 'init',
  60. 'help',
  61. 'detach',
  62. 'privileged',
  63. 'P',
  64. 'publish_all',
  65. }
  66. local key_with_val = {
  67. 'sysctl',
  68. 'add_host',
  69. 'a',
  70. 'attach',
  71. 'blkio_weight_device',
  72. 'cap_add',
  73. 'cap_drop',
  74. 'device',
  75. 'device_cgroup_rule',
  76. 'device_read_bps',
  77. 'device_read_iops',
  78. 'device_write_bps',
  79. 'device_write_iops',
  80. 'dns',
  81. 'dns_option',
  82. 'dns_search',
  83. 'e',
  84. 'env',
  85. 'env_file',
  86. 'expose',
  87. 'group_add',
  88. 'l',
  89. 'label',
  90. 'label_file',
  91. 'link',
  92. 'link_local_ip',
  93. 'log_driver',
  94. 'log_opt',
  95. 'network_alias',
  96. 'p',
  97. 'publish',
  98. 'security_opt',
  99. 'storage_opt',
  100. 'tmpfs',
  101. 'v',
  102. 'volume',
  103. 'volumes_from',
  104. 'blkio_weight',
  105. 'cgroup_parent',
  106. 'cidfile',
  107. 'cpu_period',
  108. 'cpu_quota',
  109. 'cpu_rt_period',
  110. 'cpu_rt_runtime',
  111. 'c',
  112. 'cpu_shares',
  113. 'cpus',
  114. 'cpuset_cpus',
  115. 'cpuset_mems',
  116. 'detach_keys',
  117. 'disable_content_trust',
  118. 'domainname',
  119. 'entrypoint',
  120. 'gpus',
  121. 'health_cmd',
  122. 'health_interval',
  123. 'health_retries',
  124. 'health_start_period',
  125. 'health_timeout',
  126. 'h',
  127. 'hostname',
  128. 'ip',
  129. 'ip6',
  130. 'ipc',
  131. 'isolation',
  132. 'kernel_memory',
  133. 'log_driver',
  134. 'mac_address',
  135. 'm',
  136. 'memory',
  137. 'memory_reservation',
  138. 'memory_swap',
  139. 'memory_swappiness',
  140. 'mount',
  141. 'name',
  142. 'network',
  143. 'no_healthcheck',
  144. 'oom_kill_disable',
  145. 'oom_score_adj',
  146. 'pid',
  147. 'pids_limit',
  148. 'restart',
  149. 'runtime',
  150. 'shm_size',
  151. 'sig_proxy',
  152. 'stop_signal',
  153. 'stop_timeout',
  154. 'ulimit',
  155. 'u',
  156. 'user',
  157. 'userns',
  158. 'uts',
  159. 'volume_driver',
  160. 'w',
  161. 'workdir'
  162. }
  163. local key_abb = {
  164. net='network',
  165. a='attach',
  166. c='cpu-shares',
  167. d='detach',
  168. e='env',
  169. h='hostname',
  170. i='interactive',
  171. l='label',
  172. m='memory',
  173. p='publish',
  174. P='publish_all',
  175. t='tty',
  176. u='user',
  177. v='volume',
  178. w='workdir'
  179. }
  180. local key_with_list = {
  181. 'sysctl',
  182. 'add_host',
  183. 'a',
  184. 'attach',
  185. 'blkio_weight_device',
  186. 'cap_add',
  187. 'cap_drop',
  188. 'device',
  189. 'device_cgroup_rule',
  190. 'device_read_bps',
  191. 'device_read_iops',
  192. 'device_write_bps',
  193. 'device_write_iops',
  194. 'dns',
  195. 'dns_optiondns_search',
  196. 'e',
  197. 'env',
  198. 'env_file',
  199. 'expose',
  200. 'group_add',
  201. 'l',
  202. 'label',
  203. 'label_file',
  204. 'link',
  205. 'link_local_ip',
  206. 'log_driver',
  207. 'log_opt',
  208. 'network_alias',
  209. 'p',
  210. 'publish',
  211. 'security_opt',
  212. 'storage_opt',
  213. 'tmpfs',
  214. 'v',
  215. 'volume',
  216. 'volumes_from',
  217. }
  218. local key = nil
  219. local _key = nil
  220. local val = nil
  221. local is_cmd = false
  222. cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)")
  223. for w in cmd_line:gmatch("[^%s]+") do
  224. if w =='\\' then
  225. elseif not key and not _key and not is_cmd then
  226. --key=val
  227. key, val = w:match("^%-%-([%lP%-]-)=(.+)")
  228. if not key then
  229. --key val
  230. key = w:match("^%-%-([%lP%-]+)")
  231. if not key then
  232. -- -v val
  233. key = w:match("^%-([%lP%-]+)")
  234. if key then
  235. -- for -dit
  236. if key:match("i") or key:match("t") or key:match("d") then
  237. if key:match("i") then
  238. config[key_abb["i"]] = true
  239. key:gsub("i", "")
  240. end
  241. if key:match("t") then
  242. config[key_abb["t"]] = true
  243. key:gsub("t", "")
  244. end
  245. if key:match("d") then
  246. config[key_abb["d"]] = true
  247. key:gsub("d", "")
  248. end
  249. if key:match("P") then
  250. config[key_abb["P"]] = true
  251. key:gsub("P", "")
  252. end
  253. if key == "" then
  254. key = nil
  255. end
  256. end
  257. end
  258. end
  259. end
  260. if key then
  261. key = key:gsub("-","_")
  262. key = key_abb[key] or key
  263. if contains(key_no_val, key) then
  264. config[key] = true
  265. val = nil
  266. key = nil
  267. elseif contains(key_with_val, key) then
  268. -- if key == "cap_add" then config.privileged = true end
  269. else
  270. key = nil
  271. val = nil
  272. end
  273. else
  274. config.image = w
  275. key = nil
  276. val = nil
  277. is_cmd = true
  278. end
  279. elseif (key or _key) and not is_cmd then
  280. if key == "mount" then
  281. -- we need resolve mount options here
  282. -- type=bind,source=/source,target=/app
  283. local _type = w:match("^type=([^,]+),") or "bind"
  284. local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or ""
  285. local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
  286. local ro = w:match(",readonly") and "ro" or nil
  287. if source and target then
  288. if _type ~= "tmpfs" then
  289. local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
  290. 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 ""))
  291. else
  292. local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
  293. local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
  294. key = "tmpfs"
  295. 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 "")
  296. if not config[key] then
  297. config[key] = {}
  298. end
  299. table.insert( config[key], val )
  300. key = nil
  301. val = nil
  302. end
  303. end
  304. else
  305. val = w
  306. end
  307. elseif is_cmd then
  308. config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w
  309. end
  310. if (key or _key) and val then
  311. key = _key or key
  312. if contains(key_with_list, key) then
  313. if not config[key] then
  314. config[key] = {}
  315. end
  316. if _key then
  317. config[key][#config[key]] = config[key][#config[key]] .. " " .. w
  318. else
  319. table.insert( config[key], val )
  320. end
  321. if is_quot_complete(config[key][#config[key]]) then
  322. config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
  323. _key = nil
  324. else
  325. _key = key
  326. end
  327. else
  328. config[key] = (config[key] and (config[key] .. " ") or "") .. val
  329. if is_quot_complete(config[key]) then
  330. config[key] = config[key]:gsub("[\"\']", "")
  331. _key = nil
  332. else
  333. _key = key
  334. end
  335. end
  336. key = nil
  337. val = nil
  338. end
  339. end
  340. return config
  341. end
  342. local default_config = {}
  343. if cmd_line and cmd_line:match("^DOCKERCLI.+") then
  344. default_config = resolve_cli(cmd_line)
  345. elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
  346. local container_id = cmd_line:match("^duplicate/(.+)")
  347. create_body = dk:containers_duplicate_config({id = container_id}) or {}
  348. if not create_body.HostConfig then
  349. create_body.HostConfig = {}
  350. end
  351. if next(create_body) ~= nil then
  352. default_config.name = nil
  353. default_config.image = create_body.Image
  354. default_config.hostname = create_body.Hostname
  355. default_config.tty = create_body.Tty and true or false
  356. default_config.interactive = create_body.OpenStdin and true or false
  357. default_config.privileged = create_body.HostConfig.Privileged and true or false
  358. default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
  359. default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil
  360. 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
  361. default_config.link = create_body.HostConfig.Links
  362. default_config.env = create_body.Env
  363. default_config.dns = create_body.HostConfig.Dns
  364. default_config.volume = create_body.HostConfig.Binds
  365. default_config.cap_add = create_body.HostConfig.CapAdd
  366. default_config.publish_all = create_body.HostConfig.PublishAllPorts
  367. if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then
  368. default_config.sysctl = {}
  369. for k, v in pairs(create_body.HostConfig.Sysctls) do
  370. table.insert( default_config.sysctl, k.."="..v )
  371. end
  372. end
  373. if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then
  374. default_config.log_opt = {}
  375. for k, v in pairs(create_body.HostConfig.LogConfig.Config) do
  376. table.insert( default_config.log_opt, k.."="..v )
  377. end
  378. end
  379. if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
  380. default_config.publish = {}
  381. for k, v in pairs(create_body.HostConfig.PortBindings) do
  382. table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
  383. end
  384. end
  385. default_config.user = create_body.User or nil
  386. default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil
  387. default_config.advance = 1
  388. default_config.cpus = create_body.HostConfig.NanoCPUs
  389. default_config.cpu_shares = create_body.HostConfig.CpuShares
  390. default_config.memory = create_body.HostConfig.Memory
  391. default_config.blkio_weight = create_body.HostConfig.BlkioWeight
  392. if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then
  393. default_config.device = {}
  394. for _, v in ipairs(create_body.HostConfig.Devices) do
  395. table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
  396. end
  397. end
  398. if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
  399. default_config.tmpfs = {}
  400. for k, v in pairs(create_body.HostConfig.Tmpfs) do
  401. table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v )
  402. end
  403. end
  404. end
  405. end
  406. m = SimpleForm("docker", translate("Docker - Containers"))
  407. m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
  408. s = m:section(SimpleSection)
  409. s.template = "dockerman/apply_widget"
  410. s.err=docker:read_status()
  411. s.err=s.err and s.err:gsub("\n","<br />"):gsub(" ","&#160;")
  412. if s.err then
  413. docker:clear_status()
  414. end
  415. s = m:section(SimpleSection, translate("Create new docker container"))
  416. s.addremove = true
  417. s.anonymous = true
  418. o = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
  419. o.rawhtml = true
  420. o.template = "dockerman/newcontainer_resolve"
  421. o = s:option(Value, "name", translate("Container Name"))
  422. o.rmempty = true
  423. o.default = default_config.name or nil
  424. o = s:option(Flag, "interactive", translate("Interactive (-i)"))
  425. o.rmempty = true
  426. o.disabled = 0
  427. o.enabled = 1
  428. o.default = default_config.interactive and 1 or 0
  429. o = s:option(Flag, "tty", translate("TTY (-t)"))
  430. o.rmempty = true
  431. o.disabled = 0
  432. o.enabled = 1
  433. o.default = default_config.tty and 1 or 0
  434. o = s:option(Value, "image", translate("Docker Image"))
  435. o.rmempty = true
  436. o.default = default_config.image or nil
  437. for _, v in ipairs (images) do
  438. if v.RepoTags then
  439. o:value(v.RepoTags[1], v.RepoTags[1])
  440. end
  441. end
  442. o = s:option(Flag, "_force_pull", translate("Always pull image first"))
  443. o.rmempty = true
  444. o.disabled = 0
  445. o.enabled = 1
  446. o.default = 0
  447. o = s:option(Flag, "privileged", translate("Privileged"))
  448. o.rmempty = true
  449. o.disabled = 0
  450. o.enabled = 1
  451. o.default = default_config.privileged and 1 or 0
  452. o = s:option(ListValue, "restart", translate("Restart Policy"))
  453. o.rmempty = true
  454. o:value("no", "No")
  455. o:value("unless-stopped", "Unless stopped")
  456. o:value("always", "Always")
  457. o:value("on-failure", "On failure")
  458. o.default = default_config.restart or "unless-stopped"
  459. local d_network = s:option(ListValue, "network", translate("Networks"))
  460. d_network.rmempty = true
  461. d_network.default = default_config.network or "bridge"
  462. local d_ip = s:option(Value, "ip", translate("IPv4 Address"))
  463. d_ip.datatype="ip4addr"
  464. d_ip:depends("network", "nil")
  465. d_ip.default = default_config.ip or nil
  466. o = s:option(DynamicList, "link", translate("Links with other containers"))
  467. o.placeholder = "container_name:alias"
  468. o.rmempty = true
  469. o:depends("network", "bridge")
  470. o.default = default_config.link or nil
  471. o = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
  472. o.placeholder = "8.8.8.8"
  473. o.rmempty = true
  474. o.default = default_config.dns or nil
  475. o = s:option(Value, "user",
  476. translate("User(-u)"),
  477. translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])"))
  478. o.placeholder = "1000:1000"
  479. o.rmempty = true
  480. o.default = default_config.user or nil
  481. o = s:option(DynamicList, "env",
  482. translate("Environmental Variable(-e)"),
  483. translate("Set environment variables to inside the container"))
  484. o.placeholder = "TZ=Asia/Shanghai"
  485. o.rmempty = true
  486. o.default = default_config.env or nil
  487. o = s:option(DynamicList, "volume",
  488. translate("Bind Mount(-v)"),
  489. translate("Bind mount a volume"))
  490. o.placeholder = "/media:/media:slave"
  491. o.rmempty = true
  492. o.default = default_config.volume or nil
  493. local d_publish = s:option(DynamicList, "publish",
  494. translate("Exposed Ports(-p)"),
  495. translate("Publish container's port(s) to the host"))
  496. d_publish.placeholder = "2200:22/tcp"
  497. d_publish.rmempty = true
  498. d_publish.default = default_config.publish or nil
  499. o = s:option(Value, "command", translate("Run command"))
  500. o.placeholder = "/bin/sh init.sh"
  501. o.rmempty = true
  502. o.default = default_config.command or nil
  503. o = s:option(Flag, "advance", translate("Advance"))
  504. o.rmempty = true
  505. o.disabled = 0
  506. o.enabled = 1
  507. o.default = default_config.advance or 0
  508. o = s:option(Value, "hostname",
  509. translate("Host Name"),
  510. translate("The hostname to use for the container"))
  511. o.rmempty = true
  512. o.default = default_config.hostname or nil
  513. o:depends("advance", 1)
  514. o = s:option(Flag, "publish_all",
  515. translate("Exposed All Ports(-P)"),
  516. translate("Allocates an ephemeral host port for all of a container's exposed ports"))
  517. o.rmempty = true
  518. o.disabled = 0
  519. o.enabled = 1
  520. o.default = default_config.publish_all and 1 or 0
  521. o:depends("advance", 1)
  522. o = s:option(DynamicList, "device",
  523. translate("Device(--device)"),
  524. translate("Add host device to the container"))
  525. o.placeholder = "/dev/sda:/dev/xvdc:rwm"
  526. o.rmempty = true
  527. o:depends("advance", 1)
  528. o.default = default_config.device or nil
  529. o = s:option(DynamicList, "tmpfs",
  530. translate("Tmpfs(--tmpfs)"),
  531. translate("Mount tmpfs directory"))
  532. o.placeholder = "/run:rw,noexec,nosuid,size=65536k"
  533. o.rmempty = true
  534. o:depends("advance", 1)
  535. o.default = default_config.tmpfs or nil
  536. o = s:option(DynamicList, "sysctl",
  537. translate("Sysctl(--sysctl)"),
  538. translate("Sysctls (kernel parameters) options"))
  539. o.placeholder = "net.ipv4.ip_forward=1"
  540. o.rmempty = true
  541. o:depends("advance", 1)
  542. o.default = default_config.sysctl or nil
  543. o = s:option(DynamicList, "cap_add",
  544. translate("CAP-ADD(--cap-add)"),
  545. translate("A list of kernel capabilities to add to the container"))
  546. o.placeholder = "NET_ADMIN"
  547. o.rmempty = true
  548. o:depends("advance", 1)
  549. o.default = default_config.cap_add or nil
  550. o = s:option(Value, "cpus",
  551. translate("CPUs"),
  552. translate("Number of CPUs. Number is a fractional number. 0.000 means no limit"))
  553. o.placeholder = "1.5"
  554. o.rmempty = true
  555. o:depends("advance", 1)
  556. o.datatype="ufloat"
  557. o.default = default_config.cpus or nil
  558. o = s:option(Value, "cpu_shares",
  559. translate("CPU Shares Weight"),
  560. translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024"))
  561. o.placeholder = "1024"
  562. o.rmempty = true
  563. o:depends("advance", 1)
  564. o.datatype="uinteger"
  565. o.default = default_config.cpu_shares or nil
  566. o = s:option(Value, "memory",
  567. translate("Memory"),
  568. translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M"))
  569. o.placeholder = "128m"
  570. o.rmempty = true
  571. o:depends("advance", 1)
  572. o.default = default_config.memory or nil
  573. o = s:option(Value, "blkio_weight",
  574. translate("Block IO Weight"),
  575. translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000"))
  576. o.placeholder = "500"
  577. o.rmempty = true
  578. o:depends("advance", 1)
  579. o.datatype="uinteger"
  580. o.default = default_config.blkio_weight or nil
  581. o = s:option(DynamicList, "log_opt",
  582. translate("Log driver options"),
  583. translate("The logging configuration for this container"))
  584. o.placeholder = "max-size=1m"
  585. o.rmempty = true
  586. o:depends("advance", 1)
  587. o.default = default_config.log_opt or nil
  588. for _, v in ipairs (networks) do
  589. if v.Name then
  590. local parent = v.Options and v.Options.parent or nil
  591. local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil
  592. ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil
  593. local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "")
  594. d_network:value(v.Name, network_name)
  595. if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then
  596. d_ip:depends("network", v.Name)
  597. end
  598. if v.Driver == "bridge" then
  599. d_publish:depends("network", v.Name)
  600. end
  601. end
  602. end
  603. m.handle = function(self, state, data)
  604. if state ~= FORM_VALID then
  605. return
  606. end
  607. local tmp
  608. local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S"))
  609. local hostname = data.hostname
  610. local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false
  611. local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false
  612. local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false
  613. local image = data.image
  614. local user = data.user
  615. if image and not image:match(".-:.+") then
  616. image = image .. ":latest"
  617. end
  618. local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false
  619. local restart = data.restart
  620. local env = data.env
  621. local dns = data.dns
  622. local cap_add = data.cap_add
  623. local sysctl = {}
  624. tmp = data.sysctl
  625. if type(tmp) == "table" then
  626. for i, v in ipairs(tmp) do
  627. local k,v1 = v:match("(.-)=(.+)")
  628. if k and v1 then
  629. sysctl[k]=v1
  630. end
  631. end
  632. end
  633. local log_opt = {}
  634. tmp = data.log_opt
  635. if type(tmp) == "table" then
  636. for i, v in ipairs(tmp) do
  637. local k,v1 = v:match("(.-)=(.+)")
  638. if k and v1 then
  639. log_opt[k]=v1
  640. end
  641. end
  642. end
  643. local network = data.network
  644. local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
  645. local volume = data.volume
  646. local memory = data.memory or 0
  647. local cpu_shares = data.cpu_shares or 0
  648. local cpus = data.cpus or 0
  649. local blkio_weight = data.blkio_weight or nil
  650. local portbindings = {}
  651. local exposedports = {}
  652. local tmpfs = {}
  653. tmp = data.tmpfs
  654. if type(tmp) == "table" then
  655. for i, v in ipairs(tmp)do
  656. local k= v:match("([^:]+)")
  657. local v1 = v:match(".-:([^:]+)") or ""
  658. if k then
  659. tmpfs[k]=v1
  660. end
  661. end
  662. end
  663. local device = {}
  664. tmp = data.device
  665. if type(tmp) == "table" then
  666. for i, v in ipairs(tmp) do
  667. local t = {}
  668. local _,_, h, c, p = v:find("(.-):(.-):(.+)")
  669. if h and c then
  670. t['PathOnHost'] = h
  671. t['PathInContainer'] = c
  672. t['CgroupPermissions'] = p or "rwm"
  673. else
  674. local _,_, h, c = v:find("(.-):(.+)")
  675. if h and c then
  676. t['PathOnHost'] = h
  677. t['PathInContainer'] = c
  678. t['CgroupPermissions'] = "rwm"
  679. else
  680. t['PathOnHost'] = v
  681. t['PathInContainer'] = v
  682. t['CgroupPermissions'] = "rwm"
  683. end
  684. end
  685. if next(t) ~= nil then
  686. table.insert( device, t )
  687. end
  688. end
  689. end
  690. tmp = data.publish or {}
  691. for i, v in ipairs(tmp) do
  692. for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do
  693. local _,_,p= v2:find("^%d+/(%w+)")
  694. if p == nil then
  695. v2=v2..'/tcp'
  696. end
  697. portbindings[v2] = {{HostPort=v1}}
  698. exposedports[v2] = {HostPort=v1}
  699. end
  700. end
  701. local link = data.link
  702. tmp = data.command
  703. local command = {}
  704. if tmp ~= nil then
  705. for v in string.gmatch(tmp, "[^%s]+") do
  706. command[#command+1] = v
  707. end
  708. end
  709. if memory ~= 0 then
  710. _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)")
  711. if n then
  712. unit = unit and unit:sub(1,1):upper() or "B"
  713. if unit == "M" then
  714. memory = tonumber(n) * 1024 * 1024
  715. elseif unit == "G" then
  716. memory = tonumber(n) * 1024 * 1024 * 1024
  717. elseif unit == "K" then
  718. memory = tonumber(n) * 1024
  719. else
  720. memory = tonumber(n)
  721. end
  722. end
  723. end
  724. create_body.Hostname = network ~= "host" and (hostname or name) or nil
  725. create_body.Tty = tty and true or false
  726. create_body.OpenStdin = interactive and true or false
  727. create_body.User = user
  728. create_body.Cmd = command
  729. create_body.Env = env
  730. create_body.Image = image
  731. create_body.ExposedPorts = exposedports
  732. create_body.HostConfig = create_body.HostConfig or {}
  733. create_body.HostConfig.Dns = dns
  734. create_body.HostConfig.Binds = volume
  735. create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
  736. create_body.HostConfig.Privileged = privileged and true or false
  737. create_body.HostConfig.PortBindings = portbindings
  738. create_body.HostConfig.Memory = tonumber(memory)
  739. create_body.HostConfig.CpuShares = tonumber(cpu_shares)
  740. create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
  741. create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
  742. create_body.HostConfig.PublishAllPorts = publish_all
  743. if create_body.HostConfig.NetworkMode ~= network then
  744. create_body.NetworkingConfig = nil
  745. end
  746. create_body.HostConfig.NetworkMode = network
  747. if ip then
  748. if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then
  749. for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do
  750. if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then
  751. v.IPAMConfig.IPv4Address = ip
  752. else
  753. create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } }
  754. end
  755. break
  756. end
  757. else
  758. create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } }
  759. end
  760. elseif not create_body.NetworkingConfig then
  761. create_body.NetworkingConfig = nil
  762. end
  763. create_body["HostConfig"]["Tmpfs"] = tmpfs
  764. create_body["HostConfig"]["Devices"] = device
  765. create_body["HostConfig"]["Sysctls"] = sysctl
  766. create_body["HostConfig"]["CapAdd"] = cap_add
  767. create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil
  768. if network == "bridge" then
  769. create_body["HostConfig"]["Links"] = link
  770. end
  771. local pull_image = function(image)
  772. local json_stringify = luci.jsonc and luci.jsonc.stringify
  773. docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
  774. local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb)
  775. 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
  776. docker:append_status("done\n")
  777. else
  778. res.code = (res.code == 200) and 500 or res.code
  779. docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
  780. luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
  781. end
  782. end
  783. docker:clear_status()
  784. local exist_image = false
  785. if image then
  786. for _, v in ipairs (images) do
  787. if v.RepoTags and v.RepoTags[1] == image then
  788. exist_image = true
  789. break
  790. end
  791. end
  792. if not exist_image then
  793. pull_image(image)
  794. elseif data._force_pull == 1 then
  795. pull_image(image)
  796. end
  797. end
  798. create_body = docker.clear_empty_tables(create_body)
  799. docker:append_status("Container: " .. "create" .. " " .. name .. "...")
  800. local res = dk.containers:create({name = name, body = create_body})
  801. if res and res.code == 201 then
  802. docker:clear_status()
  803. luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
  804. else
  805. docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
  806. luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
  807. end
  808. end
  809. return m