Skip to content

Commit

Permalink
feat(wasm): combine config and json_config fields
Browse files Browse the repository at this point in the history
This allows JSON filter configuration to live in the same .config
attribute as raw configuration. Filters must have an associated
JSON schema in order to use this feature.
  • Loading branch information
flrgh committed Oct 11, 2023
1 parent 18de761 commit 018c5bf
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 43 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/kong/wasm-filter-json-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: Support JSON in Wasm filter configuration
type: feature
scope: Core
18 changes: 7 additions & 11 deletions kong/db/schema/entities/filter_chains.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,33 @@ local constants = require "kong.constants"
---
---@field name string
---@field enabled boolean
---@field config string|nil
---@field json_config any|nil
---@field config any|nil


local filter = {
type = "record",
fields = {
{ name = { type = "string", required = true, one_of = wasm.filter_names,
err = "no such filter", }, },
{ config = { type = "string", required = false, }, },
{ enabled = { type = "boolean", default = true, required = true, }, },

{ json_config = {
{ config = {
type = "json",
required = false,
json_schema = {
parent_subschema_key = "name",
namespace = constants.SCHEMA_NAMESPACES.PROXY_WASM_FILTERS,
optional = true,
default = {
-- filters with no user-defined JSON schema may accept an optional
-- config, but only as a string
type = { "string", "null" },
},
},
},
},

},
entity_checks = {
{ mutually_exclusive = {
"config",
"json_config",
},
},
},
}

return {
Expand Down
2 changes: 1 addition & 1 deletion kong/db/schema/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,7 @@ validate_fields = function(self, input)

if json_subschema_key then
local schema_name = json_schema.namespace .. "/" .. json_subschema_key
inline_schema = json.get_schema(schema_name)
inline_schema = json.get_schema(schema_name) or json_schema.default

if inline_schema then
_, errors[k] = json_validate(v, inline_schema)
Expand Down
25 changes: 25 additions & 0 deletions kong/db/schema/metaschema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,38 @@ local validators = {
-- means that input validation will fail. When `optional` is `true`, the input
-- is always accepted.
--
-- Schemas which use this dynamic reference format can also optionally supply
-- a default inline schema, which will be evaluated when the dynamic schema
-- does not exist:
--
-- ```lua
-- local record = {
-- type = "record",
-- fields = {
-- { name = { type = "string" } },
-- { config = {
-- type = "json",
-- json_schema = {
-- namespace = "my-record-type",
-- parent_subschema_key = "name",
-- default = {
-- { type = { "string", "null" } },
-- },
-- },
-- },
-- },
-- },
-- }
-- ```
--
local json_metaschema = {
type = "record",
fields = {
{ namespace = { type = "string", one_of = values(constants.SCHEMA_NAMESPACES), }, },
{ parent_subschema_key = { type = "string" }, },
{ optional = { type = "boolean", }, },
{ inline = { type = "any", custom_validator = json_lib.validate_schema, }, },
{ default = { type = "any", custom_validator = json_lib.validate_schema, }, },
},
entity_checks = {
{ at_least_one_of = { "inline", "namespace", "parent_subschema_key" }, },
Expand Down
16 changes: 12 additions & 4 deletions kong/runloop/wasm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,18 @@ local function rebuild_state(db, version, old_state)

for _, filter in ipairs(chain.filters) do
if filter.enabled then
-- serialize all JSON configurations up front
if not filter.config and filter.json_config ~= nil then
filter.config = cjson_encode(filter.json_config)
filter.json_config = nil
-- Serialize all JSON configurations up front
--
-- NOTE: there is a subtle difference between a raw, non-JSON filter
-- configuration which requires no encoding (e.g. `my config bytes`)
-- and a JSON filter configuration of type=string, which should be
-- JSON-encoded (e.g. `"my config string"`).
--
-- Properly disambiguating between the two cases requires an
-- inspection of the filter metadata, which is not guaranteed to be
-- present on data-plane/proxy nodes.
if filter.config ~= nil and type(filter.config) ~= "string" then
filter.config = cjson_encode(filter.config)
end
end
end
Expand Down
11 changes: 5 additions & 6 deletions spec/02-integration/20-wasm/01-admin-api_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ local utils = require "kong.tools.utils"
local fmt = string.format

local FILTER_PATH = assert(helpers.test_conf.wasm_filters_path)
local NULL = ngx.null

local function json(body)
return {
Expand Down Expand Up @@ -207,9 +206,9 @@ describe("wasm admin API [#" .. strategy .. "]", function()
assert.same({ "foo", "bar" }, patched.tags)
assert.is_false(patched.enabled)
assert.equals(2, #patched.filters)
assert.same({ name = "tests", config = "123", enabled = true, json_config = NULL },
assert.same({ name = "tests", config = "123", enabled = true },
patched.filters[1])
assert.same({ name = "tests", config = "456", enabled = false, json_config = NULL },
assert.same({ name = "tests", config = "456", enabled = false },
patched.filters[2])
end)
end)
Expand Down Expand Up @@ -366,9 +365,9 @@ describe("wasm admin API [#" .. strategy .. "]", function()
assert.same({ "foo", "bar" }, patched.tags)
assert.is_false(patched.enabled)
assert.equals(2, #patched.filters)
assert.same({ name = "tests", config = "123", enabled = true, json_config = NULL },
assert.same({ name = "tests", config = "123", enabled = true },
patched.filters[1])
assert.same({ name = "tests", config = "456", enabled = false, json_config = NULL },
assert.same({ name = "tests", config = "456", enabled = false },
patched.filters[2])
end)
end)
Expand Down Expand Up @@ -428,7 +427,7 @@ describe("wasm admin API [#" .. strategy .. "]", function()
fcs = {
assert(bp.filter_chains:insert({
filters = {
{ name = "tests", config = ngx.null, enabled = true },
{ name = "tests", config = nil, enabled = true },
{ name = "response_transformer", config = "{}", enabled = false },
},
service = { id = service.id },
Expand Down
56 changes: 42 additions & 14 deletions spec/02-integration/20-wasm/02-db_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,13 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
end)

describe(".config", function()
it("is an optional string", function()
local schema_name = "proxy-wasm-filters/test"

lazy_teardown(function()
schema_lib.remove_schema(schema_name)
end)

it("is an optional string when no json schema exists", function()
local service = assert(db.services:insert({
url = "http://example.test",
}))
Expand All @@ -381,7 +387,7 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
filters = {
{
name = "test",
config = "foo",
config = nil,
}
}
}))
Expand All @@ -395,26 +401,48 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
filters = {
{
name = "test",
config = nil,
config = "my config",
}
}
}))
end)
end)

describe(".json_config", function()
local schema_name = "proxy-wasm-filters/test"
assert.falsy(dao:insert({
service = { id = service.id },
filters = {
{
name = "test",
config = 123,
}
}
}))

lazy_teardown(function()
schema_lib.remove_schema(schema_name)
assert.falsy(dao:insert({
service = { id = service.id },
filters = {
{
name = "test",
config = true,
}
}
}))

assert.falsy(dao:insert({
service = { id = service.id },
filters = {
{
name = "test",
config = { a = 1, b = 2 },
}
}
}))
end)

it("is validated against user schema", function()
local service = assert(db.services:insert({
url = "http://example.test",
}))

schema_lib.add_schema("proxy-wasm-filters/test", {
schema_lib.add_schema(schema_name, {
type = "object",
properties = {
foo = { type = "string" },
Expand All @@ -429,7 +457,7 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
filters = {
{
name = "test",
json_config = {
config = {
foo = "foo string",
bar = { a = 1, b = 2 },
},
Expand All @@ -446,7 +474,7 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
filters = {
{
name = "test",
json_config = {
config = {
foo = 123,
bar = { a = 1, b = 2 },
},
Expand All @@ -465,7 +493,7 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
filters = {
{
name = "test",
json_config = ngx.null,
config = ngx.null,
}
}
})
Expand All @@ -481,7 +509,7 @@ describe("wasm DB entities [#" .. strategy .. "]", function()
filters = {
{
name = "test",
json_config = nil,
config = nil,
}
}
})
Expand Down
38 changes: 31 additions & 7 deletions spec/02-integration/20-wasm/09-filter-meta_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe("filter metadata [#" .. strategy .. "]", function()
filters = {
{
name = "rt_with_validation",
json_config = {}, -- empty
config = {}, -- empty
},
},
})
Expand All @@ -185,7 +185,7 @@ describe("filter metadata [#" .. strategy .. "]", function()
assert.same({
filters = {
{
json_config = "property add is required"
config = "property add is required"
}
}
}, body.fields)
Expand All @@ -197,7 +197,7 @@ describe("filter metadata [#" .. strategy .. "]", function()
filters = {
{
name = "rt_with_validation",
json_config = {
config = {
add = {
headers = {
"x-foo:123",
Expand All @@ -217,19 +217,44 @@ describe("filter metadata [#" .. strategy .. "]", function()
end).has_no_error()
end)

it("filters without config schemas are not validated", function()
it("filters without config schemas can only accept a string", function()
local host = random_name() .. ".test"

local res = create_filter_chain(host, {
name = random_name(),
filters = {
{
name = "rt_no_validation",
json_config = {
config = {
add = {
headers = 1234,
},
},
}
},
},
})

assert.response(res).has.status(400)
local body = assert.response(res).has.jsonbody()
assert.same({
filters = {
{ config = "wrong type: expected one of string, null, got object" }
}
}, body.fields)
end)

it("filters without config schemas are not validated", function()
local host = random_name() .. ".test"
local res = create_filter_chain(host, {
name = random_name(),
filters = {
{
name = "rt_no_validation",
config = cjson.encode({
add = {
headers = 123,
},
}),
},
},
})
Expand All @@ -242,7 +267,6 @@ describe("filter metadata [#" .. strategy .. "]", function()
assert.logfile().has.line("failed parsing filter config", true, 0)
end).has_no_error()
end)

end)

end)
Expand Down

0 comments on commit 018c5bf

Please sign in to comment.