Skip to content

Commit

Permalink
chore: common validate function and misc updates
Browse files Browse the repository at this point in the history
This updates the validate schema function to be consistent with the
konnect-plugin-configuration-validation plugin. These two plugins may
get merged into one plugin so keeping them in sync with each other for a
potential helper function. The README and integration test were updated
also to add an example and fix the scoping of an integration run.
  • Loading branch information
mikefero committed May 28, 2023
1 parent 421fc72 commit 05d66b5
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 30 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,28 @@ curl -X POST http://kong:8000/konnect/plugin/schema/validation \
--header "content-type:application/json" \
--data '{"schema": "local typedefs = require \"kong.db.schema.typedefs\"\nlocal PLUGIN_NAME = \"konnect-plugin-schema-validation\"\nlocal schema = {\nname = PLUGIN_NAME,\nfields = {\n{ consumer = typedefs.no_consumer },\n{ service = typedefs.no_service },\n{ protocols = typedefs.protocols_http },\n{ config = { type = \"record\", fields = {} } }\n}\n}\nreturn schema\n"}'
```

## Example

Validate a configuration for the third party
[Moesif custom plugin](https://docs.konghq.com/hub/moesif/kong-plugin-moesif/)
which captures and logs Kong Gateway traffic for
[Moesif API Analytics](https://www.moesif.com).

- `schema`: [schema.lua](https://github.com/Moesif/kong-plugin-moesif/blob/master/kong/plugins/moesif/schema.lua)

### Request

```
curl -X POST http://kong:8000/konnect/plugin/schema/validation \
--header "content-type:application/json" \
--data '{"schema": "local typedefs = require \"kong.db.schema.typedefs\"\n\nreturn {\n name = \"moesif\",\n fields = {\n {\n consumer = typedefs.no_consumer\n },\n {\n protocols = typedefs.protocols_http\n },\n {\n config = {\n type = \"record\",\n fields = {\n {\n api_endpoint = {required = true, type = \"string\", default = \"https://api.moesif.net\"}\n },\n {\n timeout = {default = 1000, type = \"number\"}\n },\n {\n connect_timeout = {default = 1000, type = \"number\"}\n },\n {\n send_timeout = {default = 2000, type = \"number\"}\n },\n {\n keepalive = {default = 5000, type = \"number\"}\n },\n {\n event_queue_size = {default = 1000, type = \"number\"}\n },\n {\n api_version = {default = \"1.0\", type = \"string\"}\n },\n {\n application_id = {required = true, default = nil, type=\"string\"}\n },\n {\n disable_capture_request_body = {default = false, type = \"boolean\"}\n },\n {\n disable_capture_response_body = {default = false, type = \"boolean\"}\n },\n {\n request_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n request_body_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n request_header_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n response_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n response_body_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n response_header_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n batch_size = {default = 200, type = \"number\", elements = typedefs.header_name}\n },\n {\n disable_transaction_id = {default = false, type = \"boolean\"}\n },\n {\n debug = {default = false, type = \"boolean\"}\n },\n {\n disable_gzip_payload_decompression = {default = false, type = \"boolean\"}\n },\n {\n user_id_header = {default = nil, type = \"string\"}\n },\n {\n authorization_header_name = {default = \"authorization\", type = \"string\"}\n },\n {\n authorization_user_id_field = {default = \"sub\", type = \"string\"}\n },\n {\n authorization_company_id_field = {default = nil, type = \"string\"}\n },\n {\n company_id_header = {default = nil, type = \"string\"}\n },\n {\n max_callback_time_spent = {default = 750, type = \"number\"}\n },\n {\n request_max_body_size_limit = {default = 100000, type = \"number\"}\n },\n {\n response_max_body_size_limit = {default = 100000, type = \"number\"}\n },\n {\n request_query_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n enable_reading_send_event_response = {default = false, type = \"boolean\"}\n },\n {\n disable_moesif_payload_compression = {default = false, type = \"boolean\"}\n },\n },\n },\n },\n },\n entity_checks = {}\n}"}'
```

### Response

```
{
"schema": "local typedefs = require \"kong.db.schema.typedefs\"\n\nreturn {\n name = \"moesif\",\n fields = {\n {\n consumer = typedefs.no_consumer\n },\n {\n protocols = typedefs.protocols_http\n },\n {\n config = {\n type = \"record\",\n fields = {\n {\n api_endpoint = {required = true, type = \"string\", default = \"https://api.moesif.net\"}\n },\n {\n timeout = {default = 1000, type = \"number\"}\n },\n {\n connect_timeout = {default = 1000, type = \"number\"}\n },\n {\n send_timeout = {default = 2000, type = \"number\"}\n },\n {\n keepalive = {default = 5000, type = \"number\"}\n },\n {\n event_queue_size = {default = 1000, type = \"number\"}\n },\n {\n api_version = {default = \"1.0\", type = \"string\"}\n },\n {\n application_id = {required = true, default = nil, type=\"string\"}\n },\n {\n disable_capture_request_body = {default = false, type = \"boolean\"}\n },\n {\n disable_capture_response_body = {default = false, type = \"boolean\"}\n },\n {\n request_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n request_body_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n request_header_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n response_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n response_body_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n response_header_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n batch_size = {default = 200, type = \"number\", elements = typedefs.header_name}\n },\n {\n disable_transaction_id = {default = false, type = \"boolean\"}\n },\n {\n debug = {default = false, type = \"boolean\"}\n },\n {\n disable_gzip_payload_decompression = {default = false, type = \"boolean\"}\n },\n {\n user_id_header = {default = nil, type = \"string\"}\n },\n {\n authorization_header_name = {default = \"authorization\", type = \"string\"}\n },\n {\n authorization_user_id_field = {default = \"sub\", type = \"string\"}\n },\n {\n authorization_company_id_field = {default = nil, type = \"string\"}\n },\n {\n company_id_header = {default = nil, type = \"string\"}\n },\n {\n max_callback_time_spent = {default = 750, type = \"number\"}\n },\n {\n request_max_body_size_limit = {default = 100000, type = \"number\"}\n },\n {\n response_max_body_size_limit = {default = 100000, type = \"number\"}\n },\n {\n request_query_masks = {default = {}, type = \"array\", elements = typedefs.header_name}\n },\n {\n enable_reading_send_event_response = {default = false, type = \"boolean\"}\n },\n {\n disable_moesif_payload_compression = {default = false, type = \"boolean\"}\n },\n },\n },\n },\n },\n entity_checks = {}\n}"
}
```
31 changes: 16 additions & 15 deletions kong/plugins/konnect-plugin-schema-validation/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ end
-- it is valid metaschema. All fields and attributes are validated
-- while executing checks against the entire plugin schema.
-- @param input The string representation of the plugin schema.
-- @retrun Error if plugin schema falis validations; nil otherwise.
-- @return Instantiated plugins subschema entity and loaded plugin schema;
-- otherwise nil with error if validation fails.
local function validate_plugin_schema(input)
-- Load the input into a compiled Lua function which will represent the
-- plugin schema for further validations.
Expand All @@ -58,14 +59,14 @@ local function validate_plugin_schema(input)
local err
plugin_schema, err = load(input)()
if err then
return "error processing load for plugin schema: " .. err
return nil, nil, "error processing load for plugin schema: " .. err
end
end)
if not pok then
return "error processing load for plugin schema: " .. perr
return nil, nil, "error processing load for plugin schema: " .. perr
end
if is_empty(plugin_schema) then
return "invalid schema for plugin: cannot be empty"
return nil, nil, "invalid schema for plugin: cannot be empty"
end

-- Complete the validation of the plugin schema.
Expand All @@ -76,37 +77,37 @@ local function validate_plugin_schema(input)
local pok, perr = pcall(function()
local ok, err = metaschema.MetaSubSchema:validate(plugin_schema)
if not ok then
return tostring(errors:schema_violation(err))
return nil, nil, tostring(errors:schema_violation(err))
end
end)
if not pok then
return "error calling MetaSubSchema:validate: " .. perr
return nil, nil, "error calling MetaSubSchema:validate: " .. perr
end

-- Load the plugin schema for use in configuration validation when
-- associated with a plugin entity
local plugins, err = entity.new(plugins_definition)
-- associated with a plugins subschema entity
local plugins_subschema_entity, err = entity.new(plugins_definition)
if err then
return "unable to create plugin entity: " .. err
return nil, nil, "unable to create plugin entity: " .. err
end
local plugin_name = plugin_schema.name
if is_empty(plugin_name) then
return "invalid schema for plugin: missing plugin name"
return nil, nil, "invalid schema for plugin: missing plugin name"
end
-- Note: "pcall" is used for this operation to ensure proper error handling
-- for "assert" calls performed in the "entity:new_subschema" function. When
-- iterating the arrays/fields of the plugin schema an "assert" is possible.
pok, perr = pcall(function()
local ok, err = plugins:new_subschema(plugin_name, plugin_schema)
local ok, err = plugins_subschema_entity:new_subschema(plugin_name, plugin_schema)
if not ok then
return "error loading schema for plugin " .. plugin_name .. ": " .. err
return nil, nil, "error loading schema for plugin " .. plugin_name .. ": " .. err
end
end)
if not pok then
return "error validating plugin schema: " .. perr
return nil, nil, "error validating plugin schema: " .. perr
end

return nil
return plugins_subschema_entity, plugin_schema
end

--- Access handler for the Konnect Plugin Schema Validation plugin. This
Expand All @@ -130,7 +131,7 @@ function konnect_plugin_schema_validation:access(conf)
return kong.response.error(400, "missing schema field") -- Bad request
end
local plugin_schema = body.schema
err = validate_plugin_schema(plugin_schema)
local _, _, err = validate_plugin_schema(plugin_schema)
if err then
return kong.response.error(400, err) -- Bad request
end
Expand Down
30 changes: 15 additions & 15 deletions spec/konnect-plugin-schema-validation/02-integration_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -146,22 +146,22 @@ for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then
message = "missing schema field"
}, json)
end)
end)

it("fails when schema definition is empty", function()
local r = client:post("/konnect/plugin/schema/validation", {
headers = {
["Content-Type"] = "application/json"
},
body = {
schema = "return"
}
})
assert.response(r).has.status(400)
local json = assert.response(r).has.jsonbody()
assert.same({
message = "invalid schema for plugin: cannot be empty"
}, json)
it("fails when schema definition is empty", function()
local r = client:post("/konnect/plugin/schema/validation", {
headers = {
["Content-Type"] = "application/json"
},
body = {
schema = "return"
}
})
assert.response(r).has.status(400)
local json = assert.response(r).has.jsonbody()
assert.same({
message = "invalid schema for plugin: cannot be empty"
}, json)
end)
end)
end)
end
Expand Down

0 comments on commit 05d66b5

Please sign in to comment.