229 lines
5.6 KiB
Lua
229 lines
5.6 KiB
Lua
local _M = {}
|
|
|
|
local cjson = require "cjson"
|
|
local trie = require "trie"
|
|
local http = require "resty.http"
|
|
local cache = require "resty.dns.cache"
|
|
local os = require "os"
|
|
|
|
local encode = cjson.encode
|
|
local decode = cjson.decode
|
|
|
|
local table_concat = table.concat
|
|
|
|
local trie_get = trie.get
|
|
local match = string.match
|
|
local gsub = string.gsub
|
|
local lower = string.lower
|
|
|
|
|
|
-- we "cache" the config local to each worker
|
|
local ingressConfig = nil
|
|
|
|
local cluster_domain = "cluster.local"
|
|
|
|
local def_backend = nil
|
|
|
|
local custom_error = nil
|
|
|
|
local dns_cache_options = nil
|
|
|
|
function get_ingressConfig(ngx)
|
|
local d = ngx.shared["ingress"]
|
|
local value, flags, stale = d:get_stale("ingressConfig")
|
|
if not value then
|
|
-- nothing we can do
|
|
return nil, "config not set"
|
|
end
|
|
ingressConfig = decode(value)
|
|
return ingressConfig, nil
|
|
end
|
|
|
|
function worker_cache_config(ngx)
|
|
local _, err = get_ingressConfig(ngx)
|
|
if err then
|
|
ngx.log(ngx.ERR, "unable to get ingressConfig: ", err)
|
|
return
|
|
end
|
|
end
|
|
|
|
function _M.content(ngx)
|
|
local host = ngx.var.host
|
|
|
|
-- strip off any port
|
|
local h = match(host, "^(.+):?")
|
|
if h then
|
|
host = h
|
|
end
|
|
|
|
host = lower(host)
|
|
|
|
local config, err = get_ingressConfig(ngx)
|
|
if err then
|
|
ngx.log(ngx.ERR, "unable to get config: ", err)
|
|
return ngx.exit(503)
|
|
end
|
|
|
|
-- this assumes we only allow exact host matches
|
|
local paths = config[host]
|
|
if not paths then
|
|
ngx.log(ngx.ERR, "No server for host "..host.." returning 404")
|
|
if custom_error then
|
|
openCustomErrorURL(404, custom_error)
|
|
return
|
|
else
|
|
openURL(404, def_backend)
|
|
return
|
|
end
|
|
end
|
|
|
|
local backend = trie_get(paths, ngx.var.uri)
|
|
|
|
if not backend then
|
|
ngx.log(ngx.ERR, "No server for host "..host.." and path "..ngx.var.uri.." returning 404")
|
|
if custom_error then
|
|
openCustomErrorURL(404, custom_error)
|
|
return
|
|
else
|
|
openURL(404, def_backend)
|
|
return
|
|
end
|
|
end
|
|
|
|
local address = backend.host
|
|
ngx.var.upstream_port = backend.port or 80
|
|
|
|
if dns_cache_options then
|
|
local dns = cache.new(dns_cache_options)
|
|
local answer, err, stale = dns:query(address, { qtype = 1 })
|
|
if err or (not answer) then
|
|
if stale then
|
|
answer = stale
|
|
else
|
|
answer = nil
|
|
end
|
|
end
|
|
if answer and answer[1] then
|
|
local ans = answer[1]
|
|
if ans.address then
|
|
address = ans.address
|
|
end
|
|
else
|
|
ngx.log(ngx.ERR, "dns failed for ", address, " with ", err, " => ", encode(answer or ""))
|
|
end
|
|
end
|
|
|
|
ngx.var.upstream_host = address
|
|
return
|
|
end
|
|
|
|
function _M.init_worker(ngx)
|
|
end
|
|
|
|
function _M.init(ngx, options)
|
|
-- ngx.log(ngx.OK, "options: "..encode(options))
|
|
def_backend = options.def_backend
|
|
custom_error = options.custom_error
|
|
|
|
-- try to create a dns cache
|
|
local resolvers = options.resolvers
|
|
if resolvers then
|
|
cache.init_cache(512)
|
|
local servers = trie.strsplit(" ", resolvers)
|
|
dns_cache_options =
|
|
{
|
|
dict = "dns_cache",
|
|
negative_ttl = nil,
|
|
max_stale = 900,
|
|
normalise_ttl = false,
|
|
resolver = {
|
|
nameservers = {servers[1]}
|
|
}
|
|
}
|
|
end
|
|
|
|
end
|
|
|
|
-- dump config. This is the raw config (including trie) for now
|
|
function _M.config(ngx)
|
|
ngx.header.content_type = "application/json"
|
|
local config = {
|
|
ingress = ingressConfig
|
|
}
|
|
local val = encode(config)
|
|
ngx.print(val)
|
|
end
|
|
|
|
function _M.update_ingress(ngx)
|
|
ngx.header.content_type = "application/json"
|
|
|
|
if ngx.req.get_method() ~= "POST" then
|
|
ngx.print(encode({
|
|
message = "only POST request"
|
|
}))
|
|
ngx.exit(400)
|
|
return
|
|
end
|
|
|
|
ngx.req.read_body()
|
|
local data = ngx.req.get_body_data()
|
|
local val = decode(data)
|
|
|
|
if not val then
|
|
ngx.log(ngx.ERR, "failed to decode body")
|
|
return
|
|
end
|
|
|
|
config = {}
|
|
|
|
for _, ingress in ipairs(val) do
|
|
local namespace = ingress.metadata.namespace
|
|
|
|
local spec = ingress.spec
|
|
-- we do not allow default ingress backends right now.
|
|
for _, rule in ipairs(spec.rules) do
|
|
local host = rule.host
|
|
local paths = config[host]
|
|
if not paths then
|
|
paths = trie.new()
|
|
config[host] = paths
|
|
end
|
|
rule.http = rule.http or { paths = {}}
|
|
for _, path in ipairs(rule.http.paths) do
|
|
local hostname = table_concat(
|
|
{
|
|
path.backend.serviceName,
|
|
namespace,
|
|
"svc",
|
|
cluster_domain
|
|
}, ".")
|
|
local backend = {
|
|
host = hostname,
|
|
port = path.backend.servicePort
|
|
}
|
|
|
|
paths:add(path.path, backend)
|
|
end
|
|
end
|
|
end
|
|
|
|
local d = ngx.shared["ingress"]
|
|
local ok, err, _ = d:set("ingressConfig", encode(config))
|
|
if not ok then
|
|
ngx.log(ngx.ERR, "Error: "..err)
|
|
local res = encode({
|
|
message = "Error updating Ingress rules: "..err
|
|
})
|
|
ngx.print(res)
|
|
return ngx.exit(500)
|
|
end
|
|
|
|
ingressConfig = config
|
|
|
|
local res = encode({
|
|
message = "Ingress rules updated"
|
|
})
|
|
ngx.print(res)
|
|
end
|
|
|
|
return _M
|