luci-lib-docker: update conding style
[project/luci.git] / collections / luci-lib-docker / luasrc / docker.lua
index 0361c05382255381d1917923c8007cbb08dc1a20..6b14626e5d1c516a89ef29eba03253e2ed3bdea7 100644 (file)
@@ -2,6 +2,7 @@
 LuCI - Lua Configuration Interface
 Copyright 2019 lisaac <https://github.com/lisaac/luci-lib-docker>
 ]]--
+
 require "nixio.util"
 require "luci.util"
 local jsonc = require "luci.jsonc"
@@ -14,301 +15,376 @@ local json_stringify = jsonc.stringify
 local json_parse = jsonc.parse
 
 local chunksource = function(sock, buffer)
-  buffer = buffer or ""
-  return function()
-    local output
-    local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
-    if not count then
-      local newblock, code = sock:recv(1024)
-      if not newblock then return nil, code end
-      buffer = buffer .. newblock
-      _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
-    end
-    count = tonumber(count, 16)
-    if not count then
-      return nil, -1, "invalid encoding"
-    elseif count == 0 then -- finial
-      return nil
-    elseif count <= #buffer - endp then
-      --data >= count
-      output = buffer:sub(endp + 1, endp + count)
-      if count == #buffer - endp then          -- [data]
-        buffer = buffer:sub(endp + count + 1)
-        count, code = sock:recvall(2) --read \r\n
-        if not count then return nil, code end
-      elseif count + 1 == #buffer - endp then  -- [data]\r
-        buffer = buffer:sub(endp + count + 2)
-        count, code = sock:recvall(1) --read \n
-        if not count then return nil, code end
-      else                                     -- [data]\r\n[count]\r\n[data]...
-        buffer = buffer:sub(endp + count + 3) -- cut buffer
-      end
-      return output
-    else
-      -- data < count
-      output = buffer:sub(endp + 1, endp + count)
-      buffer = buffer:sub(endp + count + 1)
-      local remain, code = sock:recvall(count - #output) --need read remaining
-      if not remain then return nil, code end
-      output = output .. remain
-      count, code = sock:recvall(2) --read \r\n
-      if not count then return nil, code end
-      return output
-    end
-  end
+       buffer = buffer or ""
+
+       return function()
+               local output
+               local _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
+
+               if not count then
+                       local newblock, code = sock:recv(1024)
+                       if not newblock then
+                               return nil, code
+                       end
+                       buffer = buffer .. newblock
+                       _, endp, count = buffer:find("^([0-9a-fA-F]+)\r\n")
+               end
+
+               count = tonumber(count, 16)
+               if not count then
+                       return nil, -1, "invalid encoding"
+               elseif count == 0 then -- finial
+                       return nil
+               elseif count <= #buffer - endp then -- data >= count
+                       output = buffer:sub(endp + 1, endp + count)
+                       if count == #buffer - endp then -- [data]
+                               buffer = buffer:sub(endp + count + 1)
+                               count, code = sock:recvall(2) --read \r\n
+                               if not count then
+                                       return nil, code
+                               end
+                       elseif count + 1 == #buffer - endp then  -- [data]\r
+                               buffer = buffer:sub(endp + count + 2)
+                               count, code = sock:recvall(1) --read \n
+                               if not count then
+                                       return nil, code
+                               end
+                       else -- [data]\r\n[count]\r\n[data]...
+                               buffer = buffer:sub(endp + count + 3) -- cut buffer
+                       end
+                       return output
+               else -- data < count
+                       output = buffer:sub(endp + 1, endp + count)
+                       buffer = buffer:sub(endp + count + 1)
+                       local remain, code = sock:recvall(count - #output) --need read remaining
+                       if not remain then
+                               return nil, code
+                       end
+                       output = output .. remain
+                       count, code = sock:recvall(2) --read \r\n
+                       if not count then
+                               return nil, code
+                       end
+                       return output
+               end
+       end
 end
 
 local chunksink = function (sock)
-  return function(chunk, err)
-    if not chunk then
-      return sock:writeall("0\r\n\r\n")
-    else
-      return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, tostring(chunk)))
-    end
-  end
+       return function(chunk, err)
+               if not chunk then
+                       return sock:writeall("0\r\n\r\n")
+               else
+                       return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, tostring(chunk)))
+               end
+       end
 end
 
 local docker_stream_filter = function(buffer)
-  buffer = buffer or ""
-  if #buffer < 8 then
-    return ""
-  end
-  local stream_type = ((string.byte(buffer, 1) == 1) and "stdout") or ((string.byte(buffer, 1) == 2) and "stderr") or ((string.byte(buffer, 1) == 0) and "stdin") or "stream_err"
-  local valid_length =
-    tonumber(string.byte(buffer, 5)) * 256 * 256 * 256 + tonumber(string.byte(buffer, 6)) * 256 * 256 + tonumber(string.byte(buffer, 7)) * 256 + tonumber(string.byte(buffer, 8))
-  if valid_length > #buffer + 8 then
-    return ""
-  end
-  return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8)
-  -- return string.sub(buffer, 9, valid_length + 8)
+       buffer = buffer or ""
+       if #buffer < 8 then
+               return ""
+       end
+       local stream_type = ((string.byte(buffer, 1) == 1) and "stdout") or ((string.byte(buffer, 1) == 2) and "stderr") or ((string.byte(buffer, 1) == 0) and "stdin") or "stream_err"
+       local valid_length = tonumber(string.byte(buffer, 5)) * 256 * 256 * 256 + tonumber(string.byte(buffer, 6)) * 256 * 256 + tonumber(string.byte(buffer, 7)) * 256 + tonumber(string.byte(buffer, 8))
+       if valid_length > #buffer + 8 then
+               return ""
+       end
+       return stream_type .. ": " .. string.sub(buffer, 9, valid_length + 8)
 end
 
 local open_socket = function(req_options)
-  local socket
-  if type(req_options) ~= "table" then return socket end
-  if req_options.socket_path then
-    socket = nixio.socket("unix", "stream")
-    if socket:connect(req_options.socket_path) ~= true then return nil end
-  elseif req_options.host and req_options.port then
-    socket = nixio.connect(req_options.host, req_options.port)
-  end
-  if socket then
-    return socket
-  else
-    return nil
-  end
+       local socket
+       if type(req_options) ~= "table" then
+               return socket
+       end
+       if req_options.socket_path then
+               socket = nixio.socket("unix", "stream")
+               if socket:connect(req_options.socket_path) ~= true then
+                       return nil
+               end
+       elseif req_options.host and req_options.port then
+               socket = nixio.connect(req_options.host, req_options.port)
+       end
+       if socket then
+               return socket
+       else
+               return nil
+       end
 end
 
 local send_http_socket = function(docker_socket, req_header, req_body, callback)
-  if docker_socket:send(req_header) == 0 then
-    return {
-      headers={code=498,message="bad path", protocol="HTTP/1.1"},
-      body={message="can\'t send data to socket"}
-    }
-  end
-
-  if req_body and type(req_body) == "function" and req_header and req_header:match("chunked") then
-    -- chunked send
-    req_body(chunksink(docker_socket))
-  elseif req_body and type(req_body) == "function" then
-    -- normal send by req_body function
-    req_body(docker_socket)
-  elseif req_body and type(req_body) == "table" then
-    -- json
-    docker_socket:send(json_stringify(req_body))
-    if options.debug then io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path) end
-  elseif req_body then
-    docker_socket:send(req_body)
-    if options.debug then io.popen("echo '".. req_body .. "' >> " .. options.debug_path) end
-  end
-
-  local linesrc = docker_socket:linesource()
-  -- read socket using source http://w3.impa.br/~diego/software/luasocket/ltn12.html
-  --http://lua-users.org/wiki/FiltersSourcesAndSinks
-  -- handle response header
-  local line = linesrc()
-  if not line then
-    docker_socket:close()
-    return {
-      headers = {code=499, message="bad socket path", protocol="HTTP/1.1"},
-      body = {message="no data receive from socket"}
-    }
-  end
-  local response = {code = 0, headers = {}, body = {}}
-
-  local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
-  response.protocol = p
-  response.code = tonumber(code)
-  response.message = msg
-  line = linesrc()
-  while line and line ~= "" do
-    local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
-    if key and key ~= "Status" then
-      if type(response.headers[key]) == "string" then
-        response.headers[key] = {response.headers[key], val}
-      elseif type(response.headers[key]) == "table" then
-        response.headers[key][#response.headers[key] + 1] = val
-      else
-        response.headers[key] = val
-      end
-    end
-    line = linesrc()
-  end
-  -- handle response body
-  local body_buffer = linesrc(true)
-  response.body = {}
-  if type(callback) ~= "function" then
-    if response.headers["Transfer-Encoding"] == "chunked" then
-      local source = chunksource(docker_socket, body_buffer)
-      code = ltn12.pump.all(source, (ltn12.sink.table(response.body))) and response.code or 555
-      response.code = code
-    else
-      local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
-      code = ltn12.pump.all(body_source, (ltn12.sink.table(response.body))) and response.code or 555
-      response.code = code
-    end
-  else
-    if response.headers["Transfer-Encoding"] == "chunked" then
-      local source = chunksource(docker_socket, body_buffer)
-      callback(response, source)
-    else
-      local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
-      callback(response, body_source)
-    end
-  end
-  docker_socket:close()
-  return response
+       if docker_socket:send(req_header) == 0 then
+               return {
+                       headers={
+                               code=498,
+                               message="bad path",
+                               protocol="HTTP/1.1"
+                       },
+                       body={
+                               message="can\'t send data to socket"
+                       }
+               }
+       end
+
+       if req_body and type(req_body) == "function" and req_header and req_header:match("chunked") then
+               -- chunked send
+               req_body(chunksink(docker_socket))
+       elseif req_body and type(req_body) == "function" then
+               -- normal send by req_body function
+               req_body(docker_socket)
+       elseif req_body and type(req_body) == "table" then
+               -- json
+               docker_socket:send(json_stringify(req_body))
+               if options.debug then
+                       io.popen("echo '".. json_stringify(req_body) .. "' >> " .. options.debug_path)
+               end
+       elseif req_body then
+               docker_socket:send(req_body)
+               if options.debug then
+                       io.popen("echo '".. req_body .. "' >> " .. options.debug_path)
+               end
+       end
+
+       local linesrc = docker_socket:linesource()
+       -- read socket using source http://w3.impa.br/~diego/software/luasocket/ltn12.html
+       -- http://lua-users.org/wiki/FiltersSourcesAndSinks
+       -- handle response header
+       local line = linesrc()
+       if not line then
+               docker_socket:close()
+               return {
+                       headers = {
+                               code=499,
+                               message="bad socket path",
+                               protocol="HTTP/1.1"
+                       },
+                       body = {
+                               message="no data receive from socket"
+                       }
+               }
+       end
+
+       local response = {
+               code = 0,
+               headers = {},
+               body = {}
+       }
+
+       local p, code, msg = line:match("^([%w./]+) ([0-9]+) (.*)")
+       response.protocol = p
+       response.code = tonumber(code)
+       response.message = msg
+       line = linesrc()
+
+       while line and line ~= "" do
+               local key, val = line:match("^([%w-]+)%s?:%s?(.*)")
+               if key and key ~= "Status" then
+                       if type(response.headers[key]) == "string" then
+                               response.headers[key] = {
+                                       response.headers[key],
+                                       val
+                               }
+                       elseif type(response.headers[key]) == "table" then
+                               response.headers[key][#response.headers[key] + 1] = val
+                       else
+                               response.headers[key] = val
+                       end
+               end
+               line = linesrc()
+       end
+
+       -- handle response body
+       local body_buffer = linesrc(true)
+       response.body = {}
+
+       if type(callback) ~= "function" then
+               if response.headers["Transfer-Encoding"] == "chunked" then
+                       local source = chunksource(docker_socket, body_buffer)
+                       code = ltn12.pump.all(source, (ltn12.sink.table(response.body))) and response.code or 555
+                       response.code = code
+               else
+                       local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
+                       code = ltn12.pump.all(body_source, (ltn12.sink.table(response.body))) and response.code or 555
+                       response.code = code
+               end
+       else
+               if response.headers["Transfer-Encoding"] == "chunked" then
+                       local source = chunksource(docker_socket, body_buffer)
+                       callback(response, source)
+               else
+                       local body_source = ltn12.source.cat(ltn12.source.string(body_buffer), docker_socket:blocksource())
+                       callback(response, body_source)
+               end
+       end
+       docker_socket:close()
+       return response
 end
 
 local gen_header = function(options, http_method, api_group, api_action, name_or_id, request)
-  local header, query, path
-  name_or_id = (name_or_id ~= "") and name_or_id or nil
-
-  if request and type(request.query) == "table" then
-    local k, v
-    for k, v in pairs(request.query) do
-      if type(v) == "table" then
-        query = (query and query .. "&" or "?") .. k .. "=" .. urlencode(json_stringify(v))
-      elseif type(v) == "boolean" then
-        query = (query and query .. "&" or "?") .. k .. "=" .. (v and "true" or "false")
-      elseif type(v) == "number" or type(v) == "string" then
-        query = (query and query .. "&" or "?") .. k .. "=" .. v
-      end
-    end
-  end
-  path = (api_group and ("/" .. api_group) or "") .. (name_or_id and ("/" .. name_or_id) or "") .. (api_action and ("/" .. api_action) or "") .. (query or "")
-  header = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
-  header = header .. "Host: " .. options.host .. "\r\n"
-  header = header .. "User-Agent: " .. options.user_agent .. "\r\n"
-  header = header .. "Connection: close\r\n"
-
-  if request and type(request.header) == "table" then
-    local k, v
-    for k, v in pairs(request.header) do
-      header = header .. k .. ": " .. v .. "\r\n"
-    end
-  end
-
-  -- when requst_body is function, we need to custom header using custom header
-  if request and request.body and type(request.body) == "function" then
-    if not header:match("Content-Length:") then
-      header = header .. "Transfer-Encoding: chunked\r\n"
-    end
-  elseif http_method == "POST" and request and request.body and type(request.body) == "table" then
-    local conetnt_json = json_stringify(request.body)
-    header = header .. "Content-Type: application/json\r\n"
-    header = header .. "Content-Length: " .. #conetnt_json .. "\r\n"
-  elseif request and request.body and type(request.body) == "string" then
-    header = header .. "Content-Length: " .. #request.body .. "\r\n"
-  end
-  header = header .. "\r\n"
-  if options.debug then io.popen("echo '".. header .. "' >> " .. options.debug_path) end
-  return header
+       local header, query, path
+       name_or_id = (name_or_id ~= "") and name_or_id or nil
+
+       if request and type(request.query) == "table" then
+               local k, v
+               for k, v in pairs(request.query) do
+                       if type(v) == "table" then
+                               query = (query and query .. "&" or "?") .. k .. "=" .. urlencode(json_stringify(v))
+                       elseif type(v) == "boolean" then
+                               query = (query and query .. "&" or "?") .. k .. "=" .. (v and "true" or "false")
+                       elseif type(v) == "number" or type(v) == "string" then
+                               query = (query and query .. "&" or "?") .. k .. "=" .. v
+                       end
+               end
+       end
+
+       path = (api_group and ("/" .. api_group) or "") .. (name_or_id and ("/" .. name_or_id) or "") .. (api_action and ("/" .. api_action) or "") .. (query or "")
+       header = (http_method or "GET") .. " " .. path .. " " .. options.protocol .. "\r\n"
+       header = header .. "Host: " .. options.host .. "\r\n"
+       header = header .. "User-Agent: " .. options.user_agent .. "\r\n"
+       header = header .. "Connection: close\r\n"
+
+       if request and type(request.header) == "table" then
+               local k, v
+               for k, v in pairs(request.header) do
+                       header = header .. k .. ": " .. v .. "\r\n"
+               end
+       end
+
+       -- when requst_body is function, we need to custom header using custom header
+       if request and request.body and type(request.body) == "function" then
+               if not header:match("Content-Length:") then
+                       header = header .. "Transfer-Encoding: chunked\r\n"
+               end
+       elseif http_method == "POST" and request and request.body and type(request.body) == "table" then
+               local conetnt_json = json_stringify(request.body)
+               header = header .. "Content-Type: application/json\r\n"
+               header = header .. "Content-Length: " .. #conetnt_json .. "\r\n"
+       elseif request and request.body and type(request.body) == "string" then
+               header = header .. "Content-Length: " .. #request.body .. "\r\n"
+       end
+
+       header = header .. "\r\n"
+       if options.debug then
+               io.popen("echo '".. header .. "' >> " .. options.debug_path)
+       end
+
+       return header
 end
 
 local call_docker = function(options, http_method, api_group, api_action, name_or_id, request, callback)
-  local req_options = setmetatable({}, {__index = options})
-
-  local req_header = gen_header(req_options, http_method, api_group, api_action, name_or_id, request)
-  local req_body = request and request.body or nil
-  local docker_socket = open_socket(req_options)
-
-  if docker_socket then
-    return send_http_socket(docker_socket, req_header, req_body, callback)
-  else
-    return {
-      headers = {code=497, message="bad socket path or host", protocol="HTTP/1.1"},
-      body = {message="can\'t connect to socket"}
-    }
-  end
+       local req_options = setmetatable({}, {
+               __index = options
+       })
+
+       local req_header = gen_header(req_options,
+               http_method,
+               api_group,
+               api_action,
+               name_or_id,
+               request)
+
+       local req_body = request and request.body or nil
+       local docker_socket = open_socket(req_options)
+
+       if docker_socket then
+               return send_http_socket(docker_socket, req_header, req_body, callback)
+       else
+               return {
+                       headers = {
+                               code=497,
+                               message="bad socket path or host",
+                               protocol="HTTP/1.1"
+                       },
+                       body = {
+                               message="can\'t connect to socket"
+                       }
+               }
+       end
 end
 
 local gen_api = function(_table, http_method, api_group, api_action)
-  local _api_action
-  if api_action == "get_archive" or api_action == "put_archive" then
-    _api_action = "archive"
-  elseif api_action == "df" then
-    _api_action = "system/df"
-  elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
-    _api_action = api_action
-  elseif (api_group == "containers" or api_group == "images" or api_group == "exec") and (api_action == "list" or api_action == "inspect") then
-    _api_action = "json"
-  end
-
-  local fp = function(self, request, callback)
-    local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
-    if api_action == "list" then
-      if (name_or_id ~= "" and name_or_id ~= nil) then
-        if api_group == "images" then
-          name_or_id = nil
-        else
-          request.query = request and request.query or {}
-          request.query.filters = request.query.filters or {}
-          request.query.filters.name = request.query.filters.name or {}
-          request.query.filters.name[#request.query.filters.name + 1] = name_or_id
-          name_or_id = nil
-        end
-      end
-    elseif api_action == "create" then
-      if (name_or_id ~= "" and name_or_id ~= nil) then
-        request.query = request and request.query or {}
-        request.query.name = request.query.name or name_or_id
-        name_or_id = nil
-      end
-    elseif api_action == "logs" then
-      local body_buffer = ""
-      local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
-      if response.code >= 200 and response.code < 300 then
-        for i, v in ipairs(response.body) do
-          body_buffer = body_buffer .. docker_stream_filter(response.body[i])
-        end
-        response.body = body_buffer
-      end
-      return response
-    end
-    local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
-    if response.headers and response.headers["Content-Type"] == "application/json" then
-      if #response.body == 1 then
-        response.body = json_parse(response.body[1])
-      else
-        local tmp = {}
-        for _, v in ipairs(response.body) do
-          tmp[#tmp+1] = json_parse(v)
-        end
-        response.body = tmp
-      end
-    end
-    return response
-  end
-
-  if api_group then
-    _table[api_group][api_action] = fp
-  else
-    _table[api_action] = fp
-  end
+       local _api_action
+
+       if api_action == "get_archive" or api_action == "put_archive" then
+               api_action = "archive"
+       elseif api_action == "df" then
+               _api_action = "system/df"
+       elseif api_action ~= "list" and api_action ~= "inspect" and api_action ~= "remove" then
+               _api_action = api_action
+       elseif (api_group == "containers" or api_group == "images" or api_group == "exec") and (api_action == "list" or api_action == "inspect") then
+               _api_action = "json"
+       end
+
+       local fp = function(self, request, callback)
+               local name_or_id = request and (request.name or request.id or request.name_or_id) or nil
+
+               if api_action == "list" then
+                       if (name_or_id ~= "" and name_or_id ~= nil) then
+                               if api_group == "images" then
+                                       name_or_id = nil
+                               else
+                                       request.query = request and request.query or {}
+                                       request.query.filters = request.query.filters or {}
+                                       request.query.filters.name = request.query.filters.name or {}
+                                       request.query.filters.name[#request.query.filters.name + 1] = name_or_id
+                                       name_or_id = nil
+                               end
+                       end
+               elseif api_action == "create" then
+                       if (name_or_id ~= "" and name_or_id ~= nil) then
+                               request.query = request and request.query or {}
+                               request.query.name = request.query.name or name_or_id
+                               name_or_id = nil
+                       end
+               elseif api_action == "logs" then
+                       local body_buffer = ""
+                       local response = call_docker(self.options,
+                               http_method,
+                               api_group,
+                               _api_action,
+                               name_or_id,
+                               request,
+                               callback)
+                       if response.code >= 200 and response.code < 300 then
+                               for i, v in ipairs(response.body) do
+                                       body_buffer = body_buffer .. docker_stream_filter(response.body[i])
+                               end
+                               response.body = body_buffer
+                       end
+                       return response
+               end
+
+               local response = call_docker(self.options, http_method, api_group, _api_action, name_or_id, request, callback)
+
+               if response.headers and response.headers["Content-Type"] == "application/json" then
+                       if #response.body == 1 then
+                               response.body = json_parse(response.body[1])
+                       else
+                               local tmp = {}
+                               for _, v in ipairs(response.body) do
+                                       tmp[#tmp+1] = json_parse(v)
+                               end
+                               response.body = tmp
+                       end
+               end
+               return response
+       end
+
+       if api_group then
+               _table[api_group][api_action] = fp
+       else
+               _table[api_action] = fp
+       end
 end
 
-local _docker = {containers = {}, exec = {}, images = {}, networks = {}, volumes = {}}
+local _docker = {
+       containers = {},
+       exec = {},
+       images = {},
+       networks = {},
+       volumes = {}
+}
 
 gen_api(_docker, "GET", "containers", "list")
 gen_api(_docker, "POST", "containers", "create")
@@ -368,85 +444,93 @@ gen_api(_docker, "GET", nil, "_ping")
 gen_api(_docker, "GET", nil, "df")
 
 function _docker.new(options)
-  local docker = {}
-  local _options = options or {}
-  docker.options = {
-    socket_path = _options.socket_path or nil,
-    host = _options.socket_path and "localhost" or _options.host,
-    port = not _options.socket_path and _options.port or nil,
-    tls = _options.tls or nil,
-    tls_cacert = _options.tls and _options.tls_cacert or nil,
-    tls_cert = _options.tls and _options.tls_cert or nil,
-    tls_key = _options.tls and _options.tls_key or nil,
-    version = _options.version or "v1.40",
-    user_agent = _options.user_agent or "LuCI",
-    protocol = _options.protocol or "HTTP/1.1",
-    debug = _options.debug or false,
-    debug_path = _options.debug and _options.debug_path or nil
-  }
-  setmetatable(
-    docker,
-    {
-      __index = function(t, key)
-        if _docker[key] ~= nil then
-          return _docker[key]
-        else
-          return _docker.containers[key]
-        end
-      end
-    }
-  )
-  setmetatable(
-    docker.containers,
-    {
-      __index = function(t, key)
-        if key == "options" then
-          return docker.options
-        end
-      end
-    }
-  )
-  setmetatable(
-    docker.networks,
-    {
-      __index = function(t, key)
-        if key == "options" then
-          return docker.options
-        end
-      end
-    }
-  )
-  setmetatable(
-    docker.images,
-    {
-      __index = function(t, key)
-        if key == "options" then
-          return docker.options
-        end
-      end
-    }
-  )
-  setmetatable(
-    docker.volumes,
-    {
-      __index = function(t, key)
-        if key == "options" then
-          return docker.options
-        end
-      end
-    }
-  )
-  setmetatable(
-    docker.exec,
-    {
-      __index = function(t, key)
-        if key == "options" then
-          return docker.options
-        end
-      end
-    }
-  )
-  return docker
+       local docker = {}
+       local _options = options or {}
+
+       docker.options = {
+               socket_path = _options.socket_path or nil,
+               host = _options.socket_path and "localhost" or _options.host,
+               port = not _options.socket_path and _options.port or nil,
+               tls = _options.tls or nil,
+               tls_cacert = _options.tls and _options.tls_cacert or nil,
+               tls_cert = _options.tls and _options.tls_cert or nil,
+               tls_key = _options.tls and _options.tls_key or nil,
+               version = _options.version or "v1.40",
+               user_agent = _options.user_agent or "LuCI",
+               protocol = _options.protocol or "HTTP/1.1",
+               debug = _options.debug or false,
+               debug_path = _options.debug and _options.debug_path or nil
+       }
+
+       setmetatable(
+               docker,
+               {
+               __index = function(t, key)
+                       if _docker[key] ~= nil then
+                               return _docker[key]
+                       else
+                               return _docker.containers[key]
+                       end
+               end
+               }
+       )
+
+       setmetatable(
+               docker.containers,
+               {
+               __index = function(t, key)
+                       if key == "options" then
+                               return docker.options
+                       end
+               end
+               }
+       )
+
+       setmetatable(
+               docker.networks,
+               {
+               __index = function(t, key)
+                       if key == "options" then
+                               return docker.options
+                       end
+               end
+               }
+       )
+
+       setmetatable(
+               docker.images,
+               {
+               __index = function(t, key)
+                       if key == "options" then
+                               return docker.options
+                       end
+               end
+               }
+       )
+
+       setmetatable(
+               docker.volumes,
+               {
+               __index = function(t, key)
+                       if key == "options" then
+                               return docker.options
+                       end
+               end
+               }
+       )
+
+       setmetatable(
+               docker.exec,
+               {
+               __index = function(t, key)
+                       if key == "options" then
+                               return docker.options
+                       end
+               end
+               }
+       )
+
+       return docker
 end
 
 return _docker