Skip to content

Commit

Permalink
feat(dbless): improve validation errors from /config
Browse files Browse the repository at this point in the history
  • Loading branch information
flrgh committed Jan 31, 2023
1 parent c9bcf75 commit 9191229
Show file tree
Hide file tree
Showing 5 changed files with 1,876 additions and 57 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@
to the metrics.
[#10118](https://github.com/Kong/kong/pull/10118)

#### Admin API

- In dbless mode, `/config` API endpoint can now flatten all schema validation
errors to a single array via the optional `flatten_errors` query parameter.
[#10161](https://github.com/Kong/kong/pull/10161)

### Fixes

#### Core
Expand Down
101 changes: 75 additions & 26 deletions kong/api/routes/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,64 @@ local function reports_timer(premature)
end


local function truthy(val)
if type(val) == "string" then
val = val:lower()
end

return val == true
or val == 1
or val == "true"
or val == "1"
or val == "on"
or val == "yes"
end


local function hydrate_config_from_request(params, dc)
if params._format_version then
return params
end

local config = params.config

if not config then
local body = kong.request.get_raw_body()
if type(body) == "string" and #body > 0 then
config = body

else
return kong.response.exit(400, {
message = "expected a declarative configuration"
})
end
end

local dc_table, _, err_t, new_hash = dc:unserialize(config)
if not dc_table then
return kong.response.exit(400, errors:declarative_config(err_t))
end

return dc_table, new_hash
end


local function parse_config_post_opts(params)
local flatten_errors = truthy(params.flatten_errors)
params.flatten_errors = nil

-- XXX: this code is much older than the `flatten_errors` flag and therefore
-- does not use the same `truthy()` helper, for backwards compatibility
local check_hash = tostring(params.check_hash) == "1"
params.check_hash = nil

return {
flatten_errors = flatten_errors,
check_hash = check_hash,
}
end


return {
["/config"] = {
GET = function(self, db)
Expand Down Expand Up @@ -57,39 +115,30 @@ return {
})
end

local check_hash, old_hash
if tostring(self.params.check_hash) == "1" then
check_hash = true
old_hash = declarative.get_current_hash()
end
self.params.check_hash = nil
local opts = parse_config_post_opts(self.params)

local old_hash = opts.check_hash and declarative.get_current_hash()
local dc = declarative.new_config(kong.configuration)

local entities, _, err_t, meta, new_hash
if self.params._format_version then
entities, _, err_t, meta, new_hash = dc:parse_table(self.params)
else
local config = self.params.config
if not config then
local body = kong.request.get_raw_body()
if type(body) == "string" and #body > 0 then
config = body
else
return kong.response.exit(400, {
message = "expected a declarative configuration"
})
end
end
entities, _, err_t, meta, new_hash =
dc:parse_string(config, nil, old_hash)
local dc_table, new_hash = hydrate_config_from_request(self.params, dc)

if opts.check_hash and new_hash and old_hash == new_hash then
return kong.response.exit(304)
end

local entities, _, err_t, meta
entities, _, err_t, meta, new_hash = dc:parse_table(dc_table, new_hash)

if not entities then
if check_hash and err_t and err_t.error == "configuration is identical" then
return kong.response.exit(304)
local res

if opts.flatten_errors and dc_table then
res = errors:declarative_config_flattened(err_t, dc_table)
else
res = errors:declarative_config(err_t)
end
return kong.response.exit(400, errors:declarative_config(err_t))

return kong.response.exit(400, res)
end

local ok, err, ttl = declarative.load_into_cache_with_events(entities, meta, new_hash)
Expand Down
72 changes: 42 additions & 30 deletions kong/db/declarative/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ local function pretty_print_error(err_t, item, indent)
end



-- @treturn table|nil a table with the following format:
-- {
-- services: {
Expand Down Expand Up @@ -103,35 +104,7 @@ function _M:parse_file(filename, old_hash)
end


-- @treturn table|nil a table with the following format:
-- {
-- services: {
-- ["<uuid>"] = { ... },
-- ...
-- },

-- }
-- @tparam string contents the json/yml/lua being parsed
-- @tparam string|nil filename. If nil, json will be tried first, then yaml
-- @tparam string|nil old_hash used to avoid loading the same content more than once, if present
-- @treturn nil|string error message, only if error happened
-- @treturn nil|table err_t, only if error happened
-- @treturn table|nil a table with the following format:
-- {
-- _format_version: "2.1",
-- _transform: true,
-- }
function _M:parse_string(contents, filename, old_hash)
-- we don't care about the strength of the hash
-- because declarative config is only loaded by Kong administrators,
-- not outside actors that could exploit it for collisions
local new_hash = md5(contents)

if old_hash and old_hash == new_hash then
local err = "configuration is identical"
return nil, err, { error = err }, nil
end

function _M:unserialize(contents, filename)
local tried_one = false
local dc_table, err
if filename == nil or filename:match("json$")
Expand Down Expand Up @@ -168,7 +141,46 @@ function _M:parse_string(contents, filename, old_hash)
err = "failed parsing declarative configuration" .. (err and (": " .. err) or "")
end

return nil, err, { error = err }
return nil, err, { error = err }, nil
end

-- we don't care about the strength of the hash
-- because declarative config is only loaded by Kong administrators,
-- not outside actors that could exploit it for collisions
local new_hash = md5(contents)

return dc_table, nil, nil, new_hash
end


-- @treturn table|nil a table with the following format:
-- {
-- services: {
-- ["<uuid>"] = { ... },
-- ...
-- },

-- }
-- @tparam string contents the json/yml/lua being parsed
-- @tparam string|nil filename. If nil, json will be tried first, then yaml
-- @tparam string|nil old_hash used to avoid loading the same content more than once, if present
-- @treturn nil|string error message, only if error happened
-- @treturn nil|table err_t, only if error happened
-- @treturn table|nil a table with the following format:
-- {
-- _format_version: "2.1",
-- _transform: true,
-- }
function _M:parse_string(contents, filename, old_hash)
local dc_table, err, err_t, new_hash = self:unserialize(contents, filename)

if not dc_table then
return nil, err, err_t
end

if old_hash and old_hash == new_hash then
err = "configuration is identical"
return nil, err, { error = err }, nil
end

return self:parse_table(dc_table, new_hash)
Expand Down
Loading

0 comments on commit 9191229

Please sign in to comment.