Add a custom plugin configuration

Uses: Kong Gateway Admin API
TL;DR

Add configuration fields in the plugins’s schema.lua file, and define the features using the configuration fields in handler.lua.

Prerequisites

This page is part of the Get started with custom plugin development series.

Complete the previous page, Add custom plugin testing before completing this page.

Add configuration fields to the schema

Let’s add some configuration fields to our schema.lua file.

  1. Include the Kong Gateway typedefs module at the top of the schema.lua file:
    local typedefs = require "kong.db.schema.typedefs"
    
  2. Add the following header_name type definition within the fields array we defined earlier:
     { response_header_name = typedefs.header_name {
         required = false,
         default = "X-MyPlugin" } },
    

    This type definition defines the field to be a string that cannot be null and conforms to the rules for header names. It also indicates that the configuration value is not required, which means it’s optional for the user when configuring the plugin. We also specify a default value that will be used when a user does not specify a value.

The full schema.lua now looks like this:

local typedefs = require "kong.db.schema.typedefs"

local PLUGIN_NAME = "my-plugin"

local schema = {
  name = PLUGIN_NAME,
  fields = {
    { config = {
        type = "record",
        fields = {
          { response_header_name = typedefs.header_name {
            required = false,
            default = "X-MyPlugin" } },
        },
      },
    },
  },
}

return schema

Read configuration values from plugin code

Modify the response function in the handler.lua file to read the configuration value from the incoming conf parameter instead of the current hardcoded value:

function MyPluginHandler:response(conf)
    kong.response.set_header(conf.response_header_name, "response")
end

Manually validate the configuration

Let’s use Pongo to test the updated configuration.

  1. Launch Kong Gateway and open a shell:
    pongo shell
    
  2. Run the database migrations and start Kong Gateway:
    kms
    
  3. Add a test Gateway Service:

    curl -X POST "http://localhost:8001/services" \
         --json '{
           "name": "example_service",
           "url": "https://httpbin.konghq.com"
         }'
    
  4. Enable the plugin, this time with the configuration value:

    curl -X POST "http://localhost:8001/services/example_service/plugins" \
         --json '{
           "name": "my-plugin",
           "config": {
             "response_header_name": "X-CustomHeaderName"
           }
         }'
    
  5. Add a Route:

    curl -X POST "http://localhost:8001/services/example_service/routes" \
         --json '{
           "name": "example_route",
           "paths": [
             "/mock"
           ]
         }'
    
  6. Send a request to the Route:

    curl -i "http://localhost:8000/mock/anything"
    

    This time we should see the X-CustomHeaderName in the response.

  7. Exit the Kong Gateway shell before proceeding to the next step:
    exit
    

Add automated configuration testing

  1. Update the setup function inside the spec/01-integration_spec.lua module so that the my-plugin that is added to the database is configured with a different value for the response_header_name field:
    -- Add the custom plugin to the test Route
    blue_print.plugins:insert {
      name = PLUGIN_NAME, 
      route = { id = test_route.id },
      config = {
        response_header_name = "X-CustomHeaderName",
      },
    }
    
  2. Modify the test assertion to match the new header name:
    -- now validate and retrieve the expected response header 
    local header_value = assert.response(r).has.header("X-CustomHeaderName")
    

    The test file should now look like this:

    -- Helper functions provided by Kong Gateway, see https://github.com/Kong/kong/blob/master/spec/helpers.lua
    local helpers = require "spec.helpers"
    
    -- matches our plugin name defined in the plugins's schema.lua
    local PLUGIN_NAME = "my-plugin"
    
    -- Run the tests for each strategy. Strategies include "postgres" and "off"
    --   which represent the deployment topologies for Kong Gateway
    for _, strategy in helpers.all_strategies() do
      
      describe(PLUGIN_NAME .. ": [#" .. strategy .. "]", function()
        -- Will be initialized before_each nested test
        local client
       
        setup(function()
      
          -- A BluePrint gives us a helpful database wrapper to
          --    manage Kong Gateway entities directly.
          -- This function also truncates any existing data in an existing db.
          -- The custom plugin name is provided to this function so it mark as loaded
          local blue_print = helpers.get_db_utils(strategy, nil, { PLUGIN_NAME })
    
          -- Using the BluePrint to create a test Route, automatically attaches it
          --    to the default "echo" Service that will be created by the test framework
          local test_route = blue_print.routes:insert({
            paths = { "/mock" },
          })
    
          -- Add the custom plugin to the test Route
          blue_print.plugins:insert {
            name = PLUGIN_NAME, 
            route = { id = test_route.id },
            config = {
              response_header_name = "X-CustomHeaderName",
            },
          }
    
    
          -- start kong
          assert(helpers.start_kong({
            -- use the custom test template to create a local mock server
            nginx_conf = "spec/fixtures/custom_nginx.template",
            -- make sure our plugin gets loaded
            plugins = "bundled," .. PLUGIN_NAME,
          }))
    
        end)
    
        -- teardown runs after its parent describe block
        teardown(function()
          helpers.stop_kong(nil, true)
        end)
    
        -- before_each runs before each child describe
        before_each(function()
          client = helpers.proxy_client()
        end)
    
        -- after_each runs after each child describe
        after_each(function()
          if client then client:close() end
        end)
    
        -- a nested describe defines an actual test on the plugin behavior
        describe("The response", function()
    
          it("gets the expected header", function()
    
            -- invoke a test request
            local r = client:get("/mock/anything", {})
    
            -- validate that the request succeeded, response status 200
            assert.response(r).has.status(200)
    
            -- now validate and retrieve the expected response header 
            local header_value = assert.response(r).has.header("X-CustomHeaderName")
    
            -- validate the value of that header
            assert.equal("response", header_value)
    
          end)
        end)
      end)
    end
    
  3. Run the tests:
    pongo run
    

    Pongo should report a successful test run.

Something wrong?

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!