Skip to content

Commit

Permalink
feat(resolver) APIs schema updates.
Browse files Browse the repository at this point in the history
- APIs now expect either a `public_dns` or a `path`.
- some schemas improvements on consumers and apis
- rename the newest migration to 0.3.0, the new kong version


Former-commit-id: 63b4c5cb4dfd5929f33329129b681d61942274ad
  • Loading branch information
thibaultcha committed Jun 3, 2015
1 parent 82763f7 commit 56dca18
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local Migration = {
name = "2015-05-22-235608_apis_path",
name = "2015-05-22-235608_0.3.0",

up = function(options)
return [[
Expand Down
1 change: 0 additions & 1 deletion kong-0.3.0-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ build = {
["kong.resolver.access"] = "kong/resolver/access.lua",
["kong.resolver.header_filter"] = "kong/resolver/header_filter.lua",
["kong.resolver.certificate"] = "kong/resolver/certificate.lua",
["kong.resolver.resolver_util"] = "kong/resolver/resolver_util.lua",

["kong.dao.error"] = "kong/dao/error.lua",
["kong.dao.schemas_validation"] = "kong/dao/schemas_validation.lua",
Expand Down
33 changes: 30 additions & 3 deletions kong/dao/schemas/apis.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local url = require "socket.url"
local stringy = require "stringy"
local constants = require "kong.constants"

local function validate_target_url(value)
Expand All @@ -16,12 +17,38 @@ local function validate_target_url(value)
return false, "Invalid target URL"
end

local function check_public_dns_and_path(value, api_t)
local public_dns = type(api_t.public_dns) == "string" and stringy.strip(api_t.public_dns) or ""
local path = type(api_t.path) == "string" and stringy.strip(api_t.path) or ""

if path == "" and public_dns == "" then
return false, "At least a 'public_dns' or a 'path' must be specified"
end

return true
end

local function check_path(path, api_t)
local valid, err = check_public_dns_and_path(path, api_t)
if not valid then
return false, err
end

-- Prefix with `/` for the sake of consistency
if path and string.sub(path, 0, 1) ~= "/" then
api_t.path = "/"..path
end

return true
end

return {
id = { type = constants.DATABASE_TYPES.ID },
name = { type = "string", unique = true, queryable = true, default = function(api_t) return api_t.public_dns end },
public_dns = { type = "string", required = true, unique = true, queryable = true,
regex = "([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*)" },
path = { type = "string", queryable = true, unique = true },
public_dns = { type = "string", unique = true, queryable = true,
func = check_public_dns_and_path,
regex = "([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*)" },
path = { type = "string", queryable = true, unique = true, func = check_path },
target_url = { type = "string", required = true, func = validate_target_url },
created_at = { type = constants.DATABASE_TYPES.TIMESTAMP }
}
10 changes: 5 additions & 5 deletions kong/dao/schemas/consumers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ local stringy = require "stringy"
local constants = require "kong.constants"

local function check_custom_id_and_username(value, consumer_t)
local custom_id = consumer_t.custom_id
local username = consumer_t.username
local username = type(consumer_t.username) == "string" and stringy.strip(consumer_t.username) or ""
local custom_id = type(consumer_t.custom_id) == "string" and stringy.strip(consumer_t.custom_id) or ""

if (custom_id == nil or type(custom_id) == "string" and stringy.strip(custom_id) == "")
and (username == nil or type(username) == "string" and stringy.strip(username) == "") then
return false, "At least a 'custom_id' or a 'username' must be specified"
if custom_id == "" and username == "" then
return false, "At least a 'custom_id' or a 'username' must be specified"
end

return true
end

Expand Down
33 changes: 18 additions & 15 deletions kong/resolver/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,23 @@ local function find_api()
local retrieved_api

-- retrieve all APIs
local apis_by_public_dns, err = cache.get_or_set("APIS_BY_PUBLIC_DNS", function()
local apis_dics, err = cache.get_or_set("APIS_BY_PUBLIC_DNS", function()
local apis, err = dao.apis:find_all()
if err then
return nil, err
end

-- build a dictionnary of public_dns:api for efficient retrieval by Host.
local dic = {}
-- build dictionnaries of public_dns:api and path:apis for efficient lookup.
local dns_dic, path_dic = {}, {}
for _, api in ipairs(apis) do
dic[api.public_dns] = api
if api.public_dns then
dns_dic[api.public_dns] = api
end
if api.path then
path_dic[api.path] = api
end
end
return dic
return {dns = dns_dic, path = path_dic}
end, 60) -- 60 seconds cache

if err then
Expand All @@ -76,8 +81,8 @@ local function find_api()
-- for all values of this header, try to find an API using the apis_by_dns dictionnary
for _, host in ipairs(hosts) do
table.insert(all_hosts, host)
if apis_by_public_dns[host] then
retrieved_api = apis_by_public_dns[host]
if apis_dics.dns[host] then
retrieved_api = apis_dics.dns[host]
break
end
end
Expand All @@ -91,14 +96,12 @@ local function find_api()

-- Otherwise, we look for it by path. We have to loop over all APIs and compare the requested URI.
local request_uri = ngx.var.request_uri
for _, api in pairs(apis_by_public_dns) do
if api.path then
local m, err = ngx.re.match(request_uri, api.path)
if err then
ngx.log(ngx.ERR, "[resolver] error matching requested path: "..err)
elseif m then
retrieved_api = api
end
for path, api in pairs(apis_dics.path) do
local m, err = ngx.re.match(request_uri, path)
if err then
ngx.log(ngx.ERR, "[resolver] error matching requested path: "..err)
elseif m then
retrieved_api = api
end
end

Expand Down
25 changes: 23 additions & 2 deletions kong/resolver/certificate.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
local resolver_util = require("kong.resolver.resolver_util")
local cache = require "kong.tools.database_cache"
local stringy = require "stringy"

local _M = {}

local function find_api(hosts)
local retrieved_api, err
for _, host in ipairs(hosts) do
local sanitized_host = stringy.split(host, ":")[1]

retrieved_api, err = cache.get_or_set(cache.api_key(sanitized_host), function()
local apis, err = dao.apis:find_by_keys { public_dns = sanitized_host }
if err then
return nil, err
elseif apis and #apis == 1 then
return apis[1]
end
end)

if err or retrieved_api then
return retrieved_api, err
end
end
end

function _M.execute(conf)
local ssl = require "ngx.ssl"
local server_name = ssl.server_name()
if server_name then -- Only support SNI requests
local api, err = resolver_util.find_api({server_name})
local api, err = find_api({server_name})
if not err and api then
ngx.ctx.api = api
end
Expand Down
26 changes: 0 additions & 26 deletions kong/resolver/resolver_util.lua

This file was deleted.

2 changes: 1 addition & 1 deletion kong/tools/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local _M = {}

function _M.table_size(t)
local res = 0
for _,_ in pairs(t) do
for _ in pairs(t) do
res = res + 1
end
return res
Expand Down
2 changes: 1 addition & 1 deletion spec/integration/admin_api/admin_api_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ local ENDPOINTS = {
}
},
update_fields = { public_dns = "newapi.mockbin.com" },
error_message = '{"public_dns":"public_dns is required","target_url":"target_url is required"}\n'
error_message = '{"public_dns":"At least a \'public_dns\' or a \'path\' must be specified","path":"At least a \'public_dns\' or a \'path\' must be specified","target_url":"target_url is required"}\n'
},
{
collection = "consumers",
Expand Down
4 changes: 2 additions & 2 deletions spec/integration/admin_api/apis_routes_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe("Admin API", function()
it("[FAILURE] should return proper errors", function()
send_content_types(BASE_URL, "POST", {},
400,
'{"public_dns":"public_dns is required","target_url":"target_url is required"}')
'{"public_dns":"At least a \'public_dns\' or a \'path\' must be specified","path":"At least a \'public_dns\' or a \'path\' must be specified","target_url":"target_url is required"}')

send_content_types(BASE_URL, "POST", {public_dns="api.mockbin.com"},
400, '{"target_url":"target_url is required"}')
Expand Down Expand Up @@ -74,7 +74,7 @@ describe("Admin API", function()
it("[FAILURE] should return proper errors", function()
send_content_types(BASE_URL, "PUT", {},
400,
'{"public_dns":"public_dns is required","target_url":"target_url is required"}')
'{"public_dns":"At least a \'public_dns\' or a \'path\' must be specified","path":"At least a \'public_dns\' or a \'path\' must be specified","target_url":"target_url is required"}')

send_content_types(BASE_URL, "PUT", {public_dns="api.mockbin.com"},
400, '{"target_url":"target_url is required"}')
Expand Down
2 changes: 1 addition & 1 deletion spec/integration/admin_api/consumers_routes_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe("Admin API", function()

local response, status = http_client.patch(BASE_URL..consumer.id, {username=""})
assert.equal(400, status)
assert.equal('{"username":"username is not a string"}\n', response)
assert.equal('{"custom_id":"At least a \'custom_id\' or a \'username\' must be specified","username":"username is not a string"}\n', response)
end)
end)

Expand Down
85 changes: 74 additions & 11 deletions spec/unit/dao/entities_schemas_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local validate = require("kong.dao.schemas_validation").validate
local api_schema = require "kong.dao.schemas.apis"
local consumer_schema = require "kong.dao.schemas.consumers"

require "kong.tools.ngx_stub"

Expand All @@ -8,38 +9,100 @@ describe("Entities Schemas", function()

it("should return error with wrong target_url", function()
local valid, errors = validate({
public_dns = "hello.com",
public_dns = "mockbin.com",
target_url = "asdasd"
}, api_schema)
assert.False(valid)
assert.same("Invalid target URL", errors.target_url)
assert.equal("Invalid target URL", errors.target_url)
end)

it("should return error with wrong target_url protocol", function()
local valid, errors = validate({
public_dns = "hello.com",
target_url = "wot://hello.com/"
public_dns = "mockbin.com",
target_url = "wot://mockbin.com/"
}, api_schema)
assert.False(valid)
assert.same("Supported protocols are HTTP and HTTPS", errors.target_url)
assert.equal("Supported protocols are HTTP and HTTPS", errors.target_url)
end)

it("should work without a path", function()
it("should validate without a path", function()
local valid, errors = validate({
public_dns = "hello.com",
target_url = "http://hello.com"
public_dns = "mockbin.com",
target_url = "http://mockbin.com"
}, api_schema)
assert.falsy(errors)
assert.True(valid)
end)

it("should validate with upper case protocol", function()
local valid, errors = validate({
public_dns = "mockbin.com",
target_url = "HTTP://mockbin.com/world"
}, api_schema)
assert.falsy(errors)
assert.True(valid)
end)

it("should work without upper case protocol", function()
it("should complain if missing `public_dns` and `path`", function()
local valid, errors = validate({
name = "mockbin"
}, api_schema)
assert.False(valid)
assert.equal("At least a 'public_dns' or a 'path' must be specified", errors.path)
assert.equal("At least a 'public_dns' or a 'path' must be specified", errors.public_dns)

local valid, errors = validate({
public_dns = "hello2.com",
target_url = "HTTP://hello.com/world"
name = "mockbin",
path = true
}, api_schema)
assert.False(valid)
assert.equal("path is not a string", errors.path)
assert.equal("At least a 'public_dns' or a 'path' must be specified", errors.public_dns)
end)

it("should prefix a `path` with a slash", function()
local api_t = {
name = "mockbin",
path = "status",
target_url = "http://mockbin.com"
}

local valid, errors = validate(api_t, api_schema)
assert.falsy(errors)
assert.True(valid)
assert.equal("/status", api_t.path)

api_t = {
name = "mockbin",
path = "/status",
target_url = "http://mockbin.com"
}

local valid, errors = validate(api_t, api_schema)
assert.falsy(errors)
assert.True(valid)
assert.equal("/status", api_t.path)
end)

end)

describe("Consumers", function()

it("should require a `custom_id` or `username`", function()
local valid, errors = validate({}, consumer_schema)
assert.False(valid)
assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.username)
assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.custom_id)

valid, errors = validate({ username = "" }, consumer_schema)
assert.False(valid)
assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.username)
assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.custom_id)

valid, errors = validate({ username = true }, consumer_schema)
assert.False(valid)
assert.equal("username is not a string", errors.username)
assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.custom_id)
end)

end)
Expand Down

0 comments on commit 56dca18

Please sign in to comment.