Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Example storage adapter for lua-resty-auto-ssl #205

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 311 additions & 0 deletions lib/resty/auto-ssl/storage_adapters/example.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
-- License: Public Domain

---
-- Requisites:
-- opm get hamishforbes/lua-resty-consul
--
-- @todo: implement expire. Different from Redis, Consul or will persist
-- keys forever or will have a maximum TTL of 24h. (fititnt, 2019-11-28 19:17 BRT)
--
-- @todo: remove dump functions and keep login at at resonable way (fititnt, 2019-11-28 19:17 BRT)
--
-- How to test:
-- Copy this file to /usr/local/share/lua/5.1/resty/auto-ssl/storage_adapters/consul.lua. With ansible would be:
-- ansible -m copy -a "src=./consul.lua dest=/usr/local/share/lua/5.1/resty/auto-ssl/storage_adapters/consul.lua" aguia-pescadora-delta.etica.ai,aguia-pescadora-echo.etica.ai,aguia-pescadora-foxtrot.etica.ai
-- ansible -m copy -a "src=/alligo/code/fititnt/lua-resty-auto-ssl/lib/resty/auto-ssl/storage_adapters/consul.lua dest=/usr/local/share/lua/5.1/resty/auto-ssl/storage_adapters/consul.lua" aguia-pescadora-delta.etica.ai,aguia-pescadora-echo.etica.ai,aguia-pescadora-foxtrot.etica.ai
-- Them set the following on your OpenResty, at http context
-- auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.consul")
--
-- How to document Lua code:
-- - https://stevedonovan.github.io/ldoc/manual/doc.md.html
-- - https://keplerproject.github.io/luadoc/manual.html
-- - http://lua-users.org/wiki/LuaStyleGuide
-- - http://sputnik.freewisdom.org/en/Coding_Standard
--
-- Using ldoc (https://github.com/stevedonovan/LDoc); the lua-doc from keplerproject says it is obsolete (https://github.com/keplerproject/luadoc)
-- sudo apt install lua-ldoc
-- Then validate documentation with
-- ldoc consul.lua

-- I think the path would be /usr/local/share/lua/5.1/resty/auto-ssl/storage_adapters/consul.lua
-- to work with resty/auto-ssl
-- And then use this as reference https://github.com/GUI/lua-resty-auto-ssl/blob/master/lib/resty/auto-ssl/storage_adapters/redis.lua

-- Lean lua in an Hour https://www.youtube.com/watch?v=S4eNl1rA1Ns
-- Definitely an openresty guide/ Hello world https://www.staticshin.com/programming/definitely-an-open-resty-guide/#hello_world
-- Lua in 15 minutes http://tylerneylon.com/a/learn-lua/

-- Redis equivalent: local redis = require "resty.redis"
local consul = require('resty.consul')

--------------------------------------------------------------------------------
-- @todo remove this entire section after finish the heavy debugging
-- of the consul adapter (fititnt, 2019-11-28 20:21 BRT)

local dumpcache = {}

---
-- @author https://pastebin.com/A7JScXWk
-- @param data Anything that need to be dumped
-- @return string
local function dumpvar(data)
-- cache of tables already printed, to avoid infinite recursive loops
local tablecache = {}
local buffer = ""
local padder = " "

local function _dumpvar(d, depth)
local t = type(d)
local str = tostring(d)
if (t == "table") then
if (tablecache[str]) then
-- table already dumped before, so we dont
-- dump it again, just mention it
buffer = buffer.."<"..str..">\n"
else
tablecache[str] = (tablecache[str] or 0) + 1
buffer = buffer.."("..str..") {\n"
for k, v in pairs(d) do
buffer = buffer..string.rep(padder, depth+1).."["..k.."] => "
_dumpvar(v, depth+1)
end
buffer = buffer..string.rep(padder, depth).."}\n"
end
elseif (t == "number") then
buffer = buffer.."("..t..") "..str.."\n"
else
buffer = buffer.."("..t..") \""..str.."\"\n"
end
end
_dumpvar(data, 0)
return buffer
end

---
-- @author fititnt
local function dump(value, cache_uid)
--- print(DataDumper(...), "\n---")
-- ngx.log(ngx.ERR, DataDumper(value, varname, false, 2))
if (cache_uid) then
-- ngx.log(ngx.ERR, 'debug dump function', cache_uid .. os.date("%Y%m%d%H%M"))
if (not dumpcache[cache_uid .. os.date("%Y%m%d%H%M")]) then
dumpcache[cache_uid .. os.date("%Y%m%d%H%M")] = 1
ngx.log(ngx.ERR, dumpvar(value))
end
else
ngx.log(ngx.ERR, dumpvar(value))
end
end

ngx.log(ngx.ERR, "\n\n\n\n\n\n\n\n\n\n---")
dump({'started', os.date("!%Y-%m-%dT%TZ")})
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- @module storage_adapter_consul
local _M = {}

--- Local helper function to, if options have prefix, return a prefixed key name
-- @param self
-- @param key The umprefixed key name
-- @return The key prefixed
local function prefixed_key(self, key)
if self.options["prefix"] then
-- return self.options["prefix"] .. ":" .. key
return self.options["prefix"] .. "/" .. key
else
return key
end
end

-- @TODO: Discover what to type is the return of _M.new (fititnt, 2019-11-27 22:51 BRT)

--- Returns a stored Key Value from the Consul
-- @param auto_ssl_instance
-- @return ????
function _M.new(auto_ssl_instance)
local options = auto_ssl_instance:get("consul") or {}

if not options["prefix"] then
options["prefix"] = "lua-resty-auto-ssl"
end

if not options["host"] then
options["host"] = "127.0.0.1"
end

if not options["port"] then
options["port"] = 8500
end

if not options["connect_timeout"] then
options["connect_timeout"] = '60s'
end

if not options["read_timeout"] then
options["read_timeout"] = '60s'
end

if not options["ssl"] then
options["ssl"] = false
end

if not options["ssl_verify"] then
options["ssl_verify"] = true
end

dump({fn = '_M.new', options = options}, '_M.new')

return setmetatable({ options = options }, { __index = _M })
end

--- Get the Consul connection, creates one if already does not exist
-- @param self
-- @return connection
function _M.get_connection(self)
local connection = ngx.ctx.auto_ssl_consul_connection
if connection then
return connection
end

connection = consul:new(self.options)

dump({fn = '_M.get_connection', connection = connection}, '_M.get_connection')

ngx.ctx.auto_ssl_consul_connection = connection
return connection
end

-- Note: _M.setup() on redis.lua is empty, no arguments, no return value
function _M.setup()
end

--- Returns a stored Key Value from the Consul
-- @param self
-- @param key The umprefixed key name
-- @return The value of saved key (if exists)
function _M.get(self, key)
local connection, connection_err = self:get_connection()
local value = nil
if connection_err then
return nil, connection_err
end

-- Redis use get, Consul use get_key
-- Redis 'res' is value or nil; Consul is a lua-resty-http response object
local res, err = connection:get_key(prefixed_key(self, key))

if res.status ~= 404 and res.body[1] ~= nil and res.body[1]['Value'] ~= nil then
value = res.body[1]['Value']
else
dump({fn = '_M.get fail', res})
end

dump({fn = '_M.get', key=key, res=res, err=err, value=value}, '_M.get')

return value, err
end

--- Store a key-value on the Consul
-- @param self
-- @param key The umprefixed key name
-- @param value The values
-- @param options The values
-- @return ok Boolean if result was ok or not
-- @return res lua-resty-http response object. On error returns nil
-- @return err On error returns an error message
function _M.set(self, key, value, options)
local connection, connection_err = self:get_connection()
local ok = false

if connection_err then
return false, connection_err
end

key = prefixed_key(self, key)

local res, err = connection:put_key(key, value)

if res.status == 200 then
ok = true

-- This expire strategy is based on file.lua and not on redis.lua and
-- at the moment is not using Consul native way to expire keys. Since the
-- version resty.consul v0.3.2 does not implement Expire, even if is
-- possible to do with more RAW HTTP methods, we initialy will the ngx.timer
-- Not ideal, but it works for and functional MVP (fititnt, 2019-11-30 22:14 BRT)
if options and options["exptime"] then
ngx.timer.at(options["exptime"], function()
local _, delete_err = _M.delete(self, key)
if delete_err then
ngx.log(ngx.ERR, "auto-ss.lstorage_adapter.consul._M.delete: failed to remove the key from Consul after the expiretime ", delete_err)
else
dump({fn = '_M.set', _=_, delete_err=delete_err, 'ngx.timer worked!'})
end
end)
end
end

dump({fn = '_M.set', ok=ok, key=key, value=value, options=options, res=res, err=err}, '_M.set')
return ok, err
end

--- Delete a value from Consul based on the unprefixed key
-- @param self
-- @param key The umprefixed key name
-- @return res lua-resty-http response object. On error returns nil
-- @return err On error returns an error message
function _M.delete(self, key)
local connection, connection_err = self:get_connection()
if connection_err then
-- ngx.log(ngx.EMERG, '_M.delete: ', connection_err)
ngx.log(ngx.EMERG, 'storage_adapter.consul._M.delete: connection error:', connection_err)
return false, connection_err
end

-- local cjson = require "cjson"
-- ngx.log(ngx.ERR, '_M.delete: ', connection_err)
-- ngx.log(ngx.ERR, cjson.encode(connection_err))

-- Redis use del, Consul uses delete_key
return connection:delete_key(prefixed_key(self, key))
end

-- TODO: finish _M.keys_with_suffix (fititnt, 2019-27-23:01 BRT)
--- Returns a stored Key Value from the Consul
-- @param self
-- @param suffix The umprefixed key name
-- @return keys The keys
-- @return err On error returns an error message
function _M.keys_with_suffix(self, suffix)
local connection, connection_err = self:get_connection()
if connection_err then
ngx.log(ngx.EMERG, '_M.keys_with_suffix: ', connection_err)
return false, connection_err
end

-- Redis use keys, Consul uses list_keys
-- local keys, err = connection:keys(prefixed_key(self, "*" .. suffix))
local keys, err = connection:list_keys(prefixed_key(self, "*" .. suffix))

if keys and self.options["prefix"] then
local unprefixed_keys = {}
-- First character past the prefix and a colon
local offset = string.len(self.options["prefix"]) + 2

for _, key in ipairs(keys) do
local unprefixed = string.sub(key, offset)
table.insert(unprefixed_keys, unprefixed)
end

keys = unprefixed_keys
end

dump({fn = '_M.keys_with_suffix', keys = keys, err = err}, '_M.keys_with_suffix')

return keys, err
end


return _M