luci-app-dockerman: model/docker update coding style
authorFlorian Eckert <fe@dev.tdt.de>
Thu, 23 Jul 2020 13:43:56 +0000 (15:43 +0200)
committerFlorian Eckert <fe@dev.tdt.de>
Tue, 28 Jul 2020 12:16:14 +0000 (14:16 +0200)
Signed-off-by: Florian Eckert <fe@dev.tdt.de>
applications/luci-app-dockerman/luasrc/model/docker.lua

index 999c4c63db7ebf0758375579a5aa36e09cc82abe..2b5a3244e428696ad12b0669827864519025c0f6 100644 (file)
@@ -3,7 +3,6 @@ LuCI - Lua Configuration Interface
 Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
 ]]--
 
-require "luci.util"
 local docker = require "luci.docker"
 local uci = (require "luci.model.uci").cursor()
 
@@ -11,268 +10,325 @@ local _docker = {}
 
 --pull image and return iamge id
 local update_image = function(self, image_name)
-  local json_stringify = luci.jsonc and luci.jsonc.stringify
-  _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
-  local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb)
-  if res and res.code == 200 and (#res.body > 0 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_name)) then
-    _docker:append_status("done\n")
-  else
-    res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
-  end
-  new_image_id = self.images:inspect({name = image_name}).body.Id
-  return new_image_id, res
+       local json_stringify = luci.jsonc and luci.jsonc.stringify
+       _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
+       local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb)
+
+       if res and res.code == 200 and (#res.body > 0 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_name)) then
+               _docker:append_status("done\n")
+       else
+               res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
+       end
+
+       new_image_id = self.images:inspect({name = image_name}).body.Id
+       return new_image_id, res
 end
 
 local table_equal = function(t1, t2)
-  if not t1 then return true end
-  if not t2 then return false end
-  if #t1 ~= #t2 then return false end
-  for i, v in ipairs(t1) do
-    if t1[i] ~= t2[i] then return false end
-  end
-  return true
+       if not t1 then
+               return true
+       end
+
+       if not t2 then
+               return false
+       end
+
+       if #t1 ~= #t2 then
+               return false
+       end
+
+       for i, v in ipairs(t1) do
+               if t1[i] ~= t2[i] then
+                       return false
+               end
+       end
+
+       return true
 end
 
 local table_subtract = function(t1, t2)
-  if not t1 or next(t1) == nil then return nil end
-  if not t2 or next(t2) == nil then return t1 end
-  local res = {}
-  for _, v1 in ipairs(t1) do
-    local found = false
-    for _, v2 in ipairs(t2) do
-      if v1 == v2 then
-        found= true
-        break
-      end
-    end
-    if not found then
-      table.insert(res, v1)
-    end
-  end
-  return next(res) == nil and nil or res
+       if not t1 or next(t1) == nil then
+               return nil
+       end
+
+       if not t2 or next(t2) == nil then
+               return t1
+       end
+
+       local res = {}
+       for _, v1 in ipairs(t1) do
+               local found = false
+               for _, v2 in ipairs(t2) do
+                       if v1 == v2 then
+                               found= true
+                               break
+                       end
+               end
+               if not found then
+                       table.insert(res, v1)
+               end
+       end
+
+       return next(res) == nil and nil or res
 end
 
 local map_subtract = function(t1, t2)
-  if not t1 or next(t1) == nil then return nil end
-  if not t2 or next(t2) == nil then return t1 end
-  local res = {}
-  for k1, v1 in pairs(t1) do
-    local found = false
-    for k2, v2 in ipairs(t2) do
-      if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then
-        found= true
-        break
-      end
-    end
-    if not found then
-      res[k1] = v1
-      -- if v1 and type(v1) == "table" then
-      --   if next(v1) == nil then 
-      --     res[k1] = { k = 'v' }
-      --   else
-      --     res[k1] = v1
-      --   end
-      -- end
-    end
-  end
-
-  return next(res) ~= nil and res or nil
+       if not t1 or next(t1) == nil then
+               return nil
+       end
+
+       if not t2 or next(t2) == nil then
+               return t1
+       end
+
+       local res = {}
+       for k1, v1 in pairs(t1) do
+               local found = false
+               for k2, v2 in ipairs(t2) do
+                       if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then
+                               found= true
+                               break
+                       end
+               end
+
+               if not found then
+                       res[k1] = v1
+               end
+       end
+
+       return next(res) ~= nil and res or nil
 end
 
 _docker.clear_empty_tables = function ( t )
-  local k, v
-  if next(t) == nil then
-    t = nil
-  else
-    for k, v in pairs(t) do
-      if type(v) == 'table' then
-        t[k] = _docker.clear_empty_tables(v)
-      end
-    end
-  end
-  return t
+       local k, v
+
+       if next(t) == nil then
+               t = nil
+       else
+               for k, v in pairs(t) do
+                       if type(v) == 'table' then
+                               t[k] = _docker.clear_empty_tables(v)
+                       end
+               end
+       end
+
+       return t
 end
 
--- return create_body, extra_network
 local get_config = function(container_config, image_config)
-  local config = container_config.Config
-  local old_host_config = container_config.HostConfig
-  local old_network_setting = container_config.NetworkSettings.Networks or {}
-  if config.WorkingDir == image_config.WorkingDir then config.WorkingDir = "" end
-  if config.User == image_config.User then config.User = "" end
-  if table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end
-  if table_equal(config.Entrypoint, image_config.Entrypoint) then config.Entrypoint = nil end
-  if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil end
-  config.Env = table_subtract(config.Env, image_config.Env)
-  config.Labels = table_subtract(config.Labels, image_config.Labels)
-  config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
-  -- subtract ports exposed in image from container
-  if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
-    config.ExposedPorts = {}
-    for p, v in pairs(old_host_config.PortBindings) do
-      config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
-    end
-  end
-
-  -- handle network config, we need only one network, extras need to network connect action
-  local network_setting = {}
-  local multi_network = false
-  local extra_network = {}
-  for k, v in pairs(old_network_setting) do
-    if multi_network then
-      extra_network[k] = v
-    else
-      network_setting[k] = v
-    end
-    multi_network = true
-  end
-
-  -- handle hostconfig
-  local host_config = old_host_config
-  -- if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
-  -- host_config.LogConfig = nil
-  host_config.Mounts = {}
-  -- for volumes
-  for i, v in ipairs(container_config.Mounts) do
-    if v.Type == "volume" then
-      table.insert(host_config.Mounts, {
-        Type = v.Type,
-        Target = v.Destination,
-        Source = v.Source:match("([^/]+)\/_data"),
-        BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil,
-        ReadOnly = not v.RW
-      })
-    end
-  end
-  
-
-  -- merge configs
-  local create_body = config
-  create_body["HostConfig"] = host_config
-  create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
-  create_body = _docker.clear_empty_tables(create_body) or {}
-  extra_network = _docker.clear_empty_tables(extra_network) or {}
-  return create_body, extra_network
+       local config = container_config.Config
+       local old_host_config = container_config.HostConfig
+       local old_network_setting = container_config.NetworkSettings.Networks or {}
+
+       if config.WorkingDir == image_config.WorkingDir then
+               config.WorkingDir = ""
+       end
+
+       if config.User == image_config.User then
+               config.User = ""
+       end
+
+       if table_equal(config.Cmd, image_config.Cmd) then
+               config.Cmd = nil
+       end
+
+       if table_equal(config.Entrypoint, image_config.Entrypoint) then
+               config.Entrypoint = nil
+       end
+
+       if table_equal(config.ExposedPorts, image_config.ExposedPorts) then
+               config.ExposedPorts = nil
+       end
+
+       config.Env = table_subtract(config.Env, image_config.Env)
+       config.Labels = table_subtract(config.Labels, image_config.Labels)
+       config.Volumes = map_subtract(config.Volumes, image_config.Volumes)
+
+       if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then
+               config.ExposedPorts = {}
+               for p, v in pairs(old_host_config.PortBindings) do
+                       config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort }
+               end
+       end
+
+       local network_setting = {}
+       local multi_network = false
+       local extra_network = {}
+
+       for k, v in pairs(old_network_setting) do
+               if multi_network then
+                       extra_network[k] = v
+               else
+                       network_setting[k] = v
+               end
+               multi_network = true
+       end
+
+       local host_config = old_host_config
+       host_config.Mounts = {}
+       for i, v in ipairs(container_config.Mounts) do
+               if v.Type == "volume" then
+                       table.insert(host_config.Mounts, {
+                               Type = v.Type,
+                               Target = v.Destination,
+                               Source = v.Source:match("([^/]+)\/_data"),
+                               BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil,
+                               ReadOnly = not v.RW
+                       })
+               end
+       end
+
+       local create_body = config
+       create_body["HostConfig"] = host_config
+       create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
+       create_body = _docker.clear_empty_tables(create_body) or {}
+       extra_network = _docker.clear_empty_tables(extra_network) or {}
+
+       return create_body, extra_network
 end
 
 local upgrade = function(self, request)
-  _docker:clear_status()
-  -- get image name, image id, container name, configuration information
-  local container_info = self.containers:inspect({id = request.id})
-  if container_info.code > 300 and type(container_info.body) == "table" then
-    return container_info
-  end
-  local image_name = container_info.body.Config.Image
-  if not image_name:match(".-:.+") then image_name = image_name .. ":latest" end
-  local old_image_id = container_info.body.Image
-  local container_name = container_info.body.Name:sub(2)
-
-  local image_id, res = update_image(self, image_name)
-  if res and res.code ~= 200 then return res end
-  if image_id == old_image_id then
-    return {code = 305, body = {message = "Already up to date"}}
-  end
-
-  _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...")
-  res = self.containers:stop({name = container_name})
-  if res and res.code < 305 then
-    _docker:append_status("done\n")
-  else
-    return res
-  end
-
-  _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...")
-  res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }})
-  if res and res.code < 300 then
-    _docker:append_status("done\n")
-  else
-    return res
-  end
-
-  -- handle config
-  local image_config = self.images:inspect({id = old_image_id}).body.Config
-  local create_body, extra_network = get_config(container_info.body, image_config)
-
-  -- create new container
-  _docker:append_status("Container: Create" .. " " .. container_name .. "...")
-  create_body = _docker.clear_empty_tables(create_body)
-  res = self.containers:create({name = container_name, body = create_body})
-  if res and res.code > 300 then return res end
-  _docker:append_status("done\n")
-
-  -- extra networks need to network connect action
-  for k, v in pairs(extra_network) do
-    _docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
-    res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
-    if res.code > 300 then return res end
-
-    _docker:append_status("done\n")
-  end
-  _docker:clear_status()
-  return res
+       _docker:clear_status()
+
+       local container_info = self.containers:inspect({id = request.id})
+
+       if container_info.code > 300 and type(container_info.body) == "table" then
+               return container_info
+       end
+
+       local image_name = container_info.body.Config.Image
+       if not image_name:match(".-:.+") then
+               image_name = image_name .. ":latest"
+       end
+
+       local old_image_id = container_info.body.Image
+       local container_name = container_info.body.Name:sub(2)
+
+       local image_id, res = update_image(self, image_name)
+       if res and res.code ~= 200 then
+               return res
+       end
+
+       if image_id == old_image_id then
+               return {code = 305, body = {message = "Already up to date"}}
+       end
+
+       _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...")
+       res = self.containers:stop({name = container_name})
+       if res and res.code < 305 then
+               _docker:append_status("done\n")
+       else
+               return res
+       end
+
+       _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...")
+       res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }})
+       if res and res.code < 300 then
+               _docker:append_status("done\n")
+       else
+               return res
+       end
+
+       local image_config = self.images:inspect({id = old_image_id}).body.Config
+       local create_body, extra_network = get_config(container_info.body, image_config)
+
+       -- create new container
+       _docker:append_status("Container: Create" .. " " .. container_name .. "...")
+       create_body = _docker.clear_empty_tables(create_body)
+       res = self.containers:create({name = container_name, body = create_body})
+       if res and res.code > 300 then
+               return res
+       end
+       _docker:append_status("done\n")
+
+       -- extra networks need to network connect action
+       for k, v in pairs(extra_network) do
+               _docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
+               res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
+               if res.code > 300 then
+                       return res
+               end
+               _docker:append_status("done\n")
+       end
+
+       _docker:clear_status()
+       return res
 end
 
 local duplicate_config = function (self, request)
-  local container_info = self.containers:inspect({id = request.id})
-  if container_info.code > 300 and type(container_info.body) == "table" then return nil end
-  local old_image_id = container_info.body.Image
-  local image_config = self.images:inspect({id = old_image_id}).body.Config
-  return get_config(container_info.body, image_config)
+       local container_info = self.containers:inspect({id = request.id})
+       if container_info.code > 300 and type(container_info.body) == "table" then
+               return nil
+       end
+
+       local old_image_id = container_info.body.Image
+       local image_config = self.images:inspect({id = old_image_id}).body.Config
+
+       return get_config(container_info.body, image_config)
 end
 
 _docker.new = function(option)
-  local option = option or {}
-  local remote = uci:get("dockerd", "globals", "remote_endpoint")
-  options = {
-    host = (remote == "true") and (option.host or uci:get("dockerd", "globals", "remote_host")) or nil,
-    port = (remote == "true") and (option.port or uci:get("dockerd", "globals", "remote_port")) or nil,
-    debug = option.debug or uci:get("dockerd", "globals", "debug") == 'true' and true or false,
-    debug_path = option.debug_path or uci:get("dockerd", "globals", "debug_path") or "/tmp/.docker_debug"
-  }
-  options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock") or nil
-  local _new = docker.new(options)
-  _new.options.status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status"
-  _new.containers_upgrade = upgrade
-  _new.containers_duplicate_config = duplicate_config
-  return _new
+       local option = option or {}
+
+       local remote = uci:get("dockerd", "globals", "remote_endpoint")
+       options = {
+               host = (remote == "true") and (option.host or uci:get("dockerd", "globals", "remote_host")) or nil,
+               port = (remote == "true") and (option.port or uci:get("dockerd", "globals", "remote_port")) or nil,
+               debug = option.debug or uci:get("dockerd", "globals", "debug") == 'true' and true or false,
+               debug_path = option.debug_path or uci:get("dockerd", "globals", "debug_path") or "/tmp/.docker_debug"
+       }
+
+       options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock") or nil
+
+       local _new = docker.new(options)
+       _new.options.status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status"
+       _new.containers_upgrade = upgrade
+       _new.containers_duplicate_config = duplicate_config
+
+       return _new
 end
+
 _docker.options={}
 _docker.options.status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status"
 
 _docker.append_status=function(self,val)
-  if not val then return end
-  local file_docker_action_status=io.open(self.options.status_path, "a+")
-  file_docker_action_status:write(val)
-  file_docker_action_status:close()
+       if not val then
+               return
+       end
+       local file_docker_action_status=io.open(self.options.status_path, "a+")
+       file_docker_action_status:write(val)
+       file_docker_action_status:close()
 end
 
 _docker.write_status=function(self,val)
-  if not val then return end
-  local file_docker_action_status=io.open(self.options.status_path, "w+")
-  file_docker_action_status:write(val)
-  file_docker_action_status:close()
+       if not val then
+               return
+       end
+       local file_docker_action_status=io.open(self.options.status_path, "w+")
+       file_docker_action_status:write(val)
+       file_docker_action_status:close()
 end
 
 _docker.read_status=function(self)
-  return nixio.fs.readfile(self.options.status_path)
+       return nixio.fs.readfile(self.options.status_path)
 end
 
 _docker.clear_status=function(self)
-  nixio.fs.remove(self.options.status_path)
+       nixio.fs.remove(self.options.status_path)
 end
 
 local status_cb = function(res, source, handler)
-  res.body = res.body or {}
-  while true do
-    local chunk = source()
-    if chunk then
-      --standard output to res.body
-      table.insert(res.body, chunk)
-      handler(chunk)
-    else
-      return
-    end
-  end
+       res.body = res.body or {}
+       while true do
+               local chunk = source()
+               if chunk then
+                       --standard output to res.body
+                       table.insert(res.body, chunk)
+                       handler(chunk)
+               else
+                       return
+               end
+       end
 end
 
 --{"status":"Pulling from library\/debian","id":"latest"}
@@ -284,114 +340,128 @@ end
 --{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"}
 --{"status":"Status: Downloaded newer image for debian:latest"}
 _docker.pull_image_show_status_cb = function(res, source)
-  return status_cb(res, source, function(chunk)
-    local json_parse = luci.jsonc.parse
-    local step = json_parse(chunk)
-    if type(step) == "table" then
-      local buf = _docker:read_status()
-      local num = 0
-      local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "")  .. (step.progress and (" " .. step.progress) or "").."\n"
-      if step.id then buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) end
-      if num == 0 then
-        buf = buf .. str
-      end
-      _docker:write_status(buf)
-    end
-  end)
+       return status_cb(res, source, function(chunk)
+               local json_parse = luci.jsonc.parse
+               local step = json_parse(chunk)
+               if type(step) == "table" then
+                       local buf = _docker:read_status()
+                       local num = 0
+                       local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "")  .. (step.progress and (" " .. step.progress) or "").."\n"
+                       if step.id then
+                               buf, num = buf:gsub("\t"..step.id .. ": .-\n", str)
+                       end
+                       if num == 0 then
+                               buf = buf .. str
+                       end
+                       _docker:write_status(buf)
+               end
+       end)
 end
 
 --{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"}
 --{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e                              ]  1.572MB/3.822MB"}
 --{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
 _docker.import_image_show_status_cb = function(res, source)
-  return status_cb(res, source, function(chunk)
-    local json_parse = luci.jsonc.parse
-    local step = json_parse(chunk)
-    if type(step) == "table" then
-      local buf = _docker:read_status()
-      local num = 0
-      local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
-      if step.status then buf, num = buf:gsub("\t"..step.status .. " .-\n", str) end
-      if num == 0 then
-        buf = buf .. str
-      end
-      _docker:write_status(buf)
-    end
-  end
-  )
+       return status_cb(res, source, function(chunk)
+               local json_parse = luci.jsonc.parse
+               local step = json_parse(chunk)
+               if type(step) == "table" then
+                       local buf = _docker:read_status()
+                       local num = 0
+                       local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
+                       if step.status then
+                               buf, num = buf:gsub("\t"..step.status .. " .-\n", str)
+                       end
+                       if num == 0 then
+                               buf = buf .. str
+                       end
+                       _docker:write_status(buf)
+               end
+       end)
 end
 
--- _docker.print_status_cb = function(res, source)
---   return status_cb(res, source, function(step)
---     luci.util.perror(step)
---   end
---   )
--- end
-
 _docker.create_macvlan_interface = function(name, device, gateway, subnet)
-  if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end
-  if uci:get("dockerd", "globals", "remote_endpoint") == "true" then return end
-  local ip = require "luci.ip"
-  local if_name = "docker_"..name
-  local dev_name = "macvlan_"..name
-  local net_mask = tostring(ip.new(subnet):mask())
-  local lan_interfaces
-  -- add macvlan device
-  uci:delete("network", dev_name)
-  uci:set("network", dev_name, "device")
-  uci:set("network", dev_name, "name", dev_name)
-  uci:set("network", dev_name, "ifname", device)
-  uci:set("network", dev_name, "type", "macvlan")
-  uci:set("network", dev_name, "mode", "bridge")
-  -- add macvlan interface
-  uci:delete("network", if_name)
-  uci:set("network", if_name, "interface")
-  uci:set("network", if_name, "proto", "static")
-  uci:set("network", if_name, "ifname", dev_name)
-  uci:set("network", if_name, "ipaddr", gateway)
-  uci:set("network", if_name, "netmask", net_mask)
-  uci:foreach("firewall", "zone", function(s)
-    if s.name == "lan" then
-      local interfaces
-      if type(s.network) == "table" then
-        interfaces = table.concat(s.network, " ")
-        uci:delete("firewall", s[".name"], "network")
-      else
-        interfaces = s.network and s.network or ""
-      end
-      interfaces = interfaces .. " " .. if_name
-      interfaces = interfaces:gsub("%s+", " ")
-      uci:set("firewall", s[".name"], "network", interfaces)
-    end
-  end)
-  uci:commit("firewall")
-  uci:commit("network")
-  os.execute("ifup " .. if_name)
+       if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then
+               return
+       end
+
+       if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
+               return
+       end
+
+       local ip = require "luci.ip"
+       local if_name = "docker_"..name
+       local dev_name = "macvlan_"..name
+       local net_mask = tostring(ip.new(subnet):mask())
+       local lan_interfaces
+
+       -- add macvlan device
+       uci:delete("network", dev_name)
+       uci:set("network", dev_name, "device")
+       uci:set("network", dev_name, "name", dev_name)
+       uci:set("network", dev_name, "ifname", device)
+       uci:set("network", dev_name, "type", "macvlan")
+       uci:set("network", dev_name, "mode", "bridge")
+
+       -- add macvlan interface
+       uci:delete("network", if_name)
+       uci:set("network", if_name, "interface")
+       uci:set("network", if_name, "proto", "static")
+       uci:set("network", if_name, "ifname", dev_name)
+       uci:set("network", if_name, "ipaddr", gateway)
+       uci:set("network", if_name, "netmask", net_mask)
+       uci:foreach("firewall", "zone", function(s)
+               if s.name == "lan" then
+                       local interfaces
+                       if type(s.network) == "table" then
+                               interfaces = table.concat(s.network, " ")
+                               uci:delete("firewall", s[".name"], "network")
+                       else
+                               interfaces = s.network and s.network or ""
+                       end
+                       interfaces = interfaces .. " " .. if_name
+                       interfaces = interfaces:gsub("%s+", " ")
+                       uci:set("firewall", s[".name"], "network", interfaces)
+               end
+       end)
+
+       uci:commit("firewall")
+       uci:commit("network")
+
+       os.execute("ifup " .. if_name)
 end
 
 _docker.remove_macvlan_interface = function(name)
-  if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end
-  if uci:get("dockerd", "globals", "remote_endpoint") == "true" then return end
-  local if_name = "docker_"..name
-  local dev_name = "macvlan_"..name
-  uci:foreach("firewall", "zone", function(s)
-    if s.name == "lan" then
-      local interfaces
-      if type(s.network) == "table" then
-        interfaces = table.concat(s.network, " ")
-      else
-        interfaces = s.network and s.network or ""
-      end
-      interfaces = interfaces and interfaces:gsub(if_name, "")
-      interfaces = interfaces and interfaces:gsub("%s+", " ")
-      uci:set("firewall", s[".name"], "network", interfaces)
-    end
-  end)
-  uci:commit("firewall")
-  uci:delete("network", dev_name)
-  uci:delete("network", if_name)
-  uci:commit("network")
-  os.execute("ip link del " .. if_name)
+       if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then
+               return
+       end
+
+       if uci:get("dockerd", "globals", "remote_endpoint") == "true" then
+               return
+       end
+
+       local if_name = "docker_"..name
+       local dev_name = "macvlan_"..name
+       uci:foreach("firewall", "zone", function(s)
+               if s.name == "lan" then
+                       local interfaces
+                       if type(s.network) == "table" then
+                               interfaces = table.concat(s.network, " ")
+                       else
+                               interfaces = s.network and s.network or ""
+                       end
+                       interfaces = interfaces and interfaces:gsub(if_name, "")
+                       interfaces = interfaces and interfaces:gsub("%s+", " ")
+                       uci:set("firewall", s[".name"], "network", interfaces)
+               end
+       end)
+
+       uci:delete("network", dev_name)
+       uci:delete("network", if_name)
+       uci:commit("network")
+       uci:commit("firewall")
+
+       os.execute("ip link del " .. if_name)
 end
 
 return _docker