Skip to content

Commit 363770e

Browse files
committed
Add a built-in Redis adapter and integration tests
- Added a built-in Redis adapter for state management, allowing version tracking with hash-based storage and TTL support. - Updated configuration schema to include Redis-specific fields such as host, port, and database. - Modified existing state store logic to support passing configuration to adapter methods. - Added unit and integration tests for Redis adapter functionality and lifecycle behavior.
1 parent ddcf3fa commit 363770e

13 files changed

Lines changed: 944 additions & 15 deletions

.busted

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,20 @@ return {
33
verbose = true,
44
coverage = false,
55
output = "gtest",
6+
pattern = "_spec.lua$",
7+
["exclude-pattern"] = "integration",
8+
},
9+
unit = {
10+
verbose = true,
11+
coverage = false,
12+
output = "gtest",
13+
pattern = "_spec.lua$",
14+
["exclude-pattern"] = "integration",
15+
},
16+
integration = {
17+
verbose = true,
18+
coverage = false,
19+
output = "gtest",
20+
pattern = "integration.*_spec.lua$",
621
},
722
}

.pongo/pongo-setup.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
cd /kong-plugin || { echo "Failure to enter /kong-plugin"; exit 1; }
5+
6+
while IFS= read -r -d '' rockspec; do
7+
luarocks install --only-deps "$rockspec"
8+
done < <(find /kong-plugin -maxdepth 1 -type f -name '*.rockspec' -print0)
9+
10+
latest_rockspec=$(ls -1 /kong-plugin/kong-plugin-version-gate-*.rockspec | sort -V | tail -1)
11+
luarocks remove --force kong-plugin-version-gate >/dev/null 2>&1 || true
12+
luarocks make "$latest_rockspec"

.pongo/pongorc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
--postgres
22
--no-cassandra
3+
--redis

README.md

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Subject key resolution:
4242
State store options:
4343
- shared dict (`state_store_dict_name`, default `version_gate_state`)
4444
- optional adapter module (`state_store_adapter_module`)
45+
- built-in Redis adapter module (`kong.plugins.version-gate.state_store_redis`)
4546

4647
## Config (Common)
4748

@@ -78,8 +79,49 @@ config:
7879
state_store_dict_name: version_gate_state
7980
state_store_ttl_sec: 30
8081
# state_store_adapter_module: my.custom.state_store_adapter
82+
# state_store_adapter_module: kong.plugins.version-gate.state_store_redis
83+
# state_store_redis_host: redis.default.svc.cluster.local
84+
# state_store_redis_port: 6379
85+
# state_store_redis_password: ""
86+
# state_store_redis_database: 0
87+
# state_store_redis_timeout_ms: 100
88+
# state_store_redis_keepalive_ms: 60000
89+
# state_store_redis_pool_size: 100
90+
# state_store_redis_prefix: version-gate:state
8191
```
8292

93+
## Redis State Store Adapter
94+
95+
Enable Redis-backed state with:
96+
97+
```yaml
98+
config:
99+
state_store_adapter_module: kong.plugins.version-gate.state_store_redis
100+
state_store_redis_host: redis.default.svc.cluster.local
101+
```
102+
103+
Resolution precedence is `plugin config > env var > built-in default`.
104+
105+
Supported environment variables:
106+
- `KONG_REDIS_HOST`
107+
- `KONG_REDIS_PORT`
108+
- `KONG_REDIS_PASSWORD`
109+
- `KONG_REDIS_DATABASE`
110+
- `KONG_REDIS_TIMEOUT_MS`
111+
- `KONG_REDIS_KEEPALIVE_MS`
112+
- `KONG_REDIS_POOL_SIZE`
113+
- `KONG_REDIS_PREFIX`
114+
- `KONG_REDIS_TTL_SEC`
115+
116+
Redis key shape:
117+
- key: `<state_store_redis_prefix>:<subject_key>`
118+
- fields: `version`, `ts_ms` (via hash operations)
119+
- ttl: `state_store_ttl_sec`
120+
121+
Failure behavior:
122+
- Redis failures are fail-open for availability.
123+
- If Redis read/write fails, `state_store.lua` falls back to shared dict when configured.
124+
83125
## Example (Declarative Config)
84126

85127
```yaml
@@ -111,9 +153,9 @@ services:
111153
## Install
112154

113155
1. Install from LuaRocks (recommended):
114-
`luarocks install kong-plugin-version-gate 0.1.0-2`
156+
`luarocks install kong-plugin-version-gate 0.1.0-3`
115157
2. Optional (build from local source instead):
116-
`luarocks make kong-plugin-version-gate-0.1.0-2.rockspec`
158+
`luarocks make kong-plugin-version-gate-0.1.0-3.rockspec`
117159
3. If using shared-dict state store, define an Nginx shared dict (for example `lua_shared_dict version_gate_state 10m;`).
118160
4. Enable plugin:
119161
set `KONG_PLUGINS=bundled,version-gate`
@@ -149,7 +191,7 @@ curl -sS http://localhost:8001/plugins/enabled | grep -i version-gate
149191

150192
## Registration Readiness Checklist
151193

152-
- Rockspec dependency range and plugin modules are correct (`kong-plugin-version-gate-0.1.0-1.rockspec`).
194+
- Rockspec dependency range and plugin modules are correct (`kong-plugin-version-gate-0.1.0-3.rockspec`).
153195
- Plugin name is aligned everywhere: `version-gate` (`schema.name`, config, and `KONG_PLUGINS`).
154196
- Integration tests pass against pinned Kong:
155197
`KONG_VERSION=3.8.0 /Users/erayack/.kong-pongo/pongo.sh run -- -v -o gtest ./spec/version-gate/10-integration_spec.lua`
@@ -211,15 +253,29 @@ curl -i "http://localhost:8000/version-gate-demo/response-headers?x-actual-versi
211253

212254
## Testing (Pongo)
213255

214-
Pin Kong to a known compatible version (`3.8.0`) when running Pongo:
256+
Test suite separation:
257+
258+
- Local `busted` (default or `--run unit`) runs unit specs only.
259+
- Integration specs are separate and require Pongo/Kong runtime.
260+
261+
Unit (local):
262+
263+
```bash
264+
busted
265+
# or
266+
busted --run unit
267+
```
268+
269+
Integration (Pongo), pinned to known compatible version (`3.8.0`):
215270

216271
```bash
217-
KONG_VERSION=3.8.0 pongo up
218-
KONG_VERSION=3.8.0 pongo run
272+
PONGO_BIN="${PONGO_BIN:-$HOME/.kong-pongo/pongo.sh}"
273+
KONG_VERSION=3.8.0 "$PONGO_BIN" run -- --run integration
219274
```
220275

221276
Override the pin when needed:
222277

223278
```bash
224-
KONG_VERSION=3.9.1 pongo run
279+
PONGO_BIN="${PONGO_BIN:-$HOME/.kong-pongo/pongo.sh}"
280+
KONG_VERSION=3.9.1 "$PONGO_BIN" run -- --run integration
225281
```

kong-plugin-version-gate-0.1.0-2.rockspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ build = {
3131
["kong.plugins.version-gate.observability"] = "kong/plugins/version-gate/observability.lua",
3232
["kong.plugins.version-gate.policy"] = "kong/plugins/version-gate/policy.lua",
3333
["kong.plugins.version-gate.state_store"] = "kong/plugins/version-gate/state_store.lua",
34+
["kong.plugins.version-gate.state_store_redis"] = "kong/plugins/version-gate/state_store_redis.lua",
3435
["kong.plugins.version-gate.version_extractor"] = "kong/plugins/version-gate/version_extractor.lua",
3536
},
3637
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package = "kong-plugin-version-gate"
2+
version = "0.1.0-3"
3+
4+
source = {
5+
url = "git://github.com/erayack/version-gate-kong",
6+
tag = "v0.1.0-3",
7+
}
8+
9+
description = {
10+
summary = "Kong plugin that enforces monotonic version consistency between request and response headers.",
11+
detailed = "version-gate compares an expected version header on the request with an actual version header on the upstream response, detects invariant violations, and enforces configurable actions (log, warn, or reject) to guarantee monotonic-read consistency at the API gateway layer.",
12+
homepage = "https://github.com/erayack/version-gate-kong",
13+
license = "Apache 2.0",
14+
}
15+
16+
dependencies = {
17+
"kong >= 3.4, < 4.0",
18+
}
19+
20+
build = {
21+
type = "builtin",
22+
modules = {
23+
["kong.plugins.version-gate.handler"] = "kong/plugins/version-gate/handler.lua",
24+
["kong.plugins.version-gate.schema"] = "kong/plugins/version-gate/schema.lua",
25+
["kong.plugins.version-gate.daos"] = "kong/plugins/version-gate/daos.lua",
26+
["kong.plugins.version-gate.constants"] = "kong/plugins/version-gate/constants.lua",
27+
["kong.plugins.version-gate.ctx"] = "kong/plugins/version-gate/ctx.lua",
28+
["kong.plugins.version-gate.decision_engine"] = "kong/plugins/version-gate/decision_engine.lua",
29+
["kong.plugins.version-gate.enforcement"] = "kong/plugins/version-gate/enforcement.lua",
30+
["kong.plugins.version-gate.invariant"] = "kong/plugins/version-gate/invariant.lua",
31+
["kong.plugins.version-gate.observability"] = "kong/plugins/version-gate/observability.lua",
32+
["kong.plugins.version-gate.policy"] = "kong/plugins/version-gate/policy.lua",
33+
["kong.plugins.version-gate.state_store"] = "kong/plugins/version-gate/state_store.lua",
34+
["kong.plugins.version-gate.state_store_redis"] = "kong/plugins/version-gate/state_store_redis.lua",
35+
["kong.plugins.version-gate.version_extractor"] = "kong/plugins/version-gate/version_extractor.lua",
36+
},
37+
}

kong/plugins/version-gate/schema.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,51 @@ return {
202202
required = false,
203203
custom_validator = validate_non_empty_name,
204204
} },
205+
{ state_store_redis_host = {
206+
type = "string",
207+
required = false,
208+
custom_validator = validate_non_empty_name,
209+
} },
210+
{ state_store_redis_port = {
211+
type = "integer",
212+
required = true,
213+
default = 6379,
214+
between = { 1, 65535 },
215+
} },
216+
{ state_store_redis_password = {
217+
type = "string",
218+
required = false,
219+
} },
220+
{ state_store_redis_database = {
221+
type = "integer",
222+
required = true,
223+
default = 0,
224+
between = { 0, 1024 },
225+
} },
226+
{ state_store_redis_timeout_ms = {
227+
type = "integer",
228+
required = true,
229+
default = 100,
230+
between = { 1, 60000 },
231+
} },
232+
{ state_store_redis_keepalive_ms = {
233+
type = "integer",
234+
required = true,
235+
default = 60000,
236+
between = { 1, 3600000 },
237+
} },
238+
{ state_store_redis_pool_size = {
239+
type = "integer",
240+
required = true,
241+
default = 100,
242+
between = { 1, 10000 },
243+
} },
244+
{ state_store_redis_prefix = {
245+
type = "string",
246+
required = true,
247+
default = "version-gate:state",
248+
custom_validator = validate_non_empty_name,
249+
} },
205250
{ reject_status_code = {
206251
type = "integer",
207252
required = true,

kong/plugins/version-gate/state_store.lua

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ local function resolve_adapter(conf)
5454
return adapter
5555
end
5656

57-
local function adapter_get(adapter, subject_key)
57+
local function adapter_get(adapter, subject_key, conf)
5858
if type(adapter) ~= "table" or type(adapter.get_last_seen) ~= "function" then
5959
return nil, nil
6060
end
@@ -71,14 +71,14 @@ local function adapter_get(adapter, subject_key)
7171
return version, ts_ms
7272
end
7373

74-
local ok_with_self, version_with_self, ts_with_self = pcall(adapter.get_last_seen, adapter, subject_key)
74+
local ok_with_self, version_with_self, ts_with_self = pcall(adapter.get_last_seen, adapter, subject_key, conf)
7575
if ok_with_self then
7676
version_with_self, ts_with_self = normalize(version_with_self, ts_with_self)
7777
else
7878
version_with_self, ts_with_self = nil, nil
7979
end
8080

81-
local ok_without_self, version_without_self, ts_without_self = pcall(adapter.get_last_seen, subject_key)
81+
local ok_without_self, version_without_self, ts_without_self = pcall(adapter.get_last_seen, subject_key, conf)
8282
if ok_without_self then
8383
version_without_self, ts_without_self = normalize(version_without_self, ts_without_self)
8484
else
@@ -104,17 +104,17 @@ local function adapter_get(adapter, subject_key)
104104
return nil, nil
105105
end
106106

107-
local function adapter_set(adapter, subject_key, version, ts_ms)
107+
local function adapter_set(adapter, subject_key, version, ts_ms, conf)
108108
if type(adapter) ~= "table" or type(adapter.set_last_seen) ~= "function" then
109109
return false
110110
end
111111

112-
local ok, did_set = pcall(adapter.set_last_seen, subject_key, version, ts_ms)
112+
local ok, did_set = pcall(adapter.set_last_seen, subject_key, version, ts_ms, conf)
113113
if ok then
114114
return did_set == true
115115
end
116116

117-
ok, did_set = pcall(adapter.set_last_seen, adapter, subject_key, version, ts_ms)
117+
ok, did_set = pcall(adapter.set_last_seen, adapter, subject_key, version, ts_ms, conf)
118118
if not ok then
119119
return false
120120
end
@@ -166,7 +166,7 @@ function _M.get_last_seen(subject_key, conf)
166166
end
167167

168168
local adapter = resolve_adapter(conf)
169-
local version, ts_ms = adapter_get(adapter, subject_key)
169+
local version, ts_ms = adapter_get(adapter, subject_key, conf)
170170
if version ~= nil or ts_ms ~= nil then
171171
return version, ts_ms
172172
end
@@ -197,7 +197,7 @@ function _M.set_last_seen(subject_key, version, ts_ms, conf)
197197
end
198198

199199
local adapter = resolve_adapter(conf)
200-
if adapter_set(adapter, subject_key, version, ts_ms) then
200+
if adapter_set(adapter, subject_key, version, ts_ms, conf) then
201201
return true
202202
end
203203

0 commit comments

Comments
 (0)